backport default branch tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 07 Apr 2009 09:30:23 +0200
branchtls-sprint
changeset 1263 01152fffd593
parent 1246 76b3cd5d4f31 (diff)
parent 1262 abaadb570626 (current diff)
child 1264 fe2934a7df7f
backport default branch
__init__.py
common/mixins.py
common/registerers.py
common/selectors.py
common/uilib.py
cwconfig.py
cwctl.py
cwvreg.py
debian/control
devtools/apptest.py
devtools/devctl.py
devtools/repotest.py
etwist/server.py
goa/goactl.py
i18n/en.po
i18n/es.po
i18n/fr.po
interfaces.py
selectors.py
server/checkintegrity.py
server/hooks.py
server/migractions.py
server/msplanner.py
server/repository.py
server/schemahooks.py
server/serverconfig.py
server/serverctl.py
server/session.py
server/sources/__init__.py
server/sources/extlite.py
server/sources/ldapuser.py
server/sources/native.py
server/sources/pyrorql.py
server/sources/rql2sql.py
server/sqlutils.py
server/test/unittest_migractions.py
server/test/unittest_querier.py
server/test/unittest_repository.py
server/test/unittest_rql2sql.py
sobjects/notification.py
test/unittest_cwconfig.py
web/data/cubicweb.ajax.js
web/views/actions.py
web/views/baseviews.py
web/views/boxes.py
web/views/editcontroller.py
web/views/iprogress.py
web/views/owl.py
web/views/schema.py
web/views/tabs.py
web/widgets.py
--- a/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -88,8 +88,8 @@
         from cubicweb.rset import ResultSet
         rset = ResultSet([('A',)]*size, '%s X' % etype,
                          description=[(etype,)]*size)
-        def get_entity(row, col=0, etype=etype, vreg=self.vreg, rset=rset):
-            return self.vreg.etype_class(etype)(self, rset, row, col)
+        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)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/appobject.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,319 @@
+"""Base class for dynamically loaded objects manipulated in the web interface
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import datetime, timedelta
+
+from simplejson import dumps
+
+from logilab.common.decorators import classproperty
+from logilab.common.deprecation import obsolete
+
+from rql.nodes import VariableRef, SubQuery
+from rql.stmts import Union, Select
+
+from cubicweb import Unauthorized, NoSelectableObject
+from cubicweb.vregistry import VObject, AndSelector
+from cubicweb.selectors import yes
+from cubicweb.utils import UStringIO, ustrftime
+
+ONESECOND = timedelta(0, 1, 0)
+
+class Cache(dict):    
+    def __init__(self):
+        super(Cache, self).__init__()
+        self.cache_creation_date = None
+        self.latest_cache_lookup = datetime.now()
+    
+CACHE_REGISTRY = {}
+
+class AppRsetObject(VObject):
+    """This is the base class for CubicWeb application objects
+    which are selected according to a request and result set.
+    
+    Classes are kept in the vregistry and instantiation is done at selection
+    time.
+    
+    At registration time, the following attributes are set on the class:
+    :vreg:
+      the application's registry
+    :schema:
+      the application's schema
+    :config:
+      the application's configuration
+
+    At instantiation time, the following attributes are set on the instance:
+    :req:
+      current request
+    :rset:
+      result set on which the object is applied
+    """
+    __select__ = yes()
+
+    @classmethod
+    def registered(cls, vreg):
+        super(AppRsetObject, cls).registered(vreg)
+        cls.vreg = vreg
+        cls.schema = vreg.schema
+        cls.config = vreg.config
+        cls.register_properties()
+        return cls
+
+    @classmethod
+    def selected(cls, *args, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection according to a request, a result set, and optional
+        row and col
+        """
+        assert len(args) <= 2
+        return cls(*args, **kwargs)
+
+    # Eproperties definition:
+    # key: id of the property (the actual EProperty 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)
+        
+    @classmethod
+    def propkey(cls, propid):
+        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
+
+    @classproperty
+    @obsolete('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(AppRsetObject, 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()
+            CACHE_REGISTRY[cachename] = cache
+        _now = datetime.now()
+        if _now > cache.latest_cache_lookup + ONESECOND:
+            ecache = self.req.execute('Any C,T WHERE C is ECache, 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
+        """
+        # 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.select_component('navigation', self.req, 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
+    
+    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 view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
+        """shortcut to self.vreg.render method avoiding to pass self.req"""
+        try:
+            view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
+        except NoSelectableObject:
+            if __fallback_vid is None:
+                raise
+            view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
+        return view.dispatch(**kwargs)
+    
+    # url generation methods ##################################################
+    
+    controller = 'view'
+    
+    def build_url(self, method=None, **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.
+        """
+        # 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)
+
+    # 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)
+    
+    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
+
+    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.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 #######################################################
+
+    def tal_render(self, template, variables):
+        """render a precompiled page template with variables in the given
+        dictionary as context
+        """
+        from cubicweb.common.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()
+
+    def format_date(self, date, date_format=None, time=False):
+        """return a string for a date time according to application'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''
+
+    def format_time(self, time):
+        """return a string for a time according to application's
+        configuration
+        """
+        if time:
+            return ustrftime(time, self.req.property_value('ui.time-format'))
+        return u''
+
+    def format_float(self, num):
+        """return a string for floating point number according to application's
+        configuration
+        """
+        if num:
+            return self.req.property_value('ui.float-format') % num
+        return u''
+    
+    # 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'))
+
+        
+class AppObject(AppRsetObject):
+    """base class for application objects which are not selected
+    according to a result set, only by their identifier.
+    
+    Those objects may not have req, rset and cursor set.
+    """
+    
+    @classmethod
+    def selected(cls, *args, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection
+        """
+        return cls(*args, **kwargs)
+
+    def __init__(self, req=None, rset=None, **kwargs):
+        self.req = req
+        self.rset = rset
+        self.__dict__.update(kwargs)
--- a/common/appobject.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/appobject.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,463 +1,5 @@
-"""Base class for dynamically loaded objects manipulated in the web interface
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
+"""pre 3.2 bw compat"""
+# pylint: disable-msg=W0614,W0401
 from warnings import warn
-
-from mx.DateTime import now, oneSecond
-from simplejson import dumps
-
-from logilab.common.deprecation import obsolete
-
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
-from cubicweb import Unauthorized
-from cubicweb.vregistry import VObject
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.uilib import html_escape, ustrftime
-from cubicweb.common.registerers import yes_registerer, priority_registerer
-from cubicweb.common.selectors import yes
-
-_MARKER = object()
-
-
-class Cache(dict):    
-    def __init__(self):
-        super(Cache, self).__init__()
-        self.cache_creation_date = None
-        self.latest_cache_lookup = now()
-    
-CACHE_REGISTRY = {}
-
-class AppRsetObject(VObject):
-    """This is the base class for CubicWeb application objects
-    which are selected according to a request and result set.
-    
-    Classes are kept in the vregistry and instantiation is done at selection
-    time.
-    
-    At registration time, the following attributes are set on the class:
-    :vreg:
-      the application's registry
-    :schema:
-      the application's schema
-    :config:
-      the application's configuration
-
-    At instantiation time, the following attributes are set on the instance:
-    :req:
-      current request
-    :rset:
-      result set on which the object is applied
-    """
-
-    @classmethod
-    def registered(cls, vreg):
-        cls.vreg = vreg
-        cls.schema = vreg.schema
-        cls.config = vreg.config
-        cls.register_properties()
-        return cls
-
-    @classmethod
-    def selected(cls, req, rset, row=None, col=None, **kwargs):
-        """by default web app objects are usually instantiated on
-        selection according to a request, a result set, and optional
-        row and col
-        """
-        instance = cls(req, rset)
-        instance.row = row
-        instance.col = col
-        return instance
-
-    # Eproperties definition:
-    # key: id of the property (the actual EProperty 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)
-        
-    @classmethod
-    def propkey(cls, propid):
-        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
-            
-        
-    def __init__(self, req, rset):
-        super(AppRsetObject, self).__init__()
-        self.req = req
-        self.rset = rset
-
-    @property
-    def cursor(self): # XXX deprecate in favor of req.cursor?
-        msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.cursor
-        
-    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()
-            CACHE_REGISTRY[cachename] = cache
-        _now = now()
-        if _now > cache.latest_cache_lookup + oneSecond:
-            ecache = self.req.execute('Any C,T WHERE C is ECache, 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.empty()
-                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
-        """
-        # 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.select_component('navigation', self.req, 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
-    
-    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
-    
-    # url generation methods ##################################################
-    
-    controller = 'view'
-    
-    def build_url(self, method=None, **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.
-        """
-        # 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)
-
-    # various resources accessors #############################################
-
-    def etype_rset(self, etype, size=1):
-        """return a fake result set for a particular entity type"""
-        msg = '.etype_rset is deprecated, use req.etype_rset'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.etype_rset(etype, size=1)
-
-    def eid_rset(self, eid, etype=None):
-        """return a result set for the given eid"""
-        msg = '.eid_rset is deprecated, use req.eid_rset'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        return self.req.eid_rset(eid, etype)
-    
-    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)
-    
-    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
-
-    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.req.add_js('cubicweb.ajax.js')
-        if nonify:
-            # XXX < 2.48.3 bw compat
-            warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
-            _cb = cb
-            def cb(*args):
-                _cb(*args)
-        cbname = self.req.register_onetime_callback(cb, *args)
-        msg = dumps(msg or '') 
-        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
-            cbname, msg)
-
-    # formating methods #######################################################
-
-    def tal_render(self, template, variables):
-        """render a precompiled page template with variables in the given
-        dictionary as context
-        """
-        from cubicweb.common.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()
-
-    def format_date(self, date, date_format=None, time=False):
-        """return a string for a mx date time according to application'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''
-
-    def format_time(self, time):
-        """return a string for a mx date time according to application's
-        configuration
-        """
-        if time:
-            return ustrftime(time, self.req.property_value('ui.time-format'))
-        return u''
-
-    def format_float(self, num):
-        """return a string for floating point number according to application's
-        configuration
-        """
-        if num:
-            return self.req.property_value('ui.float-format') % num
-        return u''
-    
-    # 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'))
-
-    # .accepts handling utilities #############################################
-    
-    accepts = ('Any',)
-
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        """apply the following rules:
-        * if row is None, return the sum of values returned by the method
-          for each entity's type in the result set. If any score is 0,
-          return 0.
-        * if row is specified, return the value returned by the method with
-          the entity's type of this row
-        """
-        if row is None:
-            score = 0
-            for etype in rset.column_types(0):
-                accepted = cls.accept(req.user, etype)
-                if not accepted:
-                    return 0
-                score += accepted
-            return score
-        return cls.accept(req.user, rset.description[row][col or 0])
-        
-    @classmethod
-    def accept(cls, user, etype):
-        """score etype, returning better score on exact match"""
-        if 'Any' in cls.accepts:
-            return 1
-        eschema = cls.schema.eschema(etype)
-        matching_types = [e.type for e in eschema.ancestors()]
-        matching_types.append(etype)
-        for index, basetype in enumerate(matching_types):
-            if basetype in cls.accepts:
-                return 2 + index
-        return 0
-    
-    # .rtype  handling utilities ##############################################
-    
-    @classmethod
-    def relation_possible(cls, etype):
-        """tell if a relation with etype entity is possible according to 
-        mixed class'.etype, .rtype and .target attributes
-
-        XXX should probably be moved out to a function
-        """
-        schema = cls.schema
-        rtype = cls.rtype
-        eschema = schema.eschema(etype)
-        if hasattr(cls, 'role'):
-            role = cls.role
-        elif cls.target == 'subject':
-            role = 'object'
-        else:
-            role = 'subject'
-        # check if this relation is possible according to the schema
-        try:
-            if role == 'object':
-                rschema = eschema.object_relation(rtype)
-            else:
-                rschema = eschema.subject_relation(rtype)
-        except KeyError:
-            return False            
-        if hasattr(cls, 'etype'):
-            letype = cls.etype
-            try:
-                if role == 'object':
-                    return etype in rschema.objects(letype)
-                else:
-                    return etype in rschema.subjects(letype)
-            except KeyError, ex:
-                return False
-        return True
-
-    
-    # XXX deprecated (since 2.43) ##########################
-    
-    @obsolete('use req.datadir_url')
-    def datadir_url(self):
-        """return url of the application's data directory"""
-        return self.req.datadir_url
-
-    @obsolete('use req.external_resource()')
-    def external_resource(self, rid, default=_MARKER):
-        return self.req.external_resource(rid, default)
-
-        
-class AppObject(AppRsetObject):
-    """base class for application objects which are not selected
-    according to a result set, only by their identifier.
-    
-    Those objects may not have req, rset and cursor set.
-    """
-    
-    @classmethod
-    def selected(cls, *args, **kwargs):
-        """by default web app objects are usually instantiated on
-        selection
-        """
-        return cls(*args, **kwargs)
-
-    def __init__(self, req=None, rset=None, **kwargs):
-        self.req = req
-        self.rset = rset
-        self.__dict__.update(kwargs)
-
-
-class ReloadableMixIn(object):
-    """simple mixin for reloadable parts of UI"""
-    
-    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.req.add_js('cubicweb.ajax.js')
-        if nonify:
-            _cb = cb
-            def cb(*args):
-                _cb(*args)
-        cbname = self.req.register_onetime_callback(cb, *args)
-        return self.build_js(cbname, html_escape(msg or ''))
-        
-    def build_update_js_call(self, cbname, msg):
-        rql = html_escape(self.rset.printable_rql())
-        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
-            cbname, self.id, rql, msg, self.__registry__, self.div_id())
-    
-    def build_reload_js_call(self, cbname, msg):
-        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
-
-    build_js = build_update_js_call # expect updatable component by default
-    
-    def div_id(self):
-        return ''
-
-
-class ComponentMixIn(ReloadableMixIn):
-    """simple mixin for component object"""
-    __registry__ = 'components'
-    __registerer__ = yes_registerer
-    __selectors__ = (yes,)
-    __select__ = classmethod(*__selectors__)
-
-    def div_class(self):
-        return '%s %s' % (self.propval('htmlclass'), self.id)
-
-    def div_id(self):
-        return '%sComponent' % self.id
-
-
-class Component(ComponentMixIn, AppObject):
-    """base class for non displayable components
-    """
-
-class SingletonComponent(Component):
-    """base class for non displayable unique components
-    """
-    __registerer__ = priority_registerer
+warn('moved to cubicweb.appobject', DeprecationWarning, stacklevel=2)
+from cubicweb.appobject import *
--- a/common/entity.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/entity.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,1106 +1,6 @@
-"""Base class for entity objects manipulated in clients
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common import interface
-from logilab.common.compat import all
-from logilab.common.decorators import cached
-from logilab.mtconverter import TransformData, TransformError
-from rql.utils import rqlvar_maker
-
-from cubicweb import Unauthorized
-from cubicweb.vregistry import autoselectors
-from cubicweb.rset import ResultSet
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.registerers import id_registerer
-from cubicweb.common.selectors import yes
-from cubicweb.common.uilib import printable_value, html_escape, soup2xhtml
-from cubicweb.common.mixins import MI_REL_TRIGGERS
-from cubicweb.common.mttransforms import ENGINE
-from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
-
-_marker = object()
-
-def greater_card(rschema, subjtypes, objtypes, index):
-    for subjtype in subjtypes:
-        for objtype in objtypes:
-            card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
-            if card in '+*':
-                return card
-    return '1'
-
-
-class RelationTags(object):
-    
-    MODE_TAGS = frozenset(('link', 'create'))
-    CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
-                               'inlineview'))
-
-    def __init__(self, eclass, tagdefs):
-        # XXX if a rtag is redefined in a subclass,
-        # the rtag of the base class overwrite the rtag of the subclass
-        self.eclass = eclass
-        self._tagdefs = {}
-        for relation, tags in tagdefs.iteritems():
-            # tags must become a set
-            if isinstance(tags, basestring):
-                tags = set((tags,))
-            elif not isinstance(tags, set):
-                tags = set(tags)
-            # relation must become a 3-uple (rtype, targettype, role)
-            if isinstance(relation, basestring):
-                self._tagdefs[(relation, '*', 'subject')] = tags
-                self._tagdefs[(relation, '*', 'object')] = tags
-            elif len(relation) == 1: # useful ?
-                self._tagdefs[(relation[0], '*', 'subject')] = tags
-                self._tagdefs[(relation[0], '*', 'object')] = tags
-            elif len(relation) == 2:
-                rtype, ttype = relation
-                ttype = bw_normalize_etype(ttype) # XXX bw compat
-                self._tagdefs[rtype, ttype, 'subject'] = tags
-                self._tagdefs[rtype, ttype, 'object'] = tags
-            elif len(relation) == 3:
-                relation = list(relation)  # XXX bw compat
-                relation[1] = bw_normalize_etype(relation[1])
-                self._tagdefs[tuple(relation)] = tags
-            else:
-                raise ValueError('bad rtag definition (%r)' % (relation,))
-        
-
-    def __initialize__(self):
-        # eclass.[*]schema are only set when registering
-        self.schema = self.eclass.schema
-        eschema = self.eschema = self.eclass.e_schema
-        rtags = self._tagdefs
-        # expand wildcards in rtags and add automatic tags
-        for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
-            rtype = rschema.type
-            star_tags = rtags.pop((rtype, '*', role), set())
-            for tschema in tschemas:
-                tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
-                if role == 'subject':
-                    X, Y = eschema, tschema
-                    card = rschema.rproperty(X, Y, 'cardinality')[0]
-                    composed = rschema.rproperty(X, Y, 'composite') == 'object'
-                else:
-                    X, Y = tschema, eschema
-                    card = rschema.rproperty(X, Y, 'cardinality')[1]
-                    composed = rschema.rproperty(X, Y, 'composite') == 'subject'
-                # set default category tags if needed
-                if not tags & self.CATEGORY_TAGS:
-                    if card in '1+':
-                        if not rschema.is_final() and composed:
-                            category = 'generated'
-                        elif rschema.is_final() and (
-                            rschema.type.endswith('_format')
-                            or rschema.type.endswith('_encoding')):
-                            category = 'generated'
-                        else:
-                            category = 'primary'
-                    elif rschema.is_final():
-                        if (rschema.type.endswith('_format')
-                            or rschema.type.endswith('_encoding')):
-                            category = 'generated'
-                        else:
-                            category = 'secondary'
-                    else: 
-                        category = 'generic'
-                    tags.add(category)
-                if not tags & self.MODE_TAGS:
-                    if card in '?1':
-                        # by default, suppose link mode if cardinality doesn't allow
-                        # more than one relation
-                        mode = 'link'
-                    elif rschema.rproperty(X, Y, 'composite') == role:
-                        # if self is composed of the target type, create mode
-                        mode = 'create'
-                    else:
-                        # link mode by default
-                        mode = 'link'
-                    tags.add(mode)
-
-    def _default_target(self, rschema, role='subject'):
-        eschema = self.eschema
-        if role == 'subject':
-            return eschema.subject_relation(rschema).objects(eschema)[0]
-        else:
-            return eschema.object_relation(rschema).subjects(eschema)[0]
-
-    # dict compat
-    def __getitem__(self, key):
-        if isinstance(key, basestring):
-            key = (key,)
-        return self.get_tags(*key)
-
-    __contains__ = __getitem__
-    
-    def get_tags(self, rtype, targettype=None, role='subject'):
-        rschema = self.schema.rschema(rtype)
-        if targettype is None:
-            tschema = self._default_target(rschema, role)
-        else:
-            tschema = self.schema.eschema(targettype)
-        return self._tagdefs[(rtype, tschema.type, role)]
-
-    __call__ = get_tags
-    
-    def get_mode(self, rtype, targettype=None, role='subject'):
-        # XXX: should we make an assertion on rtype not being final ?
-        # assert not rschema.is_final()
-        tags = self.get_tags(rtype, targettype, role)
-        # do not change the intersection order !
-        modes = tags & self.MODE_TAGS
-        assert len(modes) == 1
-        return modes.pop()
-
-    def get_category(self, rtype, targettype=None, role='subject'):
-        tags = self.get_tags(rtype, targettype, role)
-        categories = tags & self.CATEGORY_TAGS
-        assert len(categories) == 1
-        return categories.pop()
-
-    def is_inlined(self, rtype, targettype=None, role='subject'):
-        # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
-        return 'inlineview' in self.get_tags(rtype, targettype, role)
-
-
-class metaentity(autoselectors):
-    """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
-        tagdefs = {}
-        widgets = {}
-        for base in bases:
-            tagdefs.update(getattr(base, '__rtags__', {}))
-            widgets.update(getattr(base, 'widgets', {}))
-        # update with the class' own rtgas
-        tagdefs.update(classdict.get('__rtags__', {}))
-        widgets.update(classdict.get('widgets', {}))
-        # XXX decide whether or not it's a good idea to replace __rtags__
-        #     good point: transparent support for inheritance levels >= 2
-        #     bad point: we loose the information of which tags are specific
-        #                to this entity class
-        classdict['__rtags__'] = tagdefs
-        classdict['widgets'] = widgets
-        eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
-        # adds the "rtags" attribute
-        eclass.rtags = RelationTags(eclass, tagdefs)
-        return eclass
-
-
-class Entity(AppRsetObject, dict):
-    """an entity instance has e_schema automagically set on
-    the class and instances has access to their issuing cursor.
-    
-    A property is set for each attribute and relation on each entity's type
-    class. Becare that among attributes, 'eid' is *NEITHER* stored in the
-    dict containment (which acts as a cache for other attributes dynamically
-    fetched)
-
-    :type e_schema: `cubicweb.schema.EntitySchema`
-    :ivar e_schema: the entity's schema
-
-    :type rest_var: str
-    :cvar rest_var: indicates which attribute should be used to build REST urls
-                    If None is specified, the first non-meta attribute will
-                    be used
-                    
-    :type skip_copy_for: list
-    :cvar skip_copy_for: a list of relations that should be skipped when copying
-                         this kind of entity. Note that some relations such
-                         as composite relations or relations that have '?1' as object
-                         cardinality
-    """
-    __metaclass__ = metaentity
-    __registry__ = 'etypes'
-    __registerer__ = id_registerer
-    __selectors__ = (yes,)
-    widgets = {}
-    id = None
-    e_schema = None
-    eid = None
-    rest_attr = None
-    skip_copy_for = ()
-
-    @classmethod
-    def registered(cls, registry):
-        """build class using descriptor at registration time"""
-        assert cls.id is not None
-        super(Entity, cls).registered(registry)
-        if cls.id != 'Any':
-            cls.__initialize__()
-        return cls
-                
-    MODE_TAGS = set(('link', 'create'))
-    CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
-    @classmethod
-    def __initialize__(cls):
-        """initialize a specific entity class by adding descriptors to access
-        entity type's attributes and relations
-        """
-        etype = cls.id
-        assert etype != 'Any', etype
-        cls.e_schema = eschema = cls.schema.eschema(etype)
-        for rschema, _ in eschema.attribute_definitions():
-            if rschema.type == 'eid':
-                continue
-            setattr(cls, rschema.type, Attribute(rschema.type))
-        mixins = []
-        for rschema, _, x in eschema.relation_definitions():
-            if (rschema, x) in MI_REL_TRIGGERS:
-                mixin = MI_REL_TRIGGERS[(rschema, x)]
-                if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
-                    mixins.append(mixin)
-                for iface in getattr(mixin, '__implements__', ()):
-                    if not interface.implements(cls, iface):
-                        interface.extend(cls, iface)
-            if x == 'subject':
-                setattr(cls, rschema.type, SubjectRelation(rschema))
-            else:
-                attr = 'reverse_%s' % rschema.type
-                setattr(cls, attr, ObjectRelation(rschema))
-        if mixins:
-            cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
-            cls.debug('plugged %s mixins on %s', mixins, etype)
-        cls.rtags.__initialize__()
-    
-    @classmethod
-    def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
-                  settype=True, ordermethod='fetch_order'):
-        """return a rql to fetch all entities of the class type"""
-        restrictions = restriction or []
-        if settype:
-            restrictions.append('%s is %s' % (mainvar, cls.id))
-        if fetchattrs is None:
-            fetchattrs = cls.fetch_attrs
-        selection = [mainvar]
-        orderby = []
-        # start from 26 to avoid possible conflicts with X
-        varmaker = rqlvar_maker(index=26)
-        cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
-                                orderby, restrictions, user, ordermethod)
-        rql = 'Any %s' % ','.join(selection)
-        if orderby:
-            rql +=  ' ORDERBY %s' % ','.join(orderby)
-        rql += ' WHERE %s' % ', '.join(restrictions)
-        return rql
-    
-    @classmethod
-    def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs,
-                            selection, orderby, restrictions, user,
-                            ordermethod='fetch_order', visited=None):
-        eschema = cls.e_schema
-        if visited is None:
-            visited = set((eschema.type,))
-        elif eschema.type in visited:
-            # avoid infinite recursion
-            return
-        else:
-            visited.add(eschema.type)
-        _fetchattrs = []
-        for attr in fetchattrs:
-            try:
-                rschema = eschema.subject_relation(attr)
-            except KeyError:
-                cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
-                            attr, cls.id)
-                continue
-            if not user.matching_groups(rschema.get_groups('read')):
-                continue
-            var = varmaker.next()
-            selection.append(var)
-            restriction = '%s %s %s' % (mainvar, attr, var)
-            restrictions.append(restriction)
-            if not rschema.is_final():
-                # XXX this does not handle several destination types
-                desttype = rschema.objects(eschema.type)[0]
-                card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
-                if card not in '?1':
-                    selection.pop()
-                    restrictions.pop()
-                    continue
-                if card == '?':
-                    restrictions[-1] += '?' # left outer join if not mandatory
-                destcls = cls.vreg.etype_class(desttype)
-                destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
-                                            selection, orderby, restrictions,
-                                            user, ordermethod, visited=visited)
-            orderterm = getattr(cls, ordermethod)(attr, var)
-            if orderterm:
-                orderby.append(orderterm)
-        return selection, orderby, restrictions
-
-    def __init__(self, req, rset, row=None, col=0):
-        AppRsetObject.__init__(self, req, rset)
-        dict.__init__(self)
-        self.row, self.col = row, col
-        self._related_cache = {}
-        if rset is not None:
-            self.eid = rset[row][col]
-        else:
-            self.eid = None
-        self._is_saved = True
-        
-    def __repr__(self):
-        return '<Entity %s %s %s at %s>' % (
-            self.e_schema, self.eid, self.keys(), id(self))
-
-    def __nonzero__(self):
-        return True
-
-    def __hash__(self):
-        return id(self)
-
-    def pre_add_hook(self):
-        """hook called by the repository before doing anything to add the entity
-        (before_add entity hooks have not been called yet). This give the
-        occasion to do weird stuff such as autocast (File -> Image for instance).
-        
-        This method must return the actual entity to be added.
-        """
-        return self
-    
-    def set_eid(self, eid):
-        self.eid = self['eid'] = eid
-
-    def has_eid(self):
-        """return True if the entity has an attributed eid (False
-        meaning that the entity has to be created
-        """
-        try:
-            int(self.eid)
-            return True
-        except (ValueError, TypeError):
-            return False
-
-    def is_saved(self):
-        """during entity creation, there is some time during which the entity
-        has an eid attributed though it's not saved (eg during before_add_entity
-        hooks). You can use this method to ensure the entity has an eid *and* is
-        saved in its source.
-        """
-        return self.has_eid() and self._is_saved
-    
-    @cached
-    def metainformation(self):
-        res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
-        res['source'] = self.req.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)
-
-    def check_perm(self, action):
-        self.e_schema.check_perm(self.req, action, self.eid)
-
-    def has_perm(self, action):
-        return self.e_schema.has_perm(self.req, action, self.eid)
-        
-    def view(self, vid, __registry='views', **kwargs):
-        """shortcut to apply a view on this entity"""
-        return self.vreg.render(__registry, vid, self.req, rset=self.rset,
-                                row=self.row, col=self.col, **kwargs)
-
-    def absolute_url(self, method=None, **kwargs):
-        """return an absolute url to view this entity"""
-        # 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':
-            kwargs['base_url'] = self.metainformation()['source'].get('base-url')
-        if method is None or method == 'view':
-            kwargs['_restpath'] = self.rest_path()
-        else:
-            kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
-        return self.build_url(method, **kwargs)
-
-    def rest_path(self):
-        """returns a REST-like (relative) path for this entity"""
-        mainattr, needcheck = self._rest_attr_info()
-        etype = str(self.e_schema)
-        if mainattr == 'eid':
-            value = self.eid
-        else:
-            value = getattr(self, mainattr)
-            if value is None:
-                return '%s/eid/%s' % (etype.lower(), self.eid)
-        if needcheck:
-            # make sure url is not ambiguous
-            rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
-            if value is not None:
-                nbresults = self.req.execute(rql, {'value' : value})[0][0]
-                # may an assertion that nbresults is not 0 would be a good idea
-                if nbresults != 1: # no ambiguity
-                    return '%s/eid/%s' % (etype.lower(), self.eid)
-        return '%s/%s' % (etype.lower(), self.req.url_quote(value))
-
-    @classmethod
-    def _rest_attr_info(cls):
-        mainattr, needcheck = 'eid', True
-        if cls.rest_attr:
-            mainattr = cls.rest_attr
-            needcheck = not cls.e_schema.has_unique_values(mainattr)
-        else:
-            for rschema in cls.e_schema.subject_relations():
-                if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
-                    mainattr = str(rschema)
-                    needcheck = False
-                    break
-        if mainattr == 'eid':
-            needcheck = False
-        return mainattr, needcheck
-
-    @cached
-    def formatted_attrs(self):
-        """returns the list of attributes which have some format information
-        (i.e. rich text strings)
-        """
-        attrs = []
-        for rschema, attrschema in self.e_schema.attribute_definitions():
-            if attrschema.type == 'String' and self.has_format(rschema):
-                attrs.append(rschema.type)
-        return attrs
-        
-    def format(self, attr):
-        """return the mime type format for an attribute (if specified)"""
-        return getattr(self, '%s_format' % attr, None)
-    
-    def text_encoding(self, attr):
-        """return the text encoding for an attribute, default to site encoding
-        """
-        encoding = getattr(self, '%s_encoding' % attr, None)
-        return encoding or self.vreg.property_value('ui.encoding')
-
-    def has_format(self, attr):
-        """return true if this entity's schema has a format field for the given
-        attribute
-        """
-        return self.e_schema.has_subject_relation('%s_format' % attr)
-    
-    def has_text_encoding(self, attr):
-        """return true if this entity's schema has ab encoding field for the
-        given attribute
-        """
-        return self.e_schema.has_subject_relation('%s_encoding' % attr)
-
-    def printable_value(self, attr, value=_marker, attrtype=None,
-                        format='text/html', displaytime=True):
-        """return a displayable value (i.e. unicode string) which may contains
-        html tags
-        """
-        attr = str(attr)
-        if value is _marker:
-            value = getattr(self, attr)
-        if isinstance(value, basestring):
-            value = value.strip()
-        if value is None or value == '': # don't use "not", 0 is an acceptable value
-            return u''
-        if attrtype is None:
-            attrtype = self.e_schema.destination(attr)
-        props = self.e_schema.rproperties(attr)
-        if attrtype == 'String':
-            # internalinalized *and* formatted string such as schema
-            # description...
-            if props.get('internationalizable'):
-                value = self.req._(value)
-            attrformat = self.format(attr)
-            if attrformat:
-                return self.mtc_transform(value, attrformat, format,
-                                          self.req.encoding)
-        elif attrtype == 'Bytes':
-            attrformat = self.format(attr)
-            if attrformat:
-                try:
-                    encoding = getattr(self, '%s_encoding' % attr)
-                except AttributeError:
-                    encoding = self.req.encoding
-                return self.mtc_transform(value.getvalue(), attrformat, format,
-                                          encoding)
-            return u''
-        value = printable_value(self.req, attrtype, value, props, displaytime)
-        if format == 'text/html':
-            value = html_escape(value)
-        return value
-
-    def mtc_transform(self, data, format, target_format, encoding,
-                      _engine=ENGINE):
-        trdata = TransformData(data, format, encoding, appobject=self)
-        data = _engine.convert(trdata, target_format).decode()
-        if format == 'text/html':
-            data = soup2xhtml(data, self.req.encoding)                
-        return data
-    
-    # entity cloning ##########################################################
-
-    def copy_relations(self, ceid):
-        """copy relations of the object with the given eid on this object
-
-        By default meta and composite relations are skipped.
-        Overrides this if you want another behaviour
-        """
-        assert self.has_eid()
-        execute = self.req.execute
-        for rschema in self.e_schema.subject_relations():
-            if rschema.meta or rschema.is_final():
-                continue
-            # skip already defined relations
-            if getattr(self, rschema.type):
-                continue
-            if rschema.type in self.skip_copy_for:
-                continue
-            if rschema.type == 'in_state':
-                # if the workflow is defining an initial state (XXX AND we are
-                # not in the managers group? not done to be more consistent)
-                # don't try to copy in_state
-                if execute('Any S WHERE S state_of ET, ET initial_state S,'
-                           'ET name %(etype)s', {'etype': str(self.e_schema)}):
-                    continue
-            # skip composite relation
-            if self.e_schema.subjrproperty(rschema, 'composite'):
-                continue
-            # skip relation with card in ?1 else we either change the copied
-            # object (inlined relation) or inserting some inconsistency
-            if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
-                continue
-            rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
-                rschema.type, rschema.type)
-            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
-            self.clear_related_cache(rschema.type, 'subject')
-        for rschema in self.e_schema.object_relations():
-            if rschema.meta:
-                continue
-            # skip already defined relations
-            if getattr(self, 'reverse_%s' % rschema.type):
-                continue
-            # skip composite relation
-            if self.e_schema.objrproperty(rschema, 'composite'):
-                continue
-            # skip relation with card in ?1 else we either change the copied
-            # object (inlined relation) or inserting some inconsistency
-            if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
-                continue
-            rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
-                rschema.type, rschema.type)
-            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
-            self.clear_related_cache(rschema.type, 'object')
-
-    # data fetching methods ###################################################
-
-    @cached
-    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)
-                       
-    def to_complete_relations(self):
-        """by default complete final relations to when calling .complete()"""
-        for rschema in self.e_schema.subject_relations():
-            if rschema.is_final():
-                continue
-            if len(rschema.objects(self.e_schema)) > 1:
-                # ambigous relations, the querier doesn't handle
-                # outer join correctly in this case
-                continue
-            if rschema.inlined:
-                matching_groups = self.req.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)):
-                    yield rschema, 'subject'
-                    
-    def to_complete_attributes(self, skip_bytes=True):
-        for rschema, attrschema in self.e_schema.attribute_definitions():
-            # skip binary data by default
-            if skip_bytes and attrschema.type == 'Bytes':
-                continue
-            attr = rschema.type
-            if attr == 'eid':
-                continue
-            # password retreival is blocked at the repository server level
-            if not self.req.user.matching_groups(rschema.get_groups('read')) \
-                   or attrschema.type == 'Password':
-                self[attr] = None
-                continue
-            yield attr
-            
-    def complete(self, attributes=None, skip_bytes=True):
-        """complete this entity by adding missing attributes (i.e. query the
-        repository to fill the entity)
-
-        :type skip_bytes: bool
-        :param skip_bytes:
-          if true, attribute of type Bytes won't be considered
-        """
-        assert self.has_eid()
-        varmaker = rqlvar_maker()
-        V = varmaker.next()
-        rql = ['WHERE %s eid %%(x)s' % V]
-        selected = []
-        for attr in (attributes or self.to_complete_attributes(skip_bytes)):
-            # if attribute already in entity, nothing to do
-            if self.has_key(attr):
-                continue
-            # case where attribute must be completed, but is not yet in entity
-            var = varmaker.next()
-            rql.append('%s %s %s' % (V, attr, var))
-            selected.append((attr, var))
-        # +1 since this doen't include the main variable
-        lastattr = len(selected) + 1
-        if attributes is None:
-            # fetch additional relations (restricted to 0..1 relations)
-            for rschema, role in self.to_complete_relations():
-                rtype = rschema.type
-                if self.relation_cached(rtype, role):
-                    continue
-                var = varmaker.next()
-                if role == 'subject':
-                    targettype = rschema.objects(self.e_schema)[0]
-                    card = rschema.rproperty(self.e_schema, targettype,
-                                             'cardinality')[0]
-                    if card == '1':
-                        rql.append('%s %s %s' % (V, rtype, var))
-                    else: # '?"
-                        rql.append('%s %s %s?' % (V, rtype, var))
-                else:
-                    targettype = rschema.subjects(self.e_schema)[1]
-                    card = rschema.rproperty(self.e_schema, targettype,
-                                             'cardinality')[1]
-                    if card == '1':
-                        rql.append('%s %s %s' % (var, rtype, V))
-                    else: # '?"
-                        rql.append('%s? %s %s' % (var, rtype, V))
-                assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
-                                                      role, card)
-                selected.append(((rtype, role), var))
-        if selected:
-            # select V, we need it as the left most selected variable
-            # 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)
-            rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
-            # handle attributes
-            for i in xrange(1, lastattr):
-                self[str(selected[i-1][0])] = rset[i]
-            # handle relations
-            for i in xrange(lastattr, len(rset)):
-                rtype, x = selected[i-1][0]
-                value = rset[i]
-                if value is None:
-                    rrset = ResultSet([], rql, {'x': self.eid})
-                    self.req.decorate_rset(rrset)
-                else:
-                    rrset = self.req.eid_rset(value)
-                self.set_related_cache(rtype, x, rrset)
-                
-    def get_value(self, name):
-        """get value for the attribute relation <name>, query the repository
-        to get the value if necessary.
-
-        :type name: str
-        :param name: name of the attribute to get
-        """
-        try:
-            value = self[name]
-        except KeyError:
-            if not self.is_saved():
-                return None
-            rql = "Any A WHERE X eid %%(x)s, X %s A" % name
-            # XXX should we really use unsafe_execute here??
-            execute = getattr(self.req, 'unsafe_execute', self.req.execute)
-            try:
-                rset = execute(rql, {'x': self.eid}, 'x')
-            except Unauthorized:
-                self[name] = value = None
-            else:
-                assert rset.rowcount <= 1, (self, rql, rset.rowcount)
-                try:
-                    self[name] = value = rset.rows[0][0]
-                except IndexError:
-                    # probably a multisource error
-                    self.critical("can't get value for attribute %s of entity with eid %s",
-                                  name, self.eid)
-                    if self.e_schema.destination(name) == 'String':
-                        self[name] = value = self.req._('unaccessible')
-                    else:
-                        self[name] = value = None
-        return value
-
-    def related(self, rtype, role='subject', limit=None, entities=False):
-        """returns a resultset of related entities
-        
-        :param role: is the role played by 'self' in the relation ('subject' or 'object')
-        :param limit: resultset's maximum size
-        :param entities: if True, the entites are returned; if False, a result set is returned
-        """
-        try:
-            return self.related_cache(rtype, role, entities, limit)
-        except KeyError:
-            pass
-        assert self.has_eid()
-        rql = self.related_rql(rtype, role)
-        rset = self.req.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'):
-        rschema = self.schema[rtype]
-        if role == 'subject':
-            targettypes = rschema.objects(self.e_schema)
-            restriction = 'E eid %%(x)s, E %s X' % rtype
-            card = greater_card(rschema, (self.e_schema,), targettypes, 0)
-        else:
-            targettypes = rschema.subjects(self.e_schema)
-            restriction = 'E eid %%(x)s, X %s E' % rtype
-            card = greater_card(rschema, targettypes, (self.e_schema,), 1)
-        if len(targettypes) > 1:
-            fetchattrs_list = []
-            for ttype in targettypes:
-                etypecls = self.vreg.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,
-                                     settype=False)
-        else:
-            etypecls = self.vreg.etype_class(targettypes[0])
-            rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
-        # optimisation: remove ORDERBY if cardinality is 1 or ? (though
-        # greater_card return 1 for those both cases)
-        if card == '1':
-            if ' ORDERBY ' in rql:
-                rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
-                                       rql.split(' WHERE ', 1)[1])
-        elif not ' ORDERBY ' in rql:
-            args = tuple(rql.split(' WHERE ', 1))
-            rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
-        return rql
-    
-    # generic vocabulary methods ##############################################
-
-    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
-        """
-        try:
-            vocabfunc = getattr(self, '%s_%s_vocabulary' % (role, rtype))
-        except AttributeError:
-            vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
-        # 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
-        #       couples (label, None) which act as separators. In these
-        #       cases, it doesn't make sense to sort results afterwards.
-        return vocabfunc(rtype, limit)
-            
-    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)
-        """
-        if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
-        done = None
-        assert not rtype.is_final(), rtype
-        if self.has_eid():
-            done = set(e.eid for e in getattr(self, str(rtype)))
-        result = []
-        rsetsize = None
-        for objtype in rtype.objects(self.e_schema):
-            if limit is not None:
-                rsetsize = limit - len(result)
-            result += self.relation_vocabulary(rtype, objtype, 'subject',
-                                               rsetsize, done)
-            if limit is not None and len(result) >= limit:
-                break
-        return result
-
-    def object_relation_vocabulary(self, rtype, limit=None):
-        """defaut vocabulary method for the given relation, looking for
-        relation's subject entities (i.e. self is the object)
-        """
-        if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
-        done = None
-        if self.has_eid():
-            done = set(e.eid for e in getattr(self, 'reverse_%s' % rtype))
-        result = []
-        rsetsize = None
-        for subjtype in rtype.subjects(self.e_schema):
-            if limit is not None:
-                rsetsize = limit - len(result)
-            result += self.relation_vocabulary(rtype, subjtype, 'object',
-                                               rsetsize, done)
-            if limit is not None and len(result) >= limit:
-                break
-        return result
-
-    def relation_vocabulary(self, rtype, targettype, role,
-                            limit=None, done=None):
-        if done is None:
-            done = set()
-        req = self.req
-        rset = self.unrelated(rtype, targettype, role, limit)
-        res = []
-        for entity in rset.entities():
-            if entity.eid in done:
-                continue
-            done.add(entity.eid)
-            res.append((entity.view('combobox'), entity.eid))
-        return res
-
-    def unrelated_rql(self, rtype, targettype, role, ordermethod=None,
-                      vocabconstraints=True):
-        """build a rql to fetch `targettype` entities unrelated to this entity
-        using (rtype, role) relation
-        """
-        ordermethod = ordermethod or 'fetch_unrelated_order'
-        if isinstance(rtype, basestring):
-            rtype = self.schema.rschema(rtype)
-        if role == 'subject':
-            evar, searchedvar = 'S', 'O'
-            subjtype, objtype = self.e_schema, targettype
-        else:
-            searchedvar, evar = 'S', 'O'
-            objtype, subjtype = self.e_schema, targettype
-        if self.has_eid():
-            restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
-        else:
-            restriction = []
-        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
-        if vocabconstraints:
-            # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
-            # will be included as well
-            restriction += [cstr.restriction for cstr in constraints
-                            if isinstance(cstr, RQLVocabularyConstraint)]
-        else:
-            restriction += [cstr.restriction for cstr in constraints
-                            if isinstance(cstr, RQLConstraint)]
-        etypecls = self.vreg.etype_class(targettype)
-        rql = etypecls.fetch_rql(self.req.user, restriction,
-                                 mainvar=searchedvar, ordermethod=ordermethod)
-        # ensure we have an order defined
-        if not ' ORDERBY ' in rql:
-            before, after = rql.split(' WHERE ', 1)
-            rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
-        return rql
-    
-    def unrelated(self, rtype, targettype, role='subject', limit=None,
-                  ordermethod=None):
-        """return a result set of target type objects that may be related
-        by a given relation, with self as subject or object
-        """
-        rql = self.unrelated_rql(rtype, targettype, role, ordermethod)
-        if limit is not None:
-            before, after = rql.split(' WHERE ', 1)
-            rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
-        if self.has_eid():
-            return self.req.execute(rql, {'x': self.eid})
-        return self.req.execute(rql)
-        
-    # relations cache handling ################################################
-    
-    def relation_cached(self, rtype, role):
-        """return true if the given relation is already cached on the instance
-        """
-        return '%s_%s' % (rtype, role) in self._related_cache
-    
-    def related_cache(self, rtype, role, entities=True, limit=None):
-        """return values for the given relation if it's cached on the instance,
-        else raise `KeyError`
-        """
-        res = self._related_cache['%s_%s' % (rtype, role)][entities]
-        if limit:
-            if entities:
-                res = res[:limit]
-            else:
-                res = res.limit(limit)
-        return res
-    
-    def set_related_cache(self, rtype, role, rset, col=0):
-        """set cached values for the given relation"""
-        if rset:
-            related = list(rset.entities(col))
-            rschema = self.schema.rschema(rtype)
-            if role == 'subject':
-                rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
-                                          'cardinality')[1]
-                target = 'object'
-            else:
-                rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
-                                          'cardinality')[0]
-                target = 'subject'
-            if rcard in '?1':
-                for rentity in related:
-                    rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self])
-        else:
-            related = []
-        self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
-        
-    def clear_related_cache(self, rtype=None, role=None):
-        """clear cached values for the given relation or the entire cache if
-        no relation is given
-        """
-        if rtype is None:
-            self._related_cache = {}
-        else:
-            assert role
-            self._related_cache.pop('%s_%s' % (rtype, role), None)
-        
-    # raw edition utilities ###################################################
-    
-    def set_attributes(self, **kwargs):
-        assert kwargs
-        relations = []
-        for key in kwargs:
-            relations.append('X %s %%(%s)s' % (key, key))
-        # update current local object
-        self.update(kwargs)
-        # and now update the database
-        kwargs['x'] = self.eid
-        self.req.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,
-                         {'x': self.eid})
-    
-    # server side utilities ###################################################
-        
-    def set_defaults(self):
-        """set default values according to the schema"""
-        self._default_set = set()
-        for attr, value in self.e_schema.defaults():
-            if not self.has_key(attr):
-                self[str(attr)] = value
-                self._default_set.add(attr)
-
-    def check(self, creation=False):
-        """check this entity against its schema. Only final relation
-        are checked here, constraint on actual relations are checked in hooks
-        """
-        # necessary since eid is handled specifically and yams require it to be
-        # in the dictionary
-        if self.req is None:
-            _ = unicode
-        else:
-            _ = self.req._
-        self.e_schema.check(self, creation=creation, _=_)
-
-    def fti_containers(self, _done=None):
-        if _done is None:
-            _done = set()
-        _done.add(self.eid)
-        containers = tuple(self.e_schema.fulltext_containers())
-        if containers:
-            yielded = False
-            for rschema, target in containers:
-                if target == 'object':
-                    targets = getattr(self, rschema.type)
-                else:
-                    targets = getattr(self, 'reverse_%s' % rschema)
-                for entity in targets:
-                    if entity.eid in _done:
-                        continue
-                    for container in entity.fti_containers(_done):
-                        yield container
-                        yielded = True
-            if not yielded:
-                yield self
-        else:
-            yield self
-                    
-    def get_words(self):
-        """used by the full text indexer to get words to index
-
-        this method should only be used on the repository side since it depends
-        on the indexer package
-        
-        :rtype: list
-        :return: the list of indexable word of this entity
-        """
-        from indexer.query_objects import tokenize
-        words = []
-        for rschema in self.e_schema.indexable_attributes():
-            try:
-                value = self.printable_value(rschema, format='text/plain')
-            except TransformError, ex:
-                continue
-            except:
-                self.exception("can't add value of %s to text index for entity %s",
-                               rschema, self.eid)
-                continue
-            if value:
-                words += tokenize(value)
-        
-        for rschema, role in self.e_schema.fulltext_relations():
-            if role == 'subject':
-                for entity in getattr(self, rschema.type):
-                    words += entity.get_words()
-            else: # if role == 'object':
-                for entity in getattr(self, 'reverse_%s' % rschema.type):
-                    words += entity.get_words()
-        return words
-
-
-# attribute and relation descriptors ##########################################
-
-class Attribute(object):
-    """descriptor that controls schema attribute access"""
-
-    def __init__(self, attrname):
-        assert attrname != 'eid'
-        self._attrname = attrname
-
-    def __get__(self, eobj, eclass):
-        if eobj is None:
-            return self
-        return eobj.get_value(self._attrname)
-
-    def __set__(self, eobj, value):
-        # XXX bw compat
-        # would be better to generate UPDATE queries than the current behaviour
-        eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)")
-        eobj[self._attrname] = value
-
-
-class Relation(object):
-    """descriptor that controls schema relation access"""
-    _role = None # for pylint
-
-    def __init__(self, rschema):
-        self._rschema = rschema
-        self._rtype = rschema.type
-
-    def __get__(self, eobj, eclass):
-        if eobj is None:
-            raise AttributeError('%s cannot be only be accessed from instances'
-                                 % self._rtype)
-        return eobj.related(self._rtype, self._role, entities=True)
-    
-    def __set__(self, eobj, value):
-        raise NotImplementedError
-
-
-class SubjectRelation(Relation):
-    """descriptor that controls schema relation access"""
-    _role = 'subject'
-    
-class ObjectRelation(Relation):
-    """descriptor that controls schema relation access"""
-    _role = 'object'
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(Entity, getLogger('cubicweb.entity'))
+"""pre 3.2 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.entity', DeprecationWarning, stacklevel=2)
+from cubicweb.entity import *
+from cubicweb.entity import _marker
--- a/common/html4zope.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision: 1.2 $
-# Date: $Date: 2005-07-04 16:36:50 $
-# Copyright: This module has been placed in the public domain.
-
-"""
-Simple HyperText Markup Language document tree Writer.
-
-The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
-HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
-minimum of formatting information.  A cascading style sheet ("default.css" by
-default) is required for proper viewing with a modern graphical browser.
-
-http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
-"""
-
-__docformat__ = 'reStructuredText'
-
-from logilab.mtconverter import html_escape
-
-from docutils import nodes
-from docutils.writers.html4css1 import Writer as CSS1Writer
-from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
-
-default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
-
-class Writer(CSS1Writer):
-    """css writer using our html translator"""
-    def __init__(self, base_url):
-        CSS1Writer.__init__(self)
-        self.translator_class = URLBinder(base_url, HTMLTranslator)
-
-    def apply_template(self):
-        """overriding this is necessary with docutils >= 0.5"""
-        return self.visitor.astext()
-
-class URLBinder:
-    def __init__(self, url, klass):
-        self.base_url = url
-        self.translator_class = HTMLTranslator
-        
-    def __call__(self, document):
-        translator = self.translator_class(document)
-        translator.base_url = self.base_url
-        return translator
-    
-class HTMLTranslator(CSS1HTMLTranslator):
-    """ReST tree to html translator"""
-
-    def astext(self):
-        """return the extracted html"""
-        return ''.join(self.body)
-    
-    def visit_title(self, node):
-        """Only 6 section levels are supported by HTML."""
-        if isinstance(node.parent, nodes.topic):
-            self.body.append(
-                  self.starttag(node, 'p', '', CLASS='topic-title'))
-            if node.parent.hasattr('id'):
-                self.body.append(
-                    self.starttag({}, 'a', '', name=node.parent['id']))
-                self.context.append('</a></p>\n')
-            else:
-                self.context.append('</p>\n')
-        elif self.section_level == 0:
-            # document title
-            self.head.append('<title>%s</title>\n'
-                             % self.encode(node.astext()))
-            self.body.append(self.starttag(node, 'h%d' % default_level, '',
-                                           CLASS='title'))
-            self.context.append('</h%d>\n' % default_level)
-        else:
-            self.body.append(
-                  self.starttag(node, 'h%s' % (
-                default_level+self.section_level-1), ''))
-            atts = {}
-            if node.hasattr('refid'):
-                atts['class'] = 'toc-backref'
-                atts['href'] = '%s#%s' % (self.base_url, node['refid'])
-            self.body.append(self.starttag({}, 'a', '', **atts))
-            self.context.append('</a></h%s>\n' % (
-                default_level+self.section_level-1))
-
-    def visit_subtitle(self, node):
-        """format a subtitle"""
-        if isinstance(node.parent, nodes.sidebar):
-            self.body.append(self.starttag(node, 'p', '',
-                                           CLASS='sidebar-subtitle'))
-            self.context.append('</p>\n')
-        else:
-            self.body.append(
-                  self.starttag(node, 'h%s' % (default_level+1), '',
-                                CLASS='subtitle'))
-            self.context.append('</h%s>\n' % (default_level+1))
-
-    def visit_document(self, node):
-        """syt: i don't want the enclosing <div class="document">"""
-    def depart_document(self, node):
-        """syt: i don't want the enclosing <div class="document">"""
-
-    def visit_reference(self, node):
-        """syt: i want absolute urls"""
-        if node.has_key('refuri'):
-            href = node['refuri']
-            if ( self.settings.cloak_email_addresses
-                 and href.startswith('mailto:')):
-                href = self.cloak_mailto(href)
-                self.in_mailto = 1
-        else:
-            assert node.has_key('refid'), \
-                   'References must have "refuri" or "refid" attribute.'
-            href = '%s#%s' % (self.base_url, node['refid'])
-        atts = {'href': href, 'class': 'reference'}
-        if not isinstance(node.parent, nodes.TextElement):
-            assert len(node) == 1 and isinstance(node[0], nodes.image)
-            atts['class'] += ' image-reference'
-        self.body.append(self.starttag(node, 'a', '', **atts))
-
-    ## override error messages to avoid XHTML problems ########################
-    def visit_problematic(self, node):
-        pass
-
-    def depart_problematic(self, node):
-        pass
-    
-    def visit_system_message(self, node):
-        backref_text = ''
-        if len(node['backrefs']):
-            backrefs = node['backrefs']
-            if len(backrefs) == 1:
-                backref_text = '; <em>backlink</em>'
-            else:
-                i = 1
-                backlinks = []
-                for backref in backrefs:
-                    backlinks.append(str(i))
-                    i += 1
-                backref_text = ('; <em>backlinks: %s</em>'
-                                % ', '.join(backlinks))
-        if node.hasattr('line'):
-            line = ', line %s' % node['line']
-        else:
-            line = ''
-        a_start = a_end = ''
-        error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
-            a_start, node['type'], node['level'], a_end,
-            self.encode(node['source']), line, backref_text)
-        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
-
-    def depart_system_message(self, node):
-        pass
--- a/common/i18n.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/i18n.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,14 +1,14 @@
 """Some i18n/gettext utilities.
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 import re
 import os
-from os.path import join, abspath, basename, splitext, exists
+from os.path import join, basename, splitext, exists
 from glob import glob
 
 from cubicweb.toolsutils import create_dir
--- a/common/mixins.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/mixins.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,9 +7,10 @@
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.common.deprecation import obsolete
 from logilab.common.decorators import cached
 
-from cubicweb.common.selectors import implement_interface
+from cubicweb.selectors import implements
 from cubicweb.interfaces import IWorkflowable, IEmailable, ITree
 
 
@@ -187,7 +188,7 @@
         if rset:
             return rset.get_entity(0, 0)
         return None
-    
+
     def change_state(self, stateeid, trcomment=None, trcommentformat=None):
         """change the entity's state according to a state defined in given
         parameters
@@ -218,26 +219,6 @@
         """return the latest transition information for this entity"""
         return self.reverse_wf_info_for[-1]
             
-    # specific vocabulary methods #############################################
-
-    def subject_in_state_vocabulary(self, rschema, limit=None):
-        """vocabulary method for the in_state relation, looking for
-        relation's object entities (i.e. self is the subject) according
-        to initial_state, state_of and next_state relation
-        """
-        if not self.has_eid() or not self.in_state:
-            # get the initial state
-            rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
-            rset = self.req.execute(rql, {'etype': str(self.e_schema)})
-            if rset:
-                return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])]
-            return []
-        results = []
-        for tr in self.in_state[0].transitions(self):
-            state = tr.destination_state[0]
-            results.append((state.view('combobox'), state.eid))
-        return sorted(results)
-            
     # __method methods ########################################################
     
     def set_state(self, params=None):
@@ -248,6 +229,13 @@
         self.change_state(int(params.pop('state')), params.get('trcomment'),
                           params.get('trcommentformat'))
         self.req.set_message(self.req._('__msg state changed'))
+            
+    # specific vocabulary methods #############################################
+    
+    @obsolete('use EntityFieldsForm.object_relation_vocabulary')
+    def subject_in_state_vocabulary(self, rschema, limit=None):
+        from cubicweb.web.form import EntityFieldsForm
+        return EntityFieldsForm(self.req, None, entity=self).subject_in_state_vocabulary(rschema, limit)
 
 
 
@@ -316,8 +304,7 @@
     """a recursive tree view"""
     id = 'tree'
     item_vid = 'treeitem'
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ITree,)
+    __select__ = implements(ITree)
 
     def call(self, done=None, **kwargs):
         if done is None:
--- a/common/mttransforms.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/mttransforms.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """mime type transformation engine for cubicweb, based on mtconverter
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -14,7 +14,7 @@
                                  register_pil_transforms, 
                                  register_pygments_transforms)
 
-from cubicweb.common.uilib import rest_publish, html_publish, remove_html_tags
+from cubicweb.common.uilib import rest_publish, html_publish
 
 HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
 
@@ -31,16 +31,7 @@
     output = 'text/html'
     def _convert(self, trdata):
         return html_publish(trdata.appobject, trdata.data)
-
-class ept_to_html(Transform):
-    inputs = ('text/cubicweb-page-template',)
-    output = 'text/html'
-    output_encoding = 'utf-8'
-    def _convert(self, trdata):
-        from cubicweb.common.tal import compile_template
-        value = trdata.encode(self.output_encoding)
-        return trdata.appobject.tal_render(compile_template(value), {})
-
+    
 
 # Instantiate and configure the transformation engine
 
@@ -49,7 +40,26 @@
 ENGINE = TransformEngine()
 ENGINE.add_transform(rest_to_html())
 ENGINE.add_transform(html_to_html())
-ENGINE.add_transform(ept_to_html())
+
+try:
+    from cubicweb.ext.tal import compile_template
+except ImportError:
+    HAS_TAL = False
+    from cubicweb.schema import FormatConstraint
+    FormatConstraint.need_perm_formats.remove('text/cubicweb-page-template')
+    
+else:
+    HAS_TAL = True
+    
+    class ept_to_html(Transform):
+        inputs = ('text/cubicweb-page-template',)
+        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), {})
+
+    ENGINE.add_transform(ept_to_html())
 
 if register_pil_transforms(ENGINE, verb=False):
     HAS_PIL_TRANSFORMS = True
--- a/common/registerers.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/registerers.py	Tue Apr 07 09:30:23 2009 +0200
@@ -5,22 +5,13 @@
 to the application's schema or to already registered object
 
 :organization: Logilab
-:copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.vregistry import registerer
-
-
-def _accepts_interfaces(obj):
-    return sorted(getattr(obj, 'accepts_interfaces', ()))
-
-
-class yes_registerer(registerer):
-    """register without any other action"""
-    def do_it_yourself(self, registered):
-        return self.vobject
+from cubicweb.vregistry import registerer, yes_registerer
+from cubicweb.cwvreg import use_interfaces
 
 class priority_registerer(registerer):
     """systematically kick previous registered class and register the
@@ -54,17 +45,6 @@
 
     def equivalent(self, other):
         raise NotImplementedError(self, self.vobject)
-
-
-class kick_registerer(registerer):
-    """systematically kick previous registered class and don't register the
-    wrapped class. This is temporarily used to discard library object registrable
-    but that we don't want to use
-    """
-    def do_it_yourself(self, registered):
-        if registered:
-            self.kick(registered, registered[-1])
-        return 
     
 
 class accepts_registerer(priority_registerer):
@@ -81,25 +61,14 @@
     """
     def do_it_yourself(self, registered):
         # if object is accepting interface, we have register it now and
-        # remove it latter if no object is implementing accepted interfaces
-        if _accepts_interfaces(self.vobject):
+        # remove it later if no object is implementing accepted interfaces
+        if use_interfaces(self.vobject):
             return self.vobject
-        if not 'Any' in self.vobject.accepts:
-            for ertype in self.vobject.accepts:
-                if ertype in self.schema:
-                    break
-            else:
-                self.skip()
-                return None
-        for required in getattr(self.vobject, 'requires', ()):
-            if required not in self.schema:
-                self.skip()
-                return
         self.remove_equivalents(registered)
         return self.vobject
     
     def equivalent(self, other):
-        if _accepts_interfaces(self.vobject) != _accepts_interfaces(other):
+        if use_interfaces(self.vobject) != use_interfaces(other):
             return False
         if getattr(self.vobject, 'require_groups', ()) != getattr(other, 'require_groups', ()):
             return False
@@ -118,90 +87,6 @@
             return False
 
 
-class id_registerer(priority_registerer):
-    """register according to the "id" attribute of the wrapped class,
-    refering to an entity type.
-    
-    * if the type is not Any and is not defined the application'schema,
-      skip the wrapped class
-    * if an object previously registered has the same .id attribute,
-      kick it out
-    * register
-    """
-    def do_it_yourself(self, registered):
-        etype = self.vobject.id
-        if etype != 'Any' and not self.schema.has_entity(etype):
-            self.skip()
-            return
-        self.remove_equivalents(registered)
-        return self.vobject
-    
-    def equivalent(self, other):
-        return other.id == self.vobject.id
-
-
-class etype_rtype_registerer(registerer):
-    """registerer handling optional .etype and .rtype attributes.:
-    
-    * if .etype is set and is not an entity type defined in the
-      application schema, skip the wrapped class
-    * if .rtype or .relname is set and is not a relation type defined in
-      the application schema, skip the wrapped class
-    * register
-    """
-    def do_it_yourself(self, registered):
-        cls = self.vobject
-        if hasattr(cls, 'etype'):
-            if not self.schema.has_entity(cls.etype):
-                return
-        rtype = getattr(cls, 'rtype', None)
-        if rtype and not self.schema.has_relation(rtype):
-            return
-        return cls
-
-class etype_rtype_priority_registerer(etype_rtype_registerer):
-    """add priority behaviour to the etype_rtype_registerer
-    """
-    def do_it_yourself(self, registered):
-        cls = super(etype_rtype_priority_registerer, self).do_it_yourself(registered)
-        if cls:
-            registerer = priority_registerer(self.registry, cls)
-            cls = registerer.do_it_yourself(registered)
-        return cls
-
-class action_registerer(etype_rtype_registerer):
-    """'all in one' actions registerer, handling optional .accepts,
-    .etype and .rtype attributes:
-    
-    * if .etype is set and is not an entity type defined in the
-      application schema, skip the wrapped class
-    * if .rtype or .relname is set and is not a relation type defined in
-      the application schema, skip the wrapped class
-    * if .accepts is set, delegate to the accepts_registerer
-    * register
-    """
-    def do_it_yourself(self, registered):
-        cls = super(action_registerer, self).do_it_yourself(registered)
-        if hasattr(cls, 'accepts'):
-            registerer = accepts_registerer(self.registry, cls)
-            cls = registerer.do_it_yourself(registered)
-        return cls
-
-
-class extresources_registerer(priority_registerer):
-    """'registerer according to a .need_resources attributes which
-    should list necessary resource identifiers for the wrapped object.
-    If one of its resources is missing, don't register
-    """
-    def do_it_yourself(self, registered):
-        if not hasattr(self.config, 'has_resource'):
-            return
-        for resourceid in self.vobject.need_resources:
-            if not self.config.has_resource(resourceid):
-                return
-        return super(extresources_registerer, self).do_it_yourself(registered)
-    
-
 __all__ = [cls.__name__ for cls in globals().values()
            if isinstance(cls, type) and issubclass(cls, registerer)
            and not cls is registerer]
--- a/common/rest.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,263 +0,0 @@
-"""rest publishing functions
-
-contains some functions and setup of docutils for cubicweb. Provides the
-following ReST directives:
-
-* `eid`, create link to entity in the repository by their eid
-
-* `card`, create link to card entity in the repository by their wikiid
-  (proposing to create it when the refered card doesn't exist yet)
-
-* `winclude`, reference to a web documentation file (in wdoc/ directories)
-
-* `sourcecode` (if pygments is installed), source code colorization
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-from itertools import chain
-from logging import getLogger
-from os.path import join
-
-from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
-from docutils.parsers.rst import Parser, states, directives
-from docutils.parsers.rst.roles import register_canonical_role, set_classes
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.common.html4zope import Writer
-
-# We provide our own parser as an attempt to get rid of
-# state machine reinstanciation
-
-import re
-# compile states.Body patterns
-for k, v in states.Body.patterns.items():
-    if isinstance(v, str):
-        states.Body.patterns[k] = re.compile(v)
-
-# register ReStructured Text mimetype / extensions
-import mimetypes
-mimetypes.add_type('text/rest', '.rest')
-mimetypes.add_type('text/rest', '.rst')
-
-
-LOGGER = getLogger('cubicweb.rest')
-
-def eid_reference_role(role, rawtext, text, lineno, inliner,
-                       options={}, content=[]):
-    try:
-        try:
-            eid_num, rest = text.split(u':', 1)
-        except:
-            eid_num, rest = text, '#'+text
-        eid_num = int(eid_num)
-        if eid_num < 0:
-            raise ValueError
-    except ValueError:
-        msg = inliner.reporter.error(
-            'EID number must be a positive number; "%s" is invalid.'
-            % text, line=lineno)
-        prb = inliner.problematic(rawtext, rawtext, msg)
-        return [prb], [msg]
-    # Base URL mainly used by inliner.pep_reference; so this is correct:
-    context = inliner.document.settings.context
-    refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
-    ref = refedentity.absolute_url()
-    set_classes(options)
-    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
-                            **options)], []
-
-register_canonical_role('eid', eid_reference_role)
-
-
-def card_reference_role(role, rawtext, text, lineno, inliner,
-                       options={}, content=[]):
-    text = text.strip()
-    try:
-        wikiid, rest = text.split(u':', 1)
-    except:
-        wikiid, rest = text, text
-    context = inliner.document.settings.context
-    cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
-                                   {'id': wikiid})
-    if cardrset:
-        ref = cardrset.get_entity(0, 0).absolute_url()
-    else:
-        schema = context.schema
-        if schema.eschema('Card').has_perm(context.req, 'add'):
-            ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
-        else:
-            ref = '#'
-    set_classes(options)
-    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
-                            **options)], []
-
-register_canonical_role('card', card_reference_role)
-
-
-def winclude_directive(name, arguments, options, content, lineno,
-                       content_offset, block_text, state, state_machine):
-    """Include a reST file as part of the content of this reST file.
-
-    same as standard include directive but using config.locate_doc_resource to
-    get actual file to include.
-
-    Most part of this implementation is copied from `include` directive defined
-    in `docutils.parsers.rst.directives.misc`
-    """
-    context = state.document.settings.context
-    source = state_machine.input_lines.source(
-        lineno - state_machine.input_offset - 1)
-    #source_dir = os.path.dirname(os.path.abspath(source))
-    fid = arguments[0]
-    for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
-                      context.config.available_languages()):
-        rid = '%s_%s.rst' % (fid, lang)
-        resourcedir = context.config.locate_doc_file(rid)
-        if resourcedir:
-            break
-    else:
-        severe = state_machine.reporter.severe(
-              'Problems with "%s" directive path:\nno resource matching %s.'
-              % (name, fid),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    path = join(resourcedir, rid)
-    encoding = options.get('encoding', state.document.settings.input_encoding)
-    try:
-        state.document.settings.record_dependencies.add(path)
-        include_file = io.FileInput(
-            source_path=path, encoding=encoding,
-            error_handler=state.document.settings.input_encoding_error_handler,
-            handle_io_errors=None)
-    except IOError, error:
-        severe = state_machine.reporter.severe(
-              'Problems with "%s" directive path:\n%s: %s.'
-              % (name, error.__class__.__name__, error),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    try:
-        include_text = include_file.read()
-    except UnicodeError, error:
-        severe = state_machine.reporter.severe(
-              'Problem with "%s" directive:\n%s: %s'
-              % (name, error.__class__.__name__, error),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    if options.has_key('literal'):
-        literal_block = nodes.literal_block(include_text, include_text,
-                                            source=path)
-        literal_block.line = 1
-        return literal_block
-    else:
-        include_lines = statemachine.string2lines(include_text,
-                                                  convert_whitespace=1)
-        state_machine.insert_input(include_lines, path)
-        return []
-
-winclude_directive.arguments = (1, 0, 1)
-winclude_directive.options = {'literal': directives.flag,
-                              'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
-
-try:
-    from pygments import highlight
-    from pygments.lexers import get_lexer_by_name, LEXERS
-    from pygments.formatters import HtmlFormatter
-except ImportError:
-    pass
-else:
-    _PYGMENTS_FORMATTER = HtmlFormatter()
-
-    def pygments_directive(name, arguments, options, content, lineno,
-                           content_offset, block_text, state, state_machine):
-        try:
-            lexer = get_lexer_by_name(arguments[0])
-        except ValueError:
-            import traceback
-            traceback.print_exc()
-            print sorted(aliases for module_name, name, aliases, _, _  in LEXERS.itervalues())
-            # no lexer found
-            lexer = get_lexer_by_name('text')
-        print 'LEXER', lexer
-        parsed = highlight(u'\n'.join(content), lexer, _PYGMENTS_FORMATTER)
-        context = state.document.settings.context
-        context.req.add_css('pygments.css')
-        return [nodes.raw('', parsed, format='html')]
-     
-    pygments_directive.arguments = (1, 0, 1)
-    pygments_directive.content = 1
-    directives.register_directive('sourcecode', pygments_directive)
-
-
-class CubicWebReSTParser(Parser):
-    """The (customized) reStructuredText parser."""
-
-    def __init__(self):
-        self.initial_state = 'Body'
-        self.state_classes = states.state_classes
-        self.inliner = states.Inliner()
-        self.statemachine = states.RSTStateMachine(
-              state_classes=self.state_classes,
-              initial_state=self.initial_state,
-              debug=0)
-
-    def parse(self, inputstring, document):
-        """Parse `inputstring` and populate `document`, a document tree."""
-        self.setup_parse(inputstring, document)
-        inputlines = statemachine.string2lines(inputstring,
-                                               convert_whitespace=1)
-        self.statemachine.run(inputlines, document, inliner=self.inliner)
-        self.finish_parse()
-
-
-_REST_PARSER = CubicWebReSTParser()
-
-def rest_publish(context, data):
-    """publish a string formatted as ReStructured Text to HTML
-    
-    :type context: a cubicweb application object
-
-    :type data: str
-    :param data: some ReST text
-
-    :rtype: unicode
-    :return:
-      the data formatted as HTML or the original data if an error occured
-    """
-    req = context.req
-    if isinstance(data, unicode):
-        encoding = 'unicode'
-    else:
-        encoding = req.encoding
-    settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
-                'warning_stream': StringIO(), 'context': context,
-                # dunno what's the max, severe is 4, and we never want a crash
-                # (though try/except may be a better option...)
-                'halt_level': 10, 
-                }
-    if context:
-        if hasattr(req, 'url'):
-            base_url = req.url()
-        elif hasattr(context, 'absolute_url'):
-            base_url = context.absolute_url()
-        else:
-            base_url = req.base_url()
-    else:
-        base_url = None
-    try:
-        return publish_string(writer=Writer(base_url=base_url),
-                              parser=_REST_PARSER, source=data,
-                              settings_overrides=settings)
-    except Exception:
-        LOGGER.exception('error while publishing ReST text')
-        if not isinstance(data, unicode):
-            data = unicode(data, encoding, 'replace')
-        return html_escape(req._('error while publishing ReST text')
-                           + '\n\n' + data)
--- a/common/schema.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/schema.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,3 +1,5 @@
+"""pre 3.0 bw compat"""
+# pylint: disable-msg=W0614,W0401
 from warnings import warn
 warn('moved to cubicweb.schema', DeprecationWarning, stacklevel=2)
 from cubicweb.schema import *
--- a/common/selectors.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/selectors.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,571 +1,6 @@
-"""This file contains some basic selectors required by application objects.
-
-A selector is responsible to score how well an object may be used with a
-given result set (publishing time selection)
-
-If you have trouble with selectors, especially if the objet (typically
-a view or a component) you want to use is not selected and you want to
-know which one(s) of its selectors fail (e.g. returns 0), you can use
-`traced_selection` or even direclty `TRACED_OIDS`.
-
-`TRACED_OIDS` is a tuple of traced object ids. The special value
-'all' may be used to log selectors for all objects.
-
-For instance, say that the following code yields a `NoSelectableObject`
-exception::
-
-    self.view('calendar', myrset)
-
-You can log the selectors involved for *calendar* by replacing the line
-above by::
-
-    # in Python2.5
-    from cubicweb.common.selectors import traced_selection
-    with traced_selection():
-        self.view('calendar', myrset)
-
-    # in Python2.4
-    from cubicweb.common import selectors
-    selectors.TRACED_OIDS = ('calendar',)
-    self.view('calendar', myrset)
-    selectors.TRACED_OIDS = ()
- 
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import logging
-
-from logilab.common.compat import all
-from logilab.common.deprecation import deprecated_function
-
-from cubicweb import Unauthorized, NoSelectableObject, role
-from cubicweb.cwvreg import DummyCursorError
-from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject
-from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.schema import split_expression
-
-# helpers for debugging selectors
-SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
-TRACED_OIDS = ()
-
-def lltrace(selector):
-    # don't wrap selectors if not in development mode
-    if CubicWebConfiguration.mode == 'installed':
-        return selector
-    def traced(cls, *args, **kwargs):
-        ret = selector(cls, *args, **kwargs)
-        if TRACED_OIDS == 'all' or cls.id in TRACED_OIDS:
-            SELECTOR_LOGGER.critical('selector %s returned %s for %s', selector.__name__, ret, cls)
-        return ret
-    traced.__name__ = selector.__name__
-    return traced
-
-class traced_selection(object):
-    """selector debugging helper.
-
-    Typical usage is :
-
-    >>> with traced_selection():
-    ...     # some code in which you want to debug selectors
-    ...     # for all objects
-
-    or
-
-    >>> with traced_selection( ('oid1', 'oid2') ):
-    ...     # some code in which you want to debug selectors
-    ...     # for objects with id 'oid1' and 'oid2'
-    
-    """
-    def __init__(self, traced='all'):
-        self.traced = traced
-        
-    def __enter__(self):
-        global TRACED_OIDS
-        TRACED_OIDS = self.traced
-
-    def __exit__(self, exctype, exc, traceback):
-        global TRACED_OIDS
-        TRACED_OIDS = ()
-        return traceback is None
-
-# very basic selectors ########################################################
-
-def yes(cls, *args, **kwargs):
-    """accept everything"""
-    return 1
-yes_selector = deprecated_function(yes)
-
-@lltrace
-def none_rset(cls, req, rset, *args, **kwargs):
-    """accept no result set"""
-    if rset is None:
-        return 1
-    return 0
-norset_selector = deprecated_function(none_rset)
-
-@lltrace
-def any_rset(cls, req, rset, *args, **kwargs):
-    """accept result set, whatever the number of result"""
-    if rset is not None:
-        return 1
-    return 0
-rset_selector = deprecated_function(any_rset)
-
-@lltrace
-def nonempty_rset(cls, req, rset, *args, **kwargs):
-    """accept any non empty result set"""
-    if rset is not None and rset.rowcount:
-        return 1
-    return 0
-anyrset_selector = deprecated_function(nonempty_rset)
-    
-@lltrace
-def empty_rset(cls, req, rset, *args, **kwargs):
-    """accept empty result set"""
-    if rset is not None and rset.rowcount == 0:
-        return 1
-    return 0
-emptyrset_selector = deprecated_function(empty_rset)
-
-@lltrace
-def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
-    """accept result set with a single line of result"""
-    if rset is not None and (row is not None or rset.rowcount == 1):
-        return 1
-    return 0
-onelinerset_selector = deprecated_function(one_line_rset)
-
-@lltrace
-def two_lines_rset(cls, req, rset, *args, **kwargs):
-    """accept result set with *at least* two lines of result"""
-    if rset is not None and rset.rowcount > 1:
-        return 1
-    return 0
-twolinerset_selector = deprecated_function(two_lines_rset)
-
-@lltrace
-def two_cols_rset(cls, req, rset, *args, **kwargs):
-    """accept result set with at least one line and two columns of result"""
-    if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
-        return 1
-    return 0
-twocolrset_selector = deprecated_function(two_cols_rset)
-
-@lltrace
-def paginated_rset(cls, req, rset, *args, **kwargs):
-    """accept result sets with more rows than the page size
-    """
-    page_size = kwargs.get('page_size')
-    if page_size is None:
-        page_size = req.form.get('page_size')
-        if page_size is None:
-            page_size = req.property_value('navigation.page-size')
-        else:
-            page_size = int(page_size)
-    if rset is None or len(rset) <= page_size:
-        return 0
-    return 1
-largerset_selector = deprecated_function(paginated_rset)
-
-@lltrace
-def sorted_rset(cls, req, rset, row=None, col=None, **kwargs):
-    """accept sorted result set"""
-    rqlst = rset.syntax_tree()
-    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
-        return 0
-    return 2
-sortedrset_selector = deprecated_function(sorted_rset)
-
-@lltrace
-def one_etype_rset(cls, req, rset, *args, **kwargs):
-    """accept result set where entities in the first columns are all of the
-    same type
-    """
-    if len(rset.column_types(0)) != 1:
-        return 0
-    return 1
-oneetyperset_selector = deprecated_function(one_etype_rset)
-
-@lltrace
-def two_etypes_rset(cls, req, rset, **kwargs):
-    """accepts resultsets containing several entity types"""
-    if rset:
-        etypes = rset.column_types(0)
-        if len(etypes) > 1:
-            return 1
-    return 0
-multitype_selector = deprecated_function(two_etypes_rset)
-
-@lltrace
-def match_search_state(cls, req, rset, row=None, col=None, **kwargs):
-    """checks if the current search state is in a .search_states attribute of
-    the wrapped class
-
-    search state should be either 'normal' or 'linksearch' (eg searching for an
-    object to create a relation with another)
-    """
-    try:
-        if not req.search_state[0] in cls.search_states:
-            return 0
-    except AttributeError:
-        return 1 # class doesn't care about search state, accept it
-    return 1
-searchstate_selector = deprecated_function(match_search_state)
-
-@lltrace
-def anonymous_user(cls, req, *args, **kwargs):
-    """accept if user is anonymous"""
-    if req.cnx.anonymous_connection:
-        return 1
-    return 0
-anonymous_selector = deprecated_function(anonymous_user)
-
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
-    """accept if user is authenticated"""
-    return not anonymous_user(cls, req, *args, **kwargs)
-not_anonymous_selector = deprecated_function(authenticated_user)
-
-@lltrace
-def match_form_params(cls, req, *args, **kwargs):
-    """check if parameters specified by the form_params attribute on
-    the wrapped class are specified in request form parameters
-    """
-    score = 0
-    for param in cls.form_params:
-        val = req.form.get(param)
-        if not val:
-            return 0
-        score += 1
-    return score + 1
-req_form_params_selector = deprecated_function(match_form_params)
-
-@lltrace
-def match_kwargs(cls, req, *args, **kwargs):
-    """check if arguments specified by the expected_kwargs attribute on
-    the wrapped class are specified in given named parameters
-    """
-    values = []
-    for arg in cls.expected_kwargs:
-        if not arg in kwargs:
-            return 0
-    return 1
-kwargs_selector = deprecated_function(match_kwargs)
-
-
-# not so basic selectors ######################################################
-
-@lltrace
-def accept_etype(cls, req, *args, **kwargs):
-    """check etype presence in request form *and* accepts conformance"""
-    if 'etype' not in req.form and 'etype' not in kwargs:
-        return 0
-    try:
-        etype = req.form['etype']
-    except KeyError:
-        etype = kwargs['etype']
-    # value is a list or a tuple if web request form received several
-    # values for etype parameter
-    assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
-    if 'Any' in cls.accepts:
-        return 1
-    # no Any found, we *need* exact match
-    if etype not in cls.accepts:
-        return 0
-    # exact match must return a greater value than 'Any'-match
-    return 2
-etype_form_selector = deprecated_function(accept_etype)
-
-@lltrace
-def _non_final_entity(cls, req, rset, row=None, col=None, **kwargs):
-    """accept non final entities
-    if row is not specified, use the first one
-    if col is not specified, use the first one
-    """
-    etype = rset.description[row or 0][col or 0]
-    if etype is None: # outer join
-        return 0
-    if cls.schema.eschema(etype).is_final():
-        return 0
-    return 1
-_nfentity_selector = deprecated_function(_non_final_entity)
-
-@lltrace
-def _rql_condition(cls, req, rset, row=None, col=None, **kwargs):
-    """accept single entity result set if the entity match an rql condition
-    """
-    if cls.condition:
-        eid = rset[row or 0][col or 0]
-        if 'U' in frozenset(split_expression(cls.condition)):
-            rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition
-        else:
-            rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition
-        try:
-            return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x'))
-        except Unauthorized:
-            return 0
-        
-    return 1
-_rqlcondition_selector = deprecated_function(_rql_condition)
-
-@lltrace
-def _implement_interface(cls, req, rset, row=None, col=None, **kwargs):
-    """accept uniform result sets, and apply the following rules:
-
-    * wrapped class must have a accepts_interfaces attribute listing the
-      accepted ORed interfaces
-    * if row is None, return the sum of values returned by the method
-      for each entity's class in the result set. If any score is 0,
-      return 0.
-    * if row is specified, return the value returned by the method with
-      the entity's class of this row
-    """
-    # XXX this selector can be refactored : extract the code testing
-    #     for entity schema / interface compliance
-    score = 0
-    # check 'accepts' to give priority to more specific classes
-    if row is None:
-        for etype in rset.column_types(col or 0):
-            eclass = cls.vreg.etype_class(etype)
-            escore = 0
-            for iface in cls.accepts_interfaces:
-                escore += iface.is_implemented_by(eclass)
-            if not escore:
-                return 0
-            score += escore
-            accepts = set(getattr(cls, 'accepts', ()))
-            # if accepts is defined on the vobject, eclass must match
-            if accepts:
-                eschema = eclass.e_schema
-                etypes = set([eschema] + eschema.ancestors())
-                if accepts & etypes:
-                    score += 2
-                elif 'Any' not in accepts:
-                    return 0
-        return score + 1
-    etype = rset.description[row][col or 0]
-    if etype is None: # outer join
-        return 0
-    eclass = cls.vreg.etype_class(etype)
-    for iface in cls.accepts_interfaces:
-        score += iface.is_implemented_by(eclass)
-    if score:
-        accepts = set(getattr(cls, 'accepts', ()))
-        # if accepts is defined on the vobject, eclass must match
-        if accepts:
-            eschema = eclass.e_schema
-            etypes = set([eschema] + eschema.ancestors())
-            if accepts & etypes:
-                score += 1
-            elif 'Any' not in accepts:
-                return 0
-        score += 1
-    return score
-_interface_selector = deprecated_function(_implement_interface)
-
-@lltrace
-def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
-    if row is None:
-        rows = xrange(rset.rowcount)
-    else:
-        rows = (row,)
-    for row in rows:
-        try:
-            score = cls.score_entity(rset.get_entity(row, col or 0))
-        except DummyCursorError:
-            # get a dummy cursor error, that means we are currently
-            # using a dummy rset to list possible views for an entity
-            # type, not for an actual result set. In that case, we
-            # don't care of the value, consider the object as selectable
-            return 1
-        if not score:
-            return 0
-    return 1
-
-@lltrace
-def accept_rset(cls, req, rset, row=None, col=None, **kwargs):
-    """simply delegate to cls.accept_rset method"""
-    return cls.accept_rset(req, rset, row=row, col=col)
-accept_rset_selector = deprecated_function(accept_rset)
-
-@lltrace
-def but_etype(cls, req, rset, row=None, col=None, **kwargs):
-    """restrict the searchstate_accept_one_selector to exclude entity's type
-    refered by the .etype attribute
-    """
-    if rset.description[row or 0][col or 0] == cls.etype:
-        return 0
-    return 1
-but_etype_selector = deprecated_function(but_etype)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
-    """only check if the user has read access on the entity's type refered
-    by the .etype attribute and on the relations's type refered by the
-    .rtype attribute if set.
-    """
-    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
-
-@lltrace
-def has_relation(cls, req, rset, row=None, col=None, **kwargs):
-    """check if the user has read access on the relations's type refered by the
-    .rtype attribute of the class, and if all entities types in the
-    result set has this relation.
-    """
-    if hasattr(cls, 'rtype'):
-        rschema = cls.schema.rschema(cls.rtype)
-        perm = getattr(cls, 'require_permission', 'read')
-        if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
-            return 0
-        if row is None:
-            for etype in rset.column_types(col or 0):
-                if not cls.relation_possible(etype):
-                    return 0
-        elif not cls.relation_possible(rset.description[row][col or 0]):
-            return 0
-    return 1
-accept_rtype_selector = deprecated_function(has_relation)
-
-@lltrace
-def one_has_relation(cls, req, rset, row=None, col=None, **kwargs):
-    """check if the user has read access on the relations's type refered by the
-    .rtype attribute of the class, and if at least one entity type in the
-    result set has this relation.
-    """
-    rschema = cls.schema.rschema(cls.rtype)
-    perm = getattr(cls, 'require_permission', 'read')
-    if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
-        return 0
-    if row is None:
-        for etype in rset.column_types(col or 0):
-            if cls.relation_possible(etype):
-                return 1
-    elif cls.relation_possible(rset.description[row][col or 0]):
-        return 1
-    return 0
-one_has_relation_selector = deprecated_function(one_has_relation)
-
-@lltrace
-def has_related_entities(cls, req, rset, row=None, col=None, **kwargs):
-    return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
-
-
-@lltrace
-def match_user_group(cls, req, rset=None, row=None, col=None, **kwargs):
-    """select according to user's groups"""
-    if not cls.require_groups:
-        return 1
-    user = req.user
-    if user is None:
-        return int('guests' in cls.require_groups)
-    score = 0
-    if 'owners' in cls.require_groups and rset:
-        if row is not None:
-            eid = rset[row][col or 0]
-            if user.owns(eid):
-                score = 1
-        else:
-            score = all(user.owns(r[col or 0]) for r in rset)
-    score += user.matching_groups(cls.require_groups)
-    if score:
-        # add 1 so that an object with one matching group take priority
-        # on an object without require_groups
-        return score + 1 
-    return 0
-in_group_selector = deprecated_function(match_user_group)
-
-@lltrace
-def user_can_add_etype(cls, req, rset, row=None, col=None, **kwargs):
-    """only check if the user has add access on the entity's type refered
-    by the .etype attribute.
-    """
-    if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
-        return 0
-    return 1
-add_etype_selector = deprecated_function(user_can_add_etype)
-
-@lltrace
-def match_context_prop(cls, req, rset, row=None, col=None, context=None,
-                       **kwargs):
-    propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
-    if not propval:
-        propval = cls.context
-    if context is not None and propval and context != propval:
-        return 0
-    return 1
-contextprop_selector = deprecated_function(match_context_prop)
-
-@lltrace
-def primary_view(cls, req, rset, row=None, col=None, view=None,
-                          **kwargs):
-    if view is not None and not view.is_primary():
-        return 0
-    return 1
-primaryview_selector = deprecated_function(primary_view)
-
-def appobject_selectable(registry, oid):
-    """return a selector that will have a positive score if an object for the
-    given registry and object id is selectable for the input context
-    """
-    @lltrace
-    def selector(cls, req, rset, *args, **kwargs):
-        try:
-            cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs)
-            return 1
-        except NoSelectableObject:
-            return 0
-    return selector
-
-
-# compound selectors ##########################################################
-
-non_final_entity = chainall(nonempty_rset, _non_final_entity)
-non_final_entity.__name__ = 'non_final_entity'
-nfentity_selector = deprecated_function(non_final_entity)
-
-implement_interface = chainall(non_final_entity, _implement_interface)
-implement_interface.__name__ = 'implement_interface'
-interface_selector = deprecated_function(implement_interface)
-
-accept = chainall(non_final_entity, accept_rset)
-accept.__name__ = 'accept'
-accept_selector = deprecated_function(accept)
-
-accept_one = chainall(one_line_rset, accept)
-accept_one.__name__ = 'accept_one'
-accept_one_selector = deprecated_function(accept_one)
-
-rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition)
-rql_condition.__name__ = 'rql_condition'
-rqlcondition_selector = deprecated_function(rql_condition)
-
-
-searchstate_accept = chainall(nonempty_rset, match_search_state, accept)
-searchstate_accept.__name__ = 'searchstate_accept'
-searchstate_accept_selector = deprecated_function(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, match_search_state,
-                                  accept, _rql_condition)
-searchstate_accept_one.__name__ = 'searchstate_accept_one'
-searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
-
-searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype)
-searchstate_accept_one_but_etype.__name__ = 'searchstate_accept_one_but_etype'
-searchstate_accept_one_but_etype_selector = deprecated_function(
-    searchstate_accept_one_but_etype)
+"""pre 3.2 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2)
+from cubicweb.selectors import *
+from cubicweb.selectors import _rql_condition
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/tags.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,42 @@
+"""helper classes to generate simple (X)HTML tags
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import simple_sgml_tag
+
+class tag(object):
+    def __init__(self, name):
+        self.name = name
+        
+    def __call__(self, __content=None, **attrs):
+        return simple_sgml_tag(self.name, __content, **attrs)
+
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+div = tag('div')
+img = tag('img')
+label = tag('label')
+option = tag('option')
+h1 = tag('h1')
+h2 = tag('h2')
+h3 = tag('h3')
+h4 = tag('h4')
+h5 = tag('h5')
+
+def select(name, id=None, multiple=False, options=[], **attrs):
+    if multiple:
+        attrs['multiple'] = 'multiple'
+    if id:
+        attrs['id'] = id
+    html = [u'<select name="%s" %s>' % (
+        name, ' '.join('%s="%s"' % kv for kv in attrs.items()))]
+    html += options
+    html.append(u'</select>')
+    return u'\n'.join(html)
+
--- a/common/tal.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-"""provides simpleTAL extensions for CubicWeb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-import re
-from os.path import exists, isdir, join
-from logging import getLogger
-from StringIO import StringIO
-        
-from simpletal import simpleTAL, simpleTALES
-
-from logilab.common.decorators import cached
-
-LOGGER = getLogger('cubicweb.tal')
-
-
-class LoggerAdapter(object):
-    def __init__(self, tal_logger):
-        self.tal_logger = tal_logger
-        
-    def debug(self, msg):
-        LOGGER.debug(msg)
-
-    def warn(self, msg):
-        LOGGER.warning(msg)
-
-    def __getattr__(self, attrname):
-        return getattr(self.tal_logger, attrname)
-
-
-class CubicWebContext(simpleTALES.Context):
-    """add facilities to access entity / resultset"""
-
-    def __init__(self, options=None, allowPythonPath=1):
-        simpleTALES.Context.__init__(self, options, allowPythonPath)
-        self.log = LoggerAdapter(self.log)
-
-    def update(self, context):
-        for varname, value in context.items():
-            self.addGlobal(varname, value)
-
-    def addRepeat(self, name, var, initialValue):
-        simpleTALES.Context.addRepeat(self, name, var, initialValue)
-
-# XXX FIXME need to find a clean to define OPCODE values for extensions
-I18N_CONTENT = 18  
-I18N_REPLACE = 19
-RQL_EXECUTE  = 20
-# simpleTAL uses the OPCODE values to define priority over commands.
-# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
-# we can't use the same OPCODE for two different commands without changing
-# the simpleTAL implementation. Another solution would be to totally override
-# the REPEAT implementation with the ITER one, but some specific operations
-# (involving len() for instance) are not implemented for ITER, so we prefer
-# to keep both implementations for now, and to fool simpleTAL by using a float
-# number between 3 and 4
-TAL_ITER     = 3.1
-
-
-# FIX simpleTAL HTML 4.01 stupidity
-# (simpleTAL never closes tags like INPUT, IMG, HR ...)
-simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
-
-class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
-    """extends default compiler by adding i18n:content commands"""
-
-    def __init__(self):
-        simpleTAL.HTMLTemplateCompiler.__init__(self)
-        self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
-        self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
-        self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
-        self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
-
-    def setTALPrefix(self, prefix):
-        simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
-        self.tal_attribute_map['i18n:content'] = I18N_CONTENT
-        self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
-        self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
-        self.tal_attribute_map['tal:iter'] = TAL_ITER
-
-    def compile_cmd_i18n_content(self, argument):
-        # XXX tal:content structure=, text= should we support this ?
-        structure_flag = 0
-        return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
-
-    def compile_cmd_i18n_replace(self, argument):
-        # XXX tal:content structure=, text= should we support this ?
-        structure_flag = 0
-        return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
-
-    def compile_cmd_rql(self, argument):
-        return (RQL_EXECUTE, (argument, self.endTagSymbol))
-
-    def compile_cmd_tal_iter(self, argument):
-        original_id, (var_name, expression, end_tag_symbol) = \
-                     simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
-        return (TAL_ITER, (var_name, expression, self.endTagSymbol))
-
-    def getTemplate(self):
-        return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
-
-    def compileCmdAttributes (self, argument):
-        """XXX modified to support single attribute
-        definition ending by a ';'
-
-        backport this to simpleTAL
-        """
-        # Compile tal:attributes into attribute command
-        # Argument: [(attributeName, expression)]
-        
-        # Break up the list of attribute settings first
-        commandArgs = []
-        # We only want to match semi-colons that are not escaped
-        argumentSplitter =  re.compile(r'(?<!;);(?!;)')
-        for attributeStmt in argumentSplitter.split(argument):
-            if not attributeStmt.strip():
-                continue
-            #  remove any leading space and un-escape any semi-colons
-            attributeStmt = attributeStmt.lstrip().replace(';;', ';')
-            # Break each attributeStmt into name and expression
-            stmtBits = attributeStmt.split(' ')
-            if (len (stmtBits) < 2):
-                # Error, badly formed attributes command
-                msg = "Badly formed attributes command '%s'.  Attributes commands must be of the form: 'name expression[;name expression]'" % argument
-                self.log.error(msg)
-                raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
-            attName = stmtBits[0]
-            attExpr = " ".join(stmtBits[1:])
-            commandArgs.append((attName, attExpr))
-        return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
-
-
-class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
-    """provides implementation for interpreting cubicweb extensions"""
-    def __init__(self):
-        simpleTAL.TemplateInterpreter.__init__(self)
-        self.commandHandler[I18N_CONTENT] = self.cmd_i18n
-        self.commandHandler[TAL_ITER] = self.cmdRepeat
-        # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
-
-    def cmd_i18n(self, command, args):
-        """i18n:content and i18n:replace implementation"""
-        string, replace_flag, structure_flag, end_symbol = args
-        if replace_flag:
-            self.outputTag = 0
-        result = self.context.globals['_'](string)
-        self.tagContent = (0, result)
-        self.movePCForward = self.symbolTable[end_symbol]
-        self.programCounter += 1
-
-
-class CubicWebTemplate(simpleTAL.HTMLTemplate):
-    """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
-    """
-    def expand(self, context, outputFile):
-        interpreter = CubicWebTemplateInterpreter()
-        interpreter.initialise(context, outputFile)
-        simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
-                                      interpreter=interpreter)
-
-    def expandInline(self, context, outputFile, interpreter):
-        """ Internally used when expanding a template that is part of a context."""
-        try:
-            interpreter.execute(self)
-        except UnicodeError, unierror:
-            LOGGER.exception(str(unierror))
-            raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
-
-
-def compile_template(template):
-    """compiles a TAL template string
-    :type template: unicode
-    :param template: a TAL-compliant template string
-    """
-    string_buffer = StringIO(template)
-    compiler = CubicWebTemplateCompiler()
-    compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
-    return compiler.getTemplate()
-
-
-def compile_template_file(filepath):
-    """compiles a TAL template file
-    :type filepath: str
-    :param template: path of the file to compile 
-    """
-    fp = file(filepath)
-    file_content = unicode(fp.read()) # template file should be pure ASCII
-    fp.close()
-    return compile_template(file_content)
-
-
-def evaluatePython (self, expr):
-    if not self.allowPythonPath:
-        return self.false
-    globals = {}
-    for name, value in self.globals.items():
-        if isinstance (value, simpleTALES.ContextVariable):
-            value = value.rawValue()
-        globals[name] = value
-    globals['path'] = self.pythonPathFuncs.path
-    globals['string'] = self.pythonPathFuncs.string
-    globals['exists'] = self.pythonPathFuncs.exists
-    globals['nocall'] = self.pythonPathFuncs.nocall
-    globals['test'] = self.pythonPathFuncs.test
-    locals = {}
-    for name, value in self.locals.items():
-        if (isinstance (value, simpleTALES.ContextVariable)):
-            value = value.rawValue()
-        locals[name] = value
-    # XXX precompile expr will avoid late syntax error
-    try:
-        result = eval(expr, globals, locals)
-    except Exception, ex:
-        ex = ex.__class__('in %r: %s' % (expr, ex))
-        raise ex, None, sys.exc_info()[-1]
-    if (isinstance (result, simpleTALES.ContextVariable)):
-        return result.value()
-    return result
-
-simpleTALES.Context.evaluatePython = evaluatePython
-
-
-class talbased(object):
-    def __init__(self, filename, write=True):
-##         if not osp.isfile(filepath):
-##             # print "[tal.py] just for tests..."
-##             # get parent frame
-##             directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
-##             filepath = osp.join(directory, filepath)
-        self.filename = filename
-        self.write = write
-
-    def __call__(self, viewfunc):
-        def wrapped(instance, *args, **kwargs):
-            variables = viewfunc(instance, *args, **kwargs)
-            html = instance.tal_render(self._compiled_template(instance), variables)
-            if self.write:
-                instance.w(html)
-            else:
-                return html
-        return wrapped
-
-    def _compiled_template(self, instance):
-        for fileordirectory in instance.config.vregistry_path():
-            filepath = join(fileordirectory, self.filename)
-            if isdir(fileordirectory) and exists(filepath):
-                return compile_template_file(filepath)
-        raise Exception('no such template %s' % self.filename)
-    _compiled_template = cached(_compiled_template, 0)
-    
--- a/common/test/data/bootstrap_cubes	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/test/data/bootstrap_cubes	Tue Apr 07 09:30:23 2009 +0200
@@ -1,1 +1,1 @@
-file, tag
+
--- a/common/test/data/entities.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-from cubicweb.entities import AnyEntity, fetch_config
-
-class Personne(AnyEntity):
-    """customized class forne Person entities"""
-    id = 'Personne'
-    fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
-    rest_attr = 'nom'
-
-
-class Societe(AnyEntity):
-    id = 'Societe'
-    fetch_attrs = ('nom',)
-    
-class AnotherNote(AnyEntity):
-    id = 'AnotherNote'
--- a/common/test/data/schema/Affaire.sql	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-sujet varchar(128)
-ref   varchar(12)
--- a/common/test/data/schema/Note.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-from cubicweb.schema import format_constraint
-
-class AnotherNote(EntityType):
-    descr_format = String(meta=True, internationalizable=True,
-                                default='text/rest', constraints=[format_constraint])
-    descr = String(fulltextindexed=True,
-                   description=_('more detailed description'))
-    descr2_format = String(meta=True, internationalizable=True,
-                                default='text/rest', constraints=[format_constraint])
-    descr2 = String(fulltextindexed=True,
-                    description=_('more detailed description'))
-    
-
-class SubNote(AnotherNote):
-    __specializes_schema__ = True
-    descr3 = String()
--- a/common/test/data/schema/Note.sql	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-date varchar(10)
-type char(1)
-para varchar(512)
--- a/common/test/data/schema/Personne.sql	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-nom    ivarchar(64) NOT NULL
-prenom ivarchar(64)
-sexe   char(1) DEFAULT 'M' 
-promo  choice('bon','pasbon')
-titre  ivarchar(128)
-adel   varchar(128)
-ass    varchar(128)
-web    varchar(128)
-tel    integer
-fax    integer
-datenaiss datetime
-test   boolean 
-description text
-salary float
--- a/common/test/data/schema/Societe.sql	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-nom  ivarchar(64)
-web varchar(128)
-tel  integer
-fax  integer
-rncs varchar(32)
-ad1  varchar(128)
-ad2  varchar(128)
-ad3  varchar(128)
-cp   varchar(12)
-ville varchar(32)
--- a/common/test/data/schema/relations.rel	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-Personne travaille Societe
-Personne evaluee Note
-EUser evaluee Note
-Societe evaluee Note
-Personne concerne Affaire
-Affaire concerne Societe
-Personne evaluee Personne
-
-Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P
-Personne connait Personne symetric
-
-Tag tags Note
-Tag tags Personne
-
-Affaire liee_a Societe
-Affaire liee_a Personne
--- a/common/test/unittest_entity.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,491 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit tests for cubicweb.web.views.entities module"""
-
-from cubicweb.devtools.apptest import EnvBasedTC
-
-from mx.DateTime import DateTimeType, now
-from cubicweb import Binary
-
-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)
-    
-    def test_boolean_value(self):
-        e = self.etype_instance('Tag')
-        self.failUnless(e)
-
-    def test_yams_inheritance(self):
-        from entities import AnotherNote
-        e = self.etype_instance('SubNote')
-        self.assertIsInstance(e, AnotherNote)
-        e2 = self.etype_instance('SubNote')
-        self.assertIs(e.__class__, e2.__class__)
-
-    def test_has_eid(self):
-        e = self.etype_instance('Tag')
-        self.assertEquals(e.eid, None)
-        self.assertEquals(e.has_eid(), False)
-        e.eid = 'X'
-        self.assertEquals(e.has_eid(), False)
-        e.eid = 0
-        self.assertEquals(e.has_eid(), True)
-        e.eid = 2
-        self.assertEquals(e.has_eid(), True)
-        
-    def test_copy(self):
-        self.add_entity('Tag', name=u'x')
-        p = self.add_entity('Personne', nom=u'toto')
-        oe = self.add_entity('Note', type=u'x')
-        self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
-                     {'t': oe.eid, 'u': p.eid}, ('t','u'))
-        self.execute('SET TAG tags X WHERE X eid %(x)s', {'x': oe.eid}, 'x')
-        e = self.add_entity('Note', type=u'z')
-        e.copy_relations(oe.eid)
-        self.assertEquals(len(e.ecrit_par), 1)
-        self.assertEquals(e.ecrit_par[0].eid, p.eid)
-        self.assertEquals(len(e.reverse_tags), 0)
-        
-    def test_copy_with_nonmeta_composite_inlined(self):
-        p = self.add_entity('Personne', nom=u'toto')
-        oe = self.add_entity('Note', type=u'x')
-        self.schema['ecrit_par'].set_rproperty('Note', 'Personne', 'composite', 'subject')
-        self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
-                     {'t': oe.eid, 'u': p.eid}, ('t','u'))
-        e = self.add_entity('Note', type=u'z')
-        e.copy_relations(oe.eid)
-        self.failIf(e.ecrit_par)
-        self.failUnless(oe.ecrit_par)
-            
-    def test_copy_with_composite(self):
-        user = self.user()
-        adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
-        e = self.entity('Any X WHERE X eid %(x)s', {'x':user.eid}, 'x')
-        self.assertEquals(e.use_email[0].address, "toto@logilab.org")
-        self.assertEquals(e.use_email[0].eid, adeleid)
-        usereid = self.execute('INSERT EUser X: X login "toto", X upassword "toto", X in_group G, X in_state S '
-                               'WHERE G name "users", S name "activated"')[0][0]
-        e = self.entity('Any X WHERE X eid %(x)s', {'x':usereid}, 'x')
-        e.copy_relations(user.eid)
-        self.failIf(e.use_email)
-        self.failIf(e.primary_email)
-        
-    def test_copy_with_non_initial_state(self):
-        user = self.user()
-        eid = self.execute('INSERT EUser X: X login "toto", X upassword %(pwd)s, X in_group G WHERE G name "users"',
-                           {'pwd': 'toto'})[0][0]
-        self.commit()
-        self.execute('SET X in_state S WHERE X eid %(x)s, S name "deactivated"', {'x': eid}, 'x')
-        self.commit()
-        eid2 = self.execute('INSERT EUser X: X login "tutu", X upassword %(pwd)s', {'pwd': 'toto'})[0][0]
-        e = self.entity('Any X WHERE X eid %(x)s', {'x': eid2}, 'x')
-        e.copy_relations(eid)
-        self.commit()
-        e.clear_related_cache('in_state', 'subject')
-        self.assertEquals(e.state, 'activated')
-
-    def test_related_cache_both(self):
-        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(), [])
-        email = user.primary_email[0]
-        self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
-        self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
-        groups = user.in_group
-        self.assertEquals(sorted(user._related_cache), ['in_group_subject', 'primary_email_subject'])
-        for group in groups:
-            self.failIf('in_group_subject' in group._related_cache, group._related_cache.keys())
-
-    def test_related_limit(self):
-        p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-        for tag in u'abcd':
-            self.add_entity('Tag', name=tag)
-        self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
-        self.assertEquals(len(p.related('tags', 'object', limit=2)), 2)
-        self.assertEquals(len(p.related('tags', 'object')), 4)
-
-        
-    def test_fetch_rql(self):
-        user = self.user()
-        Personne = self.vreg.etype_class('Personne')
-        Societe = self.vreg.etype_class('Societe')
-        Note = self.vreg.etype_class('Note')
-        peschema = Personne.e_schema
-        seschema = Societe.e_schema
-        peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '1*')
-        peschema.subject_relation('connait').set_rproperty(peschema, peschema, 'cardinality', '11')
-        peschema.subject_relation('evaluee').set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
-        seschema.subject_relation('evaluee').set_rproperty(seschema, Note.e_schema, 'cardinality', '1*')
-        # testing basic fetch_attrs attribute
-        self.assertEquals(Personne.fetch_rql(user),
-                          'Any X,AA,AB,AC ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
-        pfetch_attrs = Personne.fetch_attrs
-        sfetch_attrs = Societe.fetch_attrs
-        try:
-            # testing unknown attributes
-            Personne.fetch_attrs = ('bloug', 'beep')
-            self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne')            
-            # testing one non final relation
-            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
-            self.assertEquals(Personne.fetch_rql(user),
-                              'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC, AC nom AD')
-            # testing two non final relations
-            Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee')
-            self.assertEquals(Personne.fetch_rql(user),
-                              'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, '
-                              'X prenom AB, X travaille AC, AC nom AD, X evaluee AE, AE modification_date AF')
-            # testing one non final relation with recursion
-            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
-            Societe.fetch_attrs = ('nom', 'evaluee')
-            self.assertEquals(Personne.fetch_rql(user),
-                              'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, X prenom AB, '
-                              'X travaille AC, AC nom AD, AC evaluee AE, AE modification_date AF'
-                              )
-            # testing symetric relation
-            Personne.fetch_attrs = ('nom', 'connait')
-            self.assertEquals(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X connait AB')
-            # testing optional relation
-            peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '?*')
-            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
-            Societe.fetch_attrs = ('nom',)
-            self.assertEquals(Personne.fetch_rql(user),
-                              'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD')
-            # testing relation with cardinality > 1
-            peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '**')
-            self.assertEquals(Personne.fetch_rql(user),
-                              'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
-            # XXX test unauthorized attribute
-        finally:
-            Personne.fetch_attrs = pfetch_attrs
-            Societe.fetch_attrs = sfetch_attrs
-
-    def test_related_rql(self):
-        from cubicweb.entities import fetch_config
-        Personne = self.vreg.etype_class('Personne')
-        Societe = self.vreg.etype_class('Societe')
-        Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'prenom', 'sexe'))
-        Societe.fetch_attrs, Societe.fetch_order = fetch_config(('nom', 'web'))
-        aff = self.add_entity('Affaire', sujet=u'my subject', ref=u'the ref')
-        self.assertEquals(aff.related_rql('liee_a'),
-                          'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E liee_a X, '
-                          'X nom AA, X modification_date AB')
-        Societe.fetch_attrs = ('web',)
-        self.assertEquals(aff.related_rql('liee_a'),
-                          'Any X ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E liee_a X')
-    
-    def test_entity_unrelated(self):
-        p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-        e = self.add_entity('Tag', name=u'x')
-        rschema = e.e_schema.subject_relation('tags')
-        related = [r.eid for r in e.tags]
-        self.failUnlessEqual(related, [])
-        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
-        self.failUnless(p.eid in unrelated)
-        self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
-        e = self.entity('Any X WHERE X is Tag')
-        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
-        self.failIf(p.eid in unrelated)
-
-    def test_entity_unrelated_limit(self):
-        e = self.add_entity('Tag', name=u'x')
-        self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-        self.add_entity('Personne', nom=u'di mascio', prenom=u'gwen')
-        rschema = e.e_schema.subject_relation('tags')
-        self.assertEquals(len(e.vocabulary(rschema, 'subject', limit=1)),
-                          1)
-        
-    def test_new_entity_unrelated(self):
-        e = self.etype_instance('EUser')
-        rschema = e.e_schema.subject_relation('in_group')
-        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
-        # should be default groups but owners, i.e. managers, users, guests
-        self.assertEquals(len(unrelated), 3)
-
-
-    def test_rtags_expansion(self):
-        from cubicweb.entities import AnyEntity
-        class Personne(AnyEntity):
-            id = 'Personne'
-            __rtags__ = {
-                ('travaille', 'Societe', 'subject') : set(('primary',)),
-                ('evaluee', '*', 'subject') : set(('secondary',)),
-                'ecrit_par' : set(('inlineview',)),
-                }
-        self.vreg.register_vobject_class(Personne)
-        rtags = Personne.rtags
-        self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
-        self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
-        self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
-        self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
-        self.assertEquals(rtags.get_tags('ecrit_par', 'Note', 'object'), set(('inlineview', 'link')))
-        self.assertEquals(rtags.is_inlined('ecrit_par', 'Note', 'object'), True)
-        class Personne2(Personne):
-            id = 'Personne'
-            __rtags__ = {
-                ('evaluee', 'Note', 'subject') : set(('inlineview',)),
-                }
-        self.vreg.register_vobject_class(Personne2)
-        rtags = Personne2.rtags
-        self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
-        self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
-        self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
-        self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
-    
-    def test_relations_by_category(self):
-        e = self.etype_instance('EUser')
-        def rbc(iterable):
-            return [(rschema.type, x) for rschema, tschemas, x in iterable]
-        self.assertEquals(rbc(e.relations_by_category('primary')),
-                          [('login', 'subject'), ('upassword', 'subject'),
-                           ('in_group', 'subject'), ('in_state', 'subject'),
-                           ('eid', 'subject'),])
-        # firstname and surname are put in secondary category in views.entities.EUserEntity
-        self.assertListEquals(rbc(e.relations_by_category('secondary')),
-                              [('firstname', 'subject'), ('surname', 'subject')])
-        self.assertListEquals(rbc(e.relations_by_category('generic')),
-                              [('primary_email', 'subject'),
-                               ('evaluee', 'subject'),
-                               ('for_user', 'object')])
-        # owned_by is defined both as subject and object relations on EUser
-        self.assertListEquals(rbc(e.relations_by_category('generated')),
-                              [('last_login_time', 'subject'),
-                               ('created_by', 'subject'),
-                               ('creation_date', 'subject'),
-                               ('is', 'subject'),
-                               ('is_instance_of', 'subject'),
-                               ('modification_date', 'subject'),
-                               ('owned_by', 'subject'),
-                               ('created_by', 'object'),
-                               ('wf_info_for', 'object'),
-                               ('owned_by', 'object'),
-                               ('bookmarked_by', 'object')])
-        e = self.etype_instance('Personne')
-        self.assertListEquals(rbc(e.relations_by_category('primary')),
-                              [('nom', 'subject'), ('eid', 'subject')])
-        self.assertListEquals(rbc(e.relations_by_category('secondary')),
-                              [('prenom', 'subject'),
-                               ('sexe', 'subject'),
-                               ('promo', 'subject'),
-                               ('titre', 'subject'),
-                               ('adel', 'subject'),
-                               ('ass', 'subject'),
-                               ('web', 'subject'),
-                               ('tel', 'subject'),
-                               ('fax', 'subject'),
-                               ('datenaiss', 'subject'),
-                               ('test', 'subject'),
-                               ('description', 'subject'),
-                               ('salary', 'subject')])
-        self.assertListEquals(rbc(e.relations_by_category('generic')),
-                              [('concerne', 'subject'),
-                               ('connait', 'subject'),
-                               ('evaluee', 'subject'),
-                               ('travaille', 'subject'),
-                               ('ecrit_par', 'object'),
-                               ('evaluee', 'object'),
-                               ('liee_a', 'object'),
-                               ('tags', 'object')])
-        self.assertListEquals(rbc(e.relations_by_category('generated')),
-                              [('created_by', 'subject'),
-                               ('creation_date', 'subject'),
-                               ('is', 'subject'),
-                               ('is_instance_of', 'subject'),
-                               ('modification_date', 'subject'),
-                               ('owned_by', 'subject')])
-        
-
-    def test_printable_value_string(self):
-        e = self.add_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
-                            content_format=u'text/rest')
-        self.assertEquals(e.printable_value('content'),
-                          '<p>du <a class="reference" href="http://testing.fr/cubicweb/egroup/managers">*ReST*</a></p>\n')
-        e['content'] = 'du <em>html</em> <ref rql="EUser X">users</ref>'
-        e['content_format'] = 'text/html'
-        self.assertEquals(e.printable_value('content'),
-                          'du <em>html</em> <a href="http://testing.fr/cubicweb/view?rql=EUser%20X">users</a>')
-        e['content'] = 'du *texte*'
-        e['content_format'] = 'text/plain'
-        self.assertEquals(e.printable_value('content'),
-                          '<p>\ndu *texte*\n</p>')
-        e['title'] = 'zou'
-        e['content'] = '<h1 tal:content="self/title">titre</h1>'
-        e['content_format'] = 'text/cubicweb-page-template'
-        self.assertEquals(e.printable_value('content'),
-                          '<h1>zou</h1>')
-        
-        #e = self.etype_instance('Task')
-        e['content'] = '''\
-a title
-=======
-du :eid:`1:*ReST*`'''
-        e['content_format'] = 'text/rest'
-        self.assertEquals(e.printable_value('content', format='text/plain'),
-                          e['content'])
-
-        e['content'] = u'<b>yo (zou éà ;)</b>'
-        e['content_format'] = 'text/html'
-        self.assertEquals(e.printable_value('content', format='text/plain').strip(),
-                          u'**yo (zou éà ;)**')
-
-    def test_printable_value_bytes(self):
-        e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
-                            data_encoding=u'ascii', name=u'toto.py')
-        from cubicweb.common import mttransforms
-        if mttransforms.HAS_PYGMENTS_TRANSFORMS:
-            self.assertEquals(e.printable_value('data'),
-                              '''<div class="highlight"><pre><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="mf">1</span>
-</pre></div>
-''')
-        else:
-            self.assertEquals(e.printable_value('data'),
-                              '''<pre class="python">
-<span style="color: #C00000;">lambda</span> <span style="color: #000000;">x</span><span style="color: #0000C0;">:</span> <span style="color: #0080C0;">1</span>
-</pre>
-''')
-        
-        e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
-                            data_encoding=u'utf-8', name=u'toto.txt')
-        self.assertEquals(e.printable_value('data'),
-                          u'<p><em>héhéhé</em></p>\n')
-
-    def test_printable_value_bad_html(self):
-        """make sure we don't crash if we try to render invalid XHTML strings"""
-        e = self.add_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
-                            content_format=u'text/html')
-        tidy = lambda x: x.replace('\n', '')
-        self.assertEquals(tidy(e.printable_value('content')),
-                          '<div>R&amp;D<br/></div>')
-        e['content'] = u'yo !! R&D <div> pas fermé'
-        self.assertEquals(tidy(e.printable_value('content')),
-                          u'yo !! R&amp;D <div> pas fermé</div>')
-        e['content'] = u'R&D'
-        self.assertEquals(tidy(e.printable_value('content')), u'R&amp;D')
-        e['content'] = u'R&D;'
-        self.assertEquals(tidy(e.printable_value('content')), u'R&amp;D;')
-        e['content'] = u'yo !! R&amp;D <div> pas fermé'
-        self.assertEquals(tidy(e.printable_value('content')),
-                          u'yo !! R&amp;D <div> pas fermé</div>')
-        e['content'] = u'été <div> été'
-        self.assertEquals(tidy(e.printable_value('content')),
-                          u'été <div> été</div>')
-        e['content'] = u'C&apos;est un exemple s&eacute;rieux'
-        self.assertEquals(tidy(e.printable_value('content')),
-                          u"C'est un exemple sérieux")
-        # make sure valid xhtml is left untouched
-        e['content'] = u'<div>R&amp;D<br/></div>'
-        self.assertEquals(e.printable_value('content'), e['content'])
-        e['content'] = u'<div>été</div>'
-        self.assertEquals(e.printable_value('content'), e['content'])
-        e['content'] = u'été'
-        self.assertEquals(e.printable_value('content'), e['content'])
-        
-
-    def test_entity_formatted_attrs(self):
-        e = self.etype_instance('Note')
-        self.assertEquals(e.formatted_attrs(), [])
-        e = self.etype_instance('File')
-        self.assertEquals(e.formatted_attrs(), ['description'])
-        e = self.etype_instance('AnotherNote')
-        self.assertEquals(e.formatted_attrs(), ['descr', 'descr2'])
-        
-        
-    def test_fulltextindex(self):
-        e = self.etype_instance('File')
-        e['name'] = 'an html file'
-        e['description'] = 'du <em>html</em>'
-        e['description_format'] = 'text/html'
-        e['data'] = Binary('some <em>data</em>')
-        e['data_format'] = 'text/html'
-        e['data_encoding'] = 'ascii'
-        self.assertEquals(set(e.get_words()), 
-                          set(['an', 'html', 'file', 'du', 'html', 'some', 'data']))
-
-        
-    def test_nonregr_relation_cache(self):
-        p1 = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-        p2 = self.add_entity('Personne', nom=u'toto')
-        self.execute('SET X evaluee Y WHERE X nom "di mascio", Y nom "toto"')
-        self.assertEquals(p1.evaluee[0].nom, "toto")
-        self.failUnless(not p1.reverse_evaluee)
-        
-    def test_complete_relation(self):
-        self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
-        self.commit()
-        try:
-            eid = self.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]
-            trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
-            trinfo.complete()
-            self.failUnless(trinfo.relation_cached('from_state', 'subject'))
-            self.failUnless(trinfo.relation_cached('to_state', 'subject'))
-            self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
-            # check with a missing relation
-            eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
-                               'X to_state S2 WHERE '
-                               'U login "admin", S2 name "activated"')[0][0]
-            trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
-            trinfo.complete()
-            self.failUnless(isinstance(trinfo.creation_date, DateTimeType))
-            self.failUnless(trinfo.relation_cached('from_state', 'subject'))
-            self.failUnless(trinfo.relation_cached('to_state', 'subject'))
-            self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
-            self.assertEquals(trinfo.from_state, [])
-        finally:
-            self.rollback()
-            self.execute('DELETE RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
-            self.commit()
-
-    def test_request_cache(self):
-        req = self.request()
-        user = self.entity('EUser X WHERE X login "admin"', req=req)
-        state = user.in_state[0]
-        samestate = self.entity('State X WHERE X name "activated"', req=req)
-        self.failUnless(state is samestate)
-
-    def test_rest_path(self):
-        note = self.add_entity('Note', type=u'z')
-        self.assertEquals(note.rest_path(), 'note/%s' % note.eid)
-        # unique attr
-        tag = self.add_entity('Tag', name=u'x')
-        self.assertEquals(tag.rest_path(), 'tag/x')
-        # test explicit rest_attr
-        person = self.add_entity('Personne', prenom=u'john', nom=u'doe')
-        self.assertEquals(person.rest_path(), 'personne/doe')
-        # ambiguity test
-        person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe')
-        self.assertEquals(person.rest_path(), 'personne/eid/%s' % person.eid)
-        self.assertEquals(person2.rest_path(), 'personne/eid/%s' % person2.eid)
-        # unique attr with None value (wikiid in this case)
-        card1 = self.add_entity('Card', title=u'hop')
-        self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid)
-        card2 = self.add_entity('Card', title=u'pod', wikiid=u'zob/i')
-        self.assertEquals(card2.rest_path(), 'card/zob%2Fi')
-
-    def test_set_attributes(self):
-        person = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-        self.assertEquals(person.prenom, u'adrien')
-        self.assertEquals(person.nom, u'di mascio')
-        person.set_attributes(prenom=u'sylvain', nom=u'thénault')
-        person = self.entity('Personne P') # XXX retreival needed ?
-        self.assertEquals(person.prenom, u'sylvain')
-        self.assertEquals(person.nom, u'thénault')
-
-    def test_metainformation(self):
-        note = self.add_entity('Note', type=u'z')
-        metainf = note.metainformation()
-        self.assertEquals(metainf, {'source': {'adapter': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None})
-        self.assertEquals(note.absolute_url(), 'http://testing.fr/cubicweb/note/%s' % note.eid)
-        metainf['source'] = metainf['source'].copy()
-        metainf['source']['base-url']  = 'http://cubicweb2.com/'
-        self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/%s' % note.eid)
-        
-if __name__ == '__main__':
-    from logilab.common.testlib import unittest_main
-    unittest_main()
-
--- a/common/test/unittest_migration.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/test/unittest_migration.py	Tue Apr 07 09:30:23 2009 +0200
@@ -30,6 +30,8 @@
         self.config = MigrTestConfig('data')
         from yams.schema import Schema
         self.config.load_schema = lambda expand_cubes=False: Schema('test')
+        self.config.__class__.cubicweb_vobject_path = frozenset()
+        self.config.__class__.cube_vobject_path = frozenset()
         
     def test_migration_files_base(self):
         self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)),
--- a/common/test/unittest_utils.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-"""unit tests for module cubicweb.common.utils"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList
-
-
-class MakeUidTC(TestCase):
-    def test_1(self):
-        self.assertNotEquals(make_uid('xyz'), make_uid('abcd'))
-        self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
-        
-    def test_2(self):
-        d = {}
-        while len(d)<10000:
-            uid = make_uid('xyz')
-            if d.has_key(uid):
-                self.fail(len(d))
-            d[uid] = 1
-
-        
-class UStringIOTC(TestCase):
-    def test_boolean_value(self):
-        self.assert_(UStringIO())
-
-
-class SizeConstrainedListTC(TestCase):
-
-    def test_append(self):
-        l = SizeConstrainedList(10)
-        for i in xrange(12):
-            l.append(i)
-        self.assertEquals(l, range(2, 12))
-    
-    def test_extend(self):
-        testdata = [(range(5), range(5)),
-                    (range(10), range(10)),
-                    (range(12), range(2, 12)),
-                    ]
-        for extension, expected in testdata:
-            l = SizeConstrainedList(10)
-            l.extend(extension)
-            yield self.assertEquals, l, expected
-
-   
-if __name__ == '__main__':
-    unittest_main()
--- a/common/uilib.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/uilib.py	Tue Apr 07 09:30:23 2009 +0200
@@ -11,29 +11,14 @@
 
 import csv
 import decimal
-import locale
 import re
+from datetime import datetime, date, timedelta
 from urllib import quote as urlquote
 from cStringIO import StringIO
-from copy import deepcopy
 
-import simplejson
-
-from mx.DateTime import DateTimeType, DateTimeDeltaType
-
-from logilab.common.textutils import unormalize
 from logilab.mtconverter import html_escape, html_unescape
 
-def ustrftime(date, fmt='%Y-%m-%d'):
-    """like strftime, but returns a unicode string instead of an encoded
-    string which may be problematic with localized date.
-    
-    encoding is guessed by locale.getpreferredencoding()
-    """
-    # date format may depend on the locale
-    encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
-    return unicode(date.strftime(fmt), encoding)
-
+from cubicweb.utils import ustrftime
 
 def rql_for_eid(eid):
     """return the rql query necessary to fetch entity with the given eid.  This
@@ -226,8 +211,13 @@
     """
     value = u'<%s' % tag
     if attrs:
+        try:
+            attrs['class'] = attrs.pop('klass')
+        except KeyError:
+            pass
         value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value)))
-                                  for attr, value in attrs.items())
+                                  for attr, value in attrs.items()
+                                  if value is not None)
     if content:
         value += u'>%s</%s>' % (html_escape(unicode(content)), tag)
     else:
@@ -264,6 +254,7 @@
     elif vid:
         params.append(repr(vid))
     if extraparams:
+        import simplejson
         params.append(simplejson.dumps(extraparams))
     if swap:
         params.append('true')
@@ -353,7 +344,7 @@
             cell_12 = line[j+1] is not None
             cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None
             link_type = link_types.get((cell_11, cell_12, cell_21), 0)
-            if link_type == 0 and i > 0 and links[i-1][j] in (1,2,3):
+            if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3):
                 link_type = 2
             links[-1].append(link_type)
     
@@ -520,10 +511,10 @@
         ret = function(*args, **kwargs)
         if isinstance(ret, decimal.Decimal):
             ret = float(ret)
-        elif isinstance(ret, DateTimeType):
+        elif isinstance(ret, (date, datetime)):
             ret = ret.strftime('%Y-%m-%d %H:%M')
-        elif isinstance(ret, DateTimeDeltaType):
-            ret = ret.seconds
+        elif isinstance(ret, timedelta):
+            ret = (ret.days * 24*60*60) + ret.seconds
         try:
             return simplejson.dumps(ret)
         except TypeError:
--- a/common/utils.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/utils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,263 +1,5 @@
-"""Some utilities for CubicWeb server/clients.
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from md5 import md5
-from time import time
-from random import randint, seed
-
-# initialize random seed from current time
-seed()
-
-def make_uid(key):
-    """forge a unique identifier"""
-    msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
-    return md5(msg).hexdigest()
-
-def working_hours(mxdate):
-    """
-    Predicate returning True is the date's hour is in working hours (8h->20h)
-    """
-    if mxdate.hour > 7 and mxdate.hour < 21:
-        return True
-    return False
-    
-def date_range(begin, end, incr=1, include=None):
-    """yields each date between begin and end
-    :param begin: the start date
-    :param end: the end date
-    :param incr: the step to use to iterate over dates. Default is
-                 one day.                 
-    :param include: None (means no exclusion) or a function taking a
-                    date as parameter, and returning True if the date
-                    should be included.
-    """
-    date = begin
-    while date <= end:
-        if include is None or include(date): 
-            yield date
-        date += incr
-
-
-def dump_class(cls, clsname):
-    """create copy of a class by creating an empty class inheriting
-    from the given cls.
-
-    Those class will be used as place holder for attribute and relation
-    description
-    """
-    # type doesn't accept unicode name
-    # return type.__new__(type, str(clsname), (cls,), {})
-    # __autogenerated__ attribute is just a marker
-    return type(str(clsname), (cls,), {'__autogenerated__': True})
-
-
-def merge_dicts(dict1, dict2):
-    """update a copy of `dict1` with `dict2`"""
-    dict1 = dict(dict1)
-    dict1.update(dict2)
-    return dict1
-                
-
-class SizeConstrainedList(list):
-    """simple list that makes sure the list does not get bigger
-    than a given size.
-
-    when the list is full and a new element is added, the first
-    element of the list is removed before appending the new one
-
-    >>> l = SizeConstrainedList(2)
-    >>> l.append(1)
-    >>> l.append(2)
-    >>> l
-    [1, 2]
-    >>> l.append(3)
-    [2, 3]
-    """
-    def __init__(self, maxsize):
-        self.maxsize = maxsize
-
-    def append(self, element):
-        if len(self) == self.maxsize:
-            del self[0]
-        super(SizeConstrainedList, self).append(element)
-
-    def extend(self, sequence):
-        super(SizeConstrainedList, self).extend(sequence)
-        keepafter = len(self) - self.maxsize
-        if keepafter > 0:
-            del self[:keepafter]
-
-    __iadd__ = extend
-
-
-class UStringIO(list):
-    """a file wrapper which automatically encode unicode string to an encoding
-    specifed in the constructor
-    """
-
-    def __nonzero__(self):
-        return True
-    
-    def write(self, value):
-        assert isinstance(value, unicode), u"unicode required not %s : %s"\
-                                     % (type(value).__name__, repr(value))
-        self.append(value)
-        
-    def getvalue(self):
-        return u''.join(self)
-
-    def __repr__(self):
-        return '<%s at %#x>' % (self.__class__.__name__, id(self))
-
-
-class HTMLHead(UStringIO):
-    """wraps HTML header's stream
-
-    Request objects use a HTMLHead instance to ease adding of
-    javascripts and stylesheets
-    """
-    js_unload_code = u'jQuery(window).unload(unloadPageData);'
-
-    def __init__(self):
-        super(HTMLHead, self).__init__()
-        self.jsvars = []
-        self.jsfiles = []
-        self.cssfiles = []
-        self.ie_cssfiles = []
-        self.post_inlined_scripts = []
-        self.pagedata_unload = False
-
-
-    def add_raw(self, rawheader):
-        self.write(rawheader)
-
-    def define_var(self, var, value):
-        self.jsvars.append( (var, value) )
-
-    def add_post_inline_script(self, content):
-        self.post_inlined_scripts.append(content)
-
-    def add_onload(self, jscode):
-        self.add_post_inline_script(u"""jQuery(document).ready(function () {
- %s
- });""" % jscode)
-        
-    
-    def add_js(self, jsfile):
-        """adds `jsfile` to the list of javascripts used in the webpage
-
-        This function checks if the file has already been added
-        :param jsfile: the script's URL
-        """
-        if jsfile not in self.jsfiles:
-            self.jsfiles.append(jsfile)
-
-    def add_css(self, cssfile, media):
-        """adds `cssfile` to the list of javascripts used in the webpage
-
-        This function checks if the file has already been added
-        :param cssfile: the stylesheet's URL
-        """
-        if (cssfile, media) not in self.cssfiles:
-            self.cssfiles.append( (cssfile, media) )
-
-    def add_ie_css(self, cssfile, media='all'):
-        """registers some IE specific CSS"""
-        if (cssfile, media) not in self.ie_cssfiles:
-            self.ie_cssfiles.append( (cssfile, media) )
-
-    def add_unload_pagedata(self):
-        """registers onunload callback to clean page data on server"""
-        if not self.pagedata_unload:
-            self.post_inlined_scripts.append(self.js_unload_code)
-            self.pagedata_unload = True
-
-    def getvalue(self):
-        """reimplement getvalue to provide a consistent (and somewhat browser
-        optimzed cf. http://stevesouders.com/cuzillion) order in external
-        resources declaration
-        """
-        w = self.write
-        # 1/ variable declaration if any
-        if self.jsvars:
-            from simplejson import dumps
-            w(u'<script type="text/javascript">\n')
-            for var, value in self.jsvars:
-                w(u'%s = %s;\n' % (var, dumps(value)))
-            w(u'</script>\n')
-        # 2/ css files
-        for cssfile, media in self.cssfiles:
-            w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
-              (media, cssfile))
-        # 3/ ie css if necessary
-        if self.ie_cssfiles:
-            w(u'<!--[if lt IE 8]>\n')
-            for cssfile, media in self.ie_cssfiles:
-                w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
-                  (media, cssfile))
-            w(u'<![endif]--> \n')
-        # 4/ js files
-        for jsfile in self.jsfiles:
-            w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
-        # 5/ post inlined scripts (i.e. scripts depending on other JS files)
-        if self.post_inlined_scripts:
-            w(u'<script type="text/javascript">\n')
-            w(u'\n\n'.join(self.post_inlined_scripts))
-            w(u'\n</script>\n')
-        return u'<head>\n%s</head>\n' % super(HTMLHead, self).getvalue()
-        
-
-class HTMLStream(object):
-    """represents a HTML page.
-
-    This is used my main templates so that HTML headers can be added
-    at any time during the page generation.
-    
-    HTMLStream uses the (U)StringIO interface to be compliant with
-    existing code.
-    """
-    
-    def __init__(self, req):
-        # stream for <head>
-        self.head = req.html_headers
-        # main stream
-        self.body = UStringIO()
-        self.doctype = u''
-        # xmldecl and html opening tag
-        self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
-        self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
-                       'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
-                       'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
-
-
-    def write(self, data):
-        """StringIO interface: this method will be assigned to self.w
-        """
-        self.body.write(data)
-
-    def getvalue(self):
-        """writes HTML headers, closes </head> tag and writes HTML body"""
-        return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
-                                                 self.htmltag,
-                                                 self.head.getvalue(),
-                                                 self.body.getvalue())
-
-
-class AcceptMixIn(object):
-    """Mixin class for vobjects defining the 'accepts' attribute describing
-    a set of supported entity type (Any by default).
-    """
-    # XXX deprecated, no more necessary
-
-
-from logilab.common.deprecation import moved, class_moved
-rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid')
-ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url')
-
-import cubicweb
-Binary = class_moved(cubicweb.Binary)
+"""pre 3.2 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.utils', DeprecationWarning, stacklevel=2)
+from cubicweb.utils import *
--- a/common/view.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/common/view.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,481 +1,5 @@
-"""abstract views and templates classes for CubicWeb web client
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-
-from logilab.mtconverter import html_escape
-
-from cubicweb import NotAnEntity, NoSelectableObject
-from cubicweb.common.registerers import accepts_registerer, priority_registerer
-from cubicweb.common.selectors import (chainfirst, match_user_group, accept,
-                                       nonempty_rset, empty_rset, none_rset)
-from cubicweb.common.appobject import AppRsetObject, ComponentMixIn
-from cubicweb.common.utils import UStringIO, HTMLStream
-
-_ = unicode
-
-# robots control
-NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
-NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
-
-CW_XHTML_EXTENSIONS = '''[
-  <!ATTLIST html xmlns:cubicweb CDATA  #FIXED \'http://www.logilab.org/2008/cubicweb\'  >
-
-<!ENTITY % coreattrs
- "id          ID            #IMPLIED
-  class       CDATA         #IMPLIED
-  style       CDATA         #IMPLIED
-  title       CDATA         #IMPLIED
-
- cubicweb:sortvalue         CDATA   #IMPLIED
- cubicweb:target            CDATA   #IMPLIED
- cubicweb:limit             CDATA   #IMPLIED
- cubicweb:type              CDATA   #IMPLIED
- cubicweb:loadtype          CDATA   #IMPLIED
- cubicweb:wdgtype           CDATA   #IMPLIED
- cubicweb:initfunc          CDATA   #IMPLIED
- cubicweb:inputid           CDATA   #IMPLIED
- cubicweb:tindex            CDATA   #IMPLIED
- cubicweb:inputname         CDATA   #IMPLIED
- cubicweb:value             CDATA   #IMPLIED
- cubicweb:required          CDATA   #IMPLIED
- cubicweb:accesskey         CDATA   #IMPLIED
- cubicweb:maxlength         CDATA   #IMPLIED
- cubicweb:variables         CDATA   #IMPLIED
- cubicweb:displayactions    CDATA   #IMPLIED
- cubicweb:fallbackvid       CDATA   #IMPLIED
- cubicweb:vid               CDATA   #IMPLIED
- cubicweb:rql               CDATA   #IMPLIED
- cubicweb:actualrql         CDATA   #IMPLIED
- cubicweb:rooteid           CDATA   #IMPLIED
- cubicweb:dataurl           CDATA   #IMPLIED
- cubicweb:size              CDATA   #IMPLIED
- cubicweb:tlunit            CDATA   #IMPLIED
- cubicweb:loadurl           CDATA   #IMPLIED
- cubicweb:uselabel          CDATA   #IMPLIED
- cubicweb:facetargs         CDATA   #IMPLIED
- cubicweb:facetName         CDATA   #IMPLIED
-  "> ] '''
-
-TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
-
-STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
-
-class View(AppRsetObject):
-    """abstract view class, used as base for every renderable object such
-    as views, templates, some components...web
-
-    A view is instantiated to render a [part of a] result set. View
-    subclasses may be parametred using the following class attributes:
-
-    * `templatable` indicates if the view may be embeded in a main
-      template or if it has to be rendered standalone (i.e. XML for
-      instance)
-    * if the view is not templatable, it should set the `content_type` class
-      attribute to the correct MIME type (text/xhtml by default)
-    * the `category` attribute may be used in the interface to regroup related
-      objects together
-
-    At instantiation time, the standard `req`, `rset`, and `cursor`
-    attributes are added and the `w` attribute will be set at rendering
-    time to a write function to use.
-    """
-    __registry__ = 'views'
-
-    templatable = True
-    need_navigation = True
-    # content_type = 'application/xhtml+xml' # text/xhtml'
-    binary = False
-    add_to_breadcrumbs = True
-    category = 'view'
-
-    def __init__(self, req, rset):
-        super(View, self).__init__(req, rset)
-        self.w = None
-
-    @property
-    def content_type(self):
-        if self.req.xhtml_browser():
-            return 'application/xhtml+xml'
-        return 'text/html'
-
-    def set_stream(self, w=None):
-        if self.w is not None:
-            return
-        if w is None:
-            if self.binary:
-                self._stream = stream = StringIO()
-            else:
-                self._stream = stream = UStringIO()
-            w = stream.write
-        else:
-            stream = None
-        self.w = w
-        return stream
-
-    # main view interface #####################################################
-
-    def dispatch(self, w=None, **context):
-        """called to render a view object for a result set.
-
-        This method is a dispatched to an actual method selected
-        according to optional row and col parameters, which are locating
-        a particular row or cell in the result set:
-
-        * if row [and col] are specified, `cell_call` is called
-        * if none of them is supplied, the view is considered to apply on
-          the whole result set (which may be None in this case), `call` is
-          called
-        """
-        row, col = context.get('row'), context.get('col')
-        if row is not None:
-            context.setdefault('col', 0)
-            view_func = self.cell_call
-        else:
-            view_func = self.call
-        stream = self.set_stream(w)
-        # stream = self.set_stream(context)
-        view_func(**context)
-        # return stream content if we have created it
-        if stream is not None:
-            return self._stream.getvalue()
-
-    # should default .call() method add a <div classs="section"> around each
-    # rset item
-    add_div_section = True
-
-    def call(self, **kwargs):
-        """the view is called for an entire result set, by default loop
-        other rows of the result set and call the same view on the
-        particular row
-
-        Views applicable on None result sets have to override this method
-        """
-        rset = self.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)
-            if wrap:
-                self.w(u"</div>")
-
-    def cell_call(self, row, col, **kwargs):
-        """the view is called for a particular result set cell"""
-        raise NotImplementedError, self
-
-    def linkable(self):
-        """return True if the view may be linked in a menu
-
-        by default views without title are not meant to be displayed
-        """
-        if not getattr(self, 'title', None):
-            return False
-        return True
-
-    def is_primary(self):
-        return self.id == '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.
-        """
-        try:
-            return self.build_url(vid=self.id, rql=self.req.form['rql'])
-        except KeyError:
-            return self.build_url(vid=self.id)
-
-    def set_request_content_type(self):
-        """set the content type returned by this view"""
-        self.req.set_content_type(self.content_type)
-
-    # view utilities ##########################################################
-
-    def view(self, __vid, rset, __fallback_vid=None, **kwargs):
-        """shortcut to self.vreg.render method avoiding to pass self.req"""
-        try:
-            view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
-        except NoSelectableObject:
-            if __fallback_vid is None:
-                raise
-            view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
-        return view.dispatch(**kwargs)
-
-    def wview(self, __vid, rset, __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)
-
-    def whead(self, data):
-        self.req.html_headers.write(data)
-
-    def wdata(self, data):
-        """simple helper that escapes `data` and writes into `self.w`"""
-        self.w(html_escape(data))
-
-    def action(self, actionid, row=0):
-        """shortcut to get action object with id `actionid`"""
-        return self.vreg.select_action(actionid, self.req, self.rset,
-                                       row=row)
-
-    def action_url(self, actionid, label=None, row=0):
-        """simple method to be able to display `actionid` as a link anywhere
-        """
-        action = self.vreg.select_action(actionid, self.req, self.rset,
-                                         row=row)
-        if action:
-            label = label or self.req._(action.title)
-            return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
-        return u''
-
-    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 return a meta tag to disable robot indexation of the page
-        """
-        return [NOINDEX]
-
-    def page_title(self):
-        """returns a title according to the result set - used for the
-        title in the HTML header
-        """
-        vtitle = self.req.form.get('vtitle')
-        if vtitle:
-            return self.req._(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
-        if rset and rset.rowcount:
-            if rset.rowcount == 1:
-                try:
-                    entity = self.complete_entity(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 = 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')
-                else :
-                    clabel = u'#[*] (%s)' % vtitle
-        else:
-            clabel = vtitle
-        return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
-
-    def output_url_builder( self, name, url, args ):
-        self.w(u'<script language="JavaScript"><!--\n' \
-               u'function %s( %s ) {\n' % (name, ','.join(args) ) )
-        url_parts = url.split("%s")
-        self.w(u' url="%s"' % url_parts[0] )
-        for arg, part in zip(args, url_parts[1:]):
-            self.w(u'+str(%s)' % arg )
-            if part:
-                self.w(u'+"%s"' % part)
-        self.w('\n document.window.href=url;\n')
-        self.w('}\n-->\n</script>\n')
-
-    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)
-
-
-# concrete views base classes #################################################
-
-class EntityView(View):
-    """base class for views applying on an entity (i.e. uniform result set)
-    """
-    __registerer__ = accepts_registerer
-    __selectors__ = (accept,)
-    accepts = ('Any',)
-    category = 'entityview'
-
-    def field(self, label, value, row=True, show_label=True, w=None, tr=True):
-        """ read-only field """
-        if w is None:
-            w = self.w
-        if row:
-            w(u'<div class="row">')
-        if show_label:
-            if tr:
-                label = display_name(self.req, label)
-            w(u'<span class="label">%s</span>' % label)
-        w(u'<div class="field">%s</div>' % value)
-        if row:
-            w(u'</div>')
-
-
-class StartupView(View):
-    """base class for views which doesn't need a particular result set
-    to be displayed (so they can always be displayed !)
-    """
-    __registerer__ = priority_registerer
-    __selectors__ = (match_user_group, none_rset)
-    require_groups = ()
-    category = 'startupview'
-
-    def url(self):
-        """return the url associated with this view. We can omit rql here"""
-        return self.build_url('view', vid=self.id)
-
-    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 startup views are indexed
-        """
-        return []
-
-
-class EntityStartupView(EntityView):
-    """base class for entity views which may also be applied to None
-    result set (usually a default rql is provided by the view class)
-    """
-    __registerer__ = accepts_registerer
-    __selectors__ = (chainfirst(none_rset, accept),)
-
-    default_rql = None
-
-    def __init__(self, req, rset):
-        super(EntityStartupView, self).__init__(req, rset)
-        if rset is None:
-            # this instance is not in the "entityview" category
-            self.category = 'startupview'
-
-    def startup_rql(self):
-        """return some rql to be executedif the result set is None"""
-        return self.default_rql
-
-    def call(self, **kwargs):
-        """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
-        for i in xrange(len(rset)):
-            self.wview(self.id, rset, row=i, **kwargs)
-
-    def url(self):
-        """return the url associated with this view. We can omit rql if we
-        are on a result set on which we do not apply.
-        """
-        if not self.__select__(self.req, self.rset):
-            return self.build_url(vid=self.id)
-        return super(EntityStartupView, self).url()
-
-
-class AnyRsetView(View):
-    """base class for views applying on any non empty result sets"""
-    __registerer__ = priority_registerer
-    __selectors__ = (nonempty_rset,)
-
-    category = 'anyrsetview'
-
-    def columns_labels(self, tr=True):
-        if tr:
-            translate = display_name
-        else:
-            translate = lambda req, val: val
-        rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
-        labels = []
-        for colindex, attr in enumerate(rqlstdescr):
-            # compute column header
-            if colindex == 0 or attr == 'Any': # find a better label
-                label = ','.join(translate(self.req, et)
-                                 for et in self.rset.column_types(colindex))
-            else:
-                label = translate(self.req, attr)
-            labels.append(label)
-        return labels
-
-
-class EmptyRsetView(View):
-    """base class for views applying on any empty result sets"""
-    __registerer__ = priority_registerer
-    __selectors__ = (empty_rset,)
-
-
-# concrete template base classes ##############################################
-
-class Template(View):
-    """a template is almost like a view, except that by default a template
-    is only used globally (i.e. no result set adaptation)
-    """
-    __registry__ = 'templates'
-    __registerer__ = priority_registerer
-    __selectors__ = (match_user_group,)
-
-    require_groups = ()
-
-    def template(self, oid, **kwargs):
-        """shortcut to self.registry.render method on the templates registry"""
-        w = kwargs.pop('w', self.w)
-        self.vreg.render('templates', oid, self.req, w=w, **kwargs)
-
-
-class MainTemplate(Template):
-    """main template are primary access point to render a full HTML page.
-    There is usually at least a regular main template and a simple fallback
-    one to display error if the first one failed
-    """
-
-    base_doctype = STRICT_DOCTYPE
-
-    @property
-    def doctype(self):
-        if self.req.xhtml_browser():
-            return self.base_doctype % CW_XHTML_EXTENSIONS
-        return self.base_doctype % ''
-
-    def set_stream(self, w=None, templatable=True):
-        if templatable and self.w is not None:
-            return
-
-        if w is None:
-            if self.binary:
-                self._stream = stream = StringIO()
-            elif not templatable:
-                # not templatable means we're using a non-html view, we don't
-                # want the HTMLStream stuff to interfere during data generation
-                self._stream = stream = UStringIO()
-            else:
-                self._stream = stream = HTMLStream(self.req)
-            w = stream.write
-        else:
-            stream = None
-        self.w = w
-        return stream
-
-    def write_doctype(self, xmldecl=True):
-        assert isinstance(self._stream, HTMLStream)
-        self._stream.doctype = self.doctype
-        if not xmldecl:
-            self._stream.xmldecl = u''
-
-# viewable components base classes ############################################
-
-class VComponent(ComponentMixIn, View):
-    """base class for displayable components"""
-    property_defs = {
-        'visible':  dict(type='Boolean', default=True,
-                         help=_('display the component or not')),}
-
-class SingletonVComponent(VComponent):
-    """base class for displayable unique components"""
-    __registerer__ = priority_registerer
+"""pre 3.2 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.view', DeprecationWarning, stacklevel=2)
+from cubicweb.view import *
--- a/cwconfig.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/cwconfig.py	Tue Apr 07 09:30:23 2009 +0200
@@ -18,7 +18,7 @@
                                           ConfigurationMixIn, merge_options)
 
 from cubicweb import CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, ConfigurationError
-from cubicweb.toolsutils import env_path, read_config, create_dir
+from cubicweb.toolsutils import env_path, create_dir
 
 CONFIGURATIONS = []
 
@@ -56,17 +56,6 @@
                                  % (directory, modes))
     return modes[0]
 
-# XXX generate this according to the configuration (repository/all-in-one/web)
-VREGOPTIONS = []
-for registry in ('etypes', 'hooks', 'controllers', 'actions', 'components',
-                 'views', 'templates', 'boxes', 'contentnavigation', 'urlrewriting',
-                 'facets'):
-    VREGOPTIONS.append(('disable-%s'%registry,
-                        {'type' : 'csv', 'default': (),
-                         'help': 'list of identifier of application objects from the %s registry to disable'%registry,
-                         'group': 'appobjects', 'inputlevel': 2,
-                         }))
-VREGOPTIONS = tuple(VREGOPTIONS)
 
 # persistent options definition
 PERSISTENT_OPTIONS = (
@@ -157,7 +146,7 @@
         mode = 'installed'
         CUBES_DIR = '/usr/share/cubicweb/cubes/'
 
-    options = VREGOPTIONS + (
+    options = (
        ('log-threshold',
          {'type' : 'string', # XXX use a dedicated type?
           'default': 'ERROR',
@@ -202,6 +191,11 @@
 this option is set to yes",
           'group': 'email', 'inputlevel': 2,
           }),
+        ('disable-appobjects',
+         {'type' : 'csv', 'default': (),
+          'help': 'comma separated list of identifiers of application objects (<registry>.<oid>) to disable',
+          'group': 'appobjects', 'inputlevel': 2,
+          }),
         )
     # static and class methods used to get application independant resources ##
         
--- a/cwctl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/cwctl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,10 +7,11 @@
 from os import remove, listdir, system, kill, getpgid
 from os.path import exists, join, isfile, isdir
 
+from logilab.common.clcommands import register_commands, pop_arg
+
 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
-from cubicweb.cwconfig import CubicWebConfiguration, CONFIGURATIONS
-from cubicweb.toolsutils import (Command, register_commands, main_run, 
-                                 rm, create_dir, pop_arg, confirm)
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
+from cubicweb.toolsutils import Command, main_run,  rm, create_dir, confirm
     
 def wait_process_end(pid, maxtry=10, waittime=1):
     """wait for a process to actually die"""
@@ -63,7 +64,7 @@
         considering $REGISTRY_DIR/startorder file if it exists (useful when
         some instances depends on another as external source
         """
-        regdir = CubicWebConfiguration.registry_dir()
+        regdir = cwcfg.registry_dir()
         _allinstances = list_instances(regdir)
         if isfile(join(regdir, 'startorder')):
             allinstances = []
@@ -74,7 +75,8 @@
                         _allinstances.remove(line)
                         allinstances.append(line)
                     except ValueError:
-                        print 'ERROR: startorder file contains unexistant instance %s' % line
+                        print ('ERROR: startorder file contains unexistant '
+                               'instance %s' % line)
             allinstances += _allinstances
         else:
             allinstances = _allinstances
@@ -161,8 +163,8 @@
         """run the command with its specific arguments"""
         if args:
             raise BadCommandUsage('Too much arguments')
-        print 'CubicWeb version:', CubicWebConfiguration.cubicweb_version()
-        print 'Detected mode:', CubicWebConfiguration.mode
+        print 'CubicWeb version:', cwcfg.cubicweb_version()
+        print 'Detected mode:', cwcfg.mode
         print
         print 'Available configurations:'
         for config in CONFIGURATIONS:
@@ -173,20 +175,20 @@
                     continue
                 print '   ', line
         print 
-        cubesdirs = ', '.join(CubicWebConfiguration.cubes_search_path())
         try:
-            namesize = max(len(x) for x in CubicWebConfiguration.available_cubes())
+            cubesdir = cwcfg.cubes_dir()
+            namesize = max(len(x) for x in cwcfg.available_cubes())
         except ConfigurationError, ex:
             print 'No cubes available:', ex
         except ValueError:
-            print 'No cubes available in %s' % cubesdirs
+            print 'No cubes available in %s' % cubesdir
         else:
-            print 'Available cubes (%s):' % cubesdirs
-            for cube in CubicWebConfiguration.available_cubes():
+            print 'Available cubes (%s):' % cubesdir
+            for cube in cwcfg.available_cubes():
                 if cube in ('CVS', '.svn', 'shared', '.hg'):
                     continue
                 try:
-                    tinfo = CubicWebConfiguration.cube_pkginfo(cube)
+                    tinfo = cwcfg.cube_pkginfo(cube)
                     tversion = tinfo.version
                 except ConfigurationError:
                     tinfo = None
@@ -201,7 +203,7 @@
                     print '    available modes: %s' % ', '.join(modes)
         print
         try:
-            regdir = CubicWebConfiguration.registry_dir()
+            regdir = cwcfg.registry_dir()
         except ConfigurationError, ex:
             print 'No application available:', ex
             print
@@ -210,13 +212,13 @@
         if instances:
             print 'Available applications (%s):' % regdir
             for appid in instances:
-                modes = CubicWebConfiguration.possible_configurations(appid)
+                modes = cwcfg.possible_configurations(appid)
                 if not modes:
                     print '* %s (BROKEN application, no configuration found)' % appid
                     continue
                 print '* %s (%s)' % (appid, ', '.join(modes))
                 try:
-                    config = CubicWebConfiguration.config_for(appid, modes[0])
+                    config = cwcfg.config_for(appid, modes[0])
                 except Exception, exc: 
                     print '    (BROKEN application, %s)' % exc
                     continue
@@ -267,19 +269,19 @@
         cubes = get_csv(pop_arg(args, 1))
         appid = pop_arg(args)
         # get the configuration and helper
-        CubicWebConfiguration.creating = True
-        config = CubicWebConfiguration.config_for(appid, configname)
+        cwcfg.creating = True
+        config = cwcfg.config_for(appid, configname)
         config.set_language = False
         config.init_cubes(config.expand_cubes(cubes))
         helper = self.config_helper(config)
         # check the cube exists
         try:
-            templdirs = [CubicWebConfiguration.cube_dir(cube)
+            templdirs = [cwcfg.cube_dir(cube)
                          for cube in cubes]
         except ConfigurationError, ex:
             print ex
             print '\navailable cubes:',
-            print ', '.join(CubicWebConfiguration.available_cubes())
+            print ', '.join(cwcfg.available_cubes())
             return
         # create the registry directory for this application
         create_dir(config.apphome)
@@ -295,7 +297,6 @@
         # write down configuration
         config.save()
         # handle i18n files structure
-        # XXX currently available languages are guessed from translations found
         # in the first cube given
         from cubicweb.common import i18n
         langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
@@ -335,8 +336,8 @@
     def run(self, args):
         """run the command with its specific arguments"""
         appid = pop_arg(args, msg="No application specified !")
-        configs = [CubicWebConfiguration.config_for(appid, configname)
-                   for configname in CubicWebConfiguration.possible_configurations(appid)]
+        configs = [cwcfg.config_for(appid, configname)
+                   for configname in cwcfg.possible_configurations(appid)]
         if not configs:
             raise ExecutionError('unable to guess configuration for %s' % appid)
         for config in configs:
@@ -389,7 +390,7 @@
         # without all options defined
         debug = self.get('debug')
         force = self.get('force')
-        config = CubicWebConfiguration.config_for(appid)
+        config = cwcfg.config_for(appid)
         if self.get('profile'):
             config.global_set_option('profile', self.config.profile)
         helper = self.config_helper(config, cmdname='start')
@@ -428,7 +429,7 @@
     
     def stop_application(self, appid):
         """stop the application's server"""
-        config = CubicWebConfiguration.config_for(appid)
+        config = cwcfg.config_for(appid)
         helper = self.config_helper(config, cmdname='stop')
         helper.poststop() # do this anyway
         pidf = config['pid-file']
@@ -473,7 +474,7 @@
     actionverb = 'restarted'
 
     def run_args(self, args, askconfirm):
-        regdir = CubicWebConfiguration.registry_dir()
+        regdir = cwcfg.registry_dir()
         if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
             # no specific startorder
             super(RestartApplicationCommand, self).run_args(args, askconfirm)
@@ -527,10 +528,11 @@
     name = 'status'
     options = ()
 
-    def status_application(self, appid):
+    @staticmethod
+    def status_application(appid):
         """print running status information for an application"""
-        for mode in CubicWebConfiguration.possible_configurations(appid):
-            config = CubicWebConfiguration.config_for(appid, mode)
+        for mode in cwcfg.possible_configurations(appid):
+            config = cwcfg.config_for(appid, mode)
             print '[%s-%s]' % (appid, mode),
             try:
                 pidf = config['pid-file']
@@ -614,9 +616,9 @@
     
     def upgrade_application(self, appid):
         from logilab.common.changelog import Version
-        if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
+        if not (cwcfg.mode == 'dev' or self.config.nostartstop):
             self.stop_application(appid)
-        config = CubicWebConfiguration.config_for(appid)
+        config = cwcfg.config_for(appid)
         config.creating = True # notice we're not starting the server
         config.verbosity = self.config.verbosity
         try:
@@ -666,10 +668,9 @@
         # handle i18n upgrade:
         # * install new languages
         # * recompile catalogs
-        # XXX currently available languages are guessed from translations found
         # in the first componant given
         from cubicweb.common import i18n
-        templdir = CubicWebConfiguration.cube_dir(config.cubes()[0])
+        templdir = cwcfg.cube_dir(config.cubes()[0])
         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
         errors = config.i18ncompile(langs)
         if errors:
@@ -682,7 +683,7 @@
         mih.shutdown()
         print
         print 'application migrated'
-        if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
+        if not (cwcfg.mode == 'dev' or self.config.nostartstop):
             self.start_application(appid)
         print
 
@@ -718,7 +719,7 @@
         )
     def run(self, args):
         appid = pop_arg(args, 99, msg="No application specified !")
-        config = CubicWebConfiguration.config_for(appid)
+        config = cwcfg.config_for(appid)
         if self.config.ext_sources:
             assert not self.config.system_only
             sources = self.config.ext_sources
@@ -743,10 +744,11 @@
       given, recompile for all registered applications.
     """
     name = 'i18ncompile'
-    
-    def i18ncompile_application(self, appid):
+
+    @staticmethod
+    def i18ncompile_application(appid):
         """recompile application's messages catalogs"""
-        config = CubicWebConfiguration.config_for(appid)
+        config = cwcfg.config_for(appid)
         try:
             config.bootstrap_cubes()
         except IOError, ex:
@@ -773,7 +775,7 @@
     
     def run(self, args):
         """run the command with its specific arguments"""
-        regdir = CubicWebConfiguration.registry_dir()
+        regdir = cwcfg.registry_dir()
         for appid in sorted(listdir(regdir)):
             print appid
 
@@ -785,7 +787,7 @@
     
     def run(self, args):
         """run the command with its specific arguments"""
-        for cube in CubicWebConfiguration.available_cubes():
+        for cube in cwcfg.available_cubes():
             print cube
 
 register_commands((ListCommand,
@@ -805,7 +807,7 @@
                 
 def run(args):
     """command line tool"""
-    CubicWebConfiguration.load_cwctl_plugins()
+    cwcfg.load_cwctl_plugins()
     main_run(args, __doc__)
 
 if __name__ == '__main__':
--- a/cwvreg.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/cwvreg.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,9 +6,8 @@
 """
 __docformat__ = "restructuredtext en"
 
-from warnings import warn
-
 from logilab.common.decorators import cached, clear_cache
+from logilab.common.interface import extend
 
 from rql import RQLHelper
 
@@ -17,11 +16,25 @@
 
 _ = unicode
 
-class DummyCursorError(Exception): pass
-class RaiseCursor:
-    @classmethod
-    def execute(cls, rql, args=None, eid_key=None):
-        raise DummyCursorError()
+def use_interfaces(obj):
+    """return interfaces used by the given object by searchinf for implements
+    selectors, with a bw compat fallback to accepts_interfaces attribute
+    """
+    from cubicweb.selectors import implements
+    try:
+        # XXX deprecated
+        return sorted(obj.accepts_interfaces) 
+    except AttributeError:
+        try:
+            impl = obj.__select__.search_selector(implements)
+            if impl:
+                return sorted(impl.expected_ifaces)
+        except AttributeError:
+            pass # old-style vobject classes with no accepts_interfaces
+        except:
+            print 'bad selector %s on %s' % (obj.__select__, obj)
+            raise
+        return ()
 
 
 class CubicWebRegistry(VRegistry):
@@ -41,14 +54,16 @@
                 if not item[0] in ('propertydefs', 'propertyvalues')]
 
     def values(self):
-        return [value for key,value in self._registries.items()
+        return [value for key, value in self._registries.items()
                 if not key in ('propertydefs', 'propertyvalues')]
     
     def reset(self):
         self._registries = {}
         self._lastmodifs = {}
-        # two special registries, propertydefs which care all the property definitions, and
-        # propertyvals which contains values for those properties
+        self._needs_iface = {}
+        # two special registries, propertydefs which care all the property
+        # definitions, and propertyvals which contains values for those
+        # properties
         self._registries['propertydefs'] = {}
         self._registries['propertyvalues'] = self.eprop_values = {}
         for key, propdef in self.config.eproperty_definitions():
@@ -72,55 +87,59 @@
             for objects in regcontent.values():
                 for obj in objects:
                     obj.schema = schema
+
+    def register_if_interface_found(self, obj, ifaces, **kwargs):
+        """register an object but remove it if no entity class implements one of
+        the given interfaces
+        """
+        self.register(obj, **kwargs)
+        if not isinstance(ifaces,  (tuple, list)):
+            self._needs_iface[obj] = (ifaces,)
+        else:
+            self._needs_iface[obj] = ifaces
+
+    def register(self, obj, **kwargs):
+        if kwargs.get('registryname', obj.__registry__) == 'etypes':
+            if obj.id != 'Any' and not obj.id in self.schema:
+                self.error('don\'t register %s, %s type not defined in the '
+                           'schema', obj, obj.id)
+                return
+            kwargs['clear'] = True
+        super(CubicWebRegistry, self).register(obj, **kwargs)
+        # XXX bw compat
+        ifaces = use_interfaces(obj)
+        if ifaces:
+            self._needs_iface[obj] = ifaces
         
     def register_objects(self, path, force_reload=None):
-        """overriden to handle type class cache issue"""
-        if  super(CubicWebRegistry, self).register_objects(path, force_reload):
+        """overriden to remove objects requiring a missing interface"""
+        if super(CubicWebRegistry, self).register_objects(path, force_reload):
             # clear etype cache if you don't want to run into deep weirdness
             clear_cache(self, 'etype_class')
-            # remove vobjects that don't support any available interface
-            interfaces = set()
-            for classes in self.get('etypes', {}).values():
-                for cls in classes:
-                    interfaces.update(cls.__implements__)
+            # we may want to keep interface dependent objects (e.g.for i18n
+            # catalog generation)
             if not self.config.cleanup_interface_sobjects:
                 return
-            for registry, regcontent in self._registries.items():
-                if registry in ('propertydefs', 'propertyvalues', 'etypes'):
-                    continue
-                for oid, objects in regcontent.items():
-                    for obj in reversed(objects[:]):
-                        if not obj in objects:
-                            continue # obj has been kicked by a previous one
-                        accepted = set(getattr(obj, 'accepts_interfaces', ()))
-                        if accepted:
-                            for accepted_iface in accepted:
-                                for found_iface in interfaces:
-                                    if issubclass(found_iface, accepted_iface):
-                                        # consider priority if necessary
-                                        if hasattr(obj.__registerer__, 'remove_all_equivalents'):
-                                            registerer = obj.__registerer__(self, obj)
-                                            registerer.remove_all_equivalents(objects)
-                                        break
-                                else:
-                                    self.debug('kicking vobject %s (unsupported interface)', obj)
-                                    objects.remove(obj)
-                    # if objects is empty, remove oid from registry
-                    if not objects:
-                        del regcontent[oid]
-
-    def eid_rset(self, cursor, 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)
-        """
-        msg = '.eid_rset is deprecated, use req.eid_rset'
-        warn(msg, DeprecationWarning, stacklevel=2)
-        try:
-            return cursor.req.eid_rset(eid, etype)
-        except AttributeError:
-            # cursor is a session
-            return cursor.eid_rset(eid, etype)
+            # remove vobjects that don't support any available interface
+            implemented_interfaces = set()
+            for classes in self.get('etypes', {}).values():
+                for cls in classes:
+                    for iface in cls.__implements__:
+                        implemented_interfaces.update(iface.__mro__)
+                    implemented_interfaces.update(cls.__mro__)
+            for obj, ifaces in self._needs_iface.items():
+                ifaces = frozenset(isinstance(iface, basestring)
+                                   and iface in self.schema
+                                   and self.etype_class(iface)
+                                   or iface
+                                   for iface in ifaces)
+                if not ('Any' in ifaces or ifaces & implemented_interfaces):
+                    self.debug('kicking vobject %s (no implemented interface '
+                               'among %s)', obj, ifaces)
+                    self.unregister(obj)
+            # clear needs_iface so we don't try to remove some not-anymore-in
+            # objects on automatic reloading
+            self._needs_iface.clear()
     
     @cached
     def etype_class(self, etype):
@@ -129,6 +148,8 @@
         default to a dump of the class registered for 'Any'
         """
         etype = str(etype)
+        if etype == 'Any':
+            return self.select(self.registry_objects('etypes', 'Any'), 'Any')
         eschema = self.schema.eschema(etype)
         baseschemas = [eschema] + eschema.ancestors()
         # browse ancestors from most specific to most generic and
@@ -136,12 +157,21 @@
         for baseschema in baseschemas:
             btype = str(baseschema)
             try:
-                return self.select(self.registry_objects('etypes', btype), etype)
+                cls = self.select(self.registry_objects('etypes', btype), etype)
+                break
             except ObjectNotFound:
                 pass
-        # no entity class for any of the ancestors, fallback to the default one
-        return self.select(self.registry_objects('etypes', 'Any'), etype)
-
+        else:
+            # no entity class for any of the ancestors, fallback to the default
+            # one
+            cls = self.select(self.registry_objects('etypes', 'Any'), etype)
+        # add class itself to the list of implemented interfaces, as well as the
+        # Any entity class so we can select according to class using the
+        # `implements` selector
+        extend(cls, cls)
+        extend(cls, self.etype_class('Any'))
+        return cls
+    
     def render(self, registry, oid, req, **context):
         """select an object in a given registry and render it
 
@@ -157,12 +187,12 @@
         selected = self.select(objclss, req, rset, **context)
         return selected.dispatch(**context)
         
-    def main_template(self, req, oid='main', **context):
+    def main_template(self, req, oid='main-template', **context):
         """display query by calling the given template (default to main),
         and returning the output as a string instead of requiring the [w]rite
         method as argument
         """
-        res = self.render('templates', oid, req, **context)
+        res = self.render('views', oid, req, **context)
         if isinstance(res, unicode):
             return res.encode(req.encoding)
         assert isinstance(res, str)
@@ -378,7 +408,7 @@
             vobject.config = self.config
         return super(MulCnxCubicWebRegistry, self).select(vobjects, *args, **kwargs)
     
-from mx.DateTime import DateTime, Time, DateTimeDelta
+from datetime import datetime, date, time, timedelta
 
 YAMS_TO_PY = {
     'Boolean':  bool,
@@ -387,9 +417,9 @@
     'Bytes':    Binary,
     'Int':      int,
     'Float':    float,
-    'Date':     DateTime,
-    'Datetime': DateTime,
-    'Time':     Time,
-    'Interval': DateTimeDelta,
+    'Date':     date,
+    'Datetime': datetime,
+    'Time':     time,
+    'Interval': timedelta,
     }
 
--- a/dbapi.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/dbapi.py	Tue Apr 07 09:30:23 2009 +0200
@@ -5,12 +5,12 @@
 (most parts of this document are reported here in docstrings)
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from logging import getLogger, StreamHandler
+from logging import getLogger
 from time import time, clock
 
 from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods
@@ -42,7 +42,7 @@
         from cubicweb.server.repository import Repository
         return Repository(config, vreg=vreg)
     else: # method == 'pyro'
-        from Pyro import core, naming, config as pyroconfig
+        from Pyro import core, naming
         from Pyro.errors import NamingError, ProtocolError
         core.initClient(banner=0)
         nsid = ':%s.%s' % (config['pyro-ns-group'], database)
@@ -54,7 +54,7 @@
         except ProtocolError:
             raise ConnectionError('Could not connect to the Pyro name server '
                                   '(host: %s:%i)' % (nshost, nsport))
-        except NamingError, ex:
+        except NamingError:
             raise ConnectionError('Could not get repository for %s '
                                   '(not registered in Pyro), '
                                   'you may have to restart your server-side '
@@ -358,11 +358,7 @@
 
     def check(self):
         """raise `BadSessionId` if the connection is no more valid"""
-        try:
-            self._repo.check_session(self.sessionid)
-        except AttributeError:
-            # XXX backward compat for repository running cubicweb < 2.48.3
-            self._repo.session_data(self.sessionid)
+        self._repo.check_session(self.sessionid)
 
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
--- a/debian/control	Tue Apr 07 08:55:37 2009 +0200
+++ b/debian/control	Tue Apr 07 09:30:23 2009 +0200
@@ -10,7 +10,6 @@
 Homepage: http://www.cubicweb.org
 XS-Python-Version: >= 2.4, << 2.6
 
-
 Package: cubicweb
 Architecture: all
 XB-Python-Version: ${python:Versions}
@@ -76,7 +75,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, python-logilab-mtconverter (>= 0.6.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml, python-logilab-common (>= 0.38.1), python-yams (>= 0.20.2), python-rql (>= 0.20.2), python-simplejson (>= 1.3)
+Depends: ${python:Depends}, python-logilab-mtconverter (>= 0.6.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml, python-logilab-common (>= 0.39.0), python-yams (>= 0.21.0), python-rql (>= 0.22.0), python-simplejson (>= 1.3)
 Recommends: python-psyco
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/debian/cubicweb-web.postinst	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-#! /bin/sh -e
-
-ln -sf /usr/share/fckeditor/fckeditor.js /usr/share/cubicweb/cubes/shared/data
- 
-#DEBHELPER#
- 
-exit 0
--- a/devtools/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,19 +1,19 @@
 """Test tools for cubicweb
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 import os
 import logging
+from datetime import timedelta
 from os.path import (abspath, join, exists, basename, dirname, normpath, split,
                      isfile, isabs)
 
-from mx.DateTime import strptime, DateTimeDelta
-
 from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError
+from cubicweb.utils import strptime
 from cubicweb.toolsutils import read_config
 from cubicweb.cwconfig import CubicWebConfiguration, merge_options
 from cubicweb.server.serverconfig import ServerConfiguration
@@ -271,6 +271,7 @@
                     for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)):
                         if vtype in ('Date', 'Datetime') and type(value) is unicode:
                             found_date = True
+                            value = value.rsplit('.', 1)[0]
                             try:
                                 row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
                             except:
@@ -284,7 +285,7 @@
                                 row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
                         if vtype == 'Interval' and type(value) is int:
                             found_date = True
-                            row[cellindex] = DateTimeDelta(0, 0, 0, value)
+                            row[cellindex] = timedelta(0, value, 0) # XXX value is in number of seconds?
                     if not found_date:
                         break
             return rset
--- a/devtools/_apptest.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/_apptest.py	Tue Apr 07 09:30:23 2009 +0200
@@ -61,6 +61,7 @@
     
 
 def ignore_relations(*relations):
+    global SYSTEM_RELATIONS
     SYSTEM_RELATIONS += relations
 
 class TestEnvironment(object):
@@ -83,7 +84,6 @@
         self.restore_database()
         if verbose:
             print "init done"
-        login = source['db-user']
         config.repository = lambda x=None: self.repo
         self.app = CubicWebPublisher(config, vreg=vreg)
         self.verbose = verbose
@@ -234,15 +234,14 @@
             print "init test database ..."
         source = config.sources()['system']
         self.vreg = CubicWebRegistry(config)
-        repo, self.cnx = init_test_database(driver=source['db-driver'],
-                                            vreg=self.vreg)
+        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
-        login = source['db-user']
         
     def setup(self, config=None):
         """config is passed by TestSuite but is ignored in this environment"""
--- a/devtools/apptest.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/apptest.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """This module provides misc utilities to test applications
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -493,13 +493,10 @@
         self._prepare()
         self.session.set_pool()
         self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-        #self.maxeid = self.execute('Any MAX(X)')
         
-    def tearDown(self, close=True):
+    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()
-        #if close:
-        #    self.close()
     
--- a/devtools/devctl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/devctl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,24 +2,25 @@
 cubes development
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 import sys
-from os import walk, mkdir, chdir, listdir, getcwd
+from datetime import datetime
+from os import mkdir, chdir, listdir
 from os.path import join, exists, abspath, basename, normpath, split, isdir
 
 
 from logilab.common import STD_BLACKLIST
 from logilab.common.modutils import get_module_files
 from logilab.common.textutils import get_csv
+from logilab.common.clcommands import register_commands
 
-from cubicweb import CW_SOFTWARE_ROOT as BASEDIR
+from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
 from cubicweb.__pkginfo__ import version as cubicwebversion
-from cubicweb import BadCommandUsage
-from cubicweb.toolsutils import Command, register_commands, confirm, copy_skeleton
+from cubicweb.toolsutils import Command, confirm, copy_skeleton
 from cubicweb.web.webconfig import WebConfiguration
 from cubicweb.server.serverconfig import ServerConfiguration
 
@@ -102,9 +103,8 @@
     _generate_schema_pot(w, vreg, schema, libschema=libschema, cube=cube)
                 
 def _generate_schema_pot(w, vreg, schema, libschema=None, cube=None):
-    from mx.DateTime import now
     from cubicweb.common.i18n import add_msg
-    w('# schema pot file, generated on %s\n' % now().strftime('%Y-%m-%d %H:%M:%S'))
+    w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
     w('# \n')
     w('# singular and plural forms for each entity type\n')
     w('\n')
@@ -143,10 +143,10 @@
             add_msg(w, rschema.description)
     w('# add related box generated message\n')
     w('\n')
+    actionbox = self.vreg['actions']['edit_box'][0]
     for eschema in schema.entities():
         if eschema.is_final():
             continue
-        entity = vreg.etype_class(eschema)(None, None)
         for x, rschemas in (('subject', eschema.subject_relations()),
                             ('object', eschema.object_relations())):
             for rschema in rschemas:
@@ -155,7 +155,7 @@
                 for teschema in rschema.targets(eschema, x):
                     if defined_in_library(libschema, eschema, rschema, teschema, x):
                         continue
-                    if entity.relation_mode(rschema.type, teschema.type, x) == 'create':
+                    if actionbox.relation_mode(rschema.type, teschema.type, x) == 'create':
                         if x == 'subject':
                             label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
                             label2 = "creating %s (%s %%(linkto)s %s %s)" % (teschema, eschema, rschema, teschema)
@@ -461,14 +461,13 @@
                 dependancies = ', '.join(repr(cube) for cube in includes)
         else:
             dependancies = ''
-        from mx.DateTime import now
         context = {'cubename' : cubename,
                    'distname' : distname,
                    'shortdesc' : shortdesc,
                    'longdesc' : longdesc or shortdesc,
                    'dependancies' : dependancies,
                    'version'  : cubicwebversion,
-                   'year'  : str(now().year),
+                   'year'  : str(datetime.now().year),
                    'author': self['author'],
                    'author-email': self['author-email'],
                    'author-web-site': self['author-web-site'],
--- a/devtools/fill.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/fill.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,16 +2,16 @@
 """This modules defines func / methods for creating test repositories
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from random import randint, choice
 from copy import deepcopy
+from datetime import datetime, date, timedelta
+from decimal import Decimal
 
-from mx.DateTime import DateTime, DateTimeDelta
-from decimal import Decimal
 from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
                               IntervalBoundConstraint)
 from rql.utils import decompose_b26 as base_decompose_b26
@@ -158,15 +158,15 @@
     
     def generate_date(self, attrname, index):
         """generates a random date (format is 'yyyy-mm-dd')"""
-        return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28))
+        return date(randint(2000, 2004), randint(1, 12), randint(1, 28))
 
     def generate_time(self, attrname, index):
         """generates a random time (format is ' HH:MM')"""
-        return DateTimeDelta(0, 11, index%60) #'11:%02d' % (index % 60)
+        return timedelta(0, 11, index%60) #'11:%02d' % (index % 60)
     
     def generate_datetime(self, attrname, index):
         """generates a random date (format is 'yyyy-mm-dd HH:MM')"""
-        return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
+        return datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
         
 
     def generate_bytes(self, attrname, index, format=None):
--- a/devtools/htmlparser.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/htmlparser.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,12 +1,10 @@
 """defines a validating HTML parser used in web application tests"""
 
 import re
-from StringIO import StringIO
 
 from lxml import etree
-from lxml.builder import E
 
-from cubicweb.common.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE, CW_XHTML_EXTENSIONS
+from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE, CW_XHTML_EXTENSIONS
 
 STRICT_DOCTYPE = str(STRICT_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
 TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
@@ -55,20 +53,6 @@
         for blockquote in blockquotes:
             parent = blockquote.getparent()
             parent.remove(blockquote)
-##         # for each blockquote, wrap unauthorized child in a div
-##         for blockquote in blockquotes:
-##             if len(blockquote):
-##                 needs_wrap = [(index, child) for index, child in enumerate(blockquote)
-##                               if child.tag not in expected]
-##                 for index, child in needs_wrap:
-##                     # the child is automatically popped from blockquote when
-##                     # its parent is changed
-##                     div = E.div(child)
-##                     blockquote.insert(index, div)
-##             elif blockquote.text:
-##                 div = E.div(blockquote.text)
-##                 blockquote.text = None
-##                 blockquote.append(div)
         data = etree.tostring(tree)
         return '<?xml version="1.0" encoding="UTF-8"?>%s\n%s' % (STRICT_DOCTYPE, data)
 
--- a/devtools/livetest.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/livetest.py	Tue Apr 07 09:30:23 2009 +0200
@@ -36,12 +36,9 @@
         """Indicate which resource to use to process down the URL's path"""
         if len(segments) and segments[0] == 'data':
             # Anything in data/ is treated as static files
-            dirlist = [self.data_dir, join(dirname(cubicweb.web.__file__), 'data')]
-            for alternative in dirlist:
-                filepath = join(alternative, *segments[1:]) 
-                if exists(filepath):
-                    self.info('publish static file: %s', '/'.join(segments))
-                    return static.File(filepath), ()
+            datadir = self.config.locate_resource(segments[1])
+            if datadir:
+                return static.File(str(datadir), segments[1:])
         # Otherwise we use this single resource
         return self, ()
     
--- a/devtools/repotest.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/repotest.py	Tue Apr 07 09:30:23 2009 +0200
@@ -228,10 +228,10 @@
     variantes = _orig_build_variantes(self, newsolutions)
     sortedvariantes = []
     for variante in variantes:
-        orderedkeys = sorted((k[1], k[2], v) for k,v in variante.iteritems())
+        orderedkeys = sorted((k[1], k[2], v) for k, v in variante.iteritems())
         variante = DumbOrderedDict(sorted(variante.iteritems(),
-                                          lambda a,b: cmp((a[0][1],a[0][2],a[1]),
-                                                          (b[0][1],b[0][2],b[1]))))
+                                          lambda a, b: cmp((a[0][1],a[0][2],a[1]),
+                                                           (b[0][1],b[0][2],b[1]))))
         sortedvariantes.append( (orderedkeys, variante) )
     return [v for ok, v in sorted(sortedvariantes)]
 
@@ -241,7 +241,7 @@
 
 def _check_permissions(*args, **kwargs):
     res, restricted = _orig_check_permissions(*args, **kwargs)
-    res = DumbOrderedDict(sorted(res.iteritems(), lambda a,b: cmp(a[1], b[1])))
+    res = DumbOrderedDict(sorted(res.iteritems(), lambda a, b: cmp(a[1], b[1])))
     return res, restricted
 
 def _dummy_check_permissions(self, rqlst):
@@ -267,7 +267,7 @@
     from cubicweb.server.msplanner import PartPlanInformation
 except ImportError:
     class PartPlanInformation(object):
-        def merge_input_maps(*args):
+        def merge_input_maps(self, *args):
             pass
         def _choose_term(self, sourceterms):
             pass    
--- a/devtools/test/runtests.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
-    import sys, os
-    main(os.path.dirname(sys.argv[0]) or '.')
--- a/devtools/testlib.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/devtools/testlib.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """this module contains base classes for web tests
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -13,8 +13,6 @@
 from logilab.common.testlib import InnerTest
 from logilab.common.pytest import nocoverage
 
-from rql import parse
-
 from cubicweb.devtools import VIEW_VALIDATORS
 from cubicweb.devtools.apptest import EnvBasedTC
 from cubicweb.devtools._apptest import unprotected_entities, SYSTEM_RELATIONS
@@ -24,8 +22,6 @@
 from cubicweb.sobjects.notification import NotificationView
 
 from cubicweb.vregistry import NoSelectableObject
-from cubicweb.web.action import Action
-from cubicweb.web.views.basetemplates import TheMainTemplate
 
 
 ## TODO ###############
@@ -149,7 +145,7 @@
             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)
+            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:
@@ -158,7 +154,7 @@
         self.commit()
 
     @nocoverage
-    def _check_html(self, output, view, template='main'):
+    def _check_html(self, output, view, template='main-template'):
         """raises an exception if the HTML is invalid"""
         try:
             validatorclass = self.vid_validators[view.id]
@@ -175,7 +171,7 @@
         return validator.parse_string(output.strip())
 
 
-    def view(self, vid, rset, req=None, template='main', **kwargs):
+    def view(self, vid, rset, req=None, template='main-template', **kwargs):
         """This method tests the view `vid` on `rset` using `template`
 
         If no error occured while rendering the view, the HTML is analyzed
@@ -184,7 +180,7 @@
         :returns: an instance of `cubicweb.devtools.htmlparser.PageInfo`
                   encapsulation the generated HTML
         """
-        req = req or rset.req
+        req = req or rset and rset.req or self.request()
         # print "testing ", vid,
         # if rset:
         #     print rset, len(rset), id(rset)
@@ -197,24 +193,16 @@
             self.set_description("testing %s, mod=%s (%s)" % (vid, view.__module__, rset.printable_rql()))
         else:
             self.set_description("testing %s, mod=%s (no rset)" % (vid, view.__module__))
-        viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs)
         if template is None: # raw view testing, no template
             viewfunc = view.dispatch
-        elif template == 'main':
-            _select_view_and_rset = TheMainTemplate._select_view_and_rset
-            # patch TheMainTemplate.process_rql to avoid recomputing resultset
-            def __select_view_and_rset(self, view=view, rset=rset):
-                self.rset = rset
-                return view, rset
-            TheMainTemplate._select_view_and_rset = __select_view_and_rset
-        try:
-            return self._test_view(viewfunc, view, template, **kwargs)
-        finally:
-            if template == 'main':
-                TheMainTemplate._select_view_and_rset = _select_view_and_rset
+        else:
+            templateview = self.vreg.select_view(template, req, rset, view=view, **kwargs)
+            kwargs['view'] = view
+            viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs)
+        return self._test_view(viewfunc, view, template, kwargs)
 
 
-    def _test_view(self, viewfunc, view, template='main', **kwargs):
+    def _test_view(self, viewfunc, view, template='main-template', kwargs={}):
         """this method does the actual call to the view
 
         If no error occured while rendering the view, the HTML is analyzed
@@ -249,7 +237,7 @@
                     output = '\n'.join(line_template % (idx + 1, line)
                                 for idx, line in enumerate(output)
                                 if line_context_filter(idx+1, position))
-                    msg+= '\nfor output:\n%s' % output
+                    msg += '\nfor output:\n%s' % output
             raise AssertionError, msg, tcbk
 
 
@@ -332,15 +320,11 @@
             backup_rset = rset._prepare_copy(rset.rows, rset.description)
             yield InnerTest(self._testname(rset, view.id, 'view'),
                             self.view, view.id, rset,
-                            rset.req.reset_headers(), 'main')
+                            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):
-            # XXX this seems a bit dummy
-            #yield InnerTest(self._testname(rset, action.id, 'action'),
-            #                self.failUnless,
-            #                isinstance(action, Action))
             yield InnerTest(self._testname(rset, action.id, 'action'), action.url)
         for box in self.list_boxes_for(rset):
             yield InnerTest(self._testname(rset, box.id, 'box'), box.dispatch)
--- a/entities/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -8,12 +8,12 @@
 
 from warnings import warn
 
-from logilab.common.deprecation import deprecated_function
+from logilab.common.deprecation import deprecated_function, obsolete
 from logilab.common.decorators import cached
 
 from cubicweb import Unauthorized, typed_eid
-from cubicweb.common.utils import dump_class
-from cubicweb.common.entity import Entity
+from cubicweb.entity import Entity
+from cubicweb.utils import dump_class
 from cubicweb.schema import FormatConstraint
 
 from cubicweb.interfaces import IBreadCrumbs, IFeed
@@ -23,36 +23,7 @@
     """an entity instance has e_schema automagically set on the class and
     instances have access to their issuing cursor
     """
-    id = 'Any'   
-    __rtags__ = {
-        'is' : ('generated', 'link'),
-        'is_instance_of' : ('generated', 'link'),
-        'identity' : ('generated', 'link'),
-        
-        # use primary and not generated for eid since it has to be an hidden
-        # field in edition
-        ('eid',                '*', 'subject'): 'primary',
-        ('creation_date',      '*', 'subject'): 'generated',
-        ('modification_date',  '*', 'subject'): 'generated',
-        ('has_text',           '*', 'subject'): 'generated',
-        
-        ('require_permission', '*', 'subject') : ('generated', 'link'),
-        ('owned_by',           '*', 'subject') : ('generated', 'link'),
-        ('created_by',         '*', 'subject') : ('generated', 'link'),
-        
-        ('wf_info_for',        '*', 'subject') : ('generated', 'link'),
-        ('wf_info_for',        '*', 'object')  : ('generated', 'link'),
-                 
-        ('description',        '*', 'subject'): 'secondary',
-
-        # XXX should be moved in their respective cubes
-        ('filed_under',        '*', 'subject') : ('generic', 'link'),
-        ('filed_under',        '*', 'object')  : ('generic', 'create'),
-        # generated since there is a componant to handle comments
-        ('comments',           '*', 'subject') : ('generated', 'link'),
-        ('comments',           '*', 'object')  : ('generated', 'link'),
-        }
-
+    id = 'Any'                    
     __implements__ = (IBreadCrumbs, IFeed)
     
     @classmethod
@@ -89,27 +60,12 @@
     @classmethod
     def __initialize__(cls): 
         super(ANYENTITY, cls).__initialize__() # XXX
+        # set a default_ATTR method for rich text format fields
+        # XXX move this away once the old widgets have been dropped!
         eschema = cls.e_schema
-        eschema.format_fields = {}
-        # set a default_ATTR method for rich text format fields
-        for attr, formatattr in eschema.rich_text_fields():
-            if not hasattr(cls, 'default_%s' % formatattr):
-                setattr(cls, 'default_%s' % formatattr, cls._default_format)
-            eschema.format_fields[formatattr] = attr
-            
-    def _default_format(self):
-        return self.req.property_value('ui.default-text-format')
-
-    def use_fckeditor(self, attr):
-        """return True if fckeditor should be used to edit entity's attribute named
-        `attr`, according to user preferences
-        """
-        req = self.req
-        if req.property_value('ui.fckeditor') and self.has_format(attr):
-            if self.has_eid() or '%s_format' % attr in self:
-                return self.format(attr) == 'text/html'
-            return req.property_value('ui.default-text-format') == 'text/html'
-        return False
+        for metaattr, (metadata, attr) in eschema.meta_attributes().iteritems():
+            if metadata == 'format' and not hasattr(cls, 'default_%s' % metaattr):
+                setattr(cls, 'default_%s' % metaattr, cls._default_format)
     
     # meta data api ###########################################################
 
@@ -155,7 +111,6 @@
     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)
-    display_name = deprecated_function(dc_type) # require agueol > 0.8.1, asteretud > 0.10.0 for removal
 
     def dc_language(self):
         """return language used by this entity (translated)"""
@@ -204,6 +159,76 @@
         return self.absolute_url(vid='rss')
     
     # abstractions making the whole things (well, some at least) working ######
+        
+    def sortvalue(self, rtype=None):
+        """return a value which can be used to sort this entity or given
+        entity's attribute
+        """
+        if rtype is None:
+            return self.dc_title().lower()
+        value = self.get_value(rtype)
+        # do not restrict to `unicode` because Bytes will return a `str` value
+        if isinstance(value, basestring):
+            return self.printable_value(rtype, format='text/plain').lower()
+        return value
+
+    # edition helper functions ################################################    
+
+    def linked_to(self, rtype, target, remove=True):
+        """if entity should be linked to another using __linkto form param for
+        the given relation/target, return eids of related entities
+
+        This method is consuming matching link-to information from form params
+        if `remove` is True (by default).
+        """
+        try:
+            return self.__linkto[(rtype, target)]
+        except AttributeError:
+            self.__linkto = {}
+        except KeyError:
+            pass
+        linktos = list(self.req.list_form_param('__linkto'))
+        linkedto = []
+        for linkto in linktos[:]:
+            ltrtype, eid, lttarget = linkto.split(':')
+            if rtype == ltrtype and target == lttarget:
+                # delete __linkto from form param to avoid it being added as
+                # hidden input
+                if remove:
+                    linktos.remove(linkto)
+                    self.req.form['__linkto'] = linktos
+                linkedto.append(typed_eid(eid))
+        self.__linkto[(rtype, target)] = linkedto
+        return linkedto
+    
+    # edit controller callbacks ###############################################
+    
+    def after_deletion_path(self):
+        """return (path, parameters) which should be used as redirect
+        information when this entity is being deleted
+        """
+        return str(self.e_schema).lower(), {}
+
+    def pre_web_edit(self):
+        """callback called by the web editcontroller when an entity will be
+        created/modified, to let a chance to do some entity specific stuff.
+
+        Do nothing by default.
+        """
+        pass
+    
+    # server side helpers #####################################################
+    
+    def notification_references(self, view):
+        """used to control References field of email send on notification
+        for this entity. `view` is the notification view.
+        
+        Should return a list of eids which can be used to generate message ids
+        of previously sent email
+        """
+        return ()
+            
+    # XXX deprecates, may be killed once old widgets system is gone ###########
     
     @classmethod
     def get_widget(cls, rschema, x='subject'):
@@ -223,126 +248,29 @@
             tschema = rschema.subjects(cls.e_schema)[0]
             wdg = widget(cls.vreg, tschema, rschema, cls, 'object')
         return wdg
-        
-    def sortvalue(self, rtype=None):
-        """return a value which can be used to sort this entity or given
-        entity's attribute
-        """
-        if rtype is None:
-            return self.dc_title().lower()
-        value = self.get_value(rtype)
-        # do not restrict to `unicode` because Bytes will return a `str` value
-        if isinstance(value, basestring):
-            return self.printable_value(rtype, format='text/plain').lower()
-        return value
 
-    def after_deletion_path(self):
-        """return (path, parameters) which should be used as redirect
-        information when this entity is being deleted
-        """
-        return str(self.e_schema).lower(), {}
-
-    def add_related_schemas(self):
-        """this is actually used ui method to generate 'addrelated' actions from
-        the schema.
+    @obsolete('use EntityFieldsForm.subject_relation_vocabulary')
+    def subject_relation_vocabulary(self, rtype, limit):
+        from cubicweb.web.form import EntityFieldsForm
+        return EntityFieldsForm(self.req, None, entity=self).subject_relation_vocabulary(rtype, limit)
 
-        If you're using explicit 'addrelated' actions for an entity types, you
-        should probably overrides this method to return an empty list else you
-        may get some unexpected actions.
-        """
-        req = self.req
-        eschema = self.e_schema
-        for role, rschemas in (('subject', eschema.subject_relations()),
-                               ('object', eschema.object_relations())):
-            for rschema in rschemas:
-                if rschema.is_final():
-                    continue
-                # check the relation can be added as well
-                if role == 'subject'and not rschema.has_perm(req, 'add', fromeid=self.eid):
-                    continue
-                if role == 'object'and not rschema.has_perm(req, 'add', toeid=self.eid):
-                    continue
-                # check the target types can be added as well
-                for teschema in rschema.targets(eschema, role):
-                    if not self.relation_mode(rschema, teschema, role) == 'create':
-                        continue
-                    if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
-                        yield rschema, teschema, role
-
-    def relation_mode(self, rtype, targettype, role='subject'):
-        """return a string telling if the given relation is usually created
-        to a new entity ('create' mode) or to an existant entity ('link' mode)
-        """
-        return self.rtags.get_mode(rtype, targettype, role)
-
-    # edition helper functions ################################################
+    @obsolete('use EntityFieldsForm.object_relation_vocabulary')
+    def object_relation_vocabulary(self, rtype, limit):
+        from cubicweb.web.form import EntityFieldsForm
+        return EntityFieldsForm(self.req, None, entity=self).object_relation_vocabulary(rtype, limit)
     
+    @obsolete('use AutomaticEntityForm.[e]relations_by_category')
     def relations_by_category(self, categories=None, permission=None):
-        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, rtags  = self.e_schema, self.rtags
-        if self.has_eid():
-            eid = self.eid
-        else:
-            eid = None
-        for rschema, targetschemas, role in eschema.relation_definitions(True):
-            if rschema in ('identity', 'has_text'):
-                continue
-            # 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.get_tags(rschema.type, tschema.type, role).intersection(categories)]
-                if not targetschemas:
-                    continue
-            tags = rtags.get_tags(rschema.type, role=role)
-            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 tags:
-                    yield (rschema, targetschemas, role)
-                    continue
-                if rschema.is_final():
-                    if not rschema.has_perm(self.req, permission, eid):
-                        continue
-                elif role == 'subject':
-                    if not ((eid is None and rschema.has_local_role(permission)) or
-                            rschema.has_perm(self.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 self.has_eid() and self.related(rschema.type, role)
-                        and not rschema.has_perm(self.req, 'delete', fromeid=eid,
-                                                 toeid=self.related(rschema.type, role)[0][0])):
-                        continue
-                elif role == 'object':
-                    if not ((eid is None and rschema.has_local_role(permission)) or
-                            rschema.has_perm(self.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 self.has_eid() and self.related(rschema.type, role)
-                        and not rschema.has_perm(self.req, 'delete', toeid=eid,
-                                                 fromeid=self.related(rschema.type, role)[0][0])):
-                        continue
-            yield (rschema, targetschemas, role)
+        from cubicweb.web.views.editforms import AutomaticEntityForm
+        return AutomaticEntityForm.erelations_by_category(self, categories, permission)
 
+    @obsolete('use AutomaticEntityForm.[e]srelations_by_category')
     def srelations_by_category(self, categories=None, permission=None):
-        result = []
-        for rschema, ttypes, target in self.relations_by_category(categories,
-                                                                  permission):
-            if rschema.is_final():
-                continue
-            result.append( (rschema.display_name(self.req, target), rschema, target) )
-        return sorted(result)
+        from cubicweb.web.views.editforms import AutomaticEntityForm
+        return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
+    
+    def _default_format(self):
+        return self.req.property_value('ui.default-text-format')
                 
     def attribute_values(self, attrname):
         if self.has_eid() or attrname in self:
@@ -372,51 +300,15 @@
             values = (values,)
         return values
 
-    def linked_to(self, rtype, target, remove=True):
-        """if entity should be linked to another using __linkto form param for
-        the given relation/target, return eids of related entities
-
-        This method is consuming matching link-to information from form params
-        if `remove` is True (by default).
+    def use_fckeditor(self, attr):
+        """return True if fckeditor should be used to edit entity's attribute named
+        `attr`, according to user preferences
         """
-        try:
-            return self.__linkto[(rtype, target)]
-        except AttributeError:
-            self.__linkto = {}
-        except KeyError:
-            pass
-        linktos = list(self.req.list_form_param('__linkto'))
-        linkedto = []
-        for linkto in linktos[:]:
-            ltrtype, eid, lttarget = linkto.split(':')
-            if rtype == ltrtype and target == lttarget:
-                # delete __linkto from form param to avoid it being added as
-                # hidden input
-                if remove:
-                    linktos.remove(linkto)
-                    self.req.form['__linkto'] = linktos
-                linkedto.append(typed_eid(eid))
-        self.__linkto[(rtype, target)] = linkedto
-        return linkedto
-
-    def pre_web_edit(self):
-        """callback called by the web editcontroller when an entity will be
-        created/modified, to let a chance to do some entity specific stuff.
-
-        Do nothing by default.
-        """
-        pass
-    
-    # server side helpers #####################################################
-    
-    def notification_references(self, view):
-        """used to control References field of email send on notification
-        for this entity. `view` is the notification view.
-        
-        Should return a list of eids which can be used to generate message ids
-        of previously sent email
-        """
-        return ()
+        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.attribute_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
--- a/entities/authobjs.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/authobjs.py	Tue Apr 07 09:30:23 2009 +0200
@@ -13,7 +13,6 @@
 class EGroup(AnyEntity):
     id = 'EGroup'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = dict(in_group='create')
 
     def db_key_name(self):
         """XXX goa specific"""
@@ -24,18 +23,6 @@
     id = 'EUser'
     fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname'])
     
-    __rtags__ = { 'firstname'  : 'secondary',
-                  'surname'    : 'secondary',
-                  'last_login_time' : 'generated',
-                  'todo_by'    : 'create',
-                  'use_email'  : 'inlineview', # 'primary',
-                  'in_state'   : 'primary', 
-                  'in_group'   : 'primary', 
-                  ('owned_by', '*', 'object') : ('generated', 'link'),
-                  ('created_by','*','object') : ('generated', 'link'),
-                  ('bookmarked_by', '*', 'object'): ('generated', 'create'),
-                  }
-    
     # used by repository to check if  the user can log in or not
     AUTHENTICABLE_STATES = ('activated',)
 
--- a/entities/lib.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/lib.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,11 +7,11 @@
 __docformat__ = "restructuredtext en"
 
 from urlparse import urlsplit, urlunsplit
-from mx.DateTime import now
+from datetime import datetime
 
 from logilab.common.decorators import cached
 
-from cubicweb.common.entity import _marker
+from cubicweb.entity import _marker
 from cubicweb.entities import AnyEntity, fetch_config
 
 def mangle_email(address):
@@ -25,10 +25,6 @@
     id = 'EmailAddress'
     fetch_attrs, fetch_order = fetch_config(['address', 'alias', 'canonical'])
 
-    widgets = {
-        'address' : "EmailWidget",
-        }
-
     def dc_title(self):
         if self.alias:
             return '%s <%s>' % (self.alias, self.display_address())
@@ -94,13 +90,7 @@
 class EProperty(AnyEntity):
     id = 'EProperty'
 
-    fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
-
-    widgets = {
-        'pkey' : "PropertyKeyWidget",
-        'value' : "PropertyValueWidget",
-        }
-    
+    fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])    
     rest_attr = 'pkey'
 
     def typed_value(self):
@@ -120,10 +110,6 @@
     """customized class for Bookmark entities"""
     id = 'Bookmark'
     fetch_attrs, fetch_order = fetch_config(['title', 'path'])
-    widgets = {
-        'path' : "StringWidget",
-        }
-    __rtags__ = {'path': 'primary'}
 
     def actual_url(self):
         url = self.req.build_url(self.path)
@@ -153,6 +139,7 @@
     def dc_description(self, format='text/plain'):
         return self.synopsis or u''
 
+
 class ECache(AnyEntity):
     """Cache"""
     id = 'ECache'
@@ -160,7 +147,8 @@
     fetch_attrs, fetch_order = fetch_config(['name'])
 
     def touch(self):
-        self.req.execute('SET X timestamp %(t)s WHERE X eid %(x)s', {'t': now(), 'x': self.eid}, 'x')
+        self.req.execute('SET X timestamp %(t)s WHERE X eid %(x)s',
+                         {'t': datetime.now(), 'x': self.eid}, 'x')
 
     def valid(self, date):
         return date < self.timestamp
--- a/entities/schemaobjs.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/schemaobjs.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """schema definition related entities
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -17,14 +17,7 @@
 class EEType(AnyEntity):
     id = 'EEType'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = {
-        ('final',         '*', 'subject'): 'generated',
-        
-        ('state_of',      '*', 'object'): 'create',
-        ('transition_of', '*', 'object'): 'create',
-        ('from_entity',   '*', 'object'): 'link',
-        ('to_entity',     '*', 'object'): 'link',
-        }
+
     def dc_title(self):
         return self.req._(self.name)
     
@@ -47,11 +40,6 @@
 class ERType(AnyEntity):
     id = 'ERType'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = {
-        ('final',         '*', 'subject'): 'generated',
-        
-        ('relation_type', '*', 'object') : 'create',
-        }
     
     def dc_title(self):
         return self.req._(self.name)
@@ -100,11 +88,6 @@
 class ENFRDef(AnyEntity):
     id = 'ENFRDef'
     fetch_attrs = fetch_config(['cardinality'])[0]
-    __rtags__ = {
-        ('relation_type', 'ERType', 'subject') : 'inlineview',
-        ('from_entity', 'EEType', 'subject') : 'inlineview',
-        ('to_entity', 'EEType', 'subject') : 'inlineview',
-        }
     
     def dc_title(self):
         return u'%s %s %s' % (
@@ -171,10 +154,6 @@
     id = 'RQLExpression'
     fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression'])
 
-    widgets = {
-        'expression' : "StringWidget",
-        }
-
     def dc_title(self):
         return '%s(%s)' % (self.exprtype, self.expression or u'')
 
@@ -209,11 +188,6 @@
     id = 'EPermission'
     fetch_attrs, fetch_order = fetch_config(['name', 'label'])
 
-
-    __rtags__ = {
-        'require_group' : 'primary',
-        }
-
     def dc_title(self):
         if self.label:
             return '%s (%s)' % (self.req._(self.name), self.label)
--- a/entities/test/unittest_base.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/test/unittest_base.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,8 +1,6 @@
 # -*- coding: utf-8 -*-
 """unit tests for cubicweb.entities.base module"""
 
-from mx.DateTime import now
-
 from logilab.common.testlib import unittest_main
 from logilab.common.decorators import clear_cache
 from logilab.common.interface import implements
@@ -55,6 +53,12 @@
         self.assertEquals(card.default_content_format(), 'text/rest')
         
 
+    def test_entity_meta_attributes(self):
+        # XXX move to yams
+        self.assertEquals(self.schema['EUser'].meta_attributes(), {})
+        self.assertEquals(dict((str(k), v) for k, v in self.schema['Card'].meta_attributes().iteritems()),
+                          {'content_format': ('format', 'content')})
+        
 
 class EUserTC(BaseEntityTC):
     def test_dc_title_and_name(self):
--- a/entities/wfobjs.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/entities/wfobjs.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,14 +1,14 @@
 """workflow definition and history related entities
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from cubicweb.entities import AnyEntity, fetch_config
 
-
+ 
 class Transition(AnyEntity):
     """customized class for Transition entities
 
@@ -17,9 +17,6 @@
     """
     id = 'Transition'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = {('destination_state',  '*', 'subject'):  'create',
-                 ('allowed_transition', '*', 'object') :  'create',
-                  }
                  
     def may_be_passed(self, eid, stateeid):
         """return true if the logged user may pass this transition
@@ -66,11 +63,7 @@
     id = 'State'
     fetch_attrs, fetch_order = fetch_config(['name'])
     rest_attr = 'eid'
-    
-    __rtags__ = {'destination_state' : 'create',
-                 'allowed_transition' : 'create'
-                 }
-    
+        
     def transitions(self, entity, desteid=None):
         rql = ('Any T,N,DS where S allowed_transition T, S eid %(x)s, '
                'T name N, T destination_state DS, '
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/entity.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,951 @@
+"""Base class for entity objects manipulated in clients
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from logilab.common import interface
+from logilab.common.compat import all
+from logilab.common.decorators import cached
+from logilab.common.deprecation import obsolete
+from logilab.mtconverter import TransformData, TransformError, html_escape
+
+from rql.utils import rqlvar_maker
+
+from cubicweb import Unauthorized
+from cubicweb.rset import ResultSet
+from cubicweb.selectors import yes
+from cubicweb.appobject import AppRsetObject
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
+
+try:
+    from cubicweb.common.uilib import printable_value, soup2xhtml
+    from cubicweb.common.mixins import MI_REL_TRIGGERS
+    from cubicweb.common.mttransforms import ENGINE
+except ImportError:
+    # missing -common
+    MI_REL_TRIGGERS = {}
+
+_marker = object()
+
+def greater_card(rschema, subjtypes, objtypes, index):
+    for subjtype in subjtypes:
+        for objtype in objtypes:
+            card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+            if card in '+*':
+                return card
+    return '1'
+
+
+MODE_TAGS = set(('link', 'create'))
+CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
+
+try:
+    from cubicweb.web.views.editforms import AutomaticEntityForm
+    from cubicweb.web.views.boxes import EditBox
+
+    def dispatch_rtags(tags, rtype, role, stype, otype):
+        for tag in tags:
+            if tag in MODE_TAGS:
+                EditBox.rmode.set_rtag(tag, rtype, role, stype, otype)
+            elif tag in CATEGORY_TAGS:
+                AutomaticEntityForm.rcategories.set_rtag(tag, rtype, role, stype, otype)
+            elif tag == 'inlineview':
+                AutomaticEntityForm.rinlined.set_rtag(True, rtype, role, stype, otype)
+            else:
+                raise ValueError(tag)
+            
+except ImportError:
+    AutomaticEntityForm = None
+    
+    def dispatch_rtags(*args):
+        pass
+
+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 AutomaticEntityForm 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():
+                    AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype)
+        return super(metaentity, mcs).__new__(mcs, name, bases, classdict)
+
+
+class Entity(AppRsetObject, dict):
+    """an entity instance has e_schema automagically set on
+    the class and instances has access to their issuing cursor.
+    
+    A property is set for each attribute and relation on each entity's type
+    class. Becare that among attributes, 'eid' is *NEITHER* stored in the
+    dict containment (which acts as a cache for other attributes dynamically
+    fetched)
+
+    :type e_schema: `cubicweb.schema.EntitySchema`
+    :ivar e_schema: the entity's schema
+
+    :type rest_var: str
+    :cvar rest_var: indicates which attribute should be used to build REST urls
+                    If None is specified, the first non-meta attribute will
+                    be used
+                    
+    :type skip_copy_for: list
+    :cvar skip_copy_for: a list of relations that should be skipped when copying
+                         this kind of entity. Note that some relations such
+                         as composite relations or relations that have '?1' as object
+                         cardinality
+    """
+    __metaclass__ = metaentity
+    __registry__ = 'etypes'
+    __select__ = yes()
+    widgets = {}
+    rtags = None
+    id = None
+    e_schema = None
+    eid = None
+    rest_attr = None
+    fetch_attrs = None
+    skip_copy_for = ()
+    @classmethod
+    def registered(cls, registry):
+        """build class using descriptor at registration time"""
+        assert cls.id is not None
+        super(Entity, cls).registered(registry)
+        if cls.id != 'Any':
+            cls.__initialize__()
+        return cls
+                
+    MODE_TAGS = set(('link', 'create'))
+    CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
+    @classmethod
+    def __initialize__(cls):
+        """initialize a specific entity class by adding descriptors to access
+        entity type's attributes and relations
+        """
+        etype = cls.id
+        assert etype != 'Any', etype
+        cls.e_schema = eschema = cls.schema.eschema(etype)
+        for rschema, _ in eschema.attribute_definitions():
+            if rschema.type == 'eid':
+                continue
+            setattr(cls, rschema.type, Attribute(rschema.type))
+        mixins = []
+        for rschema, _, x in eschema.relation_definitions():
+            if (rschema, x) in MI_REL_TRIGGERS:
+                mixin = MI_REL_TRIGGERS[(rschema, x)]
+                if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
+                    mixins.append(mixin)
+                for iface in getattr(mixin, '__implements__', ()):
+                    if not interface.implements(cls, iface):
+                        interface.extend(cls, iface)
+            if x == 'subject':
+                setattr(cls, rschema.type, SubjectRelation(rschema))
+            else:
+                attr = 'reverse_%s' % rschema.type
+                setattr(cls, attr, ObjectRelation(rschema))
+        if mixins:
+            cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
+            cls.debug('plugged %s mixins on %s', mixins, etype)
+    
+    @classmethod
+    def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
+                  settype=True, ordermethod='fetch_order'):
+        """return a rql to fetch all entities of the class type"""
+        restrictions = restriction or []
+        if settype:
+            restrictions.append('%s is %s' % (mainvar, cls.id))
+        if fetchattrs is None:
+            fetchattrs = cls.fetch_attrs
+        selection = [mainvar]
+        orderby = []
+        # start from 26 to avoid possible conflicts with X
+        varmaker = rqlvar_maker(index=26)
+        cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
+                                orderby, restrictions, user, ordermethod)
+        rql = 'Any %s' % ','.join(selection)
+        if orderby:
+            rql +=  ' ORDERBY %s' % ','.join(orderby)
+        rql += ' WHERE %s' % ', '.join(restrictions)
+        return rql
+    
+    @classmethod
+    def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs,
+                            selection, orderby, restrictions, user,
+                            ordermethod='fetch_order', visited=None):
+        eschema = cls.e_schema
+        if visited is None:
+            visited = set((eschema.type,))
+        elif eschema.type in visited:
+            # avoid infinite recursion
+            return
+        else:
+            visited.add(eschema.type)
+        _fetchattrs = []
+        for attr in fetchattrs:
+            try:
+                rschema = eschema.subject_relation(attr)
+            except KeyError:
+                cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
+                            attr, cls.id)
+                continue
+            if not user.matching_groups(rschema.get_groups('read')):
+                continue
+            var = varmaker.next()
+            selection.append(var)
+            restriction = '%s %s %s' % (mainvar, attr, var)
+            restrictions.append(restriction)
+            if not rschema.is_final():
+                # XXX this does not handle several destination types
+                desttype = rschema.objects(eschema.type)[0]
+                card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+                if card not in '?1':
+                    selection.pop()
+                    restrictions.pop()
+                    continue
+                if card == '?':
+                    restrictions[-1] += '?' # left outer join if not mandatory
+                destcls = cls.vreg.etype_class(desttype)
+                destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
+                                            selection, orderby, restrictions,
+                                            user, ordermethod, visited=visited)
+            orderterm = getattr(cls, ordermethod)(attr, var)
+            if orderterm:
+                orderby.append(orderterm)
+        return selection, orderby, restrictions
+
+    def __init__(self, req, rset, row=None, col=0):
+        AppRsetObject.__init__(self, req, rset)
+        dict.__init__(self)
+        self.row, self.col = row, col
+        self._related_cache = {}
+        if rset is not None:
+            self.eid = rset[row][col]
+        else:
+            self.eid = None
+        self._is_saved = True
+        
+    def __repr__(self):
+        return '<Entity %s %s %s at %s>' % (
+            self.e_schema, self.eid, self.keys(), id(self))
+
+    def __nonzero__(self):
+        return True
+
+    def __hash__(self):
+        return id(self)
+
+    def pre_add_hook(self):
+        """hook called by the repository before doing anything to add the entity
+        (before_add entity hooks have not been called yet). This give the
+        occasion to do weird stuff such as autocast (File -> Image for instance).
+        
+        This method must return the actual entity to be added.
+        """
+        return self
+    
+    def set_eid(self, eid):
+        self.eid = self['eid'] = eid
+
+    def has_eid(self):
+        """return True if the entity has an attributed eid (False
+        meaning that the entity has to be created
+        """
+        try:
+            int(self.eid)
+            return True
+        except (ValueError, TypeError):
+            return False
+
+    def is_saved(self):
+        """during entity creation, there is some time during which the entity
+        has an eid attributed though it's not saved (eg during before_add_entity
+        hooks). You can use this method to ensure the entity has an eid *and* is
+        saved in its source.
+        """
+        return self.has_eid() and self._is_saved
+    
+    @cached
+    def metainformation(self):
+        res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
+        res['source'] = self.req.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)
+
+    def check_perm(self, action):
+        self.e_schema.check_perm(self.req, action, self.eid)
+
+    def has_perm(self, action):
+        return self.e_schema.has_perm(self.req, action, self.eid)
+        
+    def view(self, vid, __registry='views', **kwargs):
+        """shortcut to apply a view on this entity"""
+        return self.vreg.render(__registry, vid, self.req, rset=self.rset,
+                                row=self.row, col=self.col, **kwargs)
+
+    def absolute_url(self, method=None, **kwargs):
+        """return an absolute url to view this entity"""
+        # 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':
+            kwargs['base_url'] = self.metainformation()['source'].get('base-url')
+        if method is None or method == 'view':
+            kwargs['_restpath'] = self.rest_path()
+        else:
+            kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
+        return self.build_url(method, **kwargs)
+
+    def rest_path(self):
+        """returns a REST-like (relative) path for this entity"""
+        mainattr, needcheck = self._rest_attr_info()
+        etype = str(self.e_schema)
+        if mainattr == 'eid':
+            value = self.eid
+        else:
+            value = getattr(self, mainattr)
+            if value is None:
+                return '%s/eid/%s' % (etype.lower(), self.eid)
+        if needcheck:
+            # make sure url is not ambiguous
+            rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
+            if value is not None:
+                nbresults = self.req.execute(rql, {'value' : value})[0][0]
+                # may an assertion that nbresults is not 0 would be a good idea
+                if nbresults != 1: # no ambiguity
+                    return '%s/eid/%s' % (etype.lower(), self.eid)
+        return '%s/%s' % (etype.lower(), self.req.url_quote(value))
+
+    @classmethod
+    def _rest_attr_info(cls):
+        mainattr, needcheck = 'eid', True
+        if cls.rest_attr:
+            mainattr = cls.rest_attr
+            needcheck = not cls.e_schema.has_unique_values(mainattr)
+        else:
+            for rschema in cls.e_schema.subject_relations():
+                if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
+                    mainattr = str(rschema)
+                    needcheck = False
+                    break
+        if mainattr == 'eid':
+            needcheck = False
+        return mainattr, needcheck
+
+    def attribute_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')
+        return value
+
+    def printable_value(self, attr, value=_marker, attrtype=None,
+                        format='text/html', displaytime=True):
+        """return a displayable value (i.e. unicode string) which may contains
+        html tags
+        """
+        attr = str(attr)
+        if value is _marker:
+            value = getattr(self, attr)
+        if isinstance(value, basestring):
+            value = value.strip()
+        if value is None or value == '': # don't use "not", 0 is an acceptable value
+            return u''
+        if attrtype is None:
+            attrtype = self.e_schema.destination(attr)
+        props = self.e_schema.rproperties(attr)
+        if attrtype == 'String':
+            # internalinalized *and* formatted string such as schema
+            # description...
+            if props.get('internationalizable'):
+                value = self.req._(value)
+            attrformat = self.attribute_metadata(attr, 'format')
+            if attrformat:
+                return self.mtc_transform(value, attrformat, format,
+                                          self.req.encoding)
+        elif attrtype == 'Bytes':
+            attrformat = self.attribute_metadata(attr, 'format')
+            if attrformat:
+                encoding = self.attribute_metadata(attr, 'encoding')
+                return self.mtc_transform(value.getvalue(), attrformat, format,
+                                          encoding)
+            return u''
+        value = printable_value(self.req, attrtype, value, props, displaytime)
+        if format == 'text/html':
+            value = html_escape(value)
+        return value
+
+    def mtc_transform(self, data, format, target_format, encoding,
+                      _engine=ENGINE):
+        trdata = TransformData(data, format, encoding, appobject=self)
+        data = _engine.convert(trdata, target_format).decode()
+        if format == 'text/html':
+            data = soup2xhtml(data, self.req.encoding)                
+        return data
+    
+    # entity cloning ##########################################################
+
+    def copy_relations(self, ceid):
+        """copy relations of the object with the given eid on this object
+
+        By default meta and composite relations are skipped.
+        Overrides this if you want another behaviour
+        """
+        assert self.has_eid()
+        execute = self.req.execute
+        for rschema in self.e_schema.subject_relations():
+            if rschema.meta or rschema.is_final():
+                continue
+            # skip already defined relations
+            if getattr(self, rschema.type):
+                continue
+            if rschema.type in self.skip_copy_for:
+                continue
+            if rschema.type == 'in_state':
+                # if the workflow is defining an initial state (XXX AND we are
+                # not in the managers group? not done to be more consistent)
+                # don't try to copy in_state
+                if execute('Any S WHERE S state_of ET, ET initial_state S,'
+                           'ET name %(etype)s', {'etype': str(self.e_schema)}):
+                    continue
+            # skip composite relation
+            if self.e_schema.subjrproperty(rschema, 'composite'):
+                continue
+            # skip relation with card in ?1 else we either change the copied
+            # object (inlined relation) or inserting some inconsistency
+            if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
+                continue
+            rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
+                rschema.type, rschema.type)
+            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+            self.clear_related_cache(rschema.type, 'subject')
+        for rschema in self.e_schema.object_relations():
+            if rschema.meta:
+                continue
+            # skip already defined relations
+            if getattr(self, 'reverse_%s' % rschema.type):
+                continue
+            # skip composite relation
+            if self.e_schema.objrproperty(rschema, 'composite'):
+                continue
+            # skip relation with card in ?1 else we either change the copied
+            # object (inlined relation) or inserting some inconsistency
+            if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
+                continue
+            rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
+                rschema.type, rschema.type)
+            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+            self.clear_related_cache(rschema.type, 'object')
+
+    # data fetching methods ###################################################
+
+    @cached
+    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)
+                       
+    def to_complete_relations(self):
+        """by default complete final relations to when calling .complete()"""
+        for rschema in self.e_schema.subject_relations():
+            if rschema.is_final():
+                continue
+            if len(rschema.objects(self.e_schema)) > 1:
+                # ambigous relations, the querier doesn't handle
+                # outer join correctly in this case
+                continue
+            if rschema.inlined:
+                matching_groups = self.req.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)):
+                    yield rschema, 'subject'
+                    
+    def to_complete_attributes(self, skip_bytes=True):
+        for rschema, attrschema in self.e_schema.attribute_definitions():
+            # skip binary data by default
+            if skip_bytes and attrschema.type == 'Bytes':
+                continue
+            attr = rschema.type
+            if attr == 'eid':
+                continue
+            # password retreival is blocked at the repository server level
+            if not self.req.user.matching_groups(rschema.get_groups('read')) \
+                   or attrschema.type == 'Password':
+                self[attr] = None
+                continue
+            yield attr
+            
+    def complete(self, attributes=None, skip_bytes=True):
+        """complete this entity by adding missing attributes (i.e. query the
+        repository to fill the entity)
+
+        :type skip_bytes: bool
+        :param skip_bytes:
+          if true, attribute of type Bytes won't be considered
+        """
+        assert self.has_eid()
+        varmaker = rqlvar_maker()
+        V = varmaker.next()
+        rql = ['WHERE %s eid %%(x)s' % V]
+        selected = []
+        for attr in (attributes or self.to_complete_attributes(skip_bytes)):
+            # if attribute already in entity, nothing to do
+            if self.has_key(attr):
+                continue
+            # case where attribute must be completed, but is not yet in entity
+            var = varmaker.next()
+            rql.append('%s %s %s' % (V, attr, var))
+            selected.append((attr, var))
+        # +1 since this doen't include the main variable
+        lastattr = len(selected) + 1
+        if attributes is None:
+            # fetch additional relations (restricted to 0..1 relations)
+            for rschema, role in self.to_complete_relations():
+                rtype = rschema.type
+                if self.relation_cached(rtype, role):
+                    continue
+                var = varmaker.next()
+                if role == 'subject':
+                    targettype = rschema.objects(self.e_schema)[0]
+                    card = rschema.rproperty(self.e_schema, targettype,
+                                             'cardinality')[0]
+                    if card == '1':
+                        rql.append('%s %s %s' % (V, rtype, var))
+                    else: # '?"
+                        rql.append('%s %s %s?' % (V, rtype, var))
+                else:
+                    targettype = rschema.subjects(self.e_schema)[1]
+                    card = rschema.rproperty(self.e_schema, targettype,
+                                             'cardinality')[1]
+                    if card == '1':
+                        rql.append('%s %s %s' % (var, rtype, V))
+                    else: # '?"
+                        rql.append('%s? %s %s' % (var, rtype, V))
+                assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
+                                                      role, card)
+                selected.append(((rtype, role), var))
+        if selected:
+            # select V, we need it as the left most selected variable
+            # 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)
+            rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
+            # handle attributes
+            for i in xrange(1, lastattr):
+                self[str(selected[i-1][0])] = rset[i]
+            # handle relations
+            for i in xrange(lastattr, len(rset)):
+                rtype, x = selected[i-1][0]
+                value = rset[i]
+                if value is None:
+                    rrset = ResultSet([], rql, {'x': self.eid})
+                    self.req.decorate_rset(rrset)
+                else:
+                    rrset = self.req.eid_rset(value)
+                self.set_related_cache(rtype, x, rrset)
+                
+    def get_value(self, name):
+        """get value for the attribute relation <name>, query the repository
+        to get the value if necessary.
+
+        :type name: str
+        :param name: name of the attribute to get
+        """
+        try:
+            value = self[name]
+        except KeyError:
+            if not self.is_saved():
+                return None
+            rql = "Any A WHERE X eid %%(x)s, X %s A" % name
+            # XXX should we really use unsafe_execute here??
+            execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+            try:
+                rset = execute(rql, {'x': self.eid}, 'x')
+            except Unauthorized:
+                self[name] = value = None
+            else:
+                assert rset.rowcount <= 1, (self, rql, rset.rowcount)
+                try:
+                    self[name] = value = rset.rows[0][0]
+                except IndexError:
+                    # probably a multisource error
+                    self.critical("can't get value for attribute %s of entity with eid %s",
+                                  name, self.eid)
+                    if self.e_schema.destination(name) == 'String':
+                        self[name] = value = self.req._('unaccessible')
+                    else:
+                        self[name] = value = None
+        return value
+
+    def related(self, rtype, role='subject', limit=None, entities=False):
+        """returns a resultset of related entities
+        
+        :param role: is the role played by 'self' in the relation ('subject' or 'object')
+        :param limit: resultset's maximum size
+        :param entities: if True, the entites are returned; if False, a result set is returned
+        """
+        try:
+            return self.related_cache(rtype, role, entities, limit)
+        except KeyError:
+            pass
+        assert self.has_eid()
+        rql = self.related_rql(rtype, role)
+        rset = self.req.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'):
+        rschema = self.schema[rtype]
+        if role == 'subject':
+            targettypes = rschema.objects(self.e_schema)
+            restriction = 'E eid %%(x)s, E %s X' % rtype
+            card = greater_card(rschema, (self.e_schema,), targettypes, 0)
+        else:
+            targettypes = rschema.subjects(self.e_schema)
+            restriction = 'E eid %%(x)s, X %s E' % rtype
+            card = greater_card(rschema, targettypes, (self.e_schema,), 1)
+        if len(targettypes) > 1:
+            fetchattrs_list = []
+            for ttype in targettypes:
+                etypecls = self.vreg.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,
+                                     settype=False)
+        else:
+            etypecls = self.vreg.etype_class(targettypes[0])
+            rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
+        # optimisation: remove ORDERBY if cardinality is 1 or ? (though
+        # greater_card return 1 for those both cases)
+        if card == '1':
+            if ' ORDERBY ' in rql:
+                rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
+                                       rql.split(' WHERE ', 1)[1])
+        elif not ' ORDERBY ' in rql:
+            args = tuple(rql.split(' WHERE ', 1))
+            rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
+        return rql
+    
+    # generic vocabulary methods ##############################################
+
+    @obsolete('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 cubicweb.web.form import EntityFieldsForm
+        from logilab.common.testlib import mock_object
+        form = EntityFieldsForm(self.req, entity=self)
+        field = mock_object(name=rtype, role=role)
+        return form.form_field_vocabulary(field, limit)
+            
+    def unrelated_rql(self, rtype, targettype, role, ordermethod=None,
+                      vocabconstraints=True):
+        """build a rql to fetch `targettype` entities unrelated to this entity
+        using (rtype, role) relation
+        """
+        ordermethod = ordermethod or 'fetch_unrelated_order'
+        if isinstance(rtype, basestring):
+            rtype = self.schema.rschema(rtype)
+        if role == 'subject':
+            evar, searchedvar = 'S', 'O'
+            subjtype, objtype = self.e_schema, targettype
+        else:
+            searchedvar, evar = 'S', 'O'
+            objtype, subjtype = self.e_schema, targettype
+        if self.has_eid():
+            restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
+        else:
+            restriction = []
+        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+        if vocabconstraints:
+            # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
+            # will be included as well
+            restriction += [cstr.restriction for cstr in constraints
+                            if isinstance(cstr, RQLVocabularyConstraint)]
+        else:
+            restriction += [cstr.restriction for cstr in constraints
+                            if isinstance(cstr, RQLConstraint)]
+        etypecls = self.vreg.etype_class(targettype)
+        rql = etypecls.fetch_rql(self.req.user, restriction,
+                                 mainvar=searchedvar, ordermethod=ordermethod)
+        # ensure we have an order defined
+        if not ' ORDERBY ' in rql:
+            before, after = rql.split(' WHERE ', 1)
+            rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
+        return rql
+    
+    def unrelated(self, rtype, targettype, role='subject', limit=None,
+                  ordermethod=None):
+        """return a result set of target type objects that may be related
+        by a given relation, with self as subject or object
+        """
+        rql = self.unrelated_rql(rtype, targettype, role, ordermethod)
+        if limit is not None:
+            before, after = rql.split(' WHERE ', 1)
+            rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
+        if self.has_eid():
+            return self.req.execute(rql, {'x': self.eid})
+        return self.req.execute(rql)
+        
+    # relations cache handling ################################################
+    
+    def relation_cached(self, rtype, role):
+        """return true if the given relation is already cached on the instance
+        """
+        return '%s_%s' % (rtype, role) in self._related_cache
+    
+    def related_cache(self, rtype, role, entities=True, limit=None):
+        """return values for the given relation if it's cached on the instance,
+        else raise `KeyError`
+        """
+        res = self._related_cache['%s_%s' % (rtype, role)][entities]
+        if limit:
+            if entities:
+                res = res[:limit]
+            else:
+                res = res.limit(limit)
+        return res
+    
+    def set_related_cache(self, rtype, role, rset, col=0):
+        """set cached values for the given relation"""
+        if rset:
+            related = list(rset.entities(col))
+            rschema = self.schema.rschema(rtype)
+            if role == 'subject':
+                rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
+                                          'cardinality')[1]
+                target = 'object'
+            else:
+                rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
+                                          'cardinality')[0]
+                target = 'subject'
+            if rcard in '?1':
+                for rentity in related:
+                    rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self])
+        else:
+            related = []
+        self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
+        
+    def clear_related_cache(self, rtype=None, role=None):
+        """clear cached values for the given relation or the entire cache if
+        no relation is given
+        """
+        if rtype is None:
+            self._related_cache = {}
+        else:
+            assert role
+            self._related_cache.pop('%s_%s' % (rtype, role), None)
+        
+    # raw edition utilities ###################################################
+    
+    def set_attributes(self, **kwargs):
+        assert kwargs
+        relations = []
+        for key in kwargs:
+            relations.append('X %s %%(%s)s' % (key, key))
+        # update current local object
+        self.update(kwargs)
+        # and now update the database
+        kwargs['x'] = self.eid
+        self.req.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,
+                         {'x': self.eid})
+    
+    # server side utilities ###################################################
+        
+    def set_defaults(self):
+        """set default values according to the schema"""
+        self._default_set = set()
+        for attr, value in self.e_schema.defaults():
+            if not self.has_key(attr):
+                self[str(attr)] = value
+                self._default_set.add(attr)
+
+    def check(self, creation=False):
+        """check this entity against its schema. Only final relation
+        are checked here, constraint on actual relations are checked in hooks
+        """
+        # necessary since eid is handled specifically and yams require it to be
+        # in the dictionary
+        if self.req is None:
+            _ = unicode
+        else:
+            _ = self.req._
+        self.e_schema.check(self, creation=creation, _=_)
+
+    def fti_containers(self, _done=None):
+        if _done is None:
+            _done = set()
+        _done.add(self.eid)
+        containers = tuple(self.e_schema.fulltext_containers())
+        if containers:
+            yielded = False
+            for rschema, target in containers:
+                if target == 'object':
+                    targets = getattr(self, rschema.type)
+                else:
+                    targets = getattr(self, 'reverse_%s' % rschema)
+                for entity in targets:
+                    if entity.eid in _done:
+                        continue
+                    for container in entity.fti_containers(_done):
+                        yield container
+                        yielded = True
+            if not yielded:
+                yield self
+        else:
+            yield self
+                    
+    def get_words(self):
+        """used by the full text indexer to get words to index
+
+        this method should only be used on the repository side since it depends
+        on the indexer package
+        
+        :rtype: list
+        :return: the list of indexable word of this entity
+        """
+        from indexer.query_objects import tokenize
+        words = []
+        for rschema in self.e_schema.indexable_attributes():
+            try:
+                value = self.printable_value(rschema, format='text/plain')
+            except TransformError:
+                continue
+            except:
+                self.exception("can't add value of %s to text index for entity %s",
+                               rschema, self.eid)
+                continue
+            if value:
+                words += tokenize(value)
+        
+        for rschema, role in self.e_schema.fulltext_relations():
+            if role == 'subject':
+                for entity in getattr(self, rschema.type):
+                    words += entity.get_words()
+            else: # if role == 'object':
+                for entity in getattr(self, 'reverse_%s' % rschema.type):
+                    words += entity.get_words()
+        return words
+
+
+# attribute and relation descriptors ##########################################
+
+class Attribute(object):
+    """descriptor that controls schema attribute access"""
+
+    def __init__(self, attrname):
+        assert attrname != 'eid'
+        self._attrname = attrname
+
+    def __get__(self, eobj, eclass):
+        if eobj is None:
+            return self
+        return eobj.get_value(self._attrname)
+
+    def __set__(self, eobj, value):
+        # XXX bw compat
+        # would be better to generate UPDATE queries than the current behaviour
+        eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)")
+        eobj[self._attrname] = value
+
+
+class Relation(object):
+    """descriptor that controls schema relation access"""
+    _role = None # for pylint
+
+    def __init__(self, rschema):
+        self._rschema = rschema
+        self._rtype = rschema.type
+
+    def __get__(self, eobj, eclass):
+        if eobj is None:
+            raise AttributeError('%s cannot be only be accessed from instances'
+                                 % self._rtype)
+        return eobj.related(self._rtype, self._role, entities=True)
+    
+    def __set__(self, eobj, value):
+        raise NotImplementedError
+
+
+class SubjectRelation(Relation):
+    """descriptor that controls schema relation access"""
+    _role = 'subject'
+    
+class ObjectRelation(Relation):
+    """descriptor that controls schema relation access"""
+    _role = 'object'
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(Entity, getLogger('cubicweb.entity'))
--- a/etwist/request.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/etwist/request.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,14 +1,14 @@
 """Twisted request handler for CubicWeb
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from twisted.web2 import http, http_headers
+from datetime import datetime
 
-from mx.DateTime import DateTimeFromTicks
+from twisted.web2 import http, http_headers
 
 from cubicweb.web import DirectResponse
 from cubicweb.web.request import CubicWebRequestBase
@@ -117,7 +117,7 @@
         mtime = self.get_header('If-modified-since', raw=False)
         if mtime:
             # :/ twisted is returned a localized time stamp
-            return DateTimeFromTicks(mtime) + GMTOFFSET
+            return datetime.fromtimestamp(mtime) + GMTOFFSET
         return None
 
 
--- a/etwist/server.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/etwist/server.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,15 +1,15 @@
 """twisted server for CubicWeb web applications
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 import sys
 import select
-
-from mx.DateTime import today, RelativeDate
+from time import mktime
+from datetime import date, timedelta
 
 from twisted.application import service, strports
 from twisted.internet import reactor, task, threads
@@ -62,8 +62,8 @@
             # Don't provide additional resource information to error responses
             if response.code < 400:
                 # the HTTP RFC recommands not going further than 1 year ahead
-                expires = today() + RelativeDate(months=6)
-                response.headers.setHeader('Expires', int(expires.ticks()))
+                expires = date.today() + timedelta(days=6*30)
+                response.headers.setHeader('Expires', mktime(expires.timetuple()))
             return response
         d = maybeDeferred(super(LongTimeExpiringFile, self).renderHTTP, request)
         return d.addCallback(setExpireHeader)
@@ -360,7 +360,7 @@
             acount += 1
         else:
             try:
-                ocount[obj.__class__]+= 1
+                ocount[obj.__class__] += 1
             except KeyError:
                 ocount[obj.__class__] = 1
             except AttributeError:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
+minimum of formatting information.  A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_escape
+
+from docutils import nodes
+from docutils.writers.html4css1 import Writer as CSS1Writer
+from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
+import os
+
+default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
+
+class Writer(CSS1Writer):
+    """css writer using our html translator"""
+    def __init__(self, base_url):
+        CSS1Writer.__init__(self)
+        self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+    def apply_template(self):
+        """overriding this is necessary with docutils >= 0.5"""
+        return self.visitor.astext()
+
+class URLBinder:
+    def __init__(self, url, klass):
+        self.base_url = url
+        self.translator_class = HTMLTranslator
+        
+    def __call__(self, document):
+        translator = self.translator_class(document)
+        translator.base_url = self.base_url
+        return translator
+    
+class HTMLTranslator(CSS1HTMLTranslator):
+    """ReST tree to html translator"""
+
+    def astext(self):
+        """return the extracted html"""
+        return ''.join(self.body)
+    
+    def visit_title(self, node):
+        """Only 6 section levels are supported by HTML."""
+        if isinstance(node.parent, nodes.topic):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='topic-title'))
+            if node.parent.hasattr('id'):
+                self.body.append(
+                    self.starttag({}, 'a', '', name=node.parent['id']))
+                self.context.append('</a></p>\n')
+            else:
+                self.context.append('</p>\n')
+        elif self.section_level == 0:
+            # document title
+            self.head.append('<title>%s</title>\n'
+                             % self.encode(node.astext()))
+            self.body.append(self.starttag(node, 'h%d' % default_level, '',
+                                           CLASS='title'))
+            self.context.append('</h%d>\n' % default_level)
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (
+                default_level+self.section_level-1), ''))
+            atts = {}
+            if node.hasattr('refid'):
+                atts['class'] = 'toc-backref'
+                atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+            self.body.append(self.starttag({}, 'a', '', **atts))
+            self.context.append('</a></h%s>\n' % (
+                default_level+self.section_level-1))
+
+    def visit_subtitle(self, node):
+        """format a subtitle"""
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append(self.starttag(node, 'p', '',
+                                           CLASS='sidebar-subtitle'))
+            self.context.append('</p>\n')
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (default_level+1), '',
+                                CLASS='subtitle'))
+            self.context.append('</h%s>\n' % (default_level+1))
+
+    def visit_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+    def depart_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+
+    def visit_reference(self, node):
+        """syt: i want absolute urls"""
+        if node.has_key('refuri'):
+            href = node['refuri']
+            if ( self.settings.cloak_email_addresses
+                 and href.startswith('mailto:')):
+                href = self.cloak_mailto(href)
+                self.in_mailto = 1
+        else:
+            assert node.has_key('refid'), \
+                   'References must have "refuri" or "refid" attribute.'
+            href = '%s#%s' % (self.base_url, node['refid'])
+        atts = {'href': href, 'class': 'reference'}
+        if not isinstance(node.parent, nodes.TextElement):
+            assert len(node) == 1 and isinstance(node[0], nodes.image)
+            atts['class'] += ' image-reference'
+        self.body.append(self.starttag(node, 'a', '', **atts))
+
+    ## override error messages to avoid XHTML problems ########################
+    def visit_problematic(self, node):
+        pass
+
+    def depart_problematic(self, node):
+        pass
+    
+    def visit_system_message(self, node):
+        backref_text = ''
+        if len(node['backrefs']):
+            backrefs = node['backrefs']
+            if len(backrefs) == 1:
+                backref_text = '; <em>backlink</em>'
+            else:
+                i = 1
+                backlinks = []
+                for backref in backrefs:
+                    backlinks.append(str(i))
+                    i += 1
+                backref_text = ('; <em>backlinks: %s</em>'
+                                % ', '.join(backlinks))
+        if node.hasattr('line'):
+            line = ', line %s' % node['line']
+        else:
+            line = ''
+        a_start = a_end = ''
+        error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+            a_start, node['type'], node['level'], a_end,
+            self.encode(node['source']), line, backref_text)
+        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+    def depart_system_message(self, node):
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/rest.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,263 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb. Provides the
+following ReST directives:
+
+* `eid`, create link to entity in the repository by their eid
+
+* `card`, create link to card entity in the repository by their wikiid
+  (proposing to create it when the refered card doesn't exist yet)
+
+* `winclude`, reference to a web documentation file (in wdoc/ directories)
+
+* `sourcecode` (if pygments is installed), source code colorization
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.ext.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+    if isinstance(v, str):
+        states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    try:
+        try:
+            eid_num, rest = text.split(u':', 1)
+        except:
+            eid_num, rest = text, '#'+text
+        eid_num = int(eid_num)
+        if eid_num < 0:
+            raise ValueError
+    except ValueError:
+        msg = inliner.reporter.error(
+            'EID number must be a positive number; "%s" is invalid.'
+            % text, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    # Base URL mainly used by inliner.pep_reference; so this is correct:
+    context = inliner.document.settings.context
+    refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+    ref = refedentity.absolute_url()
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    text = text.strip()
+    try:
+        wikiid, rest = text.split(u':', 1)
+    except:
+        wikiid, rest = text, text
+    context = inliner.document.settings.context
+    cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+                                   {'id': wikiid})
+    if cardrset:
+        ref = cardrset.get_entity(0, 0).absolute_url()
+    else:
+        schema = context.schema
+        if schema.eschema('Card').has_perm(context.req, 'add'):
+            ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+        else:
+            ref = '#'
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    """Include a reST file as part of the content of this reST file.
+
+    same as standard include directive but using config.locate_doc_resource to
+    get actual file to include.
+
+    Most part of this implementation is copied from `include` directive defined
+    in `docutils.parsers.rst.directives.misc`
+    """
+    context = state.document.settings.context
+    source = state_machine.input_lines.source(
+        lineno - state_machine.input_offset - 1)
+    #source_dir = os.path.dirname(os.path.abspath(source))
+    fid = arguments[0]
+    for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
+                      context.config.available_languages()):
+        rid = '%s_%s.rst' % (fid, lang)
+        resourcedir = context.config.locate_doc_file(rid)
+        if resourcedir:
+            break
+    else:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\nno resource matching %s.'
+              % (name, fid),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    path = join(resourcedir, rid)
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    try:
+        state.document.settings.record_dependencies.add(path)
+        include_file = io.FileInput(
+            source_path=path, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler,
+            handle_io_errors=None)
+    except IOError, error:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\n%s: %s.'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    try:
+        include_text = include_file.read()
+    except UnicodeError, error:
+        severe = state_machine.reporter.severe(
+              'Problem with "%s" directive:\n%s: %s'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    if options.has_key('literal'):
+        literal_block = nodes.literal_block(include_text, include_text,
+                                            source=path)
+        literal_block.line = 1
+        return literal_block
+    else:
+        include_lines = statemachine.string2lines(include_text,
+                                                  convert_whitespace=1)
+        state_machine.insert_input(include_lines, path)
+        return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+                              'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+try:
+    from pygments import highlight
+    from pygments.lexers import get_lexer_by_name, LEXERS
+    from pygments.formatters import HtmlFormatter
+except ImportError:
+    pass
+else:
+    _PYGMENTS_FORMATTER = HtmlFormatter()
+
+    def pygments_directive(name, arguments, options, content, lineno,
+                           content_offset, block_text, state, state_machine):
+        try:
+            lexer = get_lexer_by_name(arguments[0])
+        except ValueError:
+            import traceback
+            traceback.print_exc()
+            print sorted(aliases for module_name, name, aliases, _, _  in LEXERS.itervalues())
+            # no lexer found
+            lexer = get_lexer_by_name('text')
+        print 'LEXER', lexer
+        parsed = highlight(u'\n'.join(content), lexer, _PYGMENTS_FORMATTER)
+        context = state.document.settings.context
+        context.req.add_css('pygments.css')
+        return [nodes.raw('', parsed, format='html')]
+     
+    pygments_directive.arguments = (1, 0, 1)
+    pygments_directive.content = 1
+    directives.register_directive('sourcecode', pygments_directive)
+
+
+class CubicWebReSTParser(Parser):
+    """The (customized) reStructuredText parser."""
+
+    def __init__(self):
+        self.initial_state = 'Body'
+        self.state_classes = states.state_classes
+        self.inliner = states.Inliner()
+        self.statemachine = states.RSTStateMachine(
+              state_classes=self.state_classes,
+              initial_state=self.initial_state,
+              debug=0)
+
+    def parse(self, inputstring, document):
+        """Parse `inputstring` and populate `document`, a document tree."""
+        self.setup_parse(inputstring, document)
+        inputlines = statemachine.string2lines(inputstring,
+                                               convert_whitespace=1)
+        self.statemachine.run(inputlines, document, inliner=self.inliner)
+        self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+    """publish a string formatted as ReStructured Text to HTML
+    
+    :type context: a cubicweb application object
+
+    :type data: str
+    :param data: some ReST text
+
+    :rtype: unicode
+    :return:
+      the data formatted as HTML or the original data if an error occured
+    """
+    req = context.req
+    if isinstance(data, unicode):
+        encoding = 'unicode'
+    else:
+        encoding = req.encoding
+    settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+                'warning_stream': StringIO(), 'context': context,
+                # dunno what's the max, severe is 4, and we never want a crash
+                # (though try/except may be a better option...)
+                'halt_level': 10, 
+                }
+    if context:
+        if hasattr(req, 'url'):
+            base_url = req.url()
+        elif hasattr(context, 'absolute_url'):
+            base_url = context.absolute_url()
+        else:
+            base_url = req.base_url()
+    else:
+        base_url = None
+    try:
+        return publish_string(writer=Writer(base_url=base_url),
+                              parser=_REST_PARSER, source=data,
+                              settings_overrides=settings)
+    except Exception:
+        LOGGER.exception('error while publishing ReST text')
+        if not isinstance(data, unicode):
+            data = unicode(data, encoding, 'replace')
+        return html_escape(req._('error while publishing ReST text')
+                           + '\n\n' + data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/tal.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,256 @@
+"""provides simpleTAL extensions for CubicWeb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+import re
+from os.path import exists, isdir, join
+from logging import getLogger
+from StringIO import StringIO
+        
+from simpletal import simpleTAL, simpleTALES
+
+from logilab.common.decorators import cached
+
+LOGGER = getLogger('cubicweb.tal')
+
+
+class LoggerAdapter(object):
+    def __init__(self, tal_logger):
+        self.tal_logger = tal_logger
+        
+    def debug(self, msg):
+        LOGGER.debug(msg)
+
+    def warn(self, msg):
+        LOGGER.warning(msg)
+
+    def __getattr__(self, attrname):
+        return getattr(self.tal_logger, attrname)
+
+
+class CubicWebContext(simpleTALES.Context):
+    """add facilities to access entity / resultset"""
+
+    def __init__(self, options=None, allowPythonPath=1):
+        simpleTALES.Context.__init__(self, options, allowPythonPath)
+        self.log = LoggerAdapter(self.log)
+
+    def update(self, context):
+        for varname, value in context.items():
+            self.addGlobal(varname, value)
+
+    def addRepeat(self, name, var, initialValue):
+        simpleTALES.Context.addRepeat(self, name, var, initialValue)
+
+# XXX FIXME need to find a clean to define OPCODE values for extensions
+I18N_CONTENT = 18  
+I18N_REPLACE = 19
+RQL_EXECUTE  = 20
+# simpleTAL uses the OPCODE values to define priority over commands.
+# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
+# we can't use the same OPCODE for two different commands without changing
+# the simpleTAL implementation. Another solution would be to totally override
+# the REPEAT implementation with the ITER one, but some specific operations
+# (involving len() for instance) are not implemented for ITER, so we prefer
+# to keep both implementations for now, and to fool simpleTAL by using a float
+# number between 3 and 4
+TAL_ITER     = 3.1
+
+
+# FIX simpleTAL HTML 4.01 stupidity
+# (simpleTAL never closes tags like INPUT, IMG, HR ...)
+simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
+
+class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
+    """extends default compiler by adding i18n:content commands"""
+
+    def __init__(self):
+        simpleTAL.HTMLTemplateCompiler.__init__(self)
+        self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
+        self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
+        self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
+        self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
+
+    def setTALPrefix(self, prefix):
+        simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
+        self.tal_attribute_map['i18n:content'] = I18N_CONTENT
+        self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
+        self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
+        self.tal_attribute_map['tal:iter'] = TAL_ITER
+
+    def compile_cmd_i18n_content(self, argument):
+        # XXX tal:content structure=, text= should we support this ?
+        structure_flag = 0
+        return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
+
+    def compile_cmd_i18n_replace(self, argument):
+        # XXX tal:content structure=, text= should we support this ?
+        structure_flag = 0
+        return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
+
+    def compile_cmd_rql(self, argument):
+        return (RQL_EXECUTE, (argument, self.endTagSymbol))
+
+    def compile_cmd_tal_iter(self, argument):
+        original_id, (var_name, expression, end_tag_symbol) = \
+                     simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
+        return (TAL_ITER, (var_name, expression, self.endTagSymbol))
+
+    def getTemplate(self):
+        return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
+
+    def compileCmdAttributes (self, argument):
+        """XXX modified to support single attribute
+        definition ending by a ';'
+
+        backport this to simpleTAL
+        """
+        # Compile tal:attributes into attribute command
+        # Argument: [(attributeName, expression)]
+        
+        # Break up the list of attribute settings first
+        commandArgs = []
+        # We only want to match semi-colons that are not escaped
+        argumentSplitter =  re.compile(r'(?<!;);(?!;)')
+        for attributeStmt in argumentSplitter.split(argument):
+            if not attributeStmt.strip():
+                continue
+            #  remove any leading space and un-escape any semi-colons
+            attributeStmt = attributeStmt.lstrip().replace(';;', ';')
+            # Break each attributeStmt into name and expression
+            stmtBits = attributeStmt.split(' ')
+            if (len (stmtBits) < 2):
+                # Error, badly formed attributes command
+                msg = "Badly formed attributes command '%s'.  Attributes commands must be of the form: 'name expression[;name expression]'" % argument
+                self.log.error(msg)
+                raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
+            attName = stmtBits[0]
+            attExpr = " ".join(stmtBits[1:])
+            commandArgs.append((attName, attExpr))
+        return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
+
+
+class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
+    """provides implementation for interpreting cubicweb extensions"""
+    def __init__(self):
+        simpleTAL.TemplateInterpreter.__init__(self)
+        self.commandHandler[I18N_CONTENT] = self.cmd_i18n
+        self.commandHandler[TAL_ITER] = self.cmdRepeat
+        # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
+
+    def cmd_i18n(self, command, args):
+        """i18n:content and i18n:replace implementation"""
+        string, replace_flag, structure_flag, end_symbol = args
+        if replace_flag:
+            self.outputTag = 0
+        result = self.context.globals['_'](string)
+        self.tagContent = (0, result)
+        self.movePCForward = self.symbolTable[end_symbol]
+        self.programCounter += 1
+
+
+class CubicWebTemplate(simpleTAL.HTMLTemplate):
+    """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
+    """
+    def expand(self, context, outputFile):
+        interpreter = CubicWebTemplateInterpreter()
+        interpreter.initialise(context, outputFile)
+        simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
+                                      interpreter=interpreter)
+
+    def expandInline(self, context, outputFile, interpreter):
+        """ Internally used when expanding a template that is part of a context."""
+        try:
+            interpreter.execute(self)
+        except UnicodeError, unierror:
+            LOGGER.exception(str(unierror))
+            raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+
+
+def compile_template(template):
+    """compiles a TAL template string
+    :type template: unicode
+    :param template: a TAL-compliant template string
+    """
+    string_buffer = StringIO(template)
+    compiler = CubicWebTemplateCompiler()
+    compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
+    return compiler.getTemplate()
+
+
+def compile_template_file(filepath):
+    """compiles a TAL template file
+    :type filepath: str
+    :param template: path of the file to compile 
+    """
+    fp = file(filepath)
+    file_content = unicode(fp.read()) # template file should be pure ASCII
+    fp.close()
+    return compile_template(file_content)
+
+
+def evaluatePython (self, expr):
+    if not self.allowPythonPath:
+        return self.false
+    globals = {}
+    for name, value in self.globals.items():
+        if isinstance (value, simpleTALES.ContextVariable):
+            value = value.rawValue()
+        globals[name] = value
+    globals['path'] = self.pythonPathFuncs.path
+    globals['string'] = self.pythonPathFuncs.string
+    globals['exists'] = self.pythonPathFuncs.exists
+    globals['nocall'] = self.pythonPathFuncs.nocall
+    globals['test'] = self.pythonPathFuncs.test
+    locals = {}
+    for name, value in self.locals.items():
+        if (isinstance (value, simpleTALES.ContextVariable)):
+            value = value.rawValue()
+        locals[name] = value
+    # XXX precompile expr will avoid late syntax error
+    try:
+        result = eval(expr, globals, locals)
+    except Exception, ex:
+        ex = ex.__class__('in %r: %s' % (expr, ex))
+        raise ex, None, sys.exc_info()[-1]
+    if (isinstance (result, simpleTALES.ContextVariable)):
+        return result.value()
+    return result
+
+simpleTALES.Context.evaluatePython = evaluatePython
+
+
+class talbased(object):
+    def __init__(self, filename, write=True):
+##         if not osp.isfile(filepath):
+##             # print "[tal.py] just for tests..."
+##             # get parent frame
+##             directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
+##             filepath = osp.join(directory, filepath)
+        self.filename = filename
+        self.write = write
+
+    def __call__(self, viewfunc):
+        def wrapped(instance, *args, **kwargs):
+            variables = viewfunc(instance, *args, **kwargs)
+            html = instance.tal_render(self._compiled_template(instance), variables)
+            if self.write:
+                instance.w(html)
+            else:
+                return html
+        return wrapped
+
+    def _compiled_template(self, instance):
+        for fileordirectory in instance.config.vregistry_path():
+            filepath = join(fileordirectory, self.filename)
+            if isdir(fileordirectory) and exists(filepath):
+                return compile_template_file(filepath)
+        raise Exception('no such template %s' % self.filename)
+    _compiled_template = cached(_compiled_template, 0)
+    
--- a/gettext.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/gettext.py	Tue Apr 07 09:30:23 2009 +0200
@@ -442,7 +442,6 @@
 
 
 def bindtextdomain(domain, localedir=None):
-    global _localedirs
     if localedir is not None:
         _localedirs[domain] = localedir
     return _localedirs.get(domain, _default_localedir)
--- a/goa/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,69 +1,12 @@
 """cubicweb on google appengine
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 
-from datetime import datetime, time, date
-from mx.DateTime import DateTime, Date, Time
-
-def mx2datetime(mxobj, yamstype):
-    """converts a mx date object (DateTime, Date or Time) into a
-    regular python datetime object
-    """
-    #if yamstype == 'Datetime':
-    # don't use date, db model doesn't actually support it, only datetime
-    return datetime(mxobj.year, mxobj.month, mxobj.day,
-                    mxobj.hour, mxobj.minute, int(mxobj.second))
-#     elif yamstype == 'Date':
-#         return date(mxobj.year, mxobj.month, mxobj.day)
-#     # XXX don't support time either, what should we do here ?
-#     return time(mxobj.hour, mxobj.minute, int(mxobj.second))
-
-def datetime2mx(datetimeobj, yamstype=None):
-    """converts a mx date object (DateTime, Date or Time) into a
-    regular python datetime object
-    """
-    if yamstype is None:
-        yamstype = guess_yamstype_for_date(datetimeobj)
-    assert yamstype is not None
-    if yamstype == 'Datetime':
-        # don't use date, db model doesn't actually support it, only datetime
-        return DateTime(datetimeobj.year, datetimeobj.month, datetimeobj.day,
-                        datetimeobj.hour, datetimeobj.minute, int(datetimeobj.second))
-    elif yamstype == 'Date':
-        return Date(datetimeobj.year, datetimeobj.month, datetimeobj.day)
-    # XXX don't support time either, what should we do here ?
-    return Time(datetimeobj.hour, datetimeobj.minute, int(datetimeobj.second))
-
-
-def guess_yamstype_for_date(datetimeobj):
-    """guesses yams correct type according to `datetimeobj`'s type"""
-    if isinstance(datetimeobj, datetime):
-        return 'Datetime'
-    elif isinstance(datetimeobj, date):
-        return 'Date'
-    elif isinstance(datetimeobj, time):
-        return 'Time'
-    return None
-
-
-def use_mx_for_dates(func):
-    """decorator to convert func's return value into mx objects
-    instead of datetime objects
-    """
-    def wrapper(*args, **kwargs):
-        value = func(*args, **kwargs)
-        yamstype = guess_yamstype_for_date(value)
-        if yamstype is None:
-            return value
-        return datetime2mx(value, yamstype)
-    return wrapper
-
-
 try:
     # WARNING: do not import the google's db module here since it will take
     #          precedence over our own db submodule
@@ -152,7 +95,7 @@
         repository.Repository.get_cubes = get_cubes
         
         from rql import RQLHelper
-        RQLHelper.simplify = lambda x,r: None
+        RQLHelper.simplify = lambda x, r: None
 
         # activate entity caching on the server side
 
--- a/goa/appobjects/components.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/appobjects/components.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,28 +1,21 @@
 """overrides some base views for cubicweb on google appengine
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from os.path import join
-
 from logilab.mtconverter import html_escape
-from logilab.common.decorators import cached
 
 from cubicweb import typed_eid
+from cubicweb.selectors import one_line_rset, match_search_state, accept
 from cubicweb.schema import display_name
 from cubicweb.common.view import StartupView, EntityView
-from cubicweb.common.selectors import (one_line_rset, match_search_state,
-                                    accept)
 from cubicweb.web import Redirect
 from cubicweb.web.views import vid_from_rset
-from cubicweb.goa.db import rset_from_objs
 
-from google.appengine.api import datastore, mail
-
-from main import APPLROOT
+from google.appengine.api import mail
 
 
 class SearchForAssociationView(EntityView):
@@ -31,9 +24,7 @@
     """
     id = 'search-associate'
     
-    __selectors__ = (one_line_rset, match_search_state, accept)
-    accepts = ('Any',)
-    search_states = ('linksearch',)
+    __select__ = one_line_rset() & match_search_state('linksearch') & accept
 
     def cell_call(self, row, col):
         entity = self.entity(0, 0)
--- a/goa/appobjects/dbmgmt.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/appobjects/dbmgmt.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,7 @@
 restoration).
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -13,6 +13,7 @@
 from logilab.common.decorators import cached
 from logilab.mtconverter import html_escape
 
+from cubicweb.selectors import none_rset, match_user_groups
 from cubicweb.common.view import StartupView
 from cubicweb.web import Redirect
 from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions
@@ -39,7 +40,7 @@
     which are doing datastore administration requests
     """
     id = 'authinfo'
-    require_groups = ('managers',)
+    __select__ = none_rset() & match_user_groups('managers')
 
     def call(self):
         cookie = self.req.get_cookie()
@@ -61,7 +62,7 @@
     step by step to avoid depassing quotas
     """
     id = 'contentinit'
-    require_groups = ('managers',)
+    __select__ = none_rset() & match_user_groups('managers')
 
     def server_session(self):
         ssession = self.config.repo_session(self.req.cnx.sessionid)
@@ -166,7 +167,7 @@
         
 class ContentClear(StartupView):
     id = 'contentclear'
-    require_groups = ('managers',)
+    __select__ = none_rset() & match_user_groups('managers')
     skip_etypes = ('EGroup', 'EUser')
     
     def call(self):
--- a/goa/appobjects/gauthservice.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/appobjects/gauthservice.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,29 +1,18 @@
 """authentication using google authentication service
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.registerers import priority_registerer
 from cubicweb.web.views.basecomponents import UserLink
 from cubicweb.web.views.actions import LogoutAction
 
 from google.appengine.api import users
 
 
-class use_google_auth_registerer(priority_registerer):
-    """register object if use-google-auth is true"""
-    
-    def do_it_yourself(self, registered):
-        if not hasattr(self.config, 'has_resource'):
-            return
-        return super(use_google_auth_registerer, self).do_it_yourself(registered)
-
-
 class GAEUserLink(UserLink):
-    __registerer__ = use_google_auth_registerer
 
     def anon_user_link(self):
         self.w(self.req._('anonymous'))
@@ -31,7 +20,11 @@
                % (users.create_login_url(self.req.url()), self.req._('login')))
 
 class GAELogoutAction(LogoutAction):
-    __registerer__ = use_google_auth_registerer
 
     def url(self):
         return users.create_logout_url(self.req.build_url('logout') )
+    
+def registration_callback(vreg):
+    if hasattr(vreg.config, 'has_resource'):
+        vreg.register(GAEUserLink, clear=True)
+        vreg.register(GAELogoutAction, clear=True)
--- a/goa/appobjects/sessions.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/appobjects/sessions.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """persistent sessions stored in big table
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 XXX TODO:
@@ -15,8 +15,9 @@
 
 from logilab.common.decorators import cached, clear_cache
 
-from cubicweb import UnknownEid, BadConnectionId
+from cubicweb import BadConnectionId
 from cubicweb.dbapi import Connection, ConnectionProperties, repo_connect
+from cubicweb.selectors import none_rset, match_user_groups
 from cubicweb.server.session import Session
 from cubicweb.web import InvalidSession
 from cubicweb.web.application import AbstractSessionManager
@@ -254,7 +255,7 @@
 
 class SessionsCleaner(StartupView):
     id = 'cleansessions'
-    require_groups = ('managers',)
+    __select__ = none_rset() & match_user_groups('managers')
     
     def call(self):
         # clean web session
@@ -268,4 +269,9 @@
         self.w(u'%s repository sessions closed<br/>\n' % nbclosed)
         self.w(u'%s remaining sessions<br/>\n' % remaining)
         self.w(u'</div>')
+
         
+def registration_callback(vreg):
+    vreg.register(SessionsCleaner)
+    vreg.register(GAEAuthenticationManager, clear=True)
+    vreg.register(GAEPersistentSessionManager, clear=True)
--- a/goa/db.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/db.py	Tue Apr 07 09:30:23 2009 +0200
@@ -25,21 +25,20 @@
 * XXX ListProperty
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from datetime import datetime
 from copy import deepcopy
 
 from logilab.common.decorators import cached, iclassmethod
 
 from cubicweb import RequestSessionMixIn, Binary, entities
 from cubicweb.rset import ResultSet
-from cubicweb.common.entity import metaentity
+from cubicweb.entity import metaentity
 from cubicweb.server.utils import crypt_password
-from cubicweb.goa import use_mx_for_dates, mx2datetime, MODE
+from cubicweb.goa import MODE
 from cubicweb.goa.dbinit import init_relations
 
 from google.appengine.api.datastore import Get, Put, Key, Entity, Query
@@ -174,15 +173,11 @@
         return '<ModelEntity %s %s %s at %s>' % (
             self.e_schema, self.eid, self.keys(), id(self))
 
-    __getattribute__ = use_mx_for_dates(entities.AnyEntity.__getattribute__)
-
     def _cubicweb_to_datastore(self, attr, value):
         attr = attr[2:] # remove 's_' / 'o_' prefix
         if attr in self._attributes:
             tschema = self.e_schema.destination(attr)
-            if tschema in ('Datetime', 'Date', 'Time'):
-                value = mx2datetime(value, tschema)
-            elif tschema == 'String':
+            if tschema == 'String':
                 if len(value) > 500:
                     value = Text(value)                
             elif tschema == 'Password':
@@ -288,7 +283,6 @@
             return 'eid', False
         return mainattr, needcheck
     
-    @use_mx_for_dates
     def get_value(self, name):
         try:
             value = self[name]
--- a/goa/dbmyams.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/dbmyams.py	Tue Apr 07 09:30:23 2009 +0200
@@ -13,12 +13,8 @@
 from google.appengine.ext import db
 from google.appengine.api import datastore_types
 
-from yams.schema2sql import eschema_attrs
-from yams.constraints import SizeConstraint
-from yams.reader import PyFileReader
 from yams.buildobjs import (String, Int, Float, Boolean, Date, Time, Datetime,
-                            Interval, Password, Bytes, ObjectRelation,
-                            SubjectRelation, RestrictedEntityType)
+                            Bytes, SubjectRelation)
 from yams.buildobjs import metadefinition, EntityType
 
 from cubicweb.schema import CubicWebSchemaLoader
@@ -172,7 +168,7 @@
     # the loader is instantiated because this is where the dbmodels
     # are registered in the yams schema
     for compname in config['included-cubes']:
-        comp = __import__('%s.schema' % compname)
+        __import__('%s.schema' % compname)
     loader = GaeSchemaLoader(use_gauthservice=config['use-google-auth'], db=db)
     loader.lib_directory = SCHEMAS_LIB_DIRECTORY
     if schemaclasses is not None:
@@ -180,7 +176,7 @@
             loader.load_dbmodel(cls.__name__, goadb.extract_dbmodel(cls))
     elif config['schema-type'] == 'dbmodel':
         import schema as appschema
-        for objname, obj in vars(appschema).items():
+        for obj in vars(appschema).values():
             if isinstance(obj, type) and issubclass(obj, goadb.Model) and obj.__module__ == appschema.__name__:
                 loader.load_dbmodel(obj.__name__, goadb.extract_dbmodel(obj))
     for erschema in ('EGroup', 'EEType', 'ERType', 'RQLExpression',
--- a/goa/gaesource.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/gaesource.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,22 +1,19 @@
 """Adapter for google appengine source.
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.decorators import cached, clear_cache
-
-from cubicweb import AuthenticationError, UnknownEid, server
+from cubicweb import AuthenticationError, UnknownEid
 from cubicweb.server.sources import AbstractSource, ConnectionWrapper
 from cubicweb.server.pool import SingleOperation
 from cubicweb.server.utils import crypt_password
 from cubicweb.goa.dbinit import set_user_groups
 from cubicweb.goa.rqlinterpreter import RQLInterpreter
 
-from google.appengine.api.datastore import Key, Entity, Get, Put, Delete
-from google.appengine.api.datastore import Query
+from google.appengine.api.datastore import Key, Entity, Put, Delete
 from google.appengine.api import datastore_errors, users
     
 def _init_groups(guser, euser):
--- a/goa/goaconfig.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/goaconfig.py	Tue Apr 07 09:30:23 2009 +0200
@@ -11,7 +11,7 @@
 
 from cubicweb import CW_SOFTWARE_ROOT
 from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.web.webconfig import WebConfiguration, merge_options, Method
+from cubicweb.web.webconfig import WebConfiguration, merge_options
 from cubicweb.server.serverconfig import ServerConfiguration
 from cubicweb.goa.dbmyams import load_schema
 
--- a/goa/goactl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/goactl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,21 +1,21 @@
 """cubicweb on appengine plugins for cubicweb-ctl
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from os.path import exists, join, split, dirname, basename, normpath, abspath
+from os.path import exists, join, split, basename, normpath, abspath
+
+from logilab.common.clcommands import register_commands
 
-from cubicweb import BadCommandUsage
-from cubicweb import CW_SOFTWARE_ROOT
-from cubicweb.toolsutils import (Command, register_commands, copy_skeleton,
-                              create_dir, create_symlink, create_copy)
+from cubicweb import CW_SOFTWARE_ROOT, BadCommandUsage
+from cubicweb.toolsutils import (Command, copy_skeleton, create_symlink,
+                                 create_dir)
 from cubicweb.cwconfig import CubicWebConfiguration
 
 from logilab import common as lgc
-from logilab.common.textutils import get_csv
 from logilab import constraint as lgcstr
 from logilab import mtconverter as lgmtc
 import rql, yams, yapps, simplejson, dateutil, vobject, docutils, roman
@@ -47,28 +47,29 @@
     '__init__.py',
     '__pkginfo__.py',
     '_exceptions.py',
+    'appobject.py',
     'dbapi.py',
     'cwvreg.py',
     'cwconfig.py',
+    'entity.py',
     'interfaces.py',
     'rset.py',
     'schema.py',
     'schemaviewer.py',
+    'selectors.py',
+    'utils.py',
     'vregistry.py',
+    'view.py',
     
-    'common/appobject.py',
-    'common/entity.py',
-    'common/html4zope.py',
     'common/mail.py',
     'common/migration.py',
     'common/mixins.py',
     'common/mttransforms.py',
     'common/registerers.py',
-    'common/rest.py',
-    'common/selectors.py',
-    'common/view.py',
     'common/uilib.py',
-    'common/utils.py',
+
+    'ext/html4zope.py',
+    'ext/rest.py',
 
     'server/hookhelper.py',
     'server/hooksmanager.py',
--- a/goa/rqlinterpreter.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/rqlinterpreter.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,20 +1,17 @@
 """provide a minimal RQL support for google appengine dbmodel
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from mx.DateTime import DateTimeType, DateTimeDeltaType
 from datetime import datetime
 
 from rql import RQLHelper, nodes
-from logilab.common.compat import any
 
 from cubicweb import Binary
 from cubicweb.rset import ResultSet
-from cubicweb.goa import mx2datetime, datetime2mx
 from cubicweb.server import SQL_CONNECT_HOOKS
 
 from google.appengine.api.datastore import Key, Get, Query, Entity
@@ -250,9 +247,6 @@
     def __init__(self, rel, kwargs):
         RelationRestriction.__init__(self, rel, None)
         value = self.rhs.eval(kwargs)
-        if isinstance(value, (DateTimeType, DateTimeDeltaType)):
-            #yamstype = self.schema.rschema(self.rtype).objects()[0]
-            value = mx2datetime(value, 'Datetime')
         self.value = value
         if self.operator == 'ILIKE':
             if value.startswith('%'):
@@ -294,11 +288,7 @@
         RelationRestriction.__init__(self, rel, None)
         values = []
         for c in self.rel.children[1].iget_nodes(nodes.Constant):
-            value = c.eval(kwargs)
-            if isinstance(value, (DateTimeType, DateTimeDeltaType)):
-                #yamstype = self.schema.rschema(self.rtype).objects()[0]
-                value = mx2datetime(value, 'Datetime')
-            values.append(value)
+            values.append(c.eval(kwargs))
         self.value = values
 
     @property
@@ -322,9 +312,7 @@
 
 def append_result(res, descr, i, j, value, etype):
     if value is not None:
-        if etype in ('Date', 'Datetime', 'Time'):
-            value = datetime2mx(value, etype)
-        elif isinstance(value, Text):
+        if isinstance(value, Text):
             value = unicode(value)
         elif isinstance(value, Blob):
             value = Binary(str(value))
--- a/goa/testlib.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/testlib.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,4 +1,4 @@
-from logilab.common.testlib import TestCase, mock_object
+from logilab.common.testlib import TestCase
 
 import os, os.path as osp
 import time
@@ -15,11 +15,10 @@
     from google.appengine.api import datastore_file_stub
     from google.appengine.ext import db as gdb
     from cubicweb.goa import db, do_monkey_patch
-    from cubicweb.goa.dbmyams import load_schema
     import_appengine_failed = None
 except ImportError, exc:
-    raise
-    class db:
+    # XXX necessary ?
+    class db: 
         class Model:
             pass
         class DummyProperty:
@@ -34,8 +33,6 @@
     import_appengine_failed = 'cannot import appengine: %s' % exc
     
 
-from cubicweb import CW_SOFTWARE_ROOT
-from cubicweb.server.utils import crypt_password
 from cubicweb.devtools.fake import FakeRequest
 from cubicweb.goa.goavreg import GAERegistry
 from cubicweb.goa.goaconfig import GAEConfiguration
--- a/goa/tools/i18n.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,282 +0,0 @@
-#!/usr/bin/env python
-"""This script is just a thin wrapper around ``msgcat`` and ``msgfmt``
-to generate ``.mo`` files
-"""
-
-import sys
-import os
-import os.path as osp
-import shutil
-from tempfile import mktemp
-from glob import glob
-from mx.DateTime import now
-
-from logilab.common.fileutils import ensure_fs_mode
-from logilab.common.shellutils import find, rm
-
-from yams import BASE_TYPES
-
-from cubicweb import CW_SOFTWARE_ROOT
-# from cubicweb.__pkginfo__ import version as cubicwebversion
-cubicwebversion = '2.48.2'
-
-DEFAULT_POT_HEAD = r'''# LAX application po file
-
-msgid ""
-msgstr ""
-"Project-Id-Version: cubicweb %s\n"
-"PO-Revision-Date: 2008-03-28 18:14+0100\n"
-"Last-Translator: Logilab Team <contact@logilab.fr>\n"
-"Language-Team: fr <contact@logilab.fr>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: cubicweb-devtools\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-''' % cubicwebversion
-
-
-STDLIB_ERTYPES = BASE_TYPES | set( ('EUser', 'EProperty', 'Card', 'identity', 'for_user') )
-
-def create_dir(directory):
-    """create a directory if it doesn't exist yet"""
-    try:
-        os.makedirs(directory)
-        print 'created directory', directory
-    except OSError, ex:
-        import errno
-        if ex.errno != errno.EEXIST:
-            raise
-        print 'directory %s already exists' % directory
-
-def execute(cmd):
-    """display the command, execute it and raise an Exception if returned
-    status != 0
-    """
-    print cmd.replace(os.getcwd() + os.sep, '')
-    status = os.system(cmd)
-    if status != 0:
-        raise Exception()
-
-def add_msg(w, msgid):
-    """write an empty pot msgid definition"""
-    if isinstance(msgid, unicode):
-        msgid = msgid.encode('utf-8')
-    msgid = msgid.replace('"', r'\"').splitlines()
-    if len(msgid) > 1:
-        w('msgid ""\n')
-        for line in msgid:
-            w('"%s"' % line.replace('"', r'\"'))
-    else:
-        w('msgid "%s"\n' % msgid[0])
-    w('msgstr ""\n\n')
-
-
-def generate_schema_pot(w, vreg, tmpldir):
-    """generate a pot file with schema specific i18n messages
-
-    notice that relation definitions description and static vocabulary
-    should be marked using '_' and extracted using xgettext
-    """
-    cube = tmpldir and osp.split(tmpldir)[-1]
-    config = vreg.config
-    vreg.register_objects(config.vregistry_path())
-    w(DEFAULT_POT_HEAD)
-    _generate_schema_pot(w, vreg, vreg.schema, libschema=None, # no libschema for now
-                         cube=cube)
-
-
-def _generate_schema_pot(w, vreg, schema, libschema=None, cube=None):
-    w('# schema pot file, generated on %s\n' % now().strftime('%Y-%m-%d %H:%M:%S'))
-    w('# \n')
-    w('# singular and plural forms for each entity type\n')
-    w('\n')
-    # XXX hard-coded list of stdlib's entity schemas
-    libschema = libschema or STDLIB_ERTYPES
-    entities = [e for e in schema.entities() if not e in libschema]
-    done = set()
-    for eschema in sorted(entities):
-        etype = eschema.type
-        add_msg(w, etype)
-        add_msg(w, '%s_plural' % etype)
-        if not eschema.is_final():
-            add_msg(w, 'This %s' % etype)
-            add_msg(w, 'New %s' % etype)
-            add_msg(w, 'add a %s' % etype)
-            add_msg(w, 'remove this %s' % etype)
-        if eschema.description and not eschema.description in done:
-            done.add(eschema.description)
-            add_msg(w, eschema.description)
-    w('# subject and object forms for each relation type\n')
-    w('# (no object form for final relation types)\n')
-    w('\n')
-    if libschema is not None:
-        relations = [r for r in schema.relations() if not r in libschema]
-    else:
-        relations = schema.relations()
-    for rschema in sorted(set(relations)):
-        rtype = rschema.type
-        add_msg(w, rtype)
-        if not (schema.rschema(rtype).is_final() or rschema.symetric):
-            add_msg(w, '%s_object' % rtype)
-        if rschema.description and rschema.description not in done:
-            done.add(rschema.description)
-            add_msg(w, rschema.description)
-    w('# add related box generated message\n')
-    w('\n')
-    for eschema in schema.entities():
-        if eschema.is_final():
-            continue
-        entity = vreg.etype_class(eschema)(None, None)
-        for x, rschemas in (('subject', eschema.subject_relations()),
-                            ('object', eschema.object_relations())):
-            for rschema in rschemas:
-                if rschema.is_final():
-                    continue
-                for teschema in rschema.targets(eschema, x):
-                    if defined_in_library(libschema, eschema, rschema, teschema, x):
-                        continue
-                    if entity.relation_mode(rschema.type, teschema.type, x) == 'create':
-                        if x == 'subject':
-                            label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
-                            label2 = "creating %s (%s %%(linkto)s %s %s)" % (teschema, eschema, rschema, teschema)
-                        else:
-                            label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
-                            label2 = "creating %s (%s %s %s %%(linkto)s)" % (teschema, teschema, rschema, eschema)
-                        add_msg(w, label)
-                        add_msg(w, label2)
-    cube = (cube or 'cubicweb') + '.'
-    done = set()
-    for reg, objdict in vreg.items():
-        for objects in objdict.values():
-            for obj in objects:
-                objid = '%s_%s' % (reg, obj.id)
-                if objid in done:
-                    continue
-                if obj.__module__.startswith(cube) and obj.property_defs:
-                    add_msg(w, '%s_description' % objid)
-                    add_msg(w, objid)
-                    done.add(objid)
-                    
-def defined_in_library(libschema, etype, rtype, tetype, x):
-    """return true if the given relation definition exists in cubicweb's library"""
-    if libschema is None:
-        return False
-    if x == 'subject':
-        subjtype, objtype = etype, tetype
-    else:
-        subjtype, objtype = tetype, etype
-    try:
-        return libschema.rschema(rtype).has_rdef(subjtype, objtype)
-    except (KeyError, AttributeError):
-        # if libschema is a simple list of entity types (lax specific)
-        # or if the relation could not be found
-        return False
-
-
-
-# XXX check if this is a pure duplication of the original
-# `cubicweb.common.i18n` function
-def compile_i18n_catalogs(sourcedirs, destdir, langs):
-    """generate .mo files for a set of languages into the `destdir` i18n directory
-    """
-    print 'compiling %s catalogs...' % destdir
-    errors = []
-    for lang in langs:
-        langdir = osp.join(destdir, lang, 'LC_MESSAGES')
-        if not osp.exists(langdir):
-            create_dir(langdir)
-        pofiles = [osp.join(path, '%s.po' % lang) for path in sourcedirs]
-        pofiles = [pof for pof in pofiles if osp.exists(pof)]
-        mergedpo = osp.join(destdir, '%s_merged.po' % lang)
-        try:
-            # merge application messages' catalog with the stdlib's one
-            execute('msgcat --use-first --sort-output --strict %s > %s'
-                    % (' '.join(pofiles), mergedpo))
-            # make sure the .mo file is writeable and compile with *msgfmt*
-            applmo = osp.join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
-            try:
-                ensure_fs_mode(applmo)
-            except OSError:
-                pass # suppose not osp.exists
-            execute('msgfmt %s -o %s' % (mergedpo, applmo))
-        except Exception, ex:
-            errors.append('while handling language %s: %s' % (lang, ex))
-        try:
-            # clean everything
-            os.unlink(mergedpo)
-        except Exception:
-            continue
-    return errors
-
-
-def update_cubes_catalog(vreg, appdirectory, langs):
-    toedit = []
-    tmpl = osp.basename(osp.normpath(appdirectory))
-    tempdir = mktemp()
-    os.mkdir(tempdir)
-    print '*' * 72
-    print 'updating %s cube...' % tmpl
-    os.chdir(appdirectory)
-    potfiles = []
-    if osp.exists(osp.join('i18n', 'entities.pot')):
-        potfiles = potfiles.append( osp.join('i18n', 'entities.pot') )
-    print '******** extract schema messages'
-    schemapot = osp.join(tempdir, 'schema.pot')
-    potfiles.append(schemapot)
-    # XXX
-    generate_schema_pot(open(schemapot, 'w').write, vreg, appdirectory)
-    print '******** extract Javascript messages'
-    jsfiles =  find('.', '.js')
-    if jsfiles:
-        tmppotfile = osp.join(tempdir, 'js.pot')
-        execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s'
-                % (tmppotfile, ' '.join(jsfiles)))
-        # no pot file created if there are no string to translate
-        if osp.exists(tmppotfile): 
-            potfiles.append(tmppotfile)
-    print '******** create cube specific catalog'
-    tmppotfile = osp.join(tempdir, 'generated.pot')
-    execute('xgettext --no-location --omit-header -k_ -o %s %s'
-            % (tmppotfile, ' '.join(glob('*.py'))))
-    if osp.exists(tmppotfile): # doesn't exists of no translation string found
-        potfiles.append(tmppotfile)
-    potfile = osp.join(tempdir, 'cube.pot')
-    print '******** merging .pot files'
-    execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
-    print '******** merging main pot file with existing translations'
-    os.chdir('i18n')
-    for lang in langs:
-        print '****', lang
-        tmplpo = '%s.po' % lang
-        if not osp.exists(tmplpo):
-            shutil.copy(potfile, tmplpo)
-        else:
-            execute('msgmerge -N -s %s %s > %snew' % (tmplpo, potfile, tmplpo))
-            ensure_fs_mode(tmplpo)
-            shutil.move('%snew' % tmplpo, tmplpo)
-        toedit.append(osp.abspath(tmplpo))
-    # cleanup
-    rm(tempdir)
-    # instructions pour la suite
-    print '*' * 72
-    print 'you can now edit the following files:'
-    print '* ' + '\n* '.join(toedit)
-             
-
-def getlangs(i18ndir):
-    return [fname[:-3] for fname in os.listdir(i18ndir)
-            if fname.endswith('.po')]
-
-
-def get_i18n_directory(appdirectory):
-    if not osp.isdir(appdirectory):
-        print '%s is not an application directory' % appdirectory
-        sys.exit(2)
-    i18ndir = osp.join(appdirectory, 'i18n')
-    if not osp.isdir(i18ndir):
-        print '%s is not an application directory ' \
-            '(i18n subdirectory missing)' % appdirectory
-        sys.exit(2)
-    return i18ndir
--- a/goa/tools/laxctl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/goa/tools/laxctl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """provides all lax instances management commands into a single utility script
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -17,14 +17,9 @@
 
 from logilab.common.clcommands import Command, register_commands, main_run
 
-from cubicweb import CW_SOFTWARE_ROOT
 from cubicweb.common.uilib import remove_html_tags
-
 APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
 
-# XXX import custom?
-
-from tools import i18n
 
 def initialize_vregistry(applroot):
     # apply monkey patches first
@@ -51,33 +46,6 @@
     def run(self, args):
         self.vreg = initialize_vregistry(APPLROOT)
         self._run(args)
-                
-
-class I18nUpdateCommand(LaxCommand):
-    """updates i18n catalogs"""
-    name = 'i18nupdate'
-    
-    def _run(self, args):
-        assert not args, 'no argument expected'
-        i18ndir = i18n.get_i18n_directory(APPLROOT)
-        i18n.update_cubes_catalog(self.vreg, APPLROOT,
-                                      langs=i18n.getlangs(i18ndir))
-
-
-class I18nCompileCommand(LaxCommand):
-    """compiles i18n catalogs"""
-    name = 'i18ncompile'
-    min_args = max_args = 0
-    
-    def _run(self, args):
-        assert not args, 'no argument expected'
-        i18ndir = i18n.get_i18n_directory(APPLROOT)
-        langs = i18n.getlangs(i18ndir)
-        print 'generating .mo files for langs', ', '.join(langs)
-        cubicweb_i18ndir = osp.join(APPLROOT, 'cubes', 'shared')
-        paths = self.vreg.config.cubes_path() + [cubicweb_i18ndir]
-        sourcedirs = [i18ndir] + [osp.join(path, 'i18n') for path in paths]
-        i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs=langs)
         
 
 class GenerateSchemaCommand(LaxCommand):
@@ -274,9 +242,7 @@
         self.extract_message(data)
             
     
-register_commands([I18nUpdateCommand,
-                   I18nCompileCommand,
-                   GenerateSchemaCommand,
+register_commands([GenerateSchemaCommand,
                    PopulateDataDirCommand,
                    DSInitCommand,
                    CleanSessionsCommand,
--- a/hercule.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/hercule.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """RQL client for cubicweb, connecting to application using pyro
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -11,8 +11,8 @@
 
 from logilab.common import flatten
 from logilab.common.cli import CLIHelper
-from logilab.common.clcommands import BadCommandUsage, pop_arg
-from cubicweb.toolsutils import CONNECT_OPTIONS, Command, register_commands
+from logilab.common.clcommands import BadCommandUsage, pop_arg, register_commands
+from cubicweb.toolsutils import CONNECT_OPTIONS, Command
  
 # result formatter ############################################################
 
@@ -37,7 +37,7 @@
         format_results(writer, layout, stream)
     finally:
         stream.close()
-        status = os.waitpid(pid, 0)
+        os.waitpid(pid, 0)
 
 def izip2(list1, list2):
     for i in xrange(len(list1)):
@@ -132,11 +132,11 @@
     
     def do_description(self):
         """display the description of the latest result"""
-        if self.cursor.description is None:
+        if self.rset.description is None:
             print _('No query has been executed')
         else:
             print '\n'.join([', '.join(line_desc)
-                             for line_desc in self.cursor.description])
+                             for line_desc in self.rset.description])
 
     help_do_description = ('description', "description", _(do_description.__doc__))
     
@@ -200,7 +200,7 @@
         self._previous_lines = []
         # search results
         try:
-            self.cursor.execute(query)
+            self.rset = rset = self.cursor.execute(query)
         except:
             if self.autocommit:
                 self.cnx.rollback()
@@ -208,18 +208,18 @@
         else:
             if self.autocommit:
                 self.cnx.commit()
-        self.handle_result(self.cursor.fetchall(), self.cursor.description)
+        self.handle_result(rset)
 
-    def handle_result(self, result, description):
+    def handle_result(self, rset):
         """display query results if any"""
-        if not result:
+        if not rset:
             print _('No result matching query')
         else:
             from logilab.common.ureports import Table
-            children = flatten(izip2(description, result), to_string)
-            layout = Table(cols=2*len(result[0]), children=children, cheaders=1)
+            children = flatten(izip2(rset.description, rset.rows), to_string)
+            layout = Table(cols=2*len(rset.rows[0]), children=children, cheaders=1)
             self._format(self.writer, layout)
-            print _('%s results matching query') % len(result)
+            print _('%s results matching query') % rset.rowcount
 
     def display_schema(self, schema):
         """display a schema object"""
--- a/i18n/en.po	Tue Apr 07 08:55:37 2009 +0200
+++ b/i18n/en.po	Tue Apr 07 09:30:23 2009 +0200
@@ -1846,6 +1846,9 @@
 msgid "i18n_login_popup"
 msgstr "login"
 
+msgid "i18n_register_user"
+msgstr "register"
+
 msgid "i18nprevnext_next"
 msgstr "next"
 
@@ -2051,6 +2054,9 @@
 msgid "login"
 msgstr ""
 
+msgid "login or email" 
+msgstr ""
+
 msgid "login_action"
 msgstr "log in"
 
--- a/i18n/es.po	Tue Apr 07 08:55:37 2009 +0200
+++ b/i18n/es.po	Tue Apr 07 09:30:23 2009 +0200
@@ -1929,6 +1929,9 @@
 msgid "i18n_login_popup"
 msgstr "identificarse"
 
+msgid "i18n_register_user"
+msgstr "registrarse"
+
 msgid "i18nprevnext_next"
 msgstr "siguiente"
 
--- a/i18n/fr.po	Tue Apr 07 08:55:37 2009 +0200
+++ b/i18n/fr.po	Tue Apr 07 09:30:23 2009 +0200
@@ -1929,6 +1929,9 @@
 msgid "i18n_login_popup"
 msgstr "s'authentifier"
 
+msgid "i18n_register_user"
+msgstr "s'enregister"
+
 msgid "i18nprevnext_next"
 msgstr "suivant"
 
@@ -2145,6 +2148,9 @@
 msgid "login"
 msgstr "identifiant"
 
+msgid "login or email"
+msgstr "identifiant ou email"
+
 msgid "login_action"
 msgstr "identifiez vous"
 
--- a/interfaces.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/interfaces.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """Specific views for entities implementing IDownloadable
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -193,8 +193,8 @@
         """embed action interface"""
     
 class ICalendarable(Interface):
-    """interface for itms that do have a begin date 'start' and an end
-date 'stop'"""    
+    """interface for items that do have a begin date 'start' and an end date 'stop'
+    """    
     
 class ICalendarViews(Interface):
     """calendar views interface"""
--- a/md5crypt.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/md5crypt.py	Tue Apr 07 09:30:23 2009 +0200
@@ -53,7 +53,7 @@
 
 
 def crypt(pw, salt, magic=None):
-    if magic==None:
+    if magic is None:
         magic = MAGIC
     # Take care of the magic string if present
     if salt[:len(magic)] == magic:
@@ -63,7 +63,7 @@
     salt = salt[:8]
     ctx = pw + magic + salt
     final = md5.md5(pw + salt + pw).digest()
-    for pl in range(len(pw),0,-16):
+    for pl in xrange(len(pw), 0, -16):
         if pl > 16:
             ctx = ctx + final[:16]
         else:
@@ -80,7 +80,7 @@
     # The following is supposed to make
     # things run slower. 
     # my question: WTF???
-    for i in range(1000):
+    for i in xrange(1000):
         ctx1 = ''
         if i & 1:
             ctx1 = ctx1 + pw
--- a/misc/migration/2.37.1_Any.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-if 'Keyword' in schema:
-    synchronize_schema('Keyword')
--- a/misc/migration/2.39.0_Any.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-try:
-    # missing on some old databases
-    sql('CREATE INDEX entities_extid_idx ON entities(extid)')
-except:
-    pass # already exists
-checkpoint() 
-sql('CREATE INDEX entities_type_idx ON entities(type)')
-checkpoint()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.2.0_Any.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,3 @@
+rql('SET X value "main-template" WHERE X is EProperty, '
+    'X pkey "ui.main-template", X value "main"')
+checkpoint()
--- a/rset.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/rset.py	Tue Apr 07 09:30:23 2009 +0200
@@ -179,8 +179,7 @@
         else:
             entities = sorted(enumerate(self),
                               key=lambda (i, e): keyfunc(e), reverse=reverse)
-
-        for index, entity in entities:
+        for index, _ in entities:
             rows.append(self.rows[index])
             descr.append(self.description[index])
         rset.rowcount = len(rows)
@@ -210,7 +209,7 @@
         for idx, line in enumerate(self):
             if col >= 0:
                 try:
-                    key = self.get_entity(idx,col)
+                    key = self.get_entity(idx, col)
                 except NotAnEntity:
                     key = line[col]
             else:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rtags.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,85 @@
+"""relation tags store
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+class RelationTags(object):
+    """RelationTags instances are a tag store for full relation definitions :
+
+         (subject type, relation type, object type, role)
+
+    allowing to set tags using wildcard (eg '*') as subject type / object type
+
+    if `use_set` is True, a set of tags is associated to each key, and you
+    should use rtags / etype_rtags / add_rtag api. Otherwise, a single tag is
+    associated to each key, and you should use rtag / etype_rtag / set_rtag api.
+    """
+    
+    def __init__(self, use_set=False):
+        self.use_set = use_set
+        self._tagdefs = {}
+        
+    def set_rtag(self, tag, rtype, role, stype='*', otype='*'):
+        assert not self.use_set
+        assert role in ('subject', 'object'), role
+        self._tagdefs[(rtype, role, stype, otype)] = tag
+        
+    def rtag(self, rtype, role, stype='*', otype='*'):
+        assert not self.use_set
+        for key in reversed(self._get_keys(rtype, role, stype, otype)):
+            try:
+                return self._tagdefs[key]
+            except KeyError:
+                continue
+        return None
+        
+    def etype_rtag(self, etype, rtype, role, ttype='*'):
+        if role == 'subject':
+            return self.rtag(rtype, role, etype, ttype)
+        return self.rtag(rtype, role, ttype, etype)
+        
+    def add_rtag(self, tag, rtype, role, stype='*', otype='*'):
+        assert self.use_set
+        assert role in ('subject', 'object'), role
+        rtags = self._tagdefs.setdefault((rtype, role, stype, otype), set())
+        rtags.add(tag)
+        
+    def rtags(self, rtype, role, stype='*', otype='*'):
+        assert self.use_set
+        rtags = set()
+        for key in self._get_keys(rtype, role, stype, otype):
+            try:
+                rtags.update(self._tagdefs[key])
+            except KeyError:
+                continue
+        return rtags
+        
+    def etype_rtags(self, etype, rtype, role, ttype='*'):
+        if role == 'subject':
+            return self.rtags(rtype, role, etype, ttype)
+        return self.rtags(rtype, role, ttype, etype)
+
+    def _get_keys(self, rtype, role, stype, otype): 
+        assert role in ('subject', 'object'), role
+        keys = [(rtype, role, '*', '*'),
+                (rtype, role, '*', otype),
+                (rtype, role, stype, '*'),
+                (rtype, role, stype, otype)]
+        if stype == '*' or otype == '*':
+            keys.remove((rtype, role, '*', '*'))
+            if stype == '*':
+                keys.remove((rtype, role, '*', otype))
+            if otype == '*':
+                keys.remove((rtype, role, stype, '*'))            
+        return keys
+    
+    # dict compat
+    def __getitem__(self, key):
+        if isinstance(key, basestring):
+            key = (key,)
+        return self.rtags(*key)
+
+    __contains__ = __getitem__
--- a/schema.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/schema.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,11 +6,11 @@
 """
 __docformat__ = "restructuredtext en"
 
-import warnings
 import re
 from logging import getLogger
+from warnings import warn
 
-from logilab.common.decorators import cached, clear_cache
+from logilab.common.decorators import cached, clear_cache, monkeypatch
 from logilab.common.compat import any
 
 from yams import BadSchemaDefinition, buildobjs as ybo
@@ -22,6 +22,12 @@
 from rql import parse, nodes, RQLSyntaxError, TypeResolverException
 
 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized
+from cubicweb import set_log_methods
+
+# XXX <3.2 bw compat
+from yams import schema
+schema.use_py_datetime()
+nodes.use_py_datetime() 
 
 _ = unicode
 
@@ -36,7 +42,6 @@
 
 def bw_normalize_etype(etype):
     if etype in ETYPE_NAME_MAP:
-        from warnings import warn
         msg = '%s has been renamed to %s, please update your code' % (
             etype, ETYPE_NAME_MAP[etype])            
         warn(msg, DeprecationWarning, stacklevel=4)
@@ -68,6 +73,40 @@
     return (etype,)
 ybo.RelationDefinition._actual_types = _actual_types
 
+
+## cubicweb provides a RichString class for convenience
+class RichString(ybo.String):
+    """Convenience RichString attribute type
+    The follwing declaration::
+      
+      class Card(EntityType):
+          content = RichString(fulltextindexed=True, default_format='text/rest')
+          
+    is equivalent to::
+      
+      class Card(EntityType):
+          content_format = String(meta=True, internationalizable=True,
+                                 default='text/rest', constraints=[format_constraint])
+          content  = String(fulltextindexed=True)
+    """
+    def __init__(self, default_format='text/plain', format_constraints=None, **kwargs):
+        self.default_format = default_format
+        self.format_constraints = format_constraints or [format_constraint]
+        super(RichString, self).__init__(**kwargs)
+
+PyFileReader.context['RichString'] = RichString
+
+## need to monkeypatch yams' _add_relation function to handle RichString
+yams_add_relation = ybo._add_relation
+@monkeypatch(ybo)
+def _add_relation(relations, rdef, name=None, insertidx=None):
+    if isinstance(rdef, RichString):
+        format_attrdef = ybo.String(meta=True, internationalizable=True,
+                                    default=rdef.default_format, maxsize=50,
+                                    constraints=rdef.format_constraints)
+        yams_add_relation(relations, format_attrdef, name+'_format', insertidx)
+    yams_add_relation(relations, rdef, name, insertidx)
+    
 def display_name(req, key, form=''):
     """return a internationalized string for the key (schema entity or relation
     name) in a given form
@@ -282,19 +321,7 @@
     def schema_entity(self):
         """return True if this entity type is used to build the schema"""
         return self.type in self.schema.schema_entity_types()
-
-    def rich_text_fields(self):
-        """return an iterator on (attribute, format attribute) of rich text field
-
-        (the first tuple element containing the text and the second the text format)
-        """
-        for rschema, _ in self.attribute_definitions():
-            if rschema.type.endswith('_format'):
-                for constraint in self.constraints(rschema):
-                    if isinstance(constraint, FormatConstraint):
-                        yield self.subject_relation(rschema.type[:-7]), rschema
-                        break
-                    
+    
     def check_perm(self, session, action, eid=None):
         # NB: session may be a server session or a request object
         user = session.user
@@ -477,13 +504,13 @@
         rdef.name = rdef.name.lower()
         rdef.subject = bw_normalize_etype(rdef.subject)
         rdef.object = bw_normalize_etype(rdef.object)
-        super(CubicWebSchema, self).add_relation_def(rdef)
-        try:
-            self._eid_index[rdef.eid] = (self.eschema(rdef.subject),
-                                         self.rschema(rdef.name),
-                                         self.eschema(rdef.object))
-        except AttributeError:
-            pass # not a serialized schema
+        if super(CubicWebSchema, self).add_relation_def(rdef):
+            try:
+                self._eid_index[rdef.eid] = (self.eschema(rdef.subject),
+                                             self.rschema(rdef.name),
+                                             self.eschema(rdef.object))
+            except AttributeError:
+                pass # not a serialized schema
     
     def del_relation_type(self, rtype):
         rschema = self.rschema(rtype)
@@ -805,7 +832,34 @@
         
 PyFileReader.context['RRQLExpression'] = RRQLExpression
 
-        
+# workflow extensions #########################################################
+
+class workflowable_definition(ybo.metadefinition):
+    """extends default EntityType's metaclass to add workflow relations
+    (i.e. in_state and wf_info_for).
+    This is the default metaclass for WorkflowableEntityType
+    """
+    def __new__(mcs, name, bases, classdict):
+        abstract = classdict.pop('abstract', False)
+        defclass = super(workflowable_definition, mcs).__new__(mcs, name, bases, classdict)
+        if not abstract:
+            existing_rels = set(rdef.name for rdef in defclass.__relations__)
+            if 'in_state' not in existing_rels and 'wf_info_for' not in existing_rels:
+                in_state = ybo.SubjectRelation('State', cardinality='1*',
+                                               # XXX automatize this
+                                               constraints=[RQLConstraint('S is ET, O state_of ET')],
+                                               description=_('account state'))
+                yams_add_relation(defclass.__relations__, in_state, 'in_state')
+                wf_info_for = ybo.ObjectRelation('TrInfo', cardinality='1*', composite='object')
+                yams_add_relation(defclass.__relations__, wf_info_for, 'wf_info_for')
+        return defclass
+
+class WorkflowableEntityType(ybo.EntityType):
+    __metaclass__ = workflowable_definition
+    abstract = True
+    
+PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
+
 # schema loading ##############################################################
 
 class CubicWebRelationFileReader(RelationFileReader):
@@ -839,13 +893,13 @@
     SchemaLoader.file_handlers.update({'.rel' : CubicWebRelationFileReader,
                                        })
 
-    def load(self, config, path=()):
+    def load(self, config, path=(), **kwargs):
         """return a Schema instance from the schema definition read
         from <directory>
         """
         self.lib_directory = config.schemas_lib_dir()
         return super(BootstrapSchemaLoader, self).load(
-            path, config.appid, register_base_types=False)
+            path, config.appid, register_base_types=False, **kwargs)
     
     def _load_definition_files(self, cubes=None):
         # bootstraping, ignore cubes
@@ -863,7 +917,7 @@
     application's schema
     """
 
-    def load(self, config):
+    def load(self, config, **kwargs):
         """return a Schema instance from the schema definition read
         from <directory>
         """
@@ -872,11 +926,12 @@
             path = reversed([config.apphome] + config.cubes_path())
         else:
             path = reversed(config.cubes_path())
-        return super(CubicWebSchemaLoader, self).load(config, path=path)
+        return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
 
     def _load_definition_files(self, cubes):
         for filepath in (self.include_schema_files('bootstrap')
                          + self.include_schema_files('base')
+                         + self.include_schema_files('workflow')
                          + self.include_schema_files('Bookmark')
                          + self.include_schema_files('Card')):
             self.info('loading %s', filepath)
@@ -892,14 +947,15 @@
 PERM_USE_TEMPLATE_FORMAT = _('use_template_format')
 
 class FormatConstraint(StaticVocabularyConstraint):
-    need_perm_formats = (_('text/cubicweb-page-template'),
-                         )
+    need_perm_formats = [_('text/cubicweb-page-template')]
+        
     regular_formats = (_('text/rest'),
                        _('text/html'),
                        _('text/plain'),
                        )
     def __init__(self):
         pass
+    
     def serialize(self):
         """called to make persistent valuable data of a constraint"""
         return None
@@ -911,9 +967,11 @@
         """
         return cls()
     
-    def vocabulary(self, entity=None):
-        if entity and entity.req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
-            return self.regular_formats + self.need_perm_formats
+    def vocabulary(self, entity=None, req=None):
+        if req is None and entity is not None:
+            req = entity.req
+        if req is not None and req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
+            return self.regular_formats + tuple(self.need_perm_formats)
         return self.regular_formats
     
     def __str__(self):
@@ -924,8 +982,6 @@
 CONSTRAINTS['FormatConstraint'] = FormatConstraint
 PyFileReader.context['format_constraint'] = format_constraint
 
-from logging import getLogger
-from cubicweb import set_log_methods
 set_log_methods(CubicWebSchemaLoader, getLogger('cubicweb.schemaloader'))
 set_log_methods(BootstrapSchemaLoader, getLogger('cubicweb.bootstrapschemaloader'))
 set_log_methods(RQLExpression, getLogger('cubicweb.schema'))
--- a/schemas/Card.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/schemas/Card.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,4 +1,4 @@
-from cubicweb.schema import format_constraint
+# from cubicweb.schema import format_constraint
 
 class Card(EntityType):
     """a card is a textual content used as documentation, reference, procedure reminder"""
@@ -12,7 +12,5 @@
     title    = String(required=True, fulltextindexed=True, maxsize=256)
     synopsis = String(fulltextindexed=True, maxsize=512,
                       description=_("an abstract for this card"))
-    content_format = String(meta=True, internationalizable=True, maxsize=50,
-                            default='text/rest', constraints=[format_constraint])
-    content  = String(fulltextindexed=True)
+    content = RichString(fulltextindexed=True, default_format='text/rest')
     wikiid = String(maxsize=64, indexed=True)
--- a/schemas/base.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/schemas/base.py	Tue Apr 07 09:30:23 2009 +0200
@@ -9,8 +9,9 @@
 from cubicweb.schema import format_constraint
 
 
-class EUser(RestrictedEntityType):
+class EUser(WorkflowableEntityType):
     """define a CubicWeb user"""
+    meta = True # XXX backported from old times, shouldn't be there anymore
     permissions = {
         'read':   ('managers', 'users', ERQLExpression('X identity U')),
         'add':    ('managers',),
@@ -33,11 +34,6 @@
     in_group = SubjectRelation('EGroup', cardinality='+*',
                                constraints=[RQLConstraint('NOT O name "owners"')],
                                description=_('groups grant permissions to the user'))
-    in_state = SubjectRelation('State', cardinality='1*',
-                               # XXX automatize this
-                               constraints=[RQLConstraint('S is ET, O state_of ET')],
-                               description=_('account state'))
-    wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
 
 
 class EmailAddress(MetaEntityType):
@@ -130,112 +126,7 @@
     cardinality = '11'
     subject = '**'
     object = 'Datetime'
-
-
-class State(MetaEntityType):
-    """used to associate simple states to an entity type and/or to define
-    workflows
-    """
-    name = String(required=True, indexed=True, internationalizable=True,
-                  maxsize=256)
-    description_format = String(meta=True, internationalizable=True, maxsize=50,
-                                default='text/rest', constraints=[format_constraint])
-    description = String(fulltextindexed=True,
-                         description=_('semantic description of this state'))
     
-    state_of = SubjectRelation('EEType', cardinality='+*',
-                    description=_('entity types which may use this state'),
-                    constraints=[RQLConstraint('O final FALSE')])
-    allowed_transition = SubjectRelation('Transition', cardinality='**',
-                                         constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
-                                         description=_('allowed transitions from this state'))
-    
-    initial_state = ObjectRelation('EEType', cardinality='?*',
-                                   # S initial_state O, O state_of S
-                                   constraints=[RQLConstraint('O state_of S')],
-                                   description=_('initial state for entities of this type'))
-
-
-class Transition(MetaEntityType):
-    """use to define a transition from one or multiple states to a destination
-    states in workflow's definitions.
-    """
-    name = String(required=True, indexed=True, internationalizable=True,
-                  maxsize=256)
-    description_format = String(meta=True, internationalizable=True, maxsize=50,
-                                default='text/rest', constraints=[format_constraint])
-    description = String(fulltextindexed=True,
-                         description=_('semantic description of this transition'))
-    condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
-                                description=_('a RQL expression which should return some results, '
-                                              'else the transition won\'t be available. '
-                                              'This query may use X and U variables '
-                                              'that will respectivly represents '
-                                              'the current entity and the current user'))
-    
-    require_group = SubjectRelation('EGroup', cardinality='**',
-                                    description=_('group in which a user should be to be '
-                                                  'allowed to pass this transition'))
-    transition_of = SubjectRelation('EEType', cardinality='+*',
-                                    description=_('entity types which may use this transition'),
-                                    constraints=[RQLConstraint('O final FALSE')])
-    destination_state = SubjectRelation('State', cardinality='?*',
-                                        constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
-                                        description=_('destination state for this transition'))
-
-
-class TrInfo(MetaEntityType):
-    from_state = SubjectRelation('State', cardinality='?*')
-    to_state = SubjectRelation('State', cardinality='1*')
-    comment_format = String(meta=True, internationalizable=True, maxsize=50,
-                            default='text/rest', constraints=[format_constraint])
-    comment = String(fulltextindexed=True)
-    # get actor and date time using owned_by and creation_date
-
-
-class from_state(MetaRelationType):
-    inlined = True
-class to_state(MetaRelationType):
-    inlined = True
-class wf_info_for(MetaRelationType):
-    """link a transition information to its object"""
-    permissions = {
-        'read':   ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
-        'add':    (), # handled automatically, no one should add one explicitly
-        'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
-        }
-    inlined = True
-    composite = 'object'
-    fulltext_container = composite
-    
-class state_of(MetaRelationType):
-    """link a state to one or more entity type"""
-class transition_of(MetaRelationType):
-    """link a transition to one or more entity type"""
-    
-class initial_state(MetaRelationType):
-    """indicate which state should be used by default when an entity using
-    states is created
-    """
-    inlined = True
-
-class destination_state(MetaRelationType):
-    """destination state of a transition"""
-    inlined = True
-    
-class allowed_transition(MetaRelationType):
-    """allowed transition from this state"""
-
-class in_state(UserRelationType):
-    """indicate the current state of an entity"""
-    meta = True
-    # not inlined intentionnaly since when using ldap sources, user'state
-    # has to be stored outside the EUser table
-    
-    # add/delete perms given to managers/users, after what most of the job
-    # is done by workflow enforcment
-    
-
 class EProperty(EntityType):
     """used for cubicweb configuration. Once a property has been created you
     can't change the key.
--- a/schemas/bootstrap.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/schemas/bootstrap.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """core CubicWeb schema necessary for bootstrapping the actual application's schema
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -14,10 +14,8 @@
     """define an entity type, used to build the application schema"""
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
-    description_format = String(meta=True, internationalizable=True, maxsize=50,
-                                default='text/plain', constraints=[format_constraint])
-    description = String(internationalizable=True,
-                         description=_('semantic description of this entity type'))
+    description = RichString(internationalizable=True, 
+                             description=_('semantic description of this entity type'))
     meta = Boolean(description=_('is it an application entity type or not ?'))
     # necessary to filter using RQL
     final = Boolean(description=_('automatic'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/workflow.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,108 @@
+"""workflow related schemas
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+class State(MetaEntityType):
+    """used to associate simple states to an entity type and/or to define
+    workflows
+    """
+    name = String(required=True, indexed=True, internationalizable=True,
+                  maxsize=256)
+    description = RichString(fulltextindexed=True, default_format='text/rest',
+                             description=_('semantic description of this state'))
+    
+    state_of = SubjectRelation('EEType', cardinality='+*',
+                    description=_('entity types which may use this state'),
+                    constraints=[RQLConstraint('O final FALSE')])
+    allowed_transition = SubjectRelation('Transition', cardinality='**',
+                                         constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
+                                         description=_('allowed transitions from this state'))
+    
+    initial_state = ObjectRelation('EEType', cardinality='?*',
+                                   # S initial_state O, O state_of S
+                                   constraints=[RQLConstraint('O state_of S')],
+                                   description=_('initial state for entities of this type'))
+
+
+class Transition(MetaEntityType):
+    """use to define a transition from one or multiple states to a destination
+    states in workflow's definitions.
+    """
+    name = String(required=True, indexed=True, internationalizable=True,
+                  maxsize=256)
+    description_format = String(meta=True, internationalizable=True, maxsize=50,
+                                default='text/rest', constraints=[format_constraint])
+    description = String(fulltextindexed=True,
+                         description=_('semantic description of this transition'))
+    condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
+                                description=_('a RQL expression which should return some results, '
+                                              'else the transition won\'t be available. '
+                                              'This query may use X and U variables '
+                                              'that will respectivly represents '
+                                              'the current entity and the current user'))
+    
+    require_group = SubjectRelation('EGroup', cardinality='**',
+                                    description=_('group in which a user should be to be '
+                                                  'allowed to pass this transition'))
+    transition_of = SubjectRelation('EEType', cardinality='+*',
+                                    description=_('entity types which may use this transition'),
+                                    constraints=[RQLConstraint('O final FALSE')])
+    destination_state = SubjectRelation('State', cardinality='?*',
+                                        constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
+                                        description=_('destination state for this transition'))
+
+
+class TrInfo(MetaEntityType):
+    from_state = SubjectRelation('State', cardinality='?*')
+    to_state = SubjectRelation('State', cardinality='1*')
+    comment_format = String(meta=True, internationalizable=True, maxsize=50,
+                            default='text/rest', constraints=[format_constraint])
+    comment = String(fulltextindexed=True)
+    # get actor and date time using owned_by and creation_date
+
+
+class from_state(MetaRelationType):
+    inlined = True
+class to_state(MetaRelationType):
+    inlined = True
+class wf_info_for(MetaRelationType):
+    """link a transition information to its object"""
+    permissions = {
+        'read':   ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
+        'add':    (), # handled automatically, no one should add one explicitly
+        'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
+        }
+    inlined = True
+    composite = 'object'
+    fulltext_container = composite
+    
+class state_of(MetaRelationType):
+    """link a state to one or more entity type"""
+class transition_of(MetaRelationType):
+    """link a transition to one or more entity type"""
+    
+class initial_state(MetaRelationType):
+    """indicate which state should be used by default when an entity using
+    states is created
+    """
+    inlined = True
+
+class destination_state(MetaRelationType):
+    """destination state of a transition"""
+    inlined = True
+    
+class allowed_transition(MetaRelationType):
+    """allowed transition from this state"""
+
+class in_state(UserRelationType):
+    """indicate the current state of an entity"""
+    meta = True
+    # not inlined intentionnaly since when using ldap sources, user'state
+    # has to be stored outside the EUser table
+    
+    # add/delete perms given to managers/users, after what most of the job
+    # is done by workflow enforcment
+    
--- a/schemaviewer.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/schemaviewer.py	Tue Apr 07 09:30:23 2009 +0200
@@ -46,13 +46,12 @@
         esection = Section(children=(Title(self.req._('Entities'),
                                            klass='titleUnderline'),))
         layout.append(esection)
-        entities = [eschema for eschema in schema.entities()
+        eschemas = [eschema for eschema in schema.entities()
                     if not eschema.is_final()]
         if skipmeta:
-            entities = [eschema for eschema in entities
+            eschemas = [eschema for eschema in eschemas
                         if not eschema.meta]
-        keys = [(eschema.type, eschema) for eschema in entities]
-        for key, eschema in sorted(keys):
+        for eschema in sorted(eschema):
             esection.append(self.visit_entityschema(eschema, skiprels))
         if display_relations:
             title = Title(self.req._('Relations'), klass='titleUnderline')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/selectors.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,1100 @@
+"""This file contains some basic selectors required by application objects.
+
+A selector is responsible to score how well an object may be used with a
+given context by returning a score.
+
+In CubicWeb Usually the context consists for a request object, a result set
+or None, a specific row/col in the result set, etc...
+
+
+If you have trouble with selectors, especially if the objet (typically
+a view or a component) you want to use is not selected and you want to
+know which one(s) of its selectors fail (e.g. returns 0), you can use
+`traced_selection` or even direclty `TRACED_OIDS`.
+
+`TRACED_OIDS` is a tuple of traced object ids. The special value
+'all' may be used to log selectors for all objects.
+
+For instance, say that the following code yields a `NoSelectableObject`
+exception::
+
+    self.view('calendar', myrset)
+
+You can log the selectors involved for *calendar* by replacing the line
+above by::
+
+    # in Python2.5
+    from cubicweb.selectors import traced_selection
+    with traced_selection():
+        self.view('calendar', myrset)
+
+    # in Python2.4
+    from cubicweb import selectors
+    selectors.TRACED_OIDS = ('calendar',)
+    self.view('calendar', myrset)
+    selectors.TRACED_OIDS = ()
+ 
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import logging
+from warnings import warn
+
+from logilab.common.compat import all
+from logilab.common.deprecation import deprecated_function
+from logilab.common.interface import implements as implements_iface
+
+from yams import BASE_TYPES
+
+from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
+from cubicweb.vregistry import (NoSelectableObject, Selector,
+                                chainall, objectify_selector)
+from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.schema import split_expression
+
+# helpers for debugging selectors
+SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
+TRACED_OIDS = ()
+
+def lltrace(selector):
+    # don't wrap selectors if not in development mode
+    if CubicWebConfiguration.mode == 'installed':
+        return selector
+    def traced(cls, *args, **kwargs):
+        # /!\ lltrace decorates pure function or __call__ method, this
+        #     means argument order may be different
+        if isinstance(cls, Selector):
+            selname = str(cls)
+            vobj = args[0]
+        else:
+            selname = selector.__name__
+            vobj = cls
+        oid = vobj.id
+        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)
+            print 'selector %s returned %s for %s' % (selname, ret, vobj)
+        return ret
+    traced.__name__ = selector.__name__
+    return traced
+
+class traced_selection(object):
+    """selector debugging helper.
+
+    Typical usage is :
+
+    >>> with traced_selection():
+    ...     # some code in which you want to debug selectors
+    ...     # for all objects
+
+    or
+
+    >>> with traced_selection( ('oid1', 'oid2') ):
+    ...     # some code in which you want to debug selectors
+    ...     # for objects with id 'oid1' and 'oid2'
+    
+    """
+    def __init__(self, traced='all'):
+        self.traced = traced
+        
+    def __enter__(self):
+        global TRACED_OIDS
+        TRACED_OIDS = self.traced
+
+    def __exit__(self, exctype, exc, traceback):
+        global TRACED_OIDS
+        TRACED_OIDS = ()
+        return traceback is None
+
+
+# abstract selectors ##########################################################
+class PartialSelectorMixIn(object):
+    """convenience mix-in for selectors that will look into the containing
+    class to find missing information.
+    
+    cf. `cubicweb.web.action.LinkToEntityAction` for instance
+    """
+    def __call__(self, cls, *args, **kwargs):
+        self.complete(cls)
+        return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
+
+class EClassSelector(Selector):
+    """abstract class for selectors working on the entity classes of the result
+    set. Its __call__ method has the following behaviour:
+
+    * if row is specified, return the score returned by the score_class method
+      called with the entity class found in the specified cell
+    * else return the sum of score returned by the score_class method for each
+      entity type found in the specified column, unless:
+      - `once_is_enough` is True, in which case the first non-zero score is
+        returned
+      - `once_is_enough` is False, in which case if score_class return 0, 0 is
+        returned
+    """
+    def __init__(self, once_is_enough=False):
+        self.once_is_enough = once_is_enough
+    
+    @lltrace
+    def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+        if not rset:
+            return 0
+        score = 0
+        if row is None:
+            for etype in rset.column_types(col):
+                if etype is None: # outer join
+                    continue
+                escore = self.score(cls, req, etype)
+                if not escore and not self.once_is_enough:
+                    return 0
+                elif self.once_is_enough:
+                    return escore
+                score += escore
+        else:
+            etype = rset.description[row][col]
+            if etype is not None:
+                score = self.score(cls, req, etype)
+        return score
+
+    def score(self, cls, req, etype):
+        if etype in BASE_TYPES:
+            return 0
+        return self.score_class(cls.vreg.etype_class(etype), req)
+        
+    def score_class(self, eclass, req):
+        raise NotImplementedError()
+
+
+class EntitySelector(EClassSelector):
+    """abstract class for selectors working on the entity instances of the
+    result set. Its __call__ method has the following behaviour:
+
+    * if row is specified, return the score returned by the score_entity method
+      called with the entity instance found in the specified cell
+    * else return the sum of score returned by the score_entity method for each
+      entity found in the specified column, unless:
+      - `once_is_enough` is True, in which case the first non-zero score is
+        returned
+      - `once_is_enough` is False, in which case if score_class return 0, 0 is
+        returned
+
+    note: None values (resulting from some outer join in the query) are not
+          considered.
+    """
+    
+    @lltrace
+    def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+        if not rset:
+            return 0
+        score = 0
+        if row is None:
+            for row, rowvalue in enumerate(rset.rows):
+                if rowvalue[col] is None: # outer join
+                    continue
+                escore = self.score(req, rset, row, col)
+                if not escore and not self.once_is_enough:
+                    return 0
+                elif self.once_is_enough:
+                    return escore
+                score += escore
+        else:
+            etype = rset.description[row][col]
+            if etype is not None: # outer join
+                score = self.score(req, rset, row, col)
+        return score
+
+    def score(self, req, rset, row, col):
+        try:
+            return self.score_entity(rset.get_entity(row, col))
+        except NotAnEntity:
+            return 0
+                                 
+    def score_entity(self, entity):
+        raise NotImplementedError()
+
+
+# very basic selectors ########################################################
+
+class yes(Selector):
+    """return arbitrary score"""
+    def __init__(self, score=1):
+        self.score = score
+    def __call__(self, *args, **kwargs):
+        return self.score
+
+@objectify_selector
+@lltrace
+def none_rset(cls, req, rset, *args, **kwargs):
+    """accept no result set (e.g. given rset is None)"""
+    if rset is None:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def any_rset(cls, req, rset, *args, **kwargs):
+    """accept result set, whatever the number of result it contains"""
+    if rset is not None:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def nonempty_rset(cls, req, rset, *args, **kwargs):
+    """accept any non empty result set"""
+    if rset is not None and rset.rowcount:
+        return 1
+    return 0
+    
+@objectify_selector
+@lltrace
+def empty_rset(cls, req, rset, *args, **kwargs):
+    """accept empty result set"""
+    if rset is not None and rset.rowcount == 0:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
+    """if row is specified, accept result set with a single line of result,
+    else accepts anyway
+    """
+    if rset is not None and (row is not None or rset.rowcount == 1):
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def two_lines_rset(cls, req, rset, *args, **kwargs):
+    """accept result set with *at least* two lines of result"""
+    if rset is not None and rset.rowcount > 1:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def two_cols_rset(cls, req, rset, *args, **kwargs):
+    """accept result set with at least one line and two columns of result"""
+    if rset is not None and rset.rowcount and len(rset.rows[0]) > 1:
+        return 1
+    return 0
+
+@objectify_selector
+@lltrace
+def paginated_rset(cls, req, rset, *args, **kwargs):
+    """accept result set with more lines than the page size.
+    
+    Page size is searched in (respecting order):
+    * a page_size argument
+    * a page_size form parameters
+    * the navigation.page-size property
+    """
+    page_size = kwargs.get('page_size')
+    if page_size is None:
+        page_size = req.form.get('page_size')
+        if page_size is None:
+            page_size = req.property_value('navigation.page-size')
+        else:
+            page_size = int(page_size)
+    if rset is None or rset.rowcount <= page_size:
+        return 0
+    return 1
+
+@objectify_selector
+@lltrace
+def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
+    """accept sorted result set"""
+    rqlst = rset.syntax_tree()
+    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+        return 0
+    return 2
+
+@objectify_selector
+@lltrace
+def one_etype_rset(cls, req, rset, row=None, col=0, *args, **kwargs):
+    """accept result set where entities in the specified column (or 0) are all
+    of the same type
+    """
+    if rset is None:
+        return 0
+    if len(rset.column_types(col)) != 1:
+        return 0
+    return 1
+
+@objectify_selector
+@lltrace
+def two_etypes_rset(cls, req, rset, row=None, col=0, **kwargs):
+    """accept result set where entities in the specified column (or 0) are not
+    of the same type
+    """
+    if rset:
+        etypes = rset.column_types(col)
+        if len(etypes) > 1:
+            return 1
+    return 0
+
+class non_final_entity(EClassSelector):
+    """accept if entity type found in the result set is non final.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
+    """
+    def score(self, cls, req, etype):
+        if etype in BASE_TYPES:
+            return 0
+        return 1
+
+@objectify_selector
+@lltrace
+def authenticated_user(cls, req, *args, **kwargs):
+    """accept if user is anonymous"""
+    if req.cnx.anonymous_connection:
+        return 0
+    return 1
+
+def anonymous_user():
+    return ~ authenticated_user()
+
+@objectify_selector
+@lltrace
+def primary_view(cls, req, rset, row=None, col=0, view=None, **kwargs):
+    """accept if view given as named argument is a primary view, or if no view
+    is given
+    """
+    if view is not None and not view.is_primary():
+        return 0
+    return 1
+
+@objectify_selector
+@lltrace
+def match_context_prop(cls, req, rset, row=None, col=0, context=None,
+                       **kwargs):
+    """accept if:
+    * no context given
+    * context (`basestring`) is matching the context property value for the
+      given cls
+    """
+    propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
+    if not propval:
+        propval = cls.context
+    if context is not None and propval and context != propval:
+        return 0
+    return 1
+
+
+class match_search_state(Selector):
+    """accept if the current request search state is in one of the expected
+    states given to the initializer
+    
+    :param expected: either 'normal' or 'linksearch' (eg searching for an
+                     object to create a relation with another)
+    """
+    def __init__(self, *expected):
+        assert expected, self
+        self.expected = frozenset(expected)
+
+    def __str__(self):
+        return '%s(%s)' % (self.__class__.__name__,
+                           ','.join(sorted(str(s) for s in self.expected)))
+        
+    @lltrace
+    def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+        try:
+            if not req.search_state[0] in self.expected:
+                return 0
+        except AttributeError:
+            return 1 # class doesn't care about search state, accept it
+        return 1
+
+
+class match_form_params(match_search_state):
+    """accept if parameters specified as initializer arguments are specified
+    in request's form parameters
+    
+    :param *expected: parameters (eg `basestring`) which are expected to be
+                      found in request's form parameters
+    """
+
+    @lltrace
+    def __call__(self, cls, req, *args, **kwargs):
+        score = 0
+        for param in self.expected:
+            val = req.form.get(param)
+            if not val:
+                return 0
+            score += 1
+        return len(self.expected)
+
+
+class match_kwargs(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):
+        for arg in self.expected:
+            if not arg in kwargs:
+                return 0
+        return len(self.expected)
+
+
+class match_user_groups(match_search_state):
+    """accept if logged users is in at least one of the given groups. Returned
+    score is the number of groups in which the user is.
+    
+    If the special 'owners' group is given:
+    * if row is specified check the entity at the given row/col is owned by the
+      logged user
+    * if row is not specified check all entities in col are owned by the logged
+      user
+
+    :param *required_groups: name of groups (`basestring`) in which the logged
+                             user should be
+    """
+    
+    @lltrace
+    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        user = req.user
+        if user is None:
+            return int('guests' in self.expected)
+        score = user.matching_groups(self.expected)
+        if not score and 'owners' in self.expected and rset:
+            if row is not None:
+                if not user.owns(rset[row][col]):
+                    return 0
+                score = 1
+            else:
+                score = all(user.owns(r[col]) for r in rset)
+        return score
+
+
+class match_view(match_search_state):
+    """accept if the current view is in one of the expected vid given to the
+    initializer
+    """
+    @lltrace
+    def __call__(self, cls, req, rset, row=None, col=0, view=None, **kwargs):
+        if view is None or not view.id in self.expected:
+            return 0
+        return 1
+
+
+class appobject_selectable(Selector):
+    """accept with another appobject is selectable using selector's input
+    context.
+
+    :param registry: a registry name (`basestring`)
+    :param oid: an object identifier (`basestring`)
+    """
+    def __init__(self, registry, oid):
+        self.registry = registry
+        self.oid = oid
+        
+    def __call__(self, cls, req, rset, *args, **kwargs):
+        try:
+            cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
+            return 1
+        except NoSelectableObject:
+            return 0
+
+
+# not so basic selectors ######################################################
+
+class implements(EClassSelector):
+    """accept if entity class found in the result set implements at least one
+    of the interfaces given as argument. Returned score is the number of
+    implemented interfaces.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
+
+    :param *expected_ifaces: expected interfaces. An interface may be a class
+                             or an entity type (e.g. `basestring`) in which case
+                             the associated class will be searched in the
+                             registry (at selection time)
+                             
+    note: when interface is an entity class, the score will reflect class
+          proximity so the most specific object'll be selected
+    """
+    def __init__(self, *expected_ifaces):
+        super(implements, self).__init__()
+        self.expected_ifaces = expected_ifaces
+
+    def __str__(self):
+        return '%s(%s)' % (self.__class__.__name__,
+                           ','.join(str(s) for s in self.expected_ifaces))
+    
+    def score_class(self, eclass, req):
+        score = 0
+        for iface in self.expected_ifaces:
+            if isinstance(iface, basestring):
+                # entity type
+                try:
+                    iface = eclass.vreg.etype_class(iface)
+                except KeyError:
+                    continue # entity type not in the schema
+            if implements_iface(eclass, iface):
+                if getattr(iface, '__registry__', None) == 'etypes':
+                    # adjust score if the interface is an entity class
+                    if iface is eclass:
+                        score += len(eclass.e_schema.ancestors()) + 4
+                    else: 
+                        parents = [e.type for e in eclass.e_schema.ancestors()]
+                        for index, etype in enumerate(reversed(parents)):
+                            basecls = eclass.vreg.etype_class(etype)
+                            if iface is basecls:
+                                score += index + 3
+                                break
+                        else: # Any
+                            score += 1
+                else:
+                    # implenting an interface takes precedence other special Any
+                    # interface
+                    score += 2
+        return score
+
+
+class specified_etype_implements(implements):
+    """accept if entity class specified using an 'etype' parameters in name
+    argument or request form implements at least one of the interfaces given as
+    argument. Returned score is the number of implemented interfaces.
+
+    :param *expected_ifaces: expected interfaces. An interface may be a class
+                             or an entity type (e.g. `basestring`) in which case
+                             the associated class will be searched in the
+                             registry (at selection time)
+                             
+    note: when interface is an entity class, the score will reflect class
+          proximity so the most specific object'll be selected
+    """
+    
+    @lltrace
+    def __call__(self, cls, req, *args, **kwargs):
+        try:
+            etype = req.form['etype']
+        except KeyError:
+            try:
+                etype = kwargs['etype']
+            except KeyError:
+                return 0
+        return self.score_class(cls.vreg.etype_class(etype), req)
+
+
+class relation_possible(EClassSelector):
+    """accept if entity class found in the result set support the relation.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
+
+    :param rtype: a relation type (`basestring`)
+    :param role: the role of the result set entity in the relation. 'subject' or
+                 'object', default to 'subject'.
+    :param target_type: if specified, check the relation's end may be of this
+                        target type (`basestring`)
+    :param action: a relation schema action (one of 'read', 'add', 'delete')
+                   which must be granted to the logged user, else a 0 score will
+                   be returned
+    """
+    def __init__(self, rtype, role='subject', target_etype=None,
+                 action='read', once_is_enough=False):
+        super(relation_possible, self).__init__(once_is_enough)
+        self.rtype = rtype
+        self.role = role
+        self.target_etype = target_etype
+        self.action = action
+
+    @lltrace
+    def __call__(self, cls, req, *args, **kwargs):
+        rschema = cls.schema.rschema(self.rtype)
+        if not (rschema.has_perm(req, self.action)
+                or rschema.has_local_role(self.action)):
+            return 0
+        score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
+        return score
+        
+    def score_class(self, eclass, req):
+        eschema = eclass.e_schema
+        try:
+            if self.role == 'object':
+                rschema = eschema.object_relation(self.rtype)
+            else:
+                rschema = eschema.subject_relation(self.rtype)
+        except KeyError:
+            return 0
+        if self.target_etype is not None:
+            try:
+                if self.role == 'subject':
+                    return int(self.target_etype in rschema.objects(eschema))
+                else:
+                    return int(self.target_etype in rschema.subjects(eschema))
+            except KeyError:
+                return 0
+        return 1
+
+
+class partial_relation_possible(PartialSelectorMixIn, relation_possible):
+    """partial version of the relation_possible selector
+
+    The selector will look for class attributes to find its missing
+    information. The list of attributes required on the class
+    for this selector are:
+
+    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+    
+    - `role`: this attribute will be passed to the `cubicweb.role` function
+      to determine the role of class in the relation
+
+    - `etype` (optional): the entity type on the other side of the relation
+    
+    :param action: a relation schema action (one of 'read', 'add', 'delete')
+                   which must be granted to the logged user, else a 0 score will
+                   be returned
+    """
+    def __init__(self, action='read', once_is_enough=False):
+        super(partial_relation_possible, self).__init__(None, None, None,
+                                                        action, once_is_enough)
+
+    def complete(self, cls):
+        self.rtype = cls.rtype
+        self.role = role(cls)
+        self.target_etype = getattr(cls, 'etype', None)
+
+
+class may_add_relation(EntitySelector):
+    """accept if the relation can be added to an entity found in the result set
+    by the logged user.
+    
+    See `EntitySelector` documentation for behaviour when row is not specified.
+
+    :param rtype: a relation type (`basestring`)
+    :param role: the role of the result set entity in the relation. 'subject' or
+                 'object', default to 'subject'.
+    """
+    
+    def __init__(self, rtype, role='subject', once_is_enough=False):
+        super(may_add_relation, self).__init__(once_is_enough)
+        self.rtype = rtype
+        self.role = role
+        
+    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):
+                return 0
+        elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
+            return 0
+        return 1
+
+
+class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
+    """partial version of the may_add_relation selector
+
+    The selector will look for class attributes to find its missing
+    information. The list of attributes required on the class
+    for this selector are:
+
+    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+    
+    - `role`: this attribute will be passed to the `cubicweb.role` function
+      to determine the role of class in the relation.
+    
+    :param action: a relation schema action (one of 'read', 'add', 'delete')
+                   which must be granted to the logged user, else a 0 score will
+                   be returned
+    """
+    def __init__(self, once_is_enough=False):
+        super(partial_may_add_relation, self).__init__(None, None, once_is_enough)
+
+    def complete(self, cls):
+        self.rtype = cls.rtype
+        self.role = role(cls)
+
+    
+class has_related_entities(EntitySelector):
+    """accept if entity found in the result set has some linked entities using
+    the specified relation (optionaly filtered according to the specified target
+    type). Checks first if the relation is possible.
+    
+    See `EntitySelector` documentation for behaviour when row is not specified.
+
+    :param rtype: a relation type (`basestring`)
+    :param role: the role of the result set entity in the relation. 'subject' or
+                 'object', default to 'subject'.
+    :param target_type: if specified, check the relation's end may be of this
+                        target type (`basestring`)
+    """
+    def __init__(self, rtype, role='subject', target_etype=None,
+                 once_is_enough=False):
+        super(has_related_entities, self).__init__(once_is_enough)
+        self.rtype = rtype
+        self.role = role
+        self.target_etype = target_etype
+    
+    def score_entity(self, entity):
+        relpossel = relation_possible(self.rtype, self.role, self.target_etype)
+        if not relpossel.score_class(entity.__class__, entity.req):
+            return 0
+        rset = entity.related(self.rtype, self.role)
+        if self.target_etype:
+            return any(r for r in rset.description if r[0] == self.target_etype)
+        return rset and 1 or 0
+
+
+class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
+    """partial version of the has_related_entities selector
+
+    The selector will look for class attributes to find its missing
+    information. The list of attributes required on the class
+    for this selector are:
+
+    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+    
+    - `role`: this attribute will be passed to the `cubicweb.role` function
+      to determine the role of class in the relation.
+
+    - `etype` (optional): the entity type on the other side of the relation
+    
+    :param action: a relation schema action (one of 'read', 'add', 'delete')
+                   which must be granted to the logged user, else a 0 score will
+                   be returned
+    """
+    def __init__(self, once_is_enough=False):
+        super(partial_has_related_entities, self).__init__(None, None,
+                                                           None, once_is_enough)
+    def complete(self, cls):
+        self.rtype = cls.rtype
+        self.role = role(cls)
+        self.target_etype = getattr(cls, 'etype', None)
+
+
+class has_permission(EntitySelector):
+    """accept if user has the permission to do the requested action on a result
+    set entity.
+
+    * if row is specified, return 1 if user has the permission on the entity
+      instance found in the specified cell
+    * else return a positive score if user has the permission for every entity
+      in the found in the specified column
+
+    note: None values (resulting from some outer join in the query) are not
+          considered.
+    
+    :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+    """
+    def __init__(self, action, once_is_enough=False):
+        super(has_permission, self).__init__(once_is_enough)
+        self.action = action
+        
+    @lltrace
+    def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+        if rset is None:
+            return 0
+        user = req.user
+        action = self.action
+        if row is None:
+            score = 0
+            need_local_check = [] 
+            geteschema = cls.schema.eschema
+            for etype in rset.column_types(0):
+                if etype in BASE_TYPES:
+                    return 0
+                eschema = geteschema(etype)
+                if not user.matching_groups(eschema.get_groups(action)):
+                    if eschema.has_local_role(action):
+                        # have to ckeck local roles
+                        need_local_check.append(eschema)
+                        continue
+                    else:
+                        # even a local role won't be enough
+                        return 0
+                score += 1
+            if need_local_check:
+                # check local role for entities of necessary types
+                for i, row in enumerate(rset):
+                    if not rset.description[i][0] in need_local_check:
+                        continue
+                    if not self.score(req, rset, i, col):
+                        return 0
+                score += 1
+            return score
+        return self.score(req, rset, row, col)
+    
+    def score_entity(self, entity):
+        if entity.has_perm(self.action):
+            return 1
+        return 0
+
+
+class has_add_permission(EClassSelector):
+    """accept if logged user has the add permission on entity class found in the
+    result set, and class is not a strict subobject.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
+    """
+    def score(self, cls, req, etype):
+        eschema = cls.schema.eschema(etype)
+        if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+               and eschema.has_perm(req, 'add'):
+            return 1
+        return 0
+
+
+class rql_condition(EntitySelector):
+    """accept if an arbitrary rql return some results for an eid found in the
+    result set. Returned score is the number of items returned by the rql
+    condition.
+    
+    See `EntitySelector` documentation for behaviour when row is not specified.
+
+    :param expression: basestring containing an rql expression, which should use
+                       X variable to represent the context entity and may use U
+                       to represent the logged user
+
+    return the sum of the number of items returned by the rql condition as score
+    or 0 at the first entity scoring to zero.
+    """
+    def __init__(self, expression, once_is_enough=False):
+        super(rql_condition, self).__init__(once_is_enough)
+        if 'U' in frozenset(split_expression(expression)):
+            rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
+        else:
+            rql = 'Any X WHERE X eid %%(x)s, %s' % expression
+        self.rql = rql
+        
+    def score(self, req, rset, row, col):
+        try:
+            return len(req.execute(self.rql, {'x': rset[row][col],
+                                              'u': req.user.eid}, 'x'))
+        except Unauthorized:
+            return 0
+
+        
+class but_etype(EntitySelector):
+    """accept if the given entity types are not found in the result set.
+
+    See `EntitySelector` documentation for behaviour when row is not specified.
+    
+    :param *etypes: entity types (`basestring`) which should be refused
+    """
+    def __init__(self, *etypes):
+        super(but_etype, self).__init__()
+        self.but_etypes = etypes
+        
+    def score(self, req, rset, row, col):
+        if rset.description[row][col] in self.but_etypes:
+            return 0
+        return 1
+
+                
+class score_entity(EntitySelector):
+    """accept if some arbitrary function return a positive score for an entity
+    found in the result set.
+    
+    See `EntitySelector` documentation for behaviour when row is not specified.
+
+    :param scorefunc: callable expected to take an entity as argument and to
+                      return a score >= 0 
+    """
+    def __init__(self, scorefunc, once_is_enough=False):
+        super(score_entity, self).__init__(once_is_enough)
+        self.score_entity = scorefunc
+
+
+# XXX DEPRECATED ##############################################################
+
+yes_selector = deprecated_function(yes)
+norset_selector = deprecated_function(none_rset)
+rset_selector = deprecated_function(any_rset)
+anyrset_selector = deprecated_function(nonempty_rset)
+emptyrset_selector = deprecated_function(empty_rset)
+onelinerset_selector = deprecated_function(one_line_rset)
+twolinerset_selector = deprecated_function(two_lines_rset)
+twocolrset_selector = deprecated_function(two_cols_rset)
+largerset_selector = deprecated_function(paginated_rset)
+sortedrset_selector = deprecated_function(sorted_rset)
+oneetyperset_selector = deprecated_function(one_etype_rset)
+multitype_selector = deprecated_function(two_etypes_rset)
+anonymous_selector = deprecated_function(anonymous_user)
+not_anonymous_selector = deprecated_function(authenticated_user)
+primaryview_selector = deprecated_function(primary_view)
+contextprop_selector = deprecated_function(match_context_prop)
+
+def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
+    return non_final_entity()(cls, req, rset, row, col)
+nfentity_selector = deprecated_function(nfentity_selector)
+
+def implement_interface(cls, req, rset, row=None, col=0, **kwargs):
+    return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
+_interface_selector = deprecated_function(implement_interface)
+interface_selector = deprecated_function(implement_interface)
+implement_interface = deprecated_function(implement_interface, 'use implements')
+
+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 = deprecated_function(accept_etype)
+accept_etype = deprecated_function(accept_etype, 'use specified_etype_implements')
+
+def searchstate_selector(cls, req, rset, row=None, col=0, **kwargs):
+    return match_search_state(cls.search_states)(cls, req, rset, row, col)
+searchstate_selector = deprecated_function(searchstate_selector)
+
+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 = deprecated_function(match_user_group)
+match_user_group = deprecated_function(match_user_group)
+
+def has_relation(cls, req, rset, 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)
+has_relation = deprecated_function(has_relation)
+
+def one_has_relation(cls, req, rset, 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)
+one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
+
+def accept_rset(cls, req, rset, 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 = deprecated_function(accept_rset)
+accept_rset = deprecated_function(accept_rset, 'use implements selector')
+
+accept = chainall(non_final_entity(), accept_rset, name='accept')
+accept_selector = deprecated_function(accept)
+accept = deprecated_function(accept, 'use implements selector')
+
+accept_one = deprecated_function(chainall(one_line_rset, accept,
+                                          name='accept_one'))
+accept_one_selector = deprecated_function(accept_one)
+
+
+def _rql_condition(cls, req, rset, row=None, col=0, **kwargs):
+    if cls.condition:
+        return rql_condition(cls.condition)(cls, req, rset, row, col)
+    return 1
+_rqlcondition_selector = deprecated_function(_rql_condition)
+
+rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition,
+                         name='rql_condition'))
+    
+def but_etype_selector(cls, req, rset, row=None, col=0, **kwargs):
+    return but_etype(cls.etype)(cls, req, rset, row, col)
+but_etype_selector = deprecated_function(but_etype_selector)
+
+@lltrace
+def etype_rtype_selector(cls, req, rset, 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_function(etype_rtype_selector)
+
+#req_form_params_selector = deprecated_function(match_form_params) # form_params
+#kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs
+
+# compound selectors ##########################################################
+
+searchstate_accept = chainall(nonempty_rset(), accept,
+                              name='searchstate_accept')
+searchstate_accept_selector = deprecated_function(searchstate_accept)
+
+searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
+                                  name='searchstate_accept_one')
+searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
+
+searchstate_accept = deprecated_function(searchstate_accept)
+searchstate_accept_one = deprecated_function(searchstate_accept_one)
+
+
+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',
+                 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	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/checkintegrity.py	Tue Apr 07 09:30:23 2009 +0200
@@ -8,8 +8,8 @@
 __docformat__ = "restructuredtext en"
 
 import sys
+from datetime import datetime
 
-from mx.DateTime import now
 from logilab.common.shellutils import ProgressBar
 
 from cubicweb.server.sqlutils import SQL_PREFIX
@@ -88,7 +88,7 @@
           ', '.join(sorted(str(e) for e in etypes))
     pb = ProgressBar(len(etypes) + 1)
     # first monkey patch Entity.check to disable validation
-    from cubicweb.common.entity import Entity
+    from cubicweb.entity import Entity
     _check = Entity.check
     Entity.check = lambda self, creation=False: True
     # clear fti table first
@@ -237,8 +237,8 @@
     eidcolumn = SQL_PREFIX + 'eid'
     for etype, in cursor.fetchall():
         table = SQL_PREFIX + etype
-        for rel, default in ( ('creation_date', now()),
-                              ('modification_date', now()), ):
+        for rel, default in ( ('creation_date', datetime.now()),
+                              ('modification_date', datetime.now()), ):
             column = SQL_PREFIX + rel
             cursor = session.system_sql("SELECT %s FROM %s WHERE %s is NULL"
                                         % (eidcolumn, table, column))
--- a/server/hookhelper.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/hookhelper.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """helper functions for application hooks
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -10,7 +10,7 @@
 from threading import Lock
 
 from cubicweb import RepositoryError
-from cubicweb.server.pool import Operation, SingleLastOperation
+from cubicweb.server.pool import SingleLastOperation
 
 
 def entity_name(session, eid):
--- a/server/hooks.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/hooks.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,12 +7,10 @@
 """
 __docformat__ = "restructuredtext en"
 
-from mx.DateTime import now
+from datetime import datetime
 
 from cubicweb import UnknownProperty, ValidationError, BadConnectionId
 
-from cubicweb.common.uilib import soup2xhtml
-
 from cubicweb.server.pool import Operation, LateOperation, PreCommitOperation
 from cubicweb.server.hookhelper import (check_internal_entity, previous_state,
                                      get_user_sessions, rproperty)
@@ -30,14 +28,14 @@
     this is a conveniency hook, you shouldn't have to disable it
     """
     if not 'creation_date' in entity:
-        entity['creation_date'] = now()
+        entity['creation_date'] = datetime.now()
     if not 'modification_date' in entity:
-        entity['modification_date'] = now()
+        entity['modification_date'] = datetime.now()
 
 def setmtime_before_update_entity(session, entity):
     """update an entity -> set modification date"""
     if not 'modification_date' in entity:
-        entity['modification_date'] = now()
+        entity['modification_date'] = datetime.now()
         
 class SetCreatorOp(PreCommitOperation):
         
@@ -88,7 +86,6 @@
         FTIndexEntityOp(session, entity=session.entity(eidto))
     elif ftcontainer == 'object':
         FTIndexEntityOp(session, entity=session.entity(eidfrom))
-
 def fti_update_after_delete_relation(session, eidfrom, rtype, eidto):
     """sync fulltext index when relevant relation is deleted. Reindexing both
     entities is necessary.
@@ -210,30 +207,6 @@
 
 
 
-class tidy_html_fields(object):
-    """tidy HTML in rich text strings
-
-    FIXME: (adim) the whole idea of having a class is to store the
-    event type. There might be another way to get dynamically the
-    event inside the hook function.
-    """
-    # FIXME hooks manager use func_name to register
-    func_name = 'tidy_html_field'
-    
-    def __init__(self, event):
-        self.event = event
-
-    def __call__(self, session, entity):
-        for attr in entity.formatted_attrs():
-            value = entity.get(attr)
-            # text was not changed
-            if self.event == 'before_add_entity':
-                fmt = entity.get('%s_format' % attr)
-            else:
-                fmt = entity.get_value('%s_format' % attr)
-            if value and fmt == 'text/html':
-                entity[attr] = soup2xhtml(value, session.encoding)
-
 
 class CheckRequiredRelationOperation(LateOperation):
     """checking relation cardinality has to be done after commit in
@@ -317,8 +290,6 @@
     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', '')
-    hm.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
-    hm.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
 
 
 # user/groups synchronisation #################################################
--- a/server/hooksmanager.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/hooksmanager.py	Tue Apr 07 09:30:23 2009 +0200
@@ -23,7 +23,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -180,13 +180,12 @@
 #         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.vregistry import autoselectors
-from cubicweb.common.appobject import AppObject
-from cubicweb.common.registerers import accepts_registerer, yes_registerer
-from cubicweb.common.selectors import yes
+from cubicweb.selectors import yes
+from cubicweb.appobject import AppObject
 
-class autoid(autoselectors):
+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))
@@ -195,8 +194,7 @@
 class Hook(AppObject):
     __metaclass__ = autoid
     __registry__ = 'hooks'
-    __registerer__ = accepts_registerer
-    __selectors__ = (yes,)
+    __select__ = yes()
     # set this in derivated classes
     events = None
     accepts = None
@@ -245,7 +243,6 @@
         raise NotImplementedError
     
 class SystemHook(Hook):
-    __registerer__ = yes_registerer
     accepts = ('',)
 
 from logging import getLogger
--- a/server/migractions.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/migractions.py	Tue Apr 07 09:30:23 2009 +0200
@@ -19,8 +19,8 @@
 import sys
 import os
 from os.path import join, exists
+from datetime import datetime
 
-from mx.DateTime import now
 from logilab.common.decorators import cached
 from logilab.common.adbh import get_adv_func_helper
 
@@ -130,7 +130,7 @@
         config = self.config
         source = config.sources()['system']
         helper = get_adv_func_helper(source['db-driver'])
-        date = now().strftime('%Y-%m-%d_%H:%M:%S')
+        date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
         app = config.appid
         backupfile = backupfile or join(config.backup_dir(),
                                         '%s-%s.dump' % (app, date))
@@ -335,7 +335,7 @@
         if not update_database:
             self.commit()
             return
-        newcubes_schema = self.config.load_schema()
+        newcubes_schema = self.config.load_schema(construction_mode='non-strict')
         new = set()
         # execute pre-create files
         for pack in reversed(newcubes):
@@ -370,7 +370,7 @@
         if not removedcubes:
             return
         fsschema = self.fs_schema
-        removedcubes_schema = self.config.load_schema()
+        removedcubes_schema = self.config.load_schema(construction_mode='non-strict')
         reposchema = self.repo.schema
         # execute pre-remove files
         for pack in reversed(removedcubes):
@@ -1127,7 +1127,6 @@
             if not self._h.confirm('execute rql: %s ?' % msg):
                 raise StopIteration
         try:
-            #print rql, kwargs
             rset = self._h.rqlcursor.execute(rql, kwargs)
         except Exception, ex:
             if self._h.confirm('error: %s\nabort?' % ex):
--- a/server/msplanner.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/msplanner.py	Tue Apr 07 09:30:23 2009 +0200
@@ -75,17 +75,16 @@
 """
 __docformat__ = "restructuredtext en"
 
-from copy import deepcopy
 from itertools import imap, ifilterfalse
 
 from logilab.common.compat import any
 from logilab.common.decorators import cached
 
 from rql.stmts import Union, Select
-from rql.nodes import VariableRef, Comparison, Relation, Constant, Exists, Variable
+from rql.nodes import VariableRef, Comparison, Relation, Constant, Variable
 
 from cubicweb import server
-from cubicweb.common.utils import make_uid
+from cubicweb.utils import make_uid
 from cubicweb.server.utils import cleanup_solutions
 from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep,
                                        add_types_restriction)
@@ -1389,7 +1388,7 @@
             return False
         if not same_scope(var):
             return False
-        if any(v for v,_ in var.stinfo['attrvars'] if not v.name in terms):
+        if any(v for v, _ in var.stinfo['attrvars'] if not v.name in variables):
             return False
         return True
         
--- a/server/pool.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/pool.py	Tue Apr 07 09:30:23 2009 +0200
@@ -11,7 +11,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
--- a/server/querier.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/querier.py	Tue Apr 07 09:30:23 2009 +0200
@@ -13,8 +13,7 @@
 from logilab.common.compat import any
 from rql import RQLHelper, RQLSyntaxError
 from rql.stmts import Union, Select
-from rql.nodes import (Relation, VariableRef, Constant, Exists, Variable,
-                       SubQuery)
+from rql.nodes import (Relation, VariableRef, Constant, SubQuery)
 
 from cubicweb import Unauthorized, QueryError, UnknownEid, typed_eid
 from cubicweb import server
--- a/server/repository.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/repository.py	Tue Apr 07 09:30:23 2009 +0200
@@ -19,10 +19,9 @@
 import sys
 import Queue
 from os.path import join, exists
+from datetime import datetime
 from time import time, localtime, strftime
 
-from mx.DateTime import now
-
 from logilab.common.decorators import cached
 
 from yams import BadSchemaDefinition
@@ -397,11 +396,13 @@
         return euser
 
     def _build_user(self, session, eid):
+        """return a EUser entity for user with the given eid"""
         cls = self.vreg.etype_class('EUser')
         rql = cls.fetch_rql(session.user, ['X eid %(x)s'])
         rset = session.execute(rql, {'x': eid}, 'x')
         assert len(rset) == 1, rset
         euser = rset.get_entity(0, 0)
+        # pylint: disable-msg=W0104
         # prefetch / cache euser's groups and properties. This is especially
         # useful for internal sessions to avoid security insertions
         euser.groups
@@ -493,6 +494,9 @@
         try:
             if session.execute('EUser X WHERE X login %(login)s', {'login': login}):
                 return False
+            if session.execute('EUser X WHERE X use_email C, C address %(login)s',
+                               {'login': login}):
+                return False
             # we have to create the user
             user = self.vreg.etype_class('EUser')(session, None)
             if isinstance(password, unicode):
@@ -504,6 +508,11 @@
             self.glob_add_entity(session, user)
             session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"',
                             {'x': user.eid})
+            # FIXME this does not work yet
+            if '@' in login:
+                session.execute('INSERT EmailAddress X: X address "%(login)s", '
+                                'U primary_email X, U use_email X WHERE U login "%(login)s"',
+                                {'login':login})
             session.commit()
         finally:
             session.close()
@@ -656,7 +665,7 @@
           deleted since the given timestamp
         """
         session = self.internal_session()
-        updatetime = now()
+        updatetime = datetime.now()
         try:
             modentities, delentities = self.system_source.modified_entities(
                 session, etypes, mtime)
--- a/server/rqlannotation.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/rqlannotation.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,18 +2,16 @@
 code generation.
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.common.compat import any
 
-from rql.nodes import Relation, Exists, VariableRef, Constant, Variable, Or
+from rql.nodes import Relation, VariableRef, Constant, Variable, Or
 from rql.utils import common_parent
 
-from cubicweb import server
-
 def _annotate_select(annotator, rqlst):
     for subquery in rqlst.with_:
         annotator._annotate_union(subquery.query)
--- a/server/rqlrewrite.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/rqlrewrite.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """RQL rewriting utilities, used for read security checking
 
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -25,7 +25,7 @@
                         except KeyError:
                             pass
                         break
-                except KeyError,ex:
+                except KeyError:
                     # variable has been rewritten
                     continue
             else:
@@ -315,7 +315,7 @@
         return cmp_
 
     def visit_mathexpression(self, mexpr):
-        cmp_ = nodes.MathExpression(cmp.operator)
+        cmp_ = nodes.MathExpression(mexpr.operator)
         for c in cmp.children:
             cmp_.append(c.accept(self))
         return cmp_
@@ -357,7 +357,7 @@
         else: # target == 'subject':
             cardindex = 1
             ttypes_func = rschema.subjects
-            rprop = lambda x,y,z: rschema.rproperty(y, x, z)
+            rprop = lambda x, y, z: rschema.rproperty(y, x, z)
         for etype in self.varstinfo['possibletypes']:
             for ttype in ttypes_func(etype):
                 if rprop(etype, ttype, 'cardinality')[cardindex] in '+*':
--- a/server/schemahooks.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/schemahooks.py	Tue Apr 07 09:30:23 2009 +0200
@@ -104,6 +104,7 @@
         
 class DropTableOp(PreCommitOperation):
     """actually remove a database from the application's schema"""
+    table = None # make pylint happy
     def precommit_event(self):
         dropped = self.session.query_data('droppedtables',
                                           default=set(), setdefault=True)
@@ -117,6 +118,7 @@
     """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
@@ -241,6 +243,7 @@
 
 class AddEETypeOp(EarlySchemaOperation):
     """actually add the entity type to the application's schema"""    
+    eid = None # make pylint happy
     def commit_event(self):
         eschema = self.schema.add_entity_type(self.kobj)
         eschema.eid = self.eid
@@ -302,6 +305,7 @@
 
 class AddERTypeOp(EarlySchemaOperation):
     """actually add the relation type to the application's schema"""    
+    eid = None # make pylint happy
     def commit_event(self):
         rschema = self.schema.add_relation_type(self.kobj)
         rschema.set_default_groups()
@@ -361,6 +365,7 @@
       
     constraints are handled by specific hooks
     """
+    entity = None # make pylint happy
     def precommit_event(self):
         session = self.session
         entity = self.entity
@@ -447,6 +452,7 @@
 
     constraints are handled by specific hooks
     """
+    entity = None # make pylint happy
     def precommit_event(self):
         session = self.session
         entity = self.entity
@@ -538,6 +544,7 @@
 
 class UpdateEntityTypeName(SchemaOperation):
     """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
@@ -556,6 +563,7 @@
 
 class UpdateRdefOp(SchemaOperation):
     """actually update some properties of a relation definition"""
+    rschema = values = None # make pylint happy
 
     def precommit_event(self):
         if 'indexed' in self.values:
@@ -593,6 +601,8 @@
 
 class UpdateRtypeOp(SchemaOperation):
     """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
@@ -673,6 +683,8 @@
 
 class ConstraintOp(SchemaOperation):
     """actually update constraint of a relation definition"""
+    entity = None # make pylint happy
+    
     def prepare_constraints(self, rtype, subjtype, objtype):
         constraints = rtype.rproperty(subjtype, objtype, 'constraints')
         self.constraints = list(constraints)
@@ -730,6 +742,7 @@
 
 class DelConstraintOp(ConstraintOp):
     """actually remove a constraint of a relation definition"""
+    rtype = subjtype = objtype = None # make pylint happy
     
     def precommit_event(self):
         self.prepare_constraints(self.rtype, self.subjtype, self.objtype)
--- a/server/serverconfig.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/serverconfig.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """server.serverconfig definition
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -232,7 +232,7 @@
                 hooks.setdefault(event, {}).setdefault(ertype, []).append(cb)
         return hooks
     
-    def load_schema(self, expand_cubes=False):
+    def load_schema(self, expand_cubes=False, construction_mode='strict'):
         from cubicweb.schema import CubicWebSchemaLoader
         if expand_cubes:
             # in case some new dependencies have been introduced, we have to
@@ -240,7 +240,7 @@
             origcubes = self.cubes()
             self._cubes = None
             self.init_cubes(self.expand_cubes(origcubes))
-        schema = CubicWebSchemaLoader().load(self)
+        schema = CubicWebSchemaLoader().load(self, construction_mode=construction_mode)
         if expand_cubes:
             # restaure original value
             self._cubes = origcubes
@@ -266,7 +266,7 @@
                     print 'not connecting to source', uri, 'during migration'
         elif 'all' in sources:
             assert len(sources) == 1
-            enabled_sources= None
+            enabled_sources = None
         else:
             known_sources = self.sources()
             for uri in sources:
--- a/server/serverctl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/serverctl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """cubicweb-ctl commands and command handlers specific to the server.serverconfig
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -9,10 +9,11 @@
 import os
 
 from logilab.common.configuration import REQUIRED, Configuration, ini_format_section
+from logilab.common.clcommands import register_commands, cmd_run, pop_arg
 
 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
-from cubicweb.toolsutils import Command, CommandHandler, pop_arg, cmd_run, \
-     register_commands, confirm, restrict_perms_to_user
+from cubicweb.toolsutils import (Command, CommandHandler, confirm,
+                                 restrict_perms_to_user)
 from cubicweb.server.serverconfig import ServerConfiguration
 
 
@@ -466,8 +467,8 @@
     if os.system(dmpcmd):
         raise ExecutionError('Error while dumping the database')
     if output is None:
-        from mx.DateTime import today
-        date = today().strftime('%Y-%m-%d')
+        from datetime import date
+        date = date.today().strftime('%Y-%m-%d')
         output = '%s-%s.dump' % (appid, date)
     cmd = 'scp %s:/tmp/%s.dump %s' % (host, appid, output)
     print cmd
--- a/server/session.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/session.py	Tue Apr 07 09:30:23 2009 +0200
@@ -9,38 +9,16 @@
 import sys
 import threading
 from time import time
-from types import NoneType
-from decimal import Decimal
 
-from mx.DateTime import DateTimeType, DateTimeDeltaType
-from rql.nodes import VariableRef, Function
+from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
 from yams import BASE_TYPES
 
 from cubicweb import RequestSessionMixIn, Binary
 from cubicweb.dbapi import ConnectionProperties
-from cubicweb.common.utils import make_uid
+from cubicweb.utils import make_uid
 from cubicweb.server.rqlrewrite import RQLRewriter
 
-_ETYPE_PYOBJ_MAP = { bool: 'Boolean',
-                     int: 'Int',
-                     long: 'Int',
-                     float: 'Float',
-                     Decimal: 'Decimal',
-                     unicode: 'String',
-                     NoneType: None,
-                     Binary: 'Bytes',
-                     DateTimeType: 'Datetime',
-                     DateTimeDeltaType: 'Interval',
-                     }
-
-def etype_from_pyobj(value):
-    """guess yams type from python value"""
-    # note:
-    # * Password is not selectable so no problem)
-    # * use type(value) and not value.__class__ since mx instances have no
-    #   __class__ attribute
-    # * XXX Date, Time
-    return _ETYPE_PYOBJ_MAP[type(value)]
+ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
 
 def is_final(rqlst, variable, args):
     # try to find if this is a final var or not
@@ -575,7 +553,7 @@
             return 'en'
         return None
 
-_IMANAGER= InternalManager()
+_IMANAGER = InternalManager()
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/server/sources/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,10 +6,9 @@
 """
 __docformat__ = "restructuredtext en"
 
+from datetime import datetime, timedelta
 from logging import getLogger
 
-from mx.DateTime import now, DateTimeDelta
-
 from cubicweb import set_log_methods
 from cubicweb.server.sqlutils import SQL_PREFIX
 
@@ -17,16 +16,16 @@
 class TimedCache(dict):
     def __init__(self, ttlm, ttls=0):
         # time to live in minutes
-        self.ttl = DateTimeDelta(0, 0, ttlm, ttls)
+        self.ttl = timedelta(0, ttlm*60 + ttls, 0)
         
     def __setitem__(self, key, value):
-        dict.__setitem__(self, key, (now(), value))
+        dict.__setitem__(self, key, (datetime.now(), value))
         
     def __getitem__(self, key):
         return dict.__getitem__(self, key)[1]
     
     def clear_expired(self):
-        now_ = now()
+        now_ = datetime.now()
         ttl = self.ttl
         for key, (timestamp, value) in self.items():
             if now_ - timestamp > ttl:
--- a/server/sources/extlite.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/extlite.py	Tue Apr 07 09:30:23 2009 +0200
@@ -203,7 +203,7 @@
         """add a new entity to the source"""
         raise NotImplementedError()
 
-    def local_update_entity(self, session, entity):
+    def local_update_entity(self, session, entity, attrs=None):
         """update an entity in the source
 
         This is not provided as update_entity implementation since usually
@@ -211,7 +211,8 @@
         and the source implementor may use this method if necessary
         """
         cu = session.pool[self.uri]
-        attrs = self.sqladapter.preprocess_entity(entity)
+        if attrs is None:
+            attrs = self.sqladapter.preprocess_entity(entity)
         sql = self.sqladapter.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, ['eid'])
         cu.execute(sql, attrs)
         
--- a/server/sources/ldapuser.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/ldapuser.py	Tue Apr 07 09:30:23 2009 +0200
@@ -48,7 +48,8 @@
     1: (636, 'ldaps'),
     2: (0,   'ldapi'),
     }
-                
+
+
 class LDAPUserSource(AbstractSource):
     """LDAP read-only EUser source"""
     support_entities = {'EUser': False} 
--- a/server/sources/native.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/native.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,8 +7,7 @@
 __docformat__ = "restructuredtext en"
 
 from threading import Lock
-
-from mx.DateTime import now
+from datetime import datetime
 
 from logilab.common.cache import Cache
 from logilab.common.configuration import REQUIRED
@@ -501,7 +500,7 @@
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
         attrs = {'type': str(entity.e_schema), 'eid': entity.eid,
-                 'extid': extid, 'source': source.uri, 'mtime': now()}
+                 'extid': extid, 'source': source.uri, 'mtime': datetime.now()}
         session.system_sql(self.sqlgen.insert('entities', attrs), attrs)
 
     def delete_info(self, session, eid, etype, uri, extid):
@@ -512,7 +511,7 @@
         session.system_sql(self.sqlgen.delete('entities', attrs), attrs)
         if self.has_deleted_entitites_table:
             attrs = {'type': etype, 'eid': eid, 'extid': extid,
-                     'source': uri, 'dtime': now()}
+                     'source': uri, 'dtime': datetime.now()}
             session.system_sql(self.sqlgen.insert('deleted_entities', attrs), attrs)
         
     def fti_unindex_entity(self, session, eid):
@@ -536,7 +535,7 @@
             if self.indexer is not None:
                 self.exception('error while reindexing %s', entity)
         # update entities.mtime
-        attrs = {'eid': entity.eid, 'mtime': now()}
+        attrs = {'eid': entity.eid, 'mtime': datetime.now()}
         session.system_sql(self.sqlgen.update('entities', attrs, ['eid']), attrs)
         
     def modified_entities(self, session, etypes, mtime):
--- a/server/sources/pyrorql.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/pyrorql.py	Tue Apr 07 09:30:23 2009 +0200
@@ -9,7 +9,8 @@
 import threading
 from os.path import join
 
-from mx.DateTime import DateTimeFromTicks
+from time import mktime
+from datetime import datetime
 
 from Pyro.errors import PyroError, ConnectionClosedError
 
@@ -23,7 +24,7 @@
 from cubicweb.cwconfig import register_persistent_options
 from cubicweb.server.sources import AbstractSource, ConnectionWrapper, TimedCache
 
-class ReplaceByInOperator:
+class ReplaceByInOperator(Exception):
     def __init__(self, eids):
         self.eids = eids
         
@@ -146,7 +147,7 @@
             else:
                 assert len(rset) == 1
                 timestamp = int(rset[0][0])
-            return DateTimeFromTicks(timestamp)
+            return datetime.fromtimestamp(timestamp)
         finally:
             session.close()
 
@@ -196,7 +197,7 @@
                     continue
             session.execute('SET X value %(v)s WHERE X pkey %(k)s',
                             {'k': u'sources.%s.latest-update-time' % self.uri,
-                             'v': unicode(int(updatetime.ticks()))})
+                             'v': unicode(int(mktime(updatetime.timetuple())))})
             session.commit()
         finally:
             session.close()
--- a/server/sources/rql2sql.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sources/rql2sql.py	Tue Apr 07 09:30:23 2009 +0200
@@ -489,8 +489,7 @@
                 sql.insert(1, 'FROM (SELECT 1) AS _T')
             sqls.append('\n'.join(sql))
         if select.need_intersect:
-            # XXX use getattr for lgc bw compat, remove once 0.37.3 is out
-            if distinct or not getattr(self.dbms_helper, 'intersect_all_support', True):
+            if distinct or not self.dbms_helper.intersect_all_support:
                 return '\nINTERSECT\n'.join(sqls)
             else:
                 return '\nINTERSECT ALL\n'.join(sqls)
--- a/server/sqlutils.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/sqlutils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,8 +6,11 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
+from datetime import datetime, timedelta
+
 from logilab.common.shellutils import ProgressBar
-from logilab.common.db import get_dbapi_compliant_module
+from logilab.common import db
 from logilab.common.adbh import get_adv_func_helper
 from logilab.common.sqlgen import SQLGenerator
 
@@ -16,10 +19,13 @@
 from cubicweb import Binary, ConfigurationError
 from cubicweb.common.uilib import remove_html_tags
 from cubicweb.server import SQL_CONNECT_HOOKS
-from cubicweb.server.utils import crypt_password, cartesian_product
+from cubicweb.server.utils import crypt_password
+
 
+db.USE_MX_DATETIME = False
 SQL_PREFIX = 'cw_'
 
+
 def sqlexec(sqlstmts, cursor_or_execute, withpb=True, delimiter=';'):
     """execute sql statements ignoring DROP/ CREATE GROUP or USER statements
     error. If a cnx is given, commit at each statement
@@ -106,7 +112,10 @@
                      skip_entities=skip_entities, skip_relations=skip_relations))
     return '\n'.join(output)
 
-
+try:
+    from mx.DateTime import DateTimeType, DateTimeDeltaType
+except ImportError:
+    DateTimeType, DateTimeDeltaType = None
 
 class SQLAdapterMixIn(object):
     """Mixin for SQL data sources, getting a connection from a configuration
@@ -125,7 +134,7 @@
         self.dbuser = source_config.get('db-user')
         self.dbpasswd = source_config.get('db-password')
         self.encoding = source_config.get('db-encoding', 'UTF-8')
-        self.dbapi_module = get_dbapi_compliant_module(self.dbdriver)
+        self.dbapi_module = db.get_dbapi_compliant_module(self.dbdriver)
         self.binary = self.dbapi_module.Binary
         self.dbhelper = self.dbapi_module.adv_func_helper
         self.sqlgen = SQLGenerator()
@@ -153,6 +162,16 @@
                 # convert cubicweb binary into db binary
                 if isinstance(val, Binary):
                     val = self.binary(val.getvalue())
+                # XXX <3.2 bw compat
+                elif type(val) is DateTimeType:
+                    warn('found mx date time instance, please update to use datetime',
+                         DeprecationWarning)
+                    val = datetime(val.year, val.month, val.day,
+                                   val.hour, val.minute, int(val.second))
+                elif type(val) is DateTimeDeltaType:
+                    warn('found mx date time instance, please update to use datetime',
+                         DeprecationWarning)
+                    val = timedelta(0, int(val.seconds), 0)
                 args[key] = val
             # should not collide
             args.update(query_args)
--- a/server/ssplanner.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/ssplanner.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """plan execution of rql queries on a single source
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
--- a/server/test/data/migrschema/relations.rel	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/data/migrschema/relations.rel	Tue Apr 07 09:30:23 2009 +0200
@@ -9,3 +9,7 @@
 Personne connait Personne symetric
 
 Societe in_state State inline
+
+Note attachment File
+Note attachment Image
+
--- a/server/test/data/schema/Affaire.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/data/schema/Affaire.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,6 +1,6 @@
 from cubicweb.schema import format_constraint
 
-class Affaire(EntityType):
+class Affaire(WorkflowableEntityType):
     permissions = {
         'read':   ('managers', 
                    ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
@@ -13,9 +13,6 @@
                  constraints=[SizeConstraint(16)])
     sujet = String(fulltextindexed=True,
                    constraints=[SizeConstraint(256)])
-    in_state = SubjectRelation('State', cardinality='1*',
-                               constraints=[RQLConstraint('O state_of ET, ET name "Affaire"')],
-                               description=_('account state'))
     descr_format = String(meta=True, internationalizable=True,
                                 default='text/rest', constraints=[format_constraint])
     descr = String(fulltextindexed=True,
@@ -23,8 +20,7 @@
 
     duration = Int()
     invoiced = Int()
-        
-    wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
+
     depends_on = SubjectRelation('Affaire')
     require_permission = SubjectRelation('EPermission')
     
--- a/server/test/runtests.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
-    import sys, os
-    main(os.path.dirname(sys.argv[0]) or '.')
--- a/server/test/unittest_migractions.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_migractions.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """unit tests for module cubicweb.server.migractions
 """
 
-from mx.DateTime import DateTime, today
+from datetime import date
 
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
@@ -80,12 +80,12 @@
         self.failUnless('mydate' in self.schema)
         self.assertEquals(self.schema['mydate'].subjects(), ('Note', ))
         self.assertEquals(self.schema['mydate'].objects(), ('Date', ))
-        testdate = DateTime(2005, 12, 13)
+        testdate = date(2005, 12, 13)
         eid1 = self.mh.rqlexec('INSERT Note N')[0][0]
         eid2 = self.mh.rqlexec('INSERT Note N: N mydate %(mydate)s', {'mydate' : testdate})[0][0]
         d1 = self.mh.rqlexec('Any D WHERE X eid %(x)s, X mydate D', {'x': eid1}, 'x')[0][0]
         d2 = self.mh.rqlexec('Any D WHERE X eid %(x)s, X mydate D', {'x': eid2}, 'x')[0][0]
-        self.assertEquals(d1, today())
+        self.assertEquals(d1, date.today())
         self.assertEquals(d2, testdate)
         self.mh.rollback()
             
--- a/server/test/unittest_multisources.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_multisources.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,6 +1,7 @@
 from os.path import dirname, join, abspath
+from datetime import datetime, timedelta
+
 from logilab.common.decorators import cached
-from mx.DateTime import now
 
 from cubicweb.devtools import TestServerConfiguration, init_test_database
 from cubicweb.devtools.apptest import RepositoryBasedTC
@@ -23,7 +24,7 @@
 aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF", X in_state S WHERE S name "pitetre"')[0][0]
 cnx2.commit()
 
-MTIME = now() - 0.1
+MTIME = datetime.now() - timedelta(0, 10)
 
 repo3, cnx3 = init_test_database('sqlite', config=ExternalSource2Configuration('data'))
 
--- a/server/test/unittest_querier.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_querier.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,19 +1,19 @@
 # -*- coding: iso-8859-1 -*-
 """unit tests for modules cubicweb.server.querier and cubicweb.server.querier_steps
 """
+from datetime import date, datetime
 
 from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools import init_test_database
-from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
-from unittest_session import Variable
-
-from mx.DateTime import today, now, DateTimeType
 from rql import BadRQLQuery, RQLSyntaxError
 
 from cubicweb import QueryError, Unauthorized
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import crypt_password
 from cubicweb.server.sources.native import make_schema
+from cubicweb.devtools import init_test_database
+from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
+
+from unittest_session import Variable
 
 
 # register priority/severity sorting registered procedure
@@ -189,7 +189,7 @@
         self.assertEquals(rset.description[0][0], 'Boolean')
         rset = self.execute('Any %(x)s', {'x': 1.0})
         self.assertEquals(rset.description[0][0], 'Float')
-        rset = self.execute('Any %(x)s', {'x': now()})
+        rset = self.execute('Any %(x)s', {'x': datetime.now()})
         self.assertEquals(rset.description[0][0], 'Datetime')
         rset = self.execute('Any %(x)s', {'x': 'str'})
         self.assertEquals(rset.description[0][0], 'String')
@@ -658,14 +658,14 @@
 #         self.assertEquals(rset.rows, [[eid]])
         
     def test_today_bug(self):
-        self.execute("INSERT Tag X: X name 'bidule', X creation_date TODAY")
+        self.execute("INSERT Tag X: X name 'bidule', X creation_date NOW")
         self.execute("INSERT Tag Y: Y name 'toto'")
         rset = self.execute("Any D WHERE X name in ('bidule', 'toto') , X creation_date D")
-        self.assert_(isinstance(rset.rows[0][0], DateTimeType), rset.rows)
+        self.assert_(isinstance(rset.rows[0][0], datetime), rset.rows)
         rset = self.execute('Tag X WHERE X creation_date TODAY')
         self.assertEqual(len(rset.rows), 2)
         rset = self.execute('Any MAX(D) WHERE X is Tag, X creation_date D')
-        self.failUnless(isinstance(rset[0][0], DateTimeType), type(rset[0][0]))
+        self.failUnless(isinstance(rset[0][0], datetime), type(rset[0][0]))
 
     def test_today(self):
         self.execute("INSERT Tag X: X name 'bidule', X creation_date TODAY")
@@ -770,10 +770,6 @@
         eid, = self.execute("INSERT Personne X: X nom 'bidule'")[0]
         rset = self.execute('Any X, NOW - CD WHERE X is Personne, X creation_date CD')
         self.failUnlessEqual(rset.description[0][1], 'Interval')
-        # sqlite bug
-        #from mx.DateTime import DateTimeDeltaType
-        #self.assertIsInstance(rset[0][1], DateTimeDeltaType) 
-        #self.failUnless(rset[0][1].seconds > 0)
 
     def test_select_subquery_aggregat(self):
         # percent users by groups
@@ -1247,7 +1243,7 @@
 
     def test_nonregr_set_datetime(self):
         # huum, psycopg specific
-        self.execute('SET X creation_date %(date)s WHERE X eid 1', {'date': today()})
+        self.execute('SET X creation_date %(date)s WHERE X eid 1', {'date': date.today()})
 
     def test_nonregr_set_query(self):
         ueid = self.execute("INSERT EUser X: X login 'bob', X upassword 'toto'")[0][0]
--- a/server/test/unittest_repository.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_repository.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,8 +6,7 @@
 import threading
 import time
 from copy import deepcopy
-
-from mx.DateTime import DateTimeType, now
+from datetime import datetime
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -339,7 +338,7 @@
         cursor = self.session.pool['system']
         cursor.execute('SELECT * FROM entities WHERE eid = -1')
         data = cursor.fetchall()
-        self.assertIsInstance(data[0][3], DateTimeType)
+        self.assertIsInstance(data[0][3], datetime)
         data[0] = list(data[0])
         data[0][3] = None
         self.assertEquals(tuplify(data), [(-1, 'Personne', 'system', None, None)])
@@ -356,7 +355,7 @@
         cursor = self.session.pool['system']
         eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
         self.commit()
-        ts = now()
+        ts = datetime.now()
         self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
         cursor.execute('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
         omtime = cursor.fetchone()[0]
--- a/server/test/unittest_rql2sql.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,6 @@
 """unit tests for module cubicweb.server.sources.rql2sql"""
 
 import sys
-from mx.DateTime import today
 
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/server/test/unittest_sqlutils.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/test/unittest_sqlutils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,6 @@
 """
 
 import sys
-from mx.DateTime import now
 
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/server/utils.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/server/utils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """Some utilities for the CubicWeb server.
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -117,7 +117,7 @@
                 func()
             finally:
                 self.running_threads.remove(self)
-        Thread.__init__(self, target=target)
+        Thread.__init__(self, target=auto_remove_func)
         self.running_threads = running_threads
         self._name = target.__name__
         
@@ -128,11 +128,3 @@
     @property
     def name(self):
         return '%s(%s)' % (self._name, Thread.getName(self))
-
-
-from logilab.common.deprecation import class_moved
-from cubicweb.server import pool
-Operation = class_moved(pool.Operation)
-PreCommitOperation = class_moved(pool.PreCommitOperation)
-LateOperation = class_moved(pool.LateOperation)
-SingleLastOperation = class_moved(pool.SingleLastOperation)
--- a/sobjects/email.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/email.py	Tue Apr 07 09:30:23 2009 +0200
@@ -14,7 +14,8 @@
     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(
--- a/sobjects/hooks.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/hooks.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,14 +1,16 @@
 """various library content hooks
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
+from cubicweb.common.uilib import soup2xhtml
 from cubicweb.server.hooksmanager import Hook
 from cubicweb.server.pool import PreCommitOperation
 
+
 class AddUpdateEUserHook(Hook):
     """ensure user logins are stripped"""
     events = ('before_add_entity', 'before_update_entity',)
@@ -20,6 +22,7 @@
 
 
 class AutoDeleteBookmark(PreCommitOperation):
+    beid = None # make pylint happy
     def precommit_event(self):
         session = self.session
         if not self.beid in session.query_data('pendingeids', ()):
@@ -35,3 +38,26 @@
     
     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	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/notification.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """some hooks and views to handle notification on entity's changes
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -16,12 +16,11 @@
         return 'XXX'
 
 from logilab.common.textutils import normalize_text
+from logilab.common.deprecation import class_renamed
 
 from cubicweb import RegistryException
-from cubicweb.common.view import EntityView
-from cubicweb.common.appobject import Component
-from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.selectors import accept
+from cubicweb.selectors import implements, yes
+from cubicweb.view import EntityView, Component
 from cubicweb.common.mail import format_mail
 
 from cubicweb.server.pool import PreCommitOperation
@@ -37,9 +36,7 @@
     email addresses specified in the configuration are used
     """
     id = 'recipients_finder'
-    __registerer__ = accepts_registerer
-    __selectors__ = (accept,)
-    accepts = ('Any',)
+    __select__ = yes()
     user_rql = ('Any X,E,A WHERE X is EUser, X in_state S, S name "activated",'
                 'X primary_email E, E address A')
     
@@ -135,13 +132,10 @@
     * set a content attribute to define the content of the email (unless you
       override call)
     """
-    accepts = ()
-    id = None
     msgid_timestamp = True
     
     def recipients(self):
-        finder = self.vreg.select_component('recipients_finder',
-                                            req=self.req, rset=self.rset)
+        finder = self.vreg.select_component('recipients_finder', self.req, self.rset)
         return finder.recipients()
         
     def subject(self):
@@ -180,8 +174,7 @@
         self._kwargs = kwargs
         recipients = self.recipients()
         if not recipients:
-            self.info('skipping %s%s notification which has no recipients',
-                      self.id, self.accepts)
+            self.info('skipping %s notification, no recipients', self.id)
             return
         if not isinstance(recipients[0], tuple):
             from warnings import warn
@@ -260,9 +253,16 @@
 """)
 
 
-class ContentAddedMixIn(object):
-    """define emailcontent view for entity types for which you want to be notified
-    """
+###############################################################################
+# Actual notification views.                                                  #
+#                                                                             #
+# disable them at the recipients_finder level if you don't want them          #
+###############################################################################
+
+# XXX should be based on dc_title/dc_description, no?
+
+class ContentAddedView(NotificationView):
+    __abstract__ = True
     id = 'notif_after_add_entity' 
     msgid_timestamp = False
     message = _('new')
@@ -273,33 +273,25 @@
 
 url: %(url)s
 """
-
-###############################################################################
-# Actual notification views.                                                  #
-#                                                                             #
-# disable them at the recipients_finder level if you don't want them          #
-###############################################################################
-
-# XXX should be based on dc_title/dc_description, no?
-
-class NormalizedTextView(ContentAddedMixIn, NotificationView):
+    
     def context(self, **kwargs):
         entity = self.entity(0, 0)
         content = entity.printable_value(self.content_attr, format='text/plain')
         if content:
             contentformat = getattr(entity, self.content_attr + '_format', 'text/rest')
             content = normalize_text(content, 80, rest=contentformat=='text/rest')
-        return super(NormalizedTextView, self).context(content=content, **kwargs)
+        return super(ContentAddedView, self).context(content=content, **kwargs)
     
     def subject(self):
         entity = self.entity(0, 0)
         return  u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema),
                                   entity.eid, self.user_login())
 
+NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
 
-class CardAddedView(NormalizedTextView):
+class CardAddedView(ContentAddedView):
     """get notified from new cards"""
-    accepts = ('Card',)
+    __select__ = implements('Card')
     content_attr = 'synopsis'
     
 
--- a/sobjects/supervising.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/supervising.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,13 +2,14 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from cubicweb import UnknownEid
-from cubicweb.common.view import ComponentMixIn, StartupView
+from cubicweb.selectors import none_rset
+from cubicweb.view import Component
 from cubicweb.common.mail import format_mail
 from cubicweb.server.hooksmanager import Hook
 from cubicweb.server.hookhelper import SendMailOp
@@ -137,9 +138,10 @@
             yield change
 
 
-class SupervisionEmailView(ComponentMixIn, StartupView):
+class SupervisionEmailView(Component):
     """view implementing the email API for data changes supervision notification
     """
+    __select__ = none_rset()
     id = 'supervision_notif'
 
     def recipients(self):
--- a/sobjects/test/unittest_notification.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/test/unittest_notification.py	Tue Apr 07 09:30:23 2009 +0200
@@ -4,8 +4,6 @@
 from logilab.common.testlib import unittest_main, TestCase
 from cubicweb.devtools.apptest import EnvBasedTC
 
-from mx.DateTime import now
-
 from cubicweb.sobjects.notification import construct_message_id, parse_message_id
 
 class MessageIdTC(TestCase):
--- a/sobjects/test/unittest_supervising.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/sobjects/test/unittest_supervising.py	Tue Apr 07 09:30:23 2009 +0200
@@ -4,8 +4,6 @@
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.apptest import EnvBasedTC
 
-from mx.DateTime import now
-
 from cubicweb.sobjects.supervising import SendMailOp, SupervisionMailOp
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/bootstrap_cubes	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,1 @@
+file, tag
--- a/test/data/bootstrap_packages	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/entities.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,15 @@
+from cubicweb.entities import AnyEntity, fetch_config
+
+class Personne(AnyEntity):
+    """customized class forne Person entities"""
+    id = 'Personne'
+    fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
+    rest_attr = 'nom'
+
+
+class Societe(AnyEntity):
+    id = 'Societe'
+    fetch_attrs = ('nom',)
+    
+class Note(AnyEntity):
+    id = 'Note'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/schema.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,27 @@
+class Personne(EntityType):
+    nom = String(required=True)
+    prenom = String()
+    type = String()
+    travaille = SubjectRelation('Societe')
+    evaluee = SubjectRelation(('Note', 'Personne'))
+    connait = SubjectRelation('Personne', symetric=True)
+    
+class Societe(EntityType):
+    nom = String()
+    evaluee = SubjectRelation('Note')
+    
+class Note(EntityType):
+    type = String()
+    ecrit_par = SubjectRelation('Personne')
+
+class SubNote(Note):
+    __specializes_schema__ = True
+    description = String()
+
+class tags(RelationDefinition):
+    subject = 'Tag'
+    object = ('Personne', 'Note')
+
+class evaluee(RelationDefinition):
+    subject = 'EUser'
+    object = 'Note'
--- a/test/unittest_cwconfig.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/test/unittest_cwconfig.py	Tue Apr 07 09:30:23 2009 +0200
@@ -70,7 +70,8 @@
         self.assertEquals([unabsolutize(p) for p in self.config.vregistry_path()],
                           ['entities', 'web/views', 'sobjects',
                            'file/entities.py', 'file/views', 'file/hooks.py',
-                           'email/entities.py', 'email/views', 'email/hooks.py'])
+                           'email/entities.py', 'email/views', 'email/hooks.py',
+                           'test/data/entities.py'])
 
     def test_cubes_path(self):
         # make sure we don't import the email cube, but the stdlib email package
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_entity.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,472 @@
+# -*- coding: utf-8 -*-
+"""unit tests for cubicweb.web.views.entities module"""
+
+from datetime import datetime
+
+from cubicweb import Binary
+from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.common.mttransforms import HAS_TAL
+
+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)
+    
+    def test_boolean_value(self):
+        e = self.etype_instance('EUser')
+        self.failUnless(e)
+
+    def test_yams_inheritance(self):
+        from entities import Note
+        e = self.etype_instance('SubNote')
+        self.assertIsInstance(e, Note)
+        e2 = self.etype_instance('SubNote')
+        self.assertIs(e.__class__, e2.__class__)
+
+    def test_has_eid(self):
+        e = self.etype_instance('EUser')
+        self.assertEquals(e.eid, None)
+        self.assertEquals(e.has_eid(), False)
+        e.eid = 'X'
+        self.assertEquals(e.has_eid(), False)
+        e.eid = 0
+        self.assertEquals(e.has_eid(), True)
+        e.eid = 2
+        self.assertEquals(e.has_eid(), True)
+        
+    def test_copy(self):
+        self.add_entity('Tag', name=u'x')
+        p = self.add_entity('Personne', nom=u'toto')
+        oe = self.add_entity('Note', type=u'x')
+        self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
+                     {'t': oe.eid, 'u': p.eid}, ('t','u'))
+        self.execute('SET TAG tags X WHERE X eid %(x)s', {'x': oe.eid}, 'x')
+        e = self.add_entity('Note', type=u'z')
+        e.copy_relations(oe.eid)
+        self.assertEquals(len(e.ecrit_par), 1)
+        self.assertEquals(e.ecrit_par[0].eid, p.eid)
+        self.assertEquals(len(e.reverse_tags), 0)
+        
+    def test_copy_with_nonmeta_composite_inlined(self):
+        p = self.add_entity('Personne', nom=u'toto')
+        oe = self.add_entity('Note', type=u'x')
+        self.schema['ecrit_par'].set_rproperty('Note', 'Personne', 'composite', 'subject')
+        self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
+                     {'t': oe.eid, 'u': p.eid}, ('t','u'))
+        e = self.add_entity('Note', type=u'z')
+        e.copy_relations(oe.eid)
+        self.failIf(e.ecrit_par)
+        self.failUnless(oe.ecrit_par)
+            
+    def test_copy_with_composite(self):
+        user = self.user()
+        adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
+        e = self.entity('Any X WHERE X eid %(x)s', {'x':user.eid}, 'x')
+        self.assertEquals(e.use_email[0].address, "toto@logilab.org")
+        self.assertEquals(e.use_email[0].eid, adeleid)
+        usereid = self.execute('INSERT EUser X: X login "toto", X upassword "toto", X in_group G, X in_state S '
+                               'WHERE G name "users", S name "activated"')[0][0]
+        e = self.entity('Any X WHERE X eid %(x)s', {'x':usereid}, 'x')
+        e.copy_relations(user.eid)
+        self.failIf(e.use_email)
+        self.failIf(e.primary_email)
+        
+    def test_copy_with_non_initial_state(self):
+        user = self.user()
+        eid = self.execute('INSERT EUser X: X login "toto", X upassword %(pwd)s, X in_group G WHERE G name "users"',
+                           {'pwd': 'toto'})[0][0]
+        self.commit()
+        self.execute('SET X in_state S WHERE X eid %(x)s, S name "deactivated"', {'x': eid}, 'x')
+        self.commit()
+        eid2 = self.execute('INSERT EUser X: X login "tutu", X upassword %(pwd)s', {'pwd': 'toto'})[0][0]
+        e = self.entity('Any X WHERE X eid %(x)s', {'x': eid2}, 'x')
+        e.copy_relations(eid)
+        self.commit()
+        e.clear_related_cache('in_state', 'subject')
+        self.assertEquals(e.state, 'activated')
+
+    def test_related_cache_both(self):
+        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(), [])
+        email = user.primary_email[0]
+        self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
+        self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
+        groups = user.in_group
+        self.assertEquals(sorted(user._related_cache), ['in_group_subject', 'primary_email_subject'])
+        for group in groups:
+            self.failIf('in_group_subject' in group._related_cache, group._related_cache.keys())
+
+    def test_related_limit(self):
+        p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+        for tag in u'abcd':
+            self.add_entity('Tag', name=tag)
+        self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
+        self.assertEquals(len(p.related('tags', 'object', limit=2)), 2)
+        self.assertEquals(len(p.related('tags', 'object')), 4)
+
+        
+    def test_fetch_rql(self):
+        user = self.user()
+        Personne = self.vreg.etype_class('Personne')
+        Societe = self.vreg.etype_class('Societe')
+        Note = self.vreg.etype_class('Note')
+        peschema = Personne.e_schema
+        seschema = Societe.e_schema
+        peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '1*')
+        peschema.subject_relation('connait').set_rproperty(peschema, peschema, 'cardinality', '11')
+        peschema.subject_relation('evaluee').set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
+        seschema.subject_relation('evaluee').set_rproperty(seschema, Note.e_schema, 'cardinality', '1*')
+        # testing basic fetch_attrs attribute
+        self.assertEquals(Personne.fetch_rql(user),
+                          'Any X,AA,AB,AC ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
+        pfetch_attrs = Personne.fetch_attrs
+        sfetch_attrs = Societe.fetch_attrs
+        try:
+            # testing unknown attributes
+            Personne.fetch_attrs = ('bloug', 'beep')
+            self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne')            
+            # testing one non final relation
+            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
+            self.assertEquals(Personne.fetch_rql(user),
+                              'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC, AC nom AD')
+            # testing two non final relations
+            Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee')
+            self.assertEquals(Personne.fetch_rql(user),
+                              'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, '
+                              'X prenom AB, X travaille AC, AC nom AD, X evaluee AE, AE modification_date AF')
+            # testing one non final relation with recursion
+            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
+            Societe.fetch_attrs = ('nom', 'evaluee')
+            self.assertEquals(Personne.fetch_rql(user),
+                              'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, X prenom AB, '
+                              'X travaille AC, AC nom AD, AC evaluee AE, AE modification_date AF'
+                              )
+            # testing symetric relation
+            Personne.fetch_attrs = ('nom', 'connait')
+            self.assertEquals(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X connait AB')
+            # testing optional relation
+            peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '?*')
+            Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
+            Societe.fetch_attrs = ('nom',)
+            self.assertEquals(Personne.fetch_rql(user),
+                              'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD')
+            # testing relation with cardinality > 1
+            peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '**')
+            self.assertEquals(Personne.fetch_rql(user),
+                              'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
+            # XXX test unauthorized attribute
+        finally:
+            Personne.fetch_attrs = pfetch_attrs
+            Societe.fetch_attrs = sfetch_attrs
+
+    def test_related_rql(self):
+        from cubicweb.entities import fetch_config
+        Personne = self.vreg.etype_class('Personne')
+        Note = self.vreg.etype_class('Note')
+        Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type'))
+        Note.fetch_attrs, Note.fetch_order = fetch_config(('type',))
+        aff = self.add_entity('Personne', nom=u'pouet')
+        self.assertEquals(aff.related_rql('evaluee'),
+                          'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, '
+                          'X type AA, X modification_date AB')
+        Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', ))
+        # XXX
+        self.assertEquals(aff.related_rql('evaluee'),
+                          'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA')
+    
+    def test_entity_unrelated(self):
+        p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+        e = self.add_entity('Tag', name=u'x')
+        rschema = e.e_schema.subject_relation('tags')
+        related = [r.eid for r in e.tags]
+        self.failUnlessEqual(related, [])
+        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+        self.failUnless(p.eid in unrelated)
+        self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
+        e = self.entity('Any X WHERE X is Tag')
+        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+        self.failIf(p.eid in unrelated)
+
+    def test_entity_unrelated_limit(self):
+        e = self.add_entity('Tag', name=u'x')
+        self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+        self.add_entity('Personne', nom=u'di mascio', prenom=u'gwen')
+        rschema = e.e_schema.subject_relation('tags')
+        self.assertEquals(len(e.unrelated(rschema, 'Personne', 'subject', limit=1)),
+                          1)
+        
+    def test_new_entity_unrelated(self):
+        e = self.etype_instance('EUser')
+        rschema = e.e_schema.subject_relation('in_group')
+        unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+        # should be default groups but owners, i.e. managers, users, guests
+        self.assertEquals(len(unrelated), 3)
+
+
+    def test_rtags_expansion(self):
+        from cubicweb.entities import AnyEntity
+        class Personne(AnyEntity):
+            id = 'Personne'
+            __rtags__ = {
+                ('travaille', 'Societe', 'subject') : set(('primary',)),
+                ('evaluee', '*', 'subject') : set(('secondary',)),
+                'ecrit_par' : set(('inlineview',)),
+                }
+        self.vreg.register_vobject_class(Personne)
+        rtags = Personne.rtags
+        self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
+        self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
+        self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+        self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+        self.assertEquals(rtags.get_tags('ecrit_par', 'Note', 'object'), set(('inlineview', 'link')))
+        self.assertEquals(rtags.is_inlined('ecrit_par', 'Note', 'object'), True)
+        class Personne2(Personne):
+            id = 'Personne'
+            __rtags__ = {
+                ('evaluee', 'Note', 'subject') : set(('inlineview',)),
+                }
+        self.vreg.register_vobject_class(Personne2)
+        rtags = Personne2.rtags
+        self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
+        self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
+        self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+        self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+    
+    def test_relations_by_category(self):
+        e = self.etype_instance('EUser')
+        def rbc(iterable):
+            return [(rschema.type, x) for rschema, tschemas, x in iterable]
+        self.assertEquals(rbc(e.relations_by_category('primary')),
+                          [('login', 'subject'), ('upassword', 'subject'),
+                           ('in_group', 'subject'), ('in_state', 'subject'),
+                           ('eid', 'subject'),])
+        # firstname and surname are put in secondary category in views.entities.EUserEntity
+        self.assertListEquals(rbc(e.relations_by_category('secondary')),
+                              [('firstname', 'subject'), ('surname', 'subject')])
+        self.assertListEquals(rbc(e.relations_by_category('generic')),
+                              [('primary_email', 'subject'),
+                               ('evaluee', 'subject'),
+                               ('for_user', 'object')])
+        # owned_by is defined both as subject and object relations on EUser
+        self.assertListEquals(rbc(e.relations_by_category('generated')),
+                              [('last_login_time', 'subject'),
+                               ('created_by', 'subject'),
+                               ('creation_date', 'subject'),
+                               ('is', 'subject'),
+                               ('is_instance_of', 'subject'),
+                               ('modification_date', 'subject'),
+                               ('owned_by', 'subject'),
+                               ('created_by', 'object'),
+                               ('wf_info_for', 'object'),
+                               ('owned_by', 'object'),
+                               ('bookmarked_by', 'object')])
+        e = self.etype_instance('Personne')
+        self.assertListEquals(rbc(e.relations_by_category('primary')),
+                              [('nom', 'subject'), ('eid', 'subject')])
+        self.assertListEquals(rbc(e.relations_by_category('secondary')),
+                              [('prenom', 'subject'),
+                               ('type', 'subject'),])
+        self.assertListEquals(rbc(e.relations_by_category('generic')),
+                              [('travaille', 'subject'),
+                               ('evaluee', 'subject'),
+                               ('connait', 'subject'),
+                               ('ecrit_par', 'object'),
+                               ('evaluee', 'object'),
+                               ('tags', 'object')])
+        self.assertListEquals(rbc(e.relations_by_category('generated')),
+                              [('created_by', 'subject'),
+                               ('creation_date', 'subject'),
+                               ('is', 'subject'),
+                               ('is_instance_of', 'subject'),
+                               ('modification_date', 'subject'),
+                               ('owned_by', 'subject')])
+        
+
+    def test_printable_value_string(self):
+        e = self.add_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
+                            content_format=u'text/rest')
+        self.assertEquals(e.printable_value('content'),
+                          '<p>du <a class="reference" href="http://testing.fr/cubicweb/egroup/managers">*ReST*</a></p>\n')
+        e['content'] = 'du <em>html</em> <ref rql="EUser X">users</ref>'
+        e['content_format'] = 'text/html'
+        self.assertEquals(e.printable_value('content'),
+                          'du <em>html</em> <a href="http://testing.fr/cubicweb/view?rql=EUser%20X">users</a>')
+        e['content'] = 'du *texte*'
+        e['content_format'] = 'text/plain'
+        self.assertEquals(e.printable_value('content'),
+                          '<p>\ndu *texte*\n</p>')
+        e['title'] = 'zou'
+        #e = self.etype_instance('Task')
+        e['content'] = '''\
+a title
+=======
+du :eid:`1:*ReST*`'''
+        e['content_format'] = 'text/rest'
+        self.assertEquals(e.printable_value('content', format='text/plain'),
+                          e['content'])
+
+        e['content'] = u'<b>yo (zou éà ;)</b>'
+        e['content_format'] = 'text/html'
+        self.assertEquals(e.printable_value('content', format='text/plain').strip(),
+                          u'**yo (zou éà ;)**')
+        if HAS_TAL:
+            e['content'] = '<h1 tal:content="self/title">titre</h1>'
+            e['content_format'] = 'text/cubicweb-page-template'
+            self.assertEquals(e.printable_value('content'),
+                              '<h1>zou</h1>')
+        
+
+    def test_printable_value_bytes(self):
+        e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
+                            data_encoding=u'ascii', name=u'toto.py')
+        from cubicweb.common import mttransforms
+        if mttransforms.HAS_PYGMENTS_TRANSFORMS:
+            self.assertEquals(e.printable_value('data'),
+                              '''<div class="highlight"><pre><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="mf">1</span>
+</pre></div>
+''')
+        else:
+            self.assertEquals(e.printable_value('data'),
+                              '''<pre class="python">
+<span style="color: #C00000;">lambda</span> <span style="color: #000000;">x</span><span style="color: #0000C0;">:</span> <span style="color: #0080C0;">1</span>
+</pre>
+''')
+        
+        e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
+                            data_encoding=u'utf-8', name=u'toto.txt')
+        self.assertEquals(e.printable_value('data'),
+                          u'<p><em>héhéhé</em></p>\n')
+
+    def test_printable_value_bad_html(self):
+        """make sure we don't crash if we try to render invalid XHTML strings"""
+        e = self.add_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
+                            content_format=u'text/html')
+        tidy = lambda x: x.replace('\n', '')
+        self.assertEquals(tidy(e.printable_value('content')),
+                          '<div>R&amp;D<br/></div>')
+        e['content'] = u'yo !! R&D <div> pas fermé'
+        self.assertEquals(tidy(e.printable_value('content')),
+                          u'yo !! R&amp;D <div> pas fermé</div>')
+        e['content'] = u'R&D'
+        self.assertEquals(tidy(e.printable_value('content')), u'R&amp;D')
+        e['content'] = u'R&D;'
+        self.assertEquals(tidy(e.printable_value('content')), u'R&amp;D;')
+        e['content'] = u'yo !! R&amp;D <div> pas fermé'
+        self.assertEquals(tidy(e.printable_value('content')),
+                          u'yo !! R&amp;D <div> pas fermé</div>')
+        e['content'] = u'été <div> été'
+        self.assertEquals(tidy(e.printable_value('content')),
+                          u'été <div> été</div>')
+        e['content'] = u'C&apos;est un exemple s&eacute;rieux'
+        self.assertEquals(tidy(e.printable_value('content')),
+                          u"C'est un exemple sérieux")
+        # make sure valid xhtml is left untouched
+        e['content'] = u'<div>R&amp;D<br/></div>'
+        self.assertEquals(e.printable_value('content'), e['content'])
+        e['content'] = u'<div>été</div>'
+        self.assertEquals(e.printable_value('content'), e['content'])
+        e['content'] = u'été'
+        self.assertEquals(e.printable_value('content'), e['content'])
+        
+        
+    def test_fulltextindex(self):
+        e = self.etype_instance('File')
+        e['name'] = 'an html file'
+        e['description'] = 'du <em>html</em>'
+        e['description_format'] = 'text/html'
+        e['data'] = Binary('some <em>data</em>')
+        e['data_format'] = 'text/html'
+        e['data_encoding'] = 'ascii'
+        self.assertEquals(set(e.get_words()), 
+                          set(['an', 'html', 'file', 'du', 'html', 'some', 'data']))
+
+        
+    def test_nonregr_relation_cache(self):
+        p1 = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+        p2 = self.add_entity('Personne', nom=u'toto')
+        self.execute('SET X evaluee Y WHERE X nom "di mascio", Y nom "toto"')
+        self.assertEquals(p1.evaluee[0].nom, "toto")
+        self.failUnless(not p1.reverse_evaluee)
+        
+    def test_complete_relation(self):
+        self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
+        self.commit()
+        try:
+            eid = self.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]
+            trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
+            trinfo.complete()
+            self.failUnless(trinfo.relation_cached('from_state', 'subject'))
+            self.failUnless(trinfo.relation_cached('to_state', 'subject'))
+            self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
+            # check with a missing relation
+            eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
+                               'X to_state S2 WHERE '
+                               'U login "admin", S2 name "activated"')[0][0]
+            trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
+            trinfo.complete()
+            self.failUnless(isinstance(trinfo.creation_date, datetime))
+            self.failUnless(trinfo.relation_cached('from_state', 'subject'))
+            self.failUnless(trinfo.relation_cached('to_state', 'subject'))
+            self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
+            self.assertEquals(trinfo.from_state, [])
+        finally:
+            self.rollback()
+            self.execute('DELETE RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
+            self.commit()
+
+    def test_request_cache(self):
+        req = self.request()
+        user = self.entity('EUser X WHERE X login "admin"', req=req)
+        state = user.in_state[0]
+        samestate = self.entity('State X WHERE X name "activated"', req=req)
+        self.failUnless(state is samestate)
+
+    def test_rest_path(self):
+        note = self.add_entity('Note', type=u'z')
+        self.assertEquals(note.rest_path(), 'note/%s' % note.eid)
+        # unique attr
+        tag = self.add_entity('Tag', name=u'x')
+        self.assertEquals(tag.rest_path(), 'tag/x')
+        # test explicit rest_attr
+        person = self.add_entity('Personne', prenom=u'john', nom=u'doe')
+        self.assertEquals(person.rest_path(), 'personne/doe')
+        # ambiguity test
+        person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe')
+        self.assertEquals(person.rest_path(), 'personne/eid/%s' % person.eid)
+        self.assertEquals(person2.rest_path(), 'personne/eid/%s' % person2.eid)
+        # unique attr with None value (wikiid in this case)
+        card1 = self.add_entity('Card', title=u'hop')
+        self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid)
+        card2 = self.add_entity('Card', title=u'pod', wikiid=u'zob/i')
+        self.assertEquals(card2.rest_path(), 'card/zob%2Fi')
+
+    def test_set_attributes(self):
+        person = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+        self.assertEquals(person.prenom, u'adrien')
+        self.assertEquals(person.nom, u'di mascio')
+        person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+        person = self.entity('Personne P') # XXX retreival needed ?
+        self.assertEquals(person.prenom, u'sylvain')
+        self.assertEquals(person.nom, u'thénault')
+
+    def test_metainformation(self):
+        note = self.add_entity('Note', type=u'z')
+        metainf = note.metainformation()
+        self.assertEquals(metainf, {'source': {'adapter': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None})
+        self.assertEquals(note.absolute_url(), 'http://testing.fr/cubicweb/note/%s' % note.eid)
+        metainf['source'] = metainf['source'].copy()
+        metainf['source']['base-url']  = 'http://cubicweb2.com/'
+        self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/%s' % note.eid)
+        
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()
+
--- a/test/unittest_rset.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/test/unittest_rset.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,8 +1,11 @@
 # coding: utf-8
 """unit tests for module cubicweb.common.utils"""
+from __future__ import with_statement
 
 from logilab.common.testlib import TestCase, unittest_main
+
 from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.selectors import traced_selection
 
 from urlparse import urlsplit
 from rql import parse
@@ -224,7 +227,8 @@
         self.assertEquals(e.col, 0)
         self.assertEquals(e['title'], 'zou')
         self.assertRaises(KeyError, e.__getitem__, 'path')
-        self.assertEquals(e.view('text'), 'zou')
+        with traced_selection():
+            self.assertEquals(e.view('text'), 'zou')
         self.assertEquals(pprelcachedict(e._related_cache), [])
         
         e = rset.get_entity(0, 1)
--- a/test/unittest_schema.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/test/unittest_schema.py	Tue Apr 07 09:30:23 2009 +0200
@@ -134,7 +134,6 @@
         self.assertListEquals([basename(f) for f in schema_files], ['Bookmark.py'])
 
     def test_knownValues_load_schema(self):
-        """read an url and return a Schema instance"""
         schema = loader.load(config)
         self.assert_(isinstance(schema, CubicWebSchema))
         self.assertEquals(schema.name, 'data')
@@ -145,10 +144,10 @@
                              'ECache', 'EConstraint', 'EConstraintType', 'EEType',
                              'EFRDef', 'EGroup', 'EmailAddress', 'ENFRDef',
                              'EPermission', 'EProperty', 'ERType', 'EUser',
-                             'Float', 'Int', 'Interval', 
-                             'Password', 
+                             'File', 'Float', 'Image', 'Int', 'Interval', 'Note',
+                             'Password', 'Personne',
                              'RQLExpression', 
-                             'State', 'String', 'Time', 
+                             'Societe', 'State', 'String', 'SubNote', 'Tag', 'Time', 
                              'Transition', 'TrInfo']
         self.assertListEquals(entities, sorted(expected_entities))
         relations = [str(r) for r in schema.relations()]
@@ -157,13 +156,13 @@
                               'allowed_transition', 'bookmarked_by', 'canonical',
 
                               'cardinality', 'comment', 'comment_format', 
-                              'composite', 'condition', 'constrained_by', 'content',
+                              'composite', 'condition', 'connait', 'constrained_by', 'content',
                               'content_format', 'created_by', 'creation_date', 'cstrtype',
 
-                              'defaultval', 'delete_permission', 'description',
-                              'description_format', 'destination_state',
+                              'data', 'data_encoding', 'data_format', 'defaultval', 'delete_permission',
+                              'description', 'description_format', 'destination_state',
 
-                              'eid', 'expression', 'exprtype',
+                              'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype',
 
                               'final', 'firstname', 'for_user',
                               'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed',
@@ -176,17 +175,17 @@
 
                               'mainvars', 'meta', 'modification_date',
 
-                              'name', 
+                              'name', 'nom',
 
                               'ordernum', 'owned_by',
 
-                              'path', 'pkey', 'primary_email', 
+                              'path', 'pkey', 'prenom', 'primary_email', 
 
                               'read_permission', 'relation_type', 'require_group',
                               
                               'specializes', 'state_of', 'surname', 'symetric', 'synopsis',
 
-                              'timestamp', 'title', 'to_entity', 'to_state', 'transition_of',
+                              'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type',
 
                               'upassword', 'update_permission', 'use_email',
 
@@ -199,7 +198,7 @@
         eschema = schema.eschema('EUser')
         rels = sorted(str(r) for r in eschema.subject_relations())
         self.assertListEquals(rels, ['created_by', 'creation_date', 'eid',
-                                     'firstname', 'has_text', 'identity',
+                                     'evaluee', 'firstname', 'has_text', 'identity',
                                      'in_group', 'in_state', 'is',
                                      'is_instance_of', 'last_login_time',
                                      'login', 'modification_date', 'owned_by',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_selectors.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,102 @@
+"""unit tests for selectors mechanism
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.vregistry import Selector, AndSelector, OrSelector
+from cubicweb.selectors import implements
+
+from cubicweb.interfaces import IDownloadable
+
+class _1_(Selector):
+    def __call__(self, *args, **kwargs):
+        return 1
+
+class _0_(Selector):
+    def __call__(self, *args, **kwargs):
+        return 0
+
+def _2_(*args, **kwargs):
+    return 2
+
+
+class SelectorsTC(TestCase):
+    def test_basic_and(self):
+        selector = _1_() & _1_()
+        self.assertEquals(selector(None), 2)
+        selector = _1_() & _0_()
+        self.assertEquals(selector(None), 0)
+        selector = _0_() & _1_()
+        self.assertEquals(selector(None), 0)
+
+    def test_basic_or(self):
+        selector = _1_() | _1_()
+        self.assertEquals(selector(None), 1)
+        selector = _1_() | _0_()
+        self.assertEquals(selector(None), 1)
+        selector = _0_() | _1_()
+        self.assertEquals(selector(None), 1)
+        selector = _0_() | _0_()
+        self.assertEquals(selector(None), 0)
+
+    def test_selector_and_function(self):
+        selector = _1_() & _2_
+        self.assertEquals(selector(None), 3)
+        selector = _2_ & _1_()
+        self.assertEquals(selector(None), 3)
+        
+    def test_three_and(self):
+        selector = _1_() & _1_() & _1_()
+        self.assertEquals(selector(None), 3)
+        selector = _1_() & _0_() & _1_()
+        self.assertEquals(selector(None), 0)
+        selector = _0_() & _1_() & _1_()
+        self.assertEquals(selector(None), 0)
+
+    def test_three_or(self):
+        selector = _1_() | _1_() | _1_()
+        self.assertEquals(selector(None), 1)
+        selector = _1_() | _0_() | _1_()
+        self.assertEquals(selector(None), 1)
+        selector = _0_() | _1_() | _1_()
+        self.assertEquals(selector(None), 1)
+        selector = _0_() | _0_() | _0_()
+        self.assertEquals(selector(None), 0)
+
+    def test_composition(self):
+        selector = (_1_() & _1_()) & (_1_() & _1_())
+        self.failUnless(isinstance(selector, AndSelector))
+        self.assertEquals(len(selector.selectors), 4)
+        self.assertEquals(selector(None), 4)
+        selector = (_1_() & _0_()) | (_1_() & _1_())
+        self.failUnless(isinstance(selector, OrSelector))
+        self.assertEquals(len(selector.selectors), 2)
+        self.assertEquals(selector(None), 2)
+
+    def test_search_selectors(self):
+        sel = implements('something')
+        self.assertIs(sel.search_selector(implements), sel)
+        csel = AndSelector(sel, Selector())
+        self.assertIs(csel.search_selector(implements), sel)
+        csel = AndSelector(Selector(), sel)
+        self.assertIs(csel.search_selector(implements), sel)
+        
+from cubicweb.devtools.testlib import EnvBasedTC
+
+class ImplementsSelectorTC(EnvBasedTC):
+    def test_etype_priority(self):
+        req = self.request()
+        cls = self.vreg.etype_class('File')
+        anyscore = implements('Any').score_class(cls, req)
+        idownscore = implements(IDownloadable).score_class(cls, req)
+        self.failUnless(idownscore > anyscore, (idownscore, anyscore))
+        filescore = implements('File').score_class(cls, req)
+        self.failUnless(filescore > idownscore, (filescore, idownscore))
+    
+if __name__ == '__main__':
+    unittest_main()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_utils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,47 @@
+"""unit tests for module cubicweb.common.utils"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList
+
+
+class MakeUidTC(TestCase):
+    def test_1(self):
+        self.assertNotEquals(make_uid('xyz'), make_uid('abcd'))
+        self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
+        
+    def test_2(self):
+        d = {}
+        while len(d)<10000:
+            uid = make_uid('xyz')
+            if d.has_key(uid):
+                self.fail(len(d))
+            d[uid] = 1
+
+        
+class UStringIOTC(TestCase):
+    def test_boolean_value(self):
+        self.assert_(UStringIO())
+
+
+class SizeConstrainedListTC(TestCase):
+
+    def test_append(self):
+        l = SizeConstrainedList(10)
+        for i in xrange(12):
+            l.append(i)
+        self.assertEquals(l, range(2, 12))
+    
+    def test_extend(self):
+        testdata = [(range(5), range(5)),
+                    (range(10), range(10)),
+                    (range(12), range(2, 12)),
+                    ]
+        for extension, expected in testdata:
+            l = SizeConstrainedList(10)
+            l.extend(extension)
+            yield self.assertEquals, l, expected
+
+   
+if __name__ == '__main__':
+    unittest_main()
--- a/test/unittest_vregistry.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/test/unittest_vregistry.py	Tue Apr 07 09:30:23 2009 +0200
@@ -5,7 +5,9 @@
 from cubicweb import CW_SOFTWARE_ROOT as BASE
 from cubicweb.vregistry import VObject
 from cubicweb.cwvreg import CubicWebRegistry, UnknownProperty
-from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.entities.lib import Card
+from cubicweb.interfaces import IMileStone
 
 class YesSchema:
     def __contains__(self, something):
@@ -14,14 +16,16 @@
 class VRegistryTC(TestCase):
 
     def setUp(self):
-        config = CubicWebConfiguration('data')
+        config = TestServerConfiguration('data')
         self.vreg = CubicWebRegistry(config)
-        self.vreg.schema = YesSchema()
+        config.bootstrap_cubes()
+        self.vreg.schema = config.load_schema()
 
     def test_load(self):
         self.vreg.load_file(join(BASE, 'web', 'views'), 'euser.py')
         self.vreg.load_file(join(BASE, 'web', 'views'), 'baseviews.py')
-        fpvc = [v for v in self.vreg.registry_objects('views', 'primary') if v.accepts[0] == 'EUser'][0]
+        fpvc = [v for v in self.vreg.registry_objects('views', 'primary')
+               if v.__module__ == 'cubicweb.web.views.euser'][0]
         fpv = fpvc(None, None)
         # don't want a TypeError due to super call
         self.assertRaises(AttributeError, fpv.render_entity_attributes, None, None)
@@ -32,17 +36,31 @@
         # check loading baseviews after idownloadable isn't kicking interface based views
         self.assertEquals(len(self.vreg['views']['primary']), 2)
                               
-    def test_autoselectors(self):
+    def test___selectors__compat(self):
         myselector1 = lambda *args: 1
         myselector2 = lambda *args: 1
         class AnAppObject(VObject):
             __selectors__ = (myselector1, myselector2)
-        self.assertEquals(AnAppObject.__select__(), 2)
+        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'))
         self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key')
+
+    def test_load_subinterface_based_vobjects(self):
+        self.vreg.reset()
+        self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')])
+        # check progressbar was kicked
+        self.failIf(self.vreg['views'].get('progressbar'))
+        class MyCard(Card):
+            __implements__ = (IMileStone,)
+        self.vreg.reset()
+        self.vreg.register_vobject_class(MyCard)
+        self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')])
+        # check progressbar isn't kicked
+        self.assertEquals(len(self.vreg['views']['progressbar']), 1)
         
 
 if __name__ == '__main__':
--- a/toolsutils.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/toolsutils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """some utilities for cubicweb tools
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -11,7 +11,7 @@
 from os.path import exists, join, abspath, normpath
 
 from logilab.common.clcommands import Command as BaseCommand, \
-     main_run as base_main_run, register_commands, pop_arg, cmd_run
+     main_run as base_main_run
 from logilab.common.compat import any
 
 from cubicweb import warning
@@ -114,7 +114,7 @@
                 tfpath = tfpath[:-5]
                 if not askconfirm or not exists(tfpath) or \
                        confirm('%s exists, overwrite?' % tfpath):
-                    fname = fill_templated_file(fpath, tfpath, context)
+                    fill_templated_file(fpath, tfpath, context)
                     print '[generate] %s <-- %s' % (tfpath, fpath)
             elif exists(tfpath):
                 show_diffs(tfpath, fpath, askconfirm)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/utils.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,278 @@
+"""Some utilities for CubicWeb server/clients.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import locale
+from md5 import md5
+from datetime import datetime, timedelta, date
+from time import time
+from random import randint, seed
+    
+# initialize random seed from current time
+seed()
+
+try:
+    strptime = datetime.strptime
+except AttributeError: # py < 2.5
+    from time import strptime as time_strptime
+    def strptime(value, format):
+        return datetime(*time_strptime(value, format)[:6])
+
+def todate(somedate):
+    """return a date from a date (leaving unchanged) or a datetime"""
+    if isinstance(somedate, datetime):
+        return date(somedate.year, somedate.month, somedate.day)
+    assert isinstance(somedate, date)
+    return date
+
+def date_range(begin, end, incr=1, include=None):
+    """yields each date between begin and end
+    :param begin: the start date
+    :param end: the end date
+    :param incr: the step to use to iterate over dates. Default is
+                 one day.                 
+    :param include: None (means no exclusion) or a function taking a
+                    date as parameter, and returning True if the date
+                    should be included.
+    """
+    incr = timedelta(incr, 0, 0)
+    while begin <= end:
+        if include is None or include(begin): 
+            yield begin
+        begin += incr
+
+def ustrftime(date, fmt='%Y-%m-%d'):
+    """like strftime, but returns a unicode string instead of an encoded
+    string which may be problematic with localized date.
+    
+    encoding is guessed by locale.getpreferredencoding()
+    """
+    # date format may depend on the locale
+    encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
+    return unicode(date.strftime(str(fmt)), encoding)
+
+def make_uid(key):
+    """forge a unique identifier"""
+    msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
+    return md5(msg).hexdigest()
+
+
+def dump_class(cls, clsname):
+    """create copy of a class by creating an empty class inheriting
+    from the given cls.
+
+    Those class will be used as place holder for attribute and relation
+    description
+    """
+    # type doesn't accept unicode name
+    # return type.__new__(type, str(clsname), (cls,), {})
+    # __autogenerated__ attribute is just a marker
+    return type(str(clsname), (cls,), {'__autogenerated__': True})
+
+
+def merge_dicts(dict1, dict2):
+    """update a copy of `dict1` with `dict2`"""
+    dict1 = dict(dict1)
+    dict1.update(dict2)
+    return dict1
+                
+
+class SizeConstrainedList(list):
+    """simple list that makes sure the list does not get bigger
+    than a given size.
+
+    when the list is full and a new element is added, the first
+    element of the list is removed before appending the new one
+
+    >>> l = SizeConstrainedList(2)
+    >>> l.append(1)
+    >>> l.append(2)
+    >>> l
+    [1, 2]
+    >>> l.append(3)
+    [2, 3]
+    """
+    def __init__(self, maxsize):
+        self.maxsize = maxsize
+
+    def append(self, element):
+        if len(self) == self.maxsize:
+            del self[0]
+        super(SizeConstrainedList, self).append(element)
+
+    def extend(self, sequence):
+        super(SizeConstrainedList, self).extend(sequence)
+        keepafter = len(self) - self.maxsize
+        if keepafter > 0:
+            del self[:keepafter]
+
+    __iadd__ = extend
+
+
+class UStringIO(list):
+    """a file wrapper which automatically encode unicode string to an encoding
+    specifed in the constructor
+    """
+
+    def __nonzero__(self):
+        return True
+    
+    def write(self, value):
+        assert isinstance(value, unicode), u"unicode required not %s : %s"\
+                                     % (type(value).__name__, repr(value))
+        self.append(value)
+        
+    def getvalue(self):
+        return u''.join(self)
+
+    def __repr__(self):
+        return '<%s at %#x>' % (self.__class__.__name__, id(self))
+
+
+class HTMLHead(UStringIO):
+    """wraps HTML header's stream
+
+    Request objects use a HTMLHead instance to ease adding of
+    javascripts and stylesheets
+    """
+    js_unload_code = u'jQuery(window).unload(unloadPageData);'
+
+    def __init__(self):
+        super(HTMLHead, self).__init__()
+        self.jsvars = []
+        self.jsfiles = []
+        self.cssfiles = []
+        self.ie_cssfiles = []
+        self.post_inlined_scripts = []
+        self.pagedata_unload = False
+
+
+    def add_raw(self, rawheader):
+        self.write(rawheader)
+
+    def define_var(self, var, value):
+        self.jsvars.append( (var, value) )
+
+    def add_post_inline_script(self, content):
+        self.post_inlined_scripts.append(content)
+
+    def add_onload(self, jscode):
+        self.add_post_inline_script(u"""jQuery(document).ready(function () {
+ %s
+ });""" % jscode)
+        
+    
+    def add_js(self, jsfile):
+        """adds `jsfile` to the list of javascripts used in the webpage
+
+        This function checks if the file has already been added
+        :param jsfile: the script's URL
+        """
+        if jsfile not in self.jsfiles:
+            self.jsfiles.append(jsfile)
+
+    def add_css(self, cssfile, media):
+        """adds `cssfile` to the list of javascripts used in the webpage
+
+        This function checks if the file has already been added
+        :param cssfile: the stylesheet's URL
+        """
+        if (cssfile, media) not in self.cssfiles:
+            self.cssfiles.append( (cssfile, media) )
+
+    def add_ie_css(self, cssfile, media='all'):
+        """registers some IE specific CSS"""
+        if (cssfile, media) not in self.ie_cssfiles:
+            self.ie_cssfiles.append( (cssfile, media) )
+
+    def add_unload_pagedata(self):
+        """registers onunload callback to clean page data on server"""
+        if not self.pagedata_unload:
+            self.post_inlined_scripts.append(self.js_unload_code)
+            self.pagedata_unload = True
+
+    def getvalue(self, skiphead=False):
+        """reimplement getvalue to provide a consistent (and somewhat browser
+        optimzed cf. http://stevesouders.com/cuzillion) order in external
+        resources declaration
+        """
+        w = self.write
+        # 1/ variable declaration if any
+        if self.jsvars:
+            from simplejson import dumps
+            w(u'<script type="text/javascript">\n')
+            for var, value in self.jsvars:
+                w(u'%s = %s;\n' % (var, dumps(value)))
+            w(u'</script>\n')
+        # 2/ css files
+        for cssfile, media in self.cssfiles:
+            w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+              (media, cssfile))
+        # 3/ ie css if necessary
+        if self.ie_cssfiles:
+            w(u'<!--[if lt IE 8]>\n')
+            for cssfile, media in self.ie_cssfiles:
+                w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+                  (media, cssfile))
+            w(u'<![endif]--> \n')
+        # 4/ js files
+        for jsfile in self.jsfiles:
+            w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
+        # 5/ post inlined scripts (i.e. scripts depending on other JS files)
+        if self.post_inlined_scripts:
+            w(u'<script type="text/javascript">\n')
+            w(u'\n\n'.join(self.post_inlined_scripts))
+            w(u'\n</script>\n')
+        header = super(HTMLHead, self).getvalue()
+        if skiphead:
+            return header
+        return u'<head>\n%s</head>\n' % header
+        
+
+class HTMLStream(object):
+    """represents a HTML page.
+
+    This is used my main templates so that HTML headers can be added
+    at any time during the page generation.
+    
+    HTMLStream uses the (U)StringIO interface to be compliant with
+    existing code.
+    """
+    
+    def __init__(self, req):
+        # stream for <head>
+        self.head = req.html_headers
+        # main stream
+        self.body = UStringIO()
+        self.doctype = u''
+        # xmldecl and html opening tag
+        self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
+        self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
+                       'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
+                       'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
+
+
+    def write(self, data):
+        """StringIO interface: this method will be assigned to self.w
+        """
+        self.body.write(data)
+
+    def getvalue(self):
+        """writes HTML headers, closes </head> tag and writes HTML body"""
+        return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
+                                                 self.htmltag,
+                                                 self.head.getvalue(),
+                                                 self.body.getvalue())
+
+
+class AcceptMixIn(object):
+    """Mixin class for vobjects defining the 'accepts' attribute describing
+    a set of supported entity type (Any by default).
+    """
+    # XXX deprecated, no more necessary
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,504 @@
+"""abstract views and templates classes for CubicWeb web client
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+
+from logilab.common.deprecation import obsolete
+from logilab.mtconverter import html_escape
+
+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 AppRsetObject
+from cubicweb.utils import UStringIO, HTMLStream
+from cubicweb.vregistry import yes_registerer
+from cubicweb.common.registerers import accepts_registerer, priority_registerer, yes_registerer
+
+_ = unicode
+
+
+# robots control
+NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
+NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
+
+CW_XHTML_EXTENSIONS = '''[
+  <!ATTLIST html xmlns:cubicweb CDATA  #FIXED \'http://www.logilab.org/2008/cubicweb\'  >
+
+<!ENTITY % coreattrs
+ "id          ID            #IMPLIED
+  class       CDATA         #IMPLIED
+  style       CDATA         #IMPLIED
+  title       CDATA         #IMPLIED
+
+ cubicweb:sortvalue         CDATA   #IMPLIED
+ cubicweb:target            CDATA   #IMPLIED
+ cubicweb:limit             CDATA   #IMPLIED
+ cubicweb:type              CDATA   #IMPLIED
+ cubicweb:loadtype          CDATA   #IMPLIED
+ cubicweb:wdgtype           CDATA   #IMPLIED
+ cubicweb:initfunc          CDATA   #IMPLIED
+ cubicweb:inputid           CDATA   #IMPLIED
+ cubicweb:tindex            CDATA   #IMPLIED
+ cubicweb:inputname         CDATA   #IMPLIED
+ cubicweb:value             CDATA   #IMPLIED
+ cubicweb:required          CDATA   #IMPLIED
+ cubicweb:accesskey         CDATA   #IMPLIED
+ cubicweb:maxlength         CDATA   #IMPLIED
+ cubicweb:variables         CDATA   #IMPLIED
+ cubicweb:displayactions    CDATA   #IMPLIED
+ cubicweb:fallbackvid       CDATA   #IMPLIED
+ cubicweb:vid               CDATA   #IMPLIED
+ cubicweb:rql               CDATA   #IMPLIED
+ cubicweb:actualrql         CDATA   #IMPLIED
+ cubicweb:rooteid           CDATA   #IMPLIED
+ cubicweb:dataurl           CDATA   #IMPLIED
+ cubicweb:size              CDATA   #IMPLIED
+ cubicweb:tlunit            CDATA   #IMPLIED
+ cubicweb:loadurl           CDATA   #IMPLIED
+ cubicweb:uselabel          CDATA   #IMPLIED
+ cubicweb:facetargs         CDATA   #IMPLIED
+ cubicweb:facetName         CDATA   #IMPLIED
+  "> ] '''
+
+TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
+
+STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
+
+# base view object ############################################################
+
+class View(AppRsetObject):
+    """abstract view class, used as base for every renderable object such
+    as views, templates, some components...web
+
+    A view is instantiated to render a [part of a] result set. View
+    subclasses may be parametred using the following class attributes:
+
+    * `templatable` indicates if the view may be embeded in a main
+      template or if it has to be rendered standalone (i.e. XML for
+      instance)
+    * if the view is not templatable, it should set the `content_type` class
+      attribute to the correct MIME type (text/xhtml by default)
+    * the `category` attribute may be used in the interface to regroup related
+      objects together
+
+    At instantiation time, the standard `req`, `rset`, and `cursor`
+    attributes are added and the `w` attribute will be set at rendering
+    time to a write function to use.
+    """
+    __registry__ = 'views'
+    __registerer__ = priority_registerer
+    registered = require_group_compat(AppRsetObject.registered)
+
+    templatable = True
+    need_navigation = True
+    # content_type = 'application/xhtml+xml' # text/xhtml'
+    binary = False
+    add_to_breadcrumbs = True
+    category = 'view'
+
+    def __init__(self, req=None, rset=None, **kwargs):
+        super(View, self).__init__(req, rset, **kwargs)
+        self.w = None
+
+    @property
+    def content_type(self):
+        if self.req.xhtml_browser():
+            return 'application/xhtml+xml'
+        return 'text/html'
+
+    def set_stream(self, w=None):
+        if self.w is not None:
+            return
+        if w is None:
+            if self.binary:
+                self._stream = stream = StringIO()
+            else:
+                self._stream = stream = UStringIO()
+            w = stream.write
+        else:
+            stream = None
+        self.w = w
+        return stream
+
+    # main view interface #####################################################
+
+    def dispatch(self, w=None, **context):
+        """called to render a view object for a result set.
+
+        This method is a dispatched to an actual method selected
+        according to optional row and col parameters, which are locating
+        a particular row or cell in the result set:
+
+        * if row is specified, `cell_call` is called
+        * if none of them is supplied, the view is considered to apply on
+          the whole result set (which may be None in this case), `call` is
+          called
+        """
+        row = context.get('row')
+        if row is not None:
+            context.setdefault('col', 0)
+            view_func = self.cell_call
+        else:
+            view_func = self.call
+        stream = self.set_stream(w)
+        # stream = self.set_stream(context)
+        view_func(**context)
+        # return stream content if we have created it
+        if stream is not None:
+            return self._stream.getvalue()
+
+    # should default .call() method add a <div classs="section"> around each
+    # rset item
+    add_div_section = True
+
+    def call(self, **kwargs):
+        """the view is called for an entire result set, by default loop
+        other rows of the result set and call the same view on the
+        particular row
+
+        Views applicable on None result sets have to override this method
+        """
+        rset = self.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)
+            if wrap:
+                self.w(u"</div>")
+
+    def cell_call(self, row, col, **kwargs):
+        """the view is called for a particular result set cell"""
+        raise NotImplementedError, self
+
+    def linkable(self):
+        """return True if the view may be linked in a menu
+
+        by default views without title are not meant to be displayed
+        """
+        if not getattr(self, 'title', None):
+            return False
+        return True
+
+    def is_primary(self):
+        return self.id == '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.
+        """
+        try:
+            return self.build_url(vid=self.id, rql=self.req.form['rql'])
+        except KeyError:
+            return self.build_url(vid=self.id)
+
+    def set_request_content_type(self):
+        """set the content type returned by this view"""
+        self.req.set_content_type(self.content_type)
+
+    # view utilities ##########################################################
+    
+    def wview(self, __vid, rset, __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)
+        
+    # XXX Template bw compat
+    template = obsolete('.template is deprecated, use .view')(wview)
+
+    def whead(self, data):
+        self.req.html_headers.write(data)
+
+    def wdata(self, data):
+        """simple helper that escapes `data` and writes into `self.w`"""
+        self.w(html_escape(data))
+
+    def action(self, actionid, row=0):
+        """shortcut to get action object with id `actionid`"""
+        return self.vreg.select_action(actionid, self.req, self.rset,
+                                       row=row)
+
+    def action_url(self, actionid, label=None, row=0):
+        """simple method to be able to display `actionid` as a link anywhere
+        """
+        action = self.vreg.select_action(actionid, self.req, self.rset,
+                                         row=row)
+        if action:
+            label = label or self.req._(action.title)
+            return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
+        return u''
+
+    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 return a meta tag to disable robot indexation of the page
+        """
+        return [NOINDEX]
+
+    def page_title(self):
+        """returns a title according to the result set - used for the
+        title in the HTML header
+        """
+        vtitle = self.req.form.get('vtitle')
+        if vtitle:
+            return self.req._(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
+        if rset and rset.rowcount:
+            if rset.rowcount == 1:
+                try:
+                    entity = self.complete_entity(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 = 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')
+                else :
+                    clabel = u'#[*] (%s)' % vtitle
+        else:
+            clabel = vtitle
+        return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
+
+    def output_url_builder( self, name, url, args ):
+        self.w(u'<script language="JavaScript"><!--\n' \
+               u'function %s( %s ) {\n' % (name, ','.join(args) ) )
+        url_parts = url.split("%s")
+        self.w(u' url="%s"' % url_parts[0] )
+        for arg, part in zip(args, url_parts[1:]):
+            self.w(u'+str(%s)' % arg )
+            if part:
+                self.w(u'+"%s"' % part)
+        self.w('\n document.window.href=url;\n')
+        self.w('}\n-->\n</script>\n')
+
+    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)
+
+    def field(self, label, value, row=True, show_label=True, w=None, tr=True):
+        """ read-only field """
+        if w is None:
+            w = self.w
+        if row:
+            w(u'<div class="row">')
+        if show_label:
+            if tr:
+                label = display_name(self.req, label)
+            w(u'<span class="label">%s</span>' % label)
+        w(u'<div class="field">%s</div>' % value)
+        if row:
+            w(u'</div>')
+            
+    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
+        
+
+
+# concrete views base classes #################################################
+
+class EntityView(View):
+    """base class for views applying on an entity (i.e. uniform result set)"""
+    __registerer__ = accepts_registerer
+    __select__ = non_final_entity()
+    registered = accepts_compat(View.registered)
+
+    category = 'entityview'
+
+
+class StartupView(View):
+    """base class for views which doesn't need a particular result set to be
+    displayed (so they can always be displayed !)
+    """
+    __registerer__ = priority_registerer
+    __select__ = none_rset()
+    registered = require_group_compat(View.registered)
+    
+    category = 'startupview'
+    
+    def url(self):
+        """return the url associated with this view. We can omit rql here"""
+        return self.build_url('view', vid=self.id)
+
+    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 startup views are indexed
+        """
+        return []
+
+
+class EntityStartupView(EntityView):
+    """base class for entity views which may also be applied to None
+    result set (usually a default rql is provided by the view class)
+    """
+    __select__ = none_rset() | non_final_entity()
+
+    default_rql = None
+
+    def __init__(self, req, rset, **kwargs):
+        super(EntityStartupView, self).__init__(req, rset, **kwargs)
+        if rset is None:
+            # this instance is not in the "entityview" category
+            self.category = 'startupview'
+
+    def startup_rql(self):
+        """return some rql to be executed if the result set is None"""
+        return self.default_rql
+
+    def call(self, **kwargs):
+        """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
+        for i in xrange(len(rset)):
+            self.wview(self.id, rset, row=i, **kwargs)
+
+    def url(self):
+        """return the url associated with this view. We can omit rql if we are
+        on a result set on which we do not apply.
+        """
+        if self.rset is None:
+            return self.build_url(vid=self.id)
+        return super(EntityStartupView, self).url()
+
+
+class AnyRsetView(View):
+    """base class for views applying on any non empty result sets"""
+    __select__ = nonempty_rset()
+
+    category = 'anyrsetview'
+
+    def columns_labels(self, tr=True):
+        if tr:
+            translate = display_name
+        else:
+            translate = lambda req, val: val
+        rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
+        labels = []
+        for colindex, attr in enumerate(rqlstdescr):
+            # compute column header
+            if colindex == 0 or attr == 'Any': # find a better label
+                label = ','.join(translate(self.req, et)
+                                 for et in self.rset.column_types(colindex))
+            else:
+                label = translate(self.req, attr)
+            labels.append(label)
+        return labels
+
+    
+# concrete template base classes ##############################################
+
+class MainTemplate(View):
+    """main template are primary access point to render a full HTML page.
+    There is usually at least a regular main template and a simple fallback
+    one to display error if the first one failed
+    """
+    base_doctype = STRICT_DOCTYPE
+    registered = require_group_compat(View.registered)
+
+    @property
+    def doctype(self):
+        if self.req.xhtml_browser():
+            return self.base_doctype % CW_XHTML_EXTENSIONS
+        return self.base_doctype % ''
+
+    def set_stream(self, w=None, templatable=True):
+        if templatable and self.w is not None:
+            return
+
+        if w is None:
+            if self.binary:
+                self._stream = stream = StringIO()
+            elif not templatable:
+                # not templatable means we're using a non-html view, we don't
+                # want the HTMLStream stuff to interfere during data generation
+                self._stream = stream = UStringIO()
+            else:
+                self._stream = stream = HTMLStream(self.req)
+            w = stream.write
+        else:
+            stream = None
+        self.w = w
+        return stream
+
+    def write_doctype(self, xmldecl=True):
+        assert isinstance(self._stream, HTMLStream)
+        self._stream.doctype = self.doctype
+        if not xmldecl:
+            self._stream.xmldecl = u''
+
+    def linkable(self):
+        return False
+    
+# concrete component base classes #############################################
+
+class ReloadableMixIn(object):
+    """simple mixin for reloadable parts of UI"""
+    
+    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.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        cbname = self.req.register_onetime_callback(cb, *args)
+        return self.build_js(cbname, html_escape(msg or ''))
+        
+    def build_update_js_call(self, cbname, msg):
+        rql = html_escape(self.rset.printable_rql())
+        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
+            cbname, self.id, rql, msg, self.__registry__, self.div_id())
+    
+    def build_reload_js_call(self, cbname, msg):
+        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
+
+    build_js = build_update_js_call # expect updatable component by default
+    
+    def div_id(self):
+        return ''
+
+
+class Component(ReloadableMixIn, View):
+    """base class for components"""
+    __registry__ = 'components'
+    __registerer__ = yes_registerer
+    __select__ = yes()
+    property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the component or not')),
+        }    
+
+    def div_class(self):
+        return '%s %s' % (self.propval('htmlclass'), self.id)
+
+    def div_id(self):
+        return '%sComponent' % self.id
--- a/vregistry.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/vregistry.py	Tue Apr 07 09:30:23 2009 +0200
@@ -29,24 +29,14 @@
 from os import listdir, stat
 from os.path import dirname, join, realpath, split, isdir
 from logging import getLogger
+import types
 
 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
 
 
-class vobject_helper(object):
-    """object instantiated at registration time to help a wrapped
-    VObject subclass
-    """
 
-    def __init__(self, registry, vobject):
-        self.registry = registry
-        self.vobject = vobject
-        self.config = registry.config
-        self.schema = registry.schema
-
-
-class registerer(vobject_helper):
+class registerer(object):
     """do whatever is needed at registration time for the wrapped
     class, according to current application schema and already
     registered objects of the same kind (i.e. same registry name and
@@ -59,7 +49,10 @@
     """
 
     def __init__(self, registry, vobject):
-        super(registerer, self).__init__(registry, vobject)
+        self.registry = registry
+        self.vobject = vobject
+        self.config = registry.config
+        self.schema = registry.schema
         self.kicked = set()
     
     def do_it_yourself(self, registered):
@@ -73,49 +66,11 @@
     def skip(self):
         self.debug('no schema compat, skipping %s', self.vobject)
 
-
-def selector(cls, *args, **kwargs):
-    """selector is called to help choosing the correct object for a
-    particular request and result set by returning a score.
-
-    it must implement a .score_method taking a request, a result set and
-    optionaly row and col arguments which return an int telling how well
-    the wrapped class apply to the given request and result set. 0 score
-    means that it doesn't apply.
-    
-    rset may be None. If not, row and col arguments may be optionally
-    given if the registry is scoring a given row or a given cell of
-    the result set (both row and col are int if provided).
-    """    
-    raise NotImplementedError(cls)
-
-
-class autoselectors(type):
-    """implements __selectors__ / __select__ compatibility layer so that:
-
-    __select__ = chainall(classmethod(A, B, C))
+class yes_registerer(registerer):
+    """register without any other action"""
+    def do_it_yourself(self, registered):
+        return self.vobject
 
-    can be replaced by something like:
-    
-    __selectors__ = (A, B, C)
-    """
-    def __new__(mcs, name, bases, classdict):
-        if '__select__' in classdict and '__selectors__' in classdict:
-            raise TypeError("__select__ and __selectors__ "
-                            "can't be used together")
-        if '__select__' not in classdict and '__selectors__' in classdict:
-            selectors = classdict['__selectors__']
-            if len(selectors) > 1:
-                classdict['__select__'] = classmethod(chainall(*selectors))
-            else:
-                classdict['__select__'] = classmethod(selectors[0])
-        return super(autoselectors, mcs).__new__(mcs, name, bases, classdict)
-
-    def __setattr__(self, attr, value):
-        if attr == '__selectors__':
-            self.__select__ = classmethod(chainall(*value))
-        super(autoselectors, self).__setattr__(attr, value)
-            
 
 class VObject(object):
     """visual object, use to be handled somehow by the visual components
@@ -129,22 +84,16 @@
     :id:
       object's identifier in the registry (string like 'main',
       'primary', 'folder_box')
-    :__registerer__:
-      registration helper class
     :__select__:
-      selection helper function
-    :__selectors__:
-      tuple of selectors to be chained
-      (__select__ and __selectors__ are mutually exclusive)
+      class'selector
       
     Moreover, the `__abstract__` attribute may be set to True to indicate
     that a vobject is abstract and should not be registered
     """
-    __metaclass__ = autoselectors
     # necessary attributes to interact with the registry
     id = None
     __registry__ = None
-    __registerer__ = None
+    __registerer__ = yes_registerer
     __select__ = None
 
     @classmethod
@@ -155,6 +104,7 @@
         may be the right hook to create an instance for example). By
         default the vobject is returned without any transformation.
         """
+        cls.build___select__()
         return cls
 
     @classmethod
@@ -173,6 +123,29 @@
         """returns a unique identifier for the vobject"""
         return '%s.%s' % (cls.__module__, cls.__name__)
 
+    # XXX bw compat code
+    @classmethod
+    def build___select__(cls):
+        for klass in cls.mro():
+            if klass.__name__ == 'AppRsetObject':
+                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
+
 
 class VRegistry(object):
     """class responsible to register, propose and select the various
@@ -204,107 +177,6 @@
 
     def __contains__(self, key):
         return key in self._registries
-        
-    def register_vobject_class(self, cls, _kicked=set()):
-        """handle vobject class registration
-        
-        vobject class with __abstract__ == True in their local dictionnary or
-        with a name starting starting by an underscore are not registered.
-        Also a vobject class needs to have __registry__ and id attributes set
-        to a non empty string to be registered.
-
-        Registration is actually handled by vobject's registerer.
-        """
-        if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
-            or not cls.__registry__ or not cls.id):
-            return
-        # while reloading a module :
-        # if cls was previously kicked, it means that there is a more specific
-        # vobject defined elsewhere re-registering cls would kick it out
-        if cls.classid() in _kicked:
-            self.debug('not re-registering %s because it was previously kicked',
-                      cls.classid())
-        else:
-            regname = cls.__registry__
-            if cls.id in self.config['disable-%s' % regname]:
-                return
-            registry = self._registries.setdefault(regname, {})
-            vobjects = registry.setdefault(cls.id, [])
-            registerer = cls.__registerer__(self, cls)
-            cls = registerer.do_it_yourself(vobjects)
-            #_kicked |= registerer.kicked
-            if cls:
-                vobject = cls.registered(self)
-                try:
-                    vname = vobject.__name__
-                except AttributeError:
-                    vname = vobject.__class__.__name__
-                self.debug('registered vobject %s in registry %s with id %s',
-                          vname, cls.__registry__, cls.id)
-                vobjects.append(vobject)
-            
-    def unregister_module_vobjects(self, modname):
-        """removes registered objects coming from a given module
-
-        returns a dictionnary classid/class of all classes that will need
-        to be updated after reload (i.e. vobjects referencing classes defined
-        in the <modname> module)
-        """
-        unregistered = {}
-        # browse each registered object
-        for registry, objdict in self.items():
-            for oid, objects in objdict.items():
-                for obj in objects[:]:
-                    objname = obj.classid()
-                    # if the vobject is defined in this module, remove it
-                    if objname.startswith(modname):
-                        unregistered[objname] = obj
-                        objects.remove(obj)
-                        self.debug('unregistering %s in %s registry',
-                                  objname, registry)
-                    # if not, check if the vobject can be found in baseclasses
-                    # (because we also want subclasses to be updated)
-                    else:
-                        if not isinstance(obj, type):
-                            obj = obj.__class__
-                        for baseclass in obj.__bases__:
-                            if hasattr(baseclass, 'classid'):
-                                baseclassid = baseclass.classid()
-                                if baseclassid.startswith(modname):
-                                    unregistered[baseclassid] = baseclass
-                # update oid entry
-                if objects:
-                    objdict[oid] = objects
-                else:
-                    del objdict[oid]
-        return unregistered
-
-
-    def update_registered_subclasses(self, oldnew_mapping):
-        """updates subclasses of re-registered vobjects
-
-        if baseviews.PrimaryView is changed, baseviews.py will be reloaded
-        automatically and the new version of PrimaryView will be registered.
-        But all existing subclasses must also be notified of this change, and
-        that's what this method does
-
-        :param oldnew_mapping: a dict mapping old version of a class to
-                               the new version
-        """
-        # browse each registered object
-        for objdict in self.values():
-            for objects in objdict.values():
-                for obj in objects:
-                    if not isinstance(obj, type):
-                        obj = obj.__class__
-                    # build new baseclasses tuple
-                    newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
-                                     for baseclass in obj.__bases__)
-                    # update obj's baseclasses tuple (__bases__) if needed
-                    if newbases != obj.__bases__:
-                        self.debug('updating %s.%s base classes',
-                                  obj.__module__, obj.__name__)
-                        obj.__bases__ = newbases
 
     def registry(self, name):
         """return the registry (dictionary of class objects) associated to
@@ -330,7 +202,92 @@
             for objs in registry.values():
                 result += objs
             return result
-        
+
+    def object_by_id(self, registry, cid, *args, **kwargs):
+        """return the most specific component according to the resultset"""
+        objects = self[registry][cid]
+        assert len(objects) == 1, objects
+        return objects[0].selected(*args, **kwargs)
+
+    # methods for explicit (un)registration ###################################
+
+#     def clear(self, key):
+#         regname, oid = key.split('.')
+#         self[regname].pop(oid, None)
+    def register_all(self, objects, modname, butclasses=()):
+        for obj in objects:
+            try:
+                if obj.__module__ != modname or obj in butclasses:
+                    continue
+                oid = obj.id
+            except AttributeError:
+                continue
+            if oid:
+                self.register(obj)
+                
+    def register(self, obj, registryname=None, oid=None, clear=False):
+        """base method to add an object in the registry"""
+        assert not '__abstract__' in obj.__dict__
+        registryname = registryname or obj.__registry__
+        oid = oid or obj.id
+        assert oid
+        registry = self._registries.setdefault(registryname, {})
+        if clear:
+            vobjects = registry[oid] =  []
+        else:
+            vobjects = registry.setdefault(oid, [])
+        # registered() is technically a classmethod but is not declared
+        # as such because we need to compose registered in some cases
+        vobject = obj.registered.im_func(obj, self)
+        assert not vobject in vobjects
+        assert callable(vobject.__select__), vobject
+        vobjects.append(vobject)
+        try:
+            vname = vobject.__name__
+        except AttributeError:
+            vname = vobject.__class__.__name__
+        self.debug('registered vobject %s in registry %s with id %s',
+                   vname, registryname, oid)
+        # automatic reloading management
+        self._registered['%s.%s' % (obj.__module__, oid)] = obj
+
+    def unregister(self, obj, registryname=None):
+        registryname = registryname or obj.__registry__
+        registry = self.registry(registryname)
+        removed_id = obj.classid()
+        for registered in registry[obj.id]:
+            # use classid() to compare classes because vreg will probably
+            # have its own version of the class, loaded through execfile
+            if registered.classid() == removed_id:
+                # XXX automatic reloading management
+                try:
+                    registry[obj.id].remove(registered)
+                except ValueError:
+                    self.warning('can\'t remove %s, no id %s in the %s registry',
+                                 removed_id, obj.id, registryname)
+                except ValueError:
+                    self.warning('can\'t remove %s, not in the %s registry with id %s',
+                                 removed_id, registryname, obj.id)
+#                 else:
+#                     # if objects is empty, remove oid from registry
+#                     if not registry[obj.id]:
+#                         del regcontent[oid]                    
+                break
+    
+    def register_and_replace(self, obj, replaced, registryname=None):
+        if hasattr(replaced, 'classid'):
+            replaced = replaced.classid()
+        registryname = registryname or obj.__registry__
+        registry = self.registry(registryname)
+        registered_objs = registry[obj.id]
+        for index, registered in enumerate(registered_objs):
+            if registered.classid() == replaced:
+                del registry[obj.id][index]
+                break
+        self.register(obj, registryname=registryname)
+
+    # dynamic selection methods ###############################################
+    
     def select(self, vobjects, *args, **kwargs):
         """return an instance of the most specific object according
         to parameters
@@ -339,21 +296,23 @@
         """
         score, winners = 0, []
         for vobject in vobjects:
-            vobjectscore = vobject.__select__(*args, **kwargs)
+            vobjectscore = vobject.__select__(vobject, *args, **kwargs)
             if vobjectscore > score:
                 score, winners = vobjectscore, [vobject]
             elif vobjectscore > 0 and vobjectscore == score:
                 winners.append(vobject)
         if not winners:
             raise NoSelectableObject('args: %s\nkwargs: %s %s'
-                                     % (args, kwargs.keys(), [repr(v) for v in vobjects]))
+                                     % (args, kwargs.keys(),
+                                        [repr(v) for v in vobjects]))
         if len(winners) > 1:
             if self.config.mode == 'installed':
                 self.error('select ambiguity, args: %s\nkwargs: %s %s',
                            args, kwargs.keys(), [repr(v) for v in winners])
             else:
                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
-                                % (args, kwargs.keys(), [repr(v) for v in winners]))
+                                % (args, kwargs.keys(),
+                                   [repr(v) for v in winners]))
         winner = winners[0]
         # return the result of the .selected method of the vobject
         return winner.selected(*args, **kwargs)
@@ -372,15 +331,8 @@
     def select_object(self, registry, cid, *args, **kwargs):
         """return the most specific component according to the resultset"""
         return self.select(self.registry_objects(registry, cid), *args, **kwargs)
-
-    def object_by_id(self, registry, cid, *args, **kwargs):
-        """return the most specific component according to the resultset"""
-        objects = self[registry][cid]
-        assert len(objects) == 1, objects
-        return objects[0].selected(*args, **kwargs)
     
     # intialization methods ###################################################
-
     
     def register_objects(self, path, force_reload=None):
         if force_reload is None:
@@ -471,15 +423,18 @@
         return True
 
     def load_module(self, module):
-        registered = {}
-        self.info('loading %s', module)
-        for objname, obj in vars(module).items():
-            if objname.startswith('_'):
-                continue
-            self.load_ancestors_then_object(module.__name__, registered, obj)
-        return registered
+        self._registered = {}
+        if hasattr(module, 'registration_callback'):
+            module.registration_callback(self)
+        else:
+            self.info('loading %s', module)
+            for objname, obj in vars(module).items():
+                if objname.startswith('_'):
+                    continue
+                self.load_ancestors_then_object(module.__name__, obj)
+        return self._registered
     
-    def load_ancestors_then_object(self, modname, registered, obj):
+    def load_ancestors_then_object(self, modname, obj):
         # skip imported classes
         if getattr(obj, '__module__', None) != modname:
             return
@@ -490,11 +445,11 @@
         except TypeError:
             return
         objname = '%s.%s' % (modname, obj.__name__)
-        if objname in registered:
+        if objname in self._registered:
             return
-        registered[objname] = obj
+        self._registered[objname] = obj
         for parent in obj.__bases__:
-            self.load_ancestors_then_object(modname, registered, parent)
+            self.load_ancestors_then_object(modname, parent)
         self.load_object(obj)
             
     def load_object(self, obj):
@@ -505,41 +460,277 @@
                 raise
             self.exception('vobject %s registration failed: %s', obj, ex)
         
+    # old automatic registration XXX deprecated ###############################
+    
+    def register_vobject_class(self, cls):
+        """handle vobject class registration
+        
+        vobject class with __abstract__ == True in their local dictionnary or
+        with a name starting starting by an underscore are not registered.
+        Also a vobject class needs to have __registry__ and id attributes set
+        to a non empty string to be registered.
+
+        Registration is actually handled by vobject's registerer.
+        """
+        if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
+            or not cls.__registry__ or not cls.id):
+            return
+        regname = cls.__registry__
+        if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
+            return
+        registry = self._registries.setdefault(regname, {})
+        vobjects = registry.setdefault(cls.id, [])
+        registerer = cls.__registerer__(self, cls)
+        cls = registerer.do_it_yourself(vobjects)
+        if cls:
+            self.register(cls)
+            
+    def unregister_module_vobjects(self, modname):
+        """removes registered objects coming from a given module
+
+        returns a dictionnary classid/class of all classes that will need
+        to be updated after reload (i.e. vobjects referencing classes defined
+        in the <modname> module)
+        """
+        unregistered = {}
+        # browse each registered object
+        for registry, objdict in self.items():
+            for oid, objects in objdict.items():
+                for obj in objects[:]:
+                    objname = obj.classid()
+                    # if the vobject is defined in this module, remove it
+                    if objname.startswith(modname):
+                        unregistered[objname] = obj
+                        objects.remove(obj)
+                        self.debug('unregistering %s in %s registry',
+                                  objname, registry)
+                    # if not, check if the vobject can be found in baseclasses
+                    # (because we also want subclasses to be updated)
+                    else:
+                        if not isinstance(obj, type):
+                            obj = obj.__class__
+                        for baseclass in obj.__bases__:
+                            if hasattr(baseclass, 'classid'):
+                                baseclassid = baseclass.classid()
+                                if baseclassid.startswith(modname):
+                                    unregistered[baseclassid] = baseclass
+                # update oid entry
+                if objects:
+                    objdict[oid] = objects
+                else:
+                    del objdict[oid]
+        return unregistered
+
+    def update_registered_subclasses(self, oldnew_mapping):
+        """updates subclasses of re-registered vobjects
+
+        if baseviews.PrimaryView is changed, baseviews.py will be reloaded
+        automatically and the new version of PrimaryView will be registered.
+        But all existing subclasses must also be notified of this change, and
+        that's what this method does
+
+        :param oldnew_mapping: a dict mapping old version of a class to
+                               the new version
+        """
+        # browse each registered object
+        for objdict in self.values():
+            for objects in objdict.values():
+                for obj in objects:
+                    if not isinstance(obj, type):
+                        obj = obj.__class__
+                    # build new baseclasses tuple
+                    newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
+                                     for baseclass in obj.__bases__)
+                    # update obj's baseclasses tuple (__bases__) if needed
+                    if newbases != obj.__bases__:
+                        self.debug('updating %s.%s base classes',
+                                  obj.__module__, obj.__name__)
+                        obj.__bases__ = newbases
+        
 # init logging 
 set_log_methods(VObject, getLogger('cubicweb'))
 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
 set_log_methods(registerer, getLogger('cubicweb.registration'))
 
 
-# advanced selector building functions ########################################
+# selector base classes and operations ########################################
+
+class Selector(object):
+    """base class for selector classes providing implementation
+    for operators ``&`` and ``|``
+
+    This class is only here to give access to binary operators, the
+    selector logic itself should be implemented in the __call__ method
+
+
+    a selector is called to help choosing the correct object for a
+    particular context by returning a score (`int`) telling how well
+    the class given as first argument apply to the given context.
+
+    0 score means that the class doesn't apply.
+    """
+
+    @property
+    def func_name(self):
+        # backward compatibility
+        return self.__class__.__name__
+
+    def search_selector(self, selector):
+        """search for the given selector or selector instance in the selectors
+        tree. Return it of None if not found
+        """
+        if self is selector:
+            return self
+        if isinstance(selector, type) and isinstance(self, selector):
+            return self
+        return None
+
+    def __str__(self):
+        return self.__class__.__name__
+    
+    def __and__(self, other):
+        return AndSelector(self, other)
+    def __rand__(self, other):
+        return AndSelector(other, self)
+
+    def __or__(self, other):
+        return OrSelector(self, other)
+    def __ror__(self, other):
+        return OrSelector(other, self)
+
+    def __invert__(self):
+        return NotSelector(self)
+    
+    # XXX (function | function) or (function & function) not managed yet
+
+    def __call__(self, cls, *args, **kwargs):
+        return NotImplementedError("selector %s must implement its logic "
+                                   "in its __call__ method" % self.__class__)
+
+class MultiSelector(Selector):
+    """base class for compound selector classes"""
+    
+    def __init__(self, *selectors):
+        self.selectors = self.merge_selectors(selectors)
+
+    def __str__(self):
+        return '%s(%s)' % (self.__class__.__name__,
+                           ','.join(str(s) for s in self.selectors))
 
-def chainall(*selectors):
-    """return a selector chaining given selectors. If one of
-    the selectors fail, selection will fail, else the returned score
-    will be the sum of each selector'score
+    @classmethod
+    def merge_selectors(cls, selectors):
+        """deal with selector instanciation when necessary and merge
+        multi-selectors if possible:
+
+        AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
+        ==> AndSelector(sel1, sel2, sel3, sel4)
+        """
+        merged_selectors = []
+        for selector in selectors:
+            try:
+                selector = _instantiate_selector(selector)
+            except:
+                pass
+            #assert isinstance(selector, Selector), selector
+            if isinstance(selector, cls):
+                merged_selectors += selector.selectors
+            else:
+                merged_selectors.append(selector)
+        return merged_selectors
+
+    def search_selector(self, selector):
+        """search for the given selector or selector instance in the selectors
+        tree. Return it of None if not found
+        """
+        for childselector in self.selectors:
+            if childselector is selector:
+                return childselector
+            found = childselector.search_selector(selector)
+            if found is not None:
+                return found
+        return None
+
+    
+def objectify_selector(selector_func):
+    """convenience decorator for simple selectors where a class definition
+    would be overkill::
+
+        @objectify_selector
+        def yes(cls, *args, **kwargs):
+            return 1
+        
     """
-    assert selectors
-    def selector(cls, *args, **kwargs):
+    return type(selector_func.__name__, (Selector,),
+                {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
+
+def _instantiate_selector(selector):
+    """ensures `selector` is a `Selector` instance
+    
+    NOTE: This should only be used locally in build___select__()
+    XXX: then, why not do it ??
+    """
+    if isinstance(selector, types.FunctionType):
+        return objectify_selector(selector)()
+    if isinstance(selector, type) and issubclass(selector, Selector):
+        return selector()
+    return selector
+
+
+class AndSelector(MultiSelector):
+    """and-chained selectors (formerly known as chainall)"""
+    def __call__(self, cls, *args, **kwargs):
         score = 0
-        for selector in selectors:
+        for selector in self.selectors:
             partscore = selector(cls, *args, **kwargs)
             if not partscore:
                 return 0
             score += partscore
         return score
+
+
+class OrSelector(MultiSelector):
+    """or-chained selectors (formerly known as chainfirst)"""
+    def __call__(self, cls, *args, **kwargs):
+        for selector in self.selectors:
+            partscore = selector(cls, *args, **kwargs)
+            if partscore:
+                return partscore
+        return 0
+
+class NotSelector(Selector):
+    """negation selector"""
+    def __init__(self, selector):
+        self.selector = selector
+
+    def __call__(self, cls, *args, **kwargs):
+        score = self.selector(cls, *args, **kwargs)
+        return int(not score)
+
+    def __str__(self):
+        return 'NOT(%s)' % super(NotSelector, self).__str__()
+
+
+# XXX bw compat functions #####################################################
+
+def chainall(*selectors, **kwargs):
+    """return a selector chaining given selectors. If one of
+    the selectors fail, selection will fail, else the returned score
+    will be the sum of each selector'score
+    """
+    assert selectors
+    # XXX do we need to create the AndSelector here, a tuple might be enough
+    selector = AndSelector(*selectors)
+    if 'name' in kwargs:
+        selector.__name__ = kwargs['name']
     return selector
 
-def chainfirst(*selectors):
+def chainfirst(*selectors, **kwargs):
     """return a selector chaining given selectors. If all
     the selectors fail, selection will fail, else the returned score
     will be the first non-zero selector score
     """
     assert selectors
-    def selector(cls, *args, **kwargs):
-        for selector in selectors:
-            partscore = selector(cls, *args, **kwargs)
-            if partscore:
-                return partscore
-        return 0
+    selector = OrSelector(*selectors)
+    if 'name' in kwargs:
+        selector.__name__ = kwargs['name']
     return selector
-
--- a/web/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -3,7 +3,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -35,12 +35,5 @@
 from logging import getLogger
 LOGGER = getLogger('cubicweb.web')
 
+# XXX deprecated
 FACETTES = set()
-
-
-## FACETTES = set( (
-##     # (relation, role, target's attribute)
-##     ('created_by', 'subject', 'login'),
-##     ('in_group', 'subject', 'name'),
-##     ('in_state', 'subject', 'name'),
-##     ))
--- a/web/action.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/action.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,17 +1,18 @@
 """abstract action classes for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import add_etype_selector, \
-     match_search_state, searchstate_accept_one, \
-     searchstate_accept_one_but_etype
-    
+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)
+from cubicweb.appobject import AppRsetObject
+from cubicweb.common.registerers import accepts_registerer
+
 _ = unicode
 
 
@@ -20,10 +21,9 @@
     request search state. 
     """
     __registry__ = 'actions'
-    __registerer__ = action_registerer
-    __selectors__ = (match_search_state,)
-    # by default actions don't appear in link search mode
-    search_states = ('normal',) 
+    __registerer__ = accepts_registerer
+    __select__ = yes()
+    
     property_defs = {
         'visible':  dict(type='Boolean', default=True,
                          help=_('display the action or not')),
@@ -37,53 +37,6 @@
     site_wide = True # don't want user to configuration actions eproperties
     category = 'moreactions'
     
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        user = req.user
-        action = cls.schema_action
-        if row is None:
-            score = 0
-            need_local_check = [] 
-            geteschema = cls.schema.eschema
-            for etype in rset.column_types(0):
-                accepted = cls.accept(user, etype)
-                if not accepted:
-                    return 0
-                if action:
-                    eschema = geteschema(etype)
-                    if not user.matching_groups(eschema.get_groups(action)):
-                        if eschema.has_local_role(action):
-                            # have to ckeck local roles
-                            need_local_check.append(eschema)
-                            continue
-                        else:
-                            # even a local role won't be enough
-                            return 0
-                score += accepted
-            if need_local_check:
-                # check local role for entities of necessary types
-                for i, row in enumerate(rset):
-                    if not rset.description[i][0] in need_local_check:
-                        continue
-                    if not cls.has_permission(rset.get_entity(i, 0), action):
-                        return 0
-                    score += 1
-            return score
-        col = col or 0
-        etype = rset.description[row][col]
-        score = cls.accept(user, etype)
-        if score and action:
-            if not cls.has_permission(rset.get_entity(row, col), action):
-                return 0
-        return score
-    
-    @classmethod
-    def has_permission(cls, entity, action):
-        """defined in a separated method to ease overriding (see ModifyAction
-        for instance)
-        """
-        return entity.has_perm(action)
-    
     def url(self):
         """return the url associated with this action"""
         raise NotImplementedError
@@ -94,6 +47,7 @@
         if self.category:
             return 'box' + self.category.capitalize()
 
+
 class UnregisteredAction(Action):
     """non registered action used to build boxes. Unless you set them
     explicitly, .vreg and .schema attributes at least are None.
@@ -111,111 +65,32 @@
         return self._path
 
 
-class AddEntityAction(Action):
-    """link to the entity creation form. Concrete class must set .etype and
-    may override .vid
-    """
-    __selectors__ = (add_etype_selector, match_search_state)
-    vid = 'creation'
-    etype = None
-    
-    def url(self):
-        return self.build_url(vid=self.vid, etype=self.etype)
-
-
-class EntityAction(Action):
-    """an action for an entity. By default entity actions are only
-    displayable on single entity result if accept match.
-    """
-    __selectors__ = (searchstate_accept_one,)
-    schema_action = None
-    condition = None
-    
-    @classmethod
-    def accept(cls, user, etype):
-        score = super(EntityAction, cls).accept(user, etype)
-        if not score:
-            return 0
-        # check if this type of entity has the necessary relation
-        if hasattr(cls, 'rtype') and not cls.relation_possible(etype):
-            return 0
-        return score
-
-    
-class LinkToEntityAction(EntityAction):
+class LinkToEntityAction(Action):
     """base class for actions consisting to create a new object
     with an initial relation set to an entity.
     Additionaly to EntityAction behaviour, this class is parametrized
     using .etype, .rtype and .target attributes to check if the
     action apply and if the logged user has access to it
     """
-    etype = None
-    rtype = None
-    target = None
+    __select__ = (match_search_state('normal') & one_line_rset()
+                  & partial_relation_possible(action='add')
+                  & partial_may_add_relation())
+    registered = accepts_compat(Action.registered)
+    
     category = 'addrelated'
-
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        entity = rset.get_entity(row or 0, col or 0)
-        # check if this type of entity has the necessary relation
-        if hasattr(cls, 'rtype') and not cls.relation_possible(entity.e_schema):
-            return 0
-        score = cls.accept(req.user, entity.e_schema)
-        if not score:
-            return 0
-        if not cls.check_perms(req, entity):
-            return 0
-        return score
-
-    @classmethod
-    def check_perms(cls, req, entity):
-        if not cls.check_rtype_perm(req, entity):
-            return False
-        # XXX document this:
-        # if user can create the relation, suppose it can create the entity
-        # this is because we usually can't check "add" permission before the
-        # entity has actually been created, and schema security should be
-        # defined considering this
-        #if not cls.check_etype_perm(req, entity):
-        #    return False
-        return True
-        
-    @classmethod
-    def check_etype_perm(cls, req, entity):
-        eschema = cls.schema.eschema(cls.etype)
-        if not eschema.has_perm(req, 'add'):
-            #print req.user.login, 'has no add perm on etype', cls.etype
-            return False
-        #print 'etype perm ok', cls
-        return True
-
-    @classmethod
-    def check_rtype_perm(cls, req, entity):
-        rschema = cls.schema.rschema(cls.rtype)
-        # cls.target is telling us if we want to add the subject or object of
-        # the relation
-        if cls.target == 'subject':
-            if not rschema.has_perm(req, 'add', toeid=entity.eid):
-                #print req.user.login, 'has no add perm on subject rel', cls.rtype, 'with', entity
-                return False
-        elif not rschema.has_perm(req, 'add', fromeid=entity.eid):
-            #print req.user.login, 'has no add perm on object rel', cls.rtype, 'with', entity
-            return False
-        #print 'rtype perm ok', cls
-        return True
-            
+                
     def url(self):
         current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
-        linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, self.target)
+        linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self))
         return self.build_url(vid='creation', etype=self.etype,
                               __linkto=linkto,
                               __redirectpath=current_entity.rest_path(), # should not be url quoted!
                               __redirectvid=self.req.form.get('__redirectvid', ''))
 
-
-class LinkToEntityAction2(LinkToEntityAction):
-    """LinkToEntity action where the action is not usable on the same
-    entity's type as the one refered by the .etype attribute
+class EntityAction(Action):
+    """DEPRECATED / BACKWARD COMPAT
     """
-    __selectors__ = (searchstate_accept_one_but_etype,)
+    registered = deprecate(condition_compat(accepts_compat(Action.registered)),
+                           msg='EntityAction is deprecated, use Action with '
+                           'appropriate selectors')
     
--- a/web/application.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/application.py	Tue Apr 07 09:30:23 2009 +0200
@@ -14,17 +14,16 @@
 from cubicweb import set_log_methods
 from cubicweb import (ValidationError, Unauthorized, AuthenticationError,
                    NoSelectableObject, RepositoryError)
-from cubicweb.cwconfig import CubicWebConfiguration
 from cubicweb.cwvreg import CubicWebRegistry
 from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, NotFound,
                        RemoteCallFailed, ExplicitLogin, InvalidSession)
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
 
 # make session manager available through a global variable so the debug view can
 # print information about web session
 SESSION_MANAGER = None
 
-class AbstractSessionManager(SingletonComponent):
+class AbstractSessionManager(Component):
     """manage session data associated to a session identifier"""
     id = 'sessionmanager'
     
@@ -87,7 +86,7 @@
         raise NotImplementedError()
 
 
-class AbstractAuthenticationManager(SingletonComponent):
+class AbstractAuthenticationManager(Component):
     """authenticate user associated to a request and check session validity"""
     id = 'authmanager'
 
@@ -384,9 +383,11 @@
             if tb:
                 req.data['excinfo'] = excinfo
             req.form['vid'] = 'error'
-            content = self.vreg.main_template(req, 'main')
+            errview = self.vreg.select_view('error', req, None)
+            template = self.main_template_id(req)
+            content = self.vreg.main_template(req, template, view=errview)
         except:
-            content = self.vreg.main_template(req, 'error')
+            content = self.vreg.main_template(req, 'error-template')
         raise StatusResponse(500, content)
     
     def need_login_content(self, req):
@@ -396,10 +397,17 @@
         return self.vreg.main_template(req, 'loggedout')
     
     def notfound_content(self, req):
-        template = req.property_value('ui.main-template') or 'main'
         req.form['vid'] = '404'
-        return self.vreg.main_template(req, template)
+        view = self.vreg.select_view('404', req, None)
+        template = self.main_template_id(req)
+        return self.vreg.main_template(req, template, view=view)
 
+    def main_template_id(self, req):
+        template = req.property_value('ui.main-template')
+        if template not in self.vreg.registry('views') :
+            template = 'main-template'
+        return template
+        
 
 set_log_methods(CubicWebPublisher, LOGGER)
 set_log_methods(CookieSessionHandler, LOGGER)
--- a/web/box.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/box.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,24 +1,19 @@
 """abstract box classes for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.decorators import cached
 from logilab.mtconverter import html_escape
 
-from cubicweb import Unauthorized, role as get_role
-from cubicweb.common.registerers import (
-    accepts_registerer, extresources_registerer,
-    etype_rtype_priority_registerer)
-from cubicweb.common.selectors import (
-    etype_rtype_selector, one_line_rset, accept, has_relation,
-    primary_view, match_context_prop, has_related_entities,
-    _rql_condition)
-from cubicweb.common.view import Template
-from cubicweb.common.appobject import ReloadableMixIn
+from cubicweb import Unauthorized, role as get_role, target as get_target
+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)
+from cubicweb.view import View, ReloadableMixIn
 
 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
                                       RawBoxItem, BoxSeparator)
@@ -27,7 +22,7 @@
 _ = unicode
 
 
-class BoxTemplate(Template):
+class BoxTemplate(View):
     """base template for boxes, usually a (contextual) list of possible
     
     actions. Various classes attributes may be used to control the box
@@ -42,7 +37,8 @@
         box.render(self.w)
     """
     __registry__ = 'boxes'
-    __selectors__ = Template.__selectors__ + (match_context_prop,)
+    __select__ = match_context_prop()
+    registered = classmethod(require_group_compat(View.registered))
     
     categories_in_order = ()
     property_defs = {
@@ -105,8 +101,7 @@
     according to application schema and display according to connected
     user's rights) and rql attributes
     """
-    __registerer__ = etype_rtype_priority_registerer
-    __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
+#XXX    __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
 
     rql  = None
     
@@ -139,23 +134,11 @@
         return (self.rql, {'x': self.req.user.eid}, 'x')
     
 
-class ExtResourcesBoxTemplate(BoxTemplate):
-    """base class for boxes displaying external resources such as the RSS logo.
-    It should list necessary resources with the .need_resources attribute.
-    """
-    __registerer__ = extresources_registerer
-    need_resources = ()
-
-
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
-    __registerer__ = accepts_registerer
-    __selectors__ = (one_line_rset, primary_view,
-                     match_context_prop, etype_rtype_selector,
-                     has_relation, accept, _rql_condition)
-    accepts = ('Any',)
+    __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+    registered = accepts_compat(has_relation_compat(condition_compat(BoxTemplate.registered)))
     context = 'incontext'
-    condition = None
     
     def call(self, row=0, col=0, **kwargs):
         """classes inheriting from EntityBoxTemplate should define cell_call"""
@@ -163,8 +146,8 @@
 
 
 class RelatedEntityBoxTemplate(EntityBoxTemplate):
-    __selectors__ = EntityBoxTemplate.__selectors__ + (has_related_entities,)
-    
+    __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
+
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
         limit = self.req.property_value('navigation.related-limit') + 1
@@ -195,18 +178,12 @@
 
     def div_id(self):
         return self.id
-
-    @cached
-    def xtarget(self):
-        if self.target == 'subject':
-            return 'object', 'subject'
-        return 'subject', 'object'
         
     def box_item(self, entity, etarget, rql, label):
         """builds HTML link to edit relation between `entity` and `etarget`
         """
-        x, target = self.xtarget()
-        args = {x[0] : entity.eid, target[0] : etarget.eid}
+        role, target = get_role(self), get_target(self)
+        args = {role[0] : entity.eid, target[0] : etarget.eid}
         url = self.user_rql_callback((rql, args))
         # for each target, provide a link to edit the relation
         label = u'[<a href="%s">%s</a>] %s' % (url, label,
@@ -233,10 +210,9 @@
         if etype is not defined on the Box's class, the default
         behaviour is to use the entity's appropraite vocabulary function
         """
-        x, target = self.xtarget()
         # use entity.unrelated if we've been asked for a particular etype
         if hasattr(self, 'etype'):
-            return entity.unrelated(self.rtype, self.etype, x).entities()
+            return entity.unrelated(self.rtype, self.etype, get_role(self)).entities()
         # in other cases, use vocabulary functions
         entities = []
         for _, eid in entity.vocabulary(self.rtype, x):
@@ -246,6 +222,5 @@
         return entities
         
     def related_entities(self, entity):
-        x, target = self.xtarget()
-        return entity.related(self.rtype, x, entities=True)
+        return entity.related(self.rtype, get_role(self), entities=True)
 
--- a/web/component.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/component.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,25 +1,26 @@
 """abstract component class and base components definition for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.appobject import Component, SingletonComponent
-from cubicweb.common.utils import merge_dicts
-from cubicweb.common.view import VComponent, SingletonVComponent
-from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import (paginated_rset, one_line_rset,
-                                       rql_condition, accept, primary_view,
-                                       match_context_prop, has_relation,
-                                       etype_rtype_selector)
-from cubicweb.common.uilib import html_escape
+from logilab.common.deprecation import class_renamed
+from logilab.mtconverter import html_escape
+
+from cubicweb import role
+from cubicweb.utils import merge_dicts
+from cubicweb.view import View, 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)
+from cubicweb.common.registerers import accepts_registerer
 
 _ = unicode
 
-
-class EntityVComponent(VComponent):
+class EntityVComponent(Component):
     """abstract base class for additinal components displayed in content
     headers and footer according to:
     
@@ -31,11 +32,9 @@
     """
     
     __registry__ = 'contentnavigation'
-    __registerer__ = action_registerer    
-    __selectors__ = (one_line_rset, primary_view,
-                     match_context_prop, etype_rtype_selector,
-                     has_relation, accept,
-                     rql_condition)
+    __registerer__ = accepts_registerer    
+    __select__ = one_line_rset() & primary_view() & match_context_prop()
+    registered = accepts_compat(has_relation_compat(condition_compat(View.registered)))
     
     property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
@@ -51,21 +50,20 @@
                             help=_('html class of the component')),
     }
     
-    accepts = ('Any',)
     context = 'navcontentbottom' # 'footer' | 'header' | 'incontext'
-    condition = None
     
-    def call(self, view):
+    def call(self, view=None):
         return self.cell_call(0, 0, view)
 
-    def cell_call(self, row, col, view):
+    def cell_call(self, row, col, view=None):
         raise NotImplementedError()
 
     
-class NavigationComponent(VComponent):
+class NavigationComponent(Component):
     """abstract base class for navigation components"""
-    __selectors__ = (paginated_rset,)
     id = 'navigation'
+    __select__ = paginated_rset()
+    
     page_size_property = 'navigation.page-size'
     start_param = '__start'
     stop_param = '__stop'
@@ -73,19 +71,6 @@
     selected_page_link_templ = u'<span class="selectedSlice"><a href="%s" title="%s">%s</a></span>'
     previous_page_link_templ = next_page_link_templ = page_link_templ
     no_previous_page_link = no_next_page_link = u''
-
-    @classmethod
-    def selected(cls, req, rset, row=None, col=None, page_size=None, **kwargs):
-        """by default web app objects are usually instantiated on
-        selection according to a request, a result set, and optional
-        row and col
-        """
-        instance = super(NavigationComponent, cls).selected(req, rset, row, col, **kwargs)
-        if page_size is not None:
-            instance.page_size = page_size
-        elif 'page_size' in req.form:
-            instance.page_size = int(req.form['page_size'])
-        return instance
     
     def __init__(self, req, rset):
         super(NavigationComponent, self).__init__(req, rset)
@@ -96,8 +81,14 @@
         try:
             return self._page_size
         except AttributeError:
-            self._page_size = self.req.property_value(self.page_size_property)
-            return self._page_size
+            page_size = self.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'])
+                else:
+                    page_size = self.req.property_value(self.page_size_property)
+            self._page_size = page_size
+            return page_size
 
     def set_page_size(self, page_size):
         self._page_size = page_size
@@ -151,25 +142,19 @@
 
 class RelatedObjectsVComponent(EntityVComponent):
     """a section to display some related entities"""
-    __selectors__ = (one_line_rset, primary_view,
-                     etype_rtype_selector, has_relation,
-                     match_context_prop, accept)
+    __select__ = EntityVComponent.__select__ & partial_has_related_entities()
+    
     vid = 'list'
-
+    
     def rql(self):
-        """override this method if you want to use a custom rql query.
-        """
+        """override this method if you want to use a custom rql query"""
         return None
     
     def cell_call(self, row, col, view=None):
         rql = self.rql()
         if rql is None:
             entity = self.rset.get_entity(row, col)
-            if self.target == 'object':
-                role = 'subject'
-            else:
-                role = 'object'
-            rset = entity.related(self.rtype, role)
+            rset = entity.related(self.rtype, role(self))
         else:
             eid = self.rset[row][col]
             rset = self.req.execute(self.rql(), {'x': eid}, 'x')
@@ -178,3 +163,10 @@
         self.w(u'<div class="%s">' % self.div_class())
         self.wview(self.vid, rset, title=self.req._(self.title).capitalize())
         self.w(u'</div>')
+
+
+VComponent = class_renamed('VComponent', Component,
+                           'VComponent is deprecated, use Component')
+SingletonVComponent = class_renamed('SingletonVComponent', Component,
+                                    'SingletonVComponent is deprecated, use '
+                                    'Component and explicit registration control')
--- a/web/controller.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/controller.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,17 +2,18 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from mx.DateTime import strptime, Error as MxDTError, TimeDelta
+from datetime import timedelta
 
 from cubicweb import typed_eid
+from cubicweb.utils import strptime
 from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.selectors import match_user_group
-from cubicweb.common.appobject import AppObject
+from cubicweb.selectors import yes, require_group_compat
+from cubicweb.appobject import AppObject
 from cubicweb.web import LOGGER, Redirect, RequestError
 
 
@@ -21,7 +22,7 @@
                          ('__redirectpath', '__redirectpath'),
                          ('__redirectparams', '__redirectparams'),
                          )
-NAV_FORM_PARAMETERS = [fp for ap, fp in NAVIGATION_PARAMETERS]
+NAV_FORM_PARAMETERS = tuple(fp for ap, fp in NAVIGATION_PARAMETERS)
 
 def redirect_params(form):
     """transform redirection parameters into navigation parameters
@@ -68,8 +69,8 @@
     """
     __registry__ = 'controllers'
     __registerer__ = priority_registerer
-    __selectors__ = (match_user_group,)
-    require_groups = ()
+    __select__ = yes()
+    registered = require_group_compat(AppObject.registered)
 
     def __init__(self, *args, **kwargs):
         super(Controller, self).__init__(*args, **kwargs)
@@ -84,6 +85,17 @@
         raise NotImplementedError
 
     # generic methods useful for concret implementations ######################
+
+    def process_rql(self, rql):
+        """execute rql if specified"""
+        if rql:
+            self.ensure_ro_rql(rql)
+            if not isinstance(rql, unicode):
+                rql = unicode(rql, self.req.encoding)
+            pp = self.vreg.select_component('magicsearch', self.req)
+            self.rset = pp.process_query(rql, self.req)
+            return self.rset
+        return None
     
     def check_expected_params(self, params):
         """check that the given list of parameters are specified in the form
@@ -107,20 +119,20 @@
             format = self.req.property_value('ui.datetime-format')
             try:
                 return strptime(value, format)
-            except MxDTError:
+            except:
                 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 TimeDelta(date.hour, date.minute, date.second)
-            except MxDTError:
+                return timedelta(0, date.hour *60*60 + date.minute*60 + date.second, 0)
+            except:
                 raise ValueError('can\'t parse %r (expected %s)' % (value, format))
         try:
             format = self.req.property_value('ui.date-format')
             return strptime(value, format)
-        except MxDTError:
+        except:
             raise ValueError('can\'t parse %r (expected %s)' % (value, format))
 
 
--- a/web/data/cubicweb.ajax.js	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Tue Apr 07 09:30:23 2009 +0200
@@ -9,7 +9,18 @@
 
 var JSON_BASE_URL = baseuri() + 'json?';
 
-function postAjaxLoad(node) {
+/*
+ * inspect dom response, search for a <div class="ajaxHtmlHead"> node and
+ * put its content into the real document's head.
+ * This enables dynamic css and js loading and is used by replacePageChunk
+ */
+function loadAjaxHtmlHead(node) {
+    jQuery(node).find('div.ajaxHtmlHead').appendTo(jQuery('head'));
+}
+
+function postAjaxLoad(node, req) {
+    // addStylesheets(evalJSON(req.getResponseHeader('X-Cubicweb-Stylesheets') || '[]'));
+    loadAjaxHtmlHead(node);
     // find sortable tables if there are some
     if (typeof(Sortable) != 'undefined') {
 	Sortable.sortTables(node);
@@ -31,7 +42,6 @@
 
 // cubicweb loadxhtml plugin to make jquery handle xhtml response
 jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
-
     var ajax = null;
     if (reqtype == 'post') {
 	ajax = jQuery.post;
@@ -41,6 +51,7 @@
     if (this.size() > 1) {
 	log('loadxhtml was called with more than one element');
     }
+    var node = this.get(0); // only consider the first element
     mode = mode || 'replace';
     var callback = null;
     if (data && data.callback) {
@@ -62,7 +73,7 @@
 	} else if (mode == 'append') {
 	    jQuery(node).append(domnode);
 	}
-	postAjaxLoad(node);
+	postAjaxLoad(node, req);
 	while (jQuery.isFunction(callback)) {
 	    callback = callback.apply(this, [domnode]);
 	}
--- a/web/facet.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/facet.py	Tue Apr 07 09:30:23 2009 +0200
@@ -19,10 +19,9 @@
 from rql import parse, nodes
 
 from cubicweb import Unauthorized, typed_eid
-from cubicweb.common.selectors import match_context_prop, one_has_relation
+from cubicweb.selectors import match_context_prop, partial_relation_possible
+from cubicweb.appobject import AppRsetObject
 from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.utils import AcceptMixIn
 from cubicweb.web.htmlwidgets import HTMLWidget
 
 ## rqlst manipulation functions used by facets ################################
@@ -119,12 +118,17 @@
     return None
 
 def _add_rtype_relation(rqlst, mainvar, rtype, role):
+    """add a relation relying `mainvar` to entities linked by the `rtype`
+    relation (where `mainvar` has `role`)
+
+    return the inserted variable for linked entities.
+    """
     newvar = rqlst.make_variable()
     if role == 'object':
-        rel = rqlst.add_relation(newvar, rtype, mainvar)
+        rqlst.add_relation(newvar, rtype, mainvar)
     else:
-        rel = rqlst.add_relation(mainvar, rtype, newvar)
-    return newvar, rel
+        rqlst.add_relation(mainvar, rtype, newvar)
+    return newvar
 
 def _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role):
     """prepare a syntax tree to generate a filter vocabulary rql using the given
@@ -134,11 +138,11 @@
     * add the new variable to GROUPBY clause if necessary
     * add the new variable to the selection
     """
-    newvar, rel = _add_rtype_relation(rqlst, mainvar, rtype, role)
+    newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)
     if rqlst.groupby:
         rqlst.add_group_var(newvar)
     rqlst.add_selected(newvar)
-    return newvar, rel
+    return newvar
 
 def _remove_relation(rqlst, rel, var):
     """remove a constraint relation from the syntax tree"""
@@ -171,10 +175,10 @@
                                 sortfuncname=None, sortasc=True):
     """modify a syntax tree to retrieve only relevant attribute `attr` of `var`"""
     _cleanup_rqlst(rqlst, mainvar)
-    var, mainrel = _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role)
+    var = _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role)
     # not found, create one
     attrvar = rqlst.make_variable()
-    attrrel = rqlst.add_relation(var, attrname, attrvar)
+    rqlst.add_relation(var, attrname, attrvar)
     # if query is grouped, we have to add the attribute variable
     if rqlst.groupby:
         if not attrvar in rqlst.groupby:
@@ -234,7 +238,7 @@
 
 
 ## base facet classes #########################################################
-class AbstractFacet(AcceptMixIn, AppRsetObject):
+class AbstractFacet(AppRsetObject):
     __registerer__ = priority_registerer
     __abstract__ = True
     __registry__ = 'facets'
@@ -334,10 +338,10 @@
 
 
 class RelationFacet(VocabularyFacet):
-    __selectors__ = (one_has_relation, match_context_prop)
-    # class attributes to configure the relation facet
+    __select__ = partial_relation_possible() & match_context_prop()
+    # class attributes to configure the rel ation facet
     rtype = None
-    role = 'subject'
+    role = 'subject' 
     target_attr = 'eid'
     # set this to a stored procedure name if you want to sort on the result of
     # this function's result instead of direct value
@@ -411,7 +415,7 @@
         if not value:
             return
         mainvar = self.filtered_variable
-        restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)[0]
+        restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
         if isinstance(value, basestring):
             # only one value selected
             self.rqlst.add_eid_restriction(restrvar, value)
@@ -425,7 +429,7 @@
             # multiple values with AND operator
             self.rqlst.add_eid_restriction(restrvar, value.pop())
             while value:
-                restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)[0]
+                restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
                 self.rqlst.add_eid_restriction(restrvar, value.pop())
 
 
@@ -443,7 +447,7 @@
         try:
             mainvar = self.filtered_variable
             _cleanup_rqlst(rqlst, mainvar)
-            newvar, rel = _prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
+            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)
@@ -521,7 +525,7 @@
         if not self.facet.start_unfolded:
             cssclass += ' hidden'
         if len(self.items) > 6:
-            cssclass +=' overflowed'
+            cssclass += ' overflowed'
         self.w(u'<div class="facetBody%s">\n' % cssclass)
         for item in self.items:
             item.render(self.w)
--- a/web/form.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/form.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,32 +1,34 @@
 """abstract form classes for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from simplejson import dumps
+from warnings import warn
 
-from logilab.mtconverter import html_escape
+from logilab.common.compat import any
+from logilab.common.decorators import iclassmethod
 
-from cubicweb import typed_eid
-from cubicweb.common.selectors import match_form_params
-from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.view import NOINDEX, NOFOLLOW, View, EntityView, AnyRsetView
-from cubicweb.web import stdmsgs
+from cubicweb.appobject import AppRsetObject
+from cubicweb.selectors import yes, non_final_entity, match_kwargs, one_line_rset
+from cubicweb.view import NOINDEX, NOFOLLOW
+from cubicweb.common import tags
+from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
 from cubicweb.web.httpcache import NoHTTPCacheManager
-from cubicweb.web.controller import redirect_params
+from cubicweb.web.controller import NAV_FORM_PARAMETERS
+from cubicweb.web.formfields import (Field, StringField, RelationField,
+                                     HiddenInitialValueField)
+from cubicweb.web.formrenderers import FormRenderer
+from cubicweb.web import formwidgets as fwdgs
 
 
-def relation_id(eid, rtype, target, reid):
-    if target == 'subject':
-        return u'%s:%s:%s' % (eid, rtype, reid)
-    return u'%s:%s:%s' % (reid, rtype, eid)
 
-
+# XXX should disappear 
 class FormMixIn(object):
-    """abstract form mix-in"""
+    """abstract form mix-in
+    XXX: you should inherit from this FIRST (obscure pb with super call)"""
     category = 'form'
     controller = 'edit'
     domid = 'entityForm'
@@ -35,8 +37,8 @@
     add_to_breadcrumbs = False
     skip_relations = set()
     
-    def __init__(self, req, rset):
-        super(FormMixIn, self).__init__(req, rset)
+    def __init__(self, req, rset, **kwargs):
+        super(FormMixIn, self).__init__(req, rset, **kwargs)
         self.maxrelitems = self.req.property_value('navigation.related-limit')
         self.maxcomboitems = self.req.property_value('navigation.combobox-limit')
         self.force_display = not not req.form.get('__force_display')
@@ -96,7 +98,42 @@
                 if inlined_entity.get_widget(irschema, x).need_multipart:
                     return True
         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)
+
+    # XXX deprecated with new form system
     def error_message(self):
         """return formatted error message
 
@@ -123,131 +160,429 @@
                     errormsg = '<ul>%s</ul>' % errormsg
             return u'<div class="errorMessage">%s</div>' % errormsg
         return u''
-    
-    def restore_pending_inserts(self, entity, cell=False):
-        """used to restore edition page as it was before clicking on
-        'search for <some entity type>'
-        
-        """
-        eid = entity.eid
-        cell = cell and "div_insert_" or "tr"
-        pending_inserts = set(self.req.get_pending_inserts(eid))
-        for pendingid in pending_inserts:
-            eidfrom, rtype, eidto = pendingid.split(':')
-            if typed_eid(eidfrom) == entity.eid: # subject
-                label = display_name(self.req, rtype, 'subject')
-                reid = eidto
-            else:
-                label = display_name(self.req, rtype, 'object')
-                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)
-            # XXX find a clean way to handle baskets
-            if rset.description[0][0] == 'Basket':
-                eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
-            yield rtype, pendingid, jscall, label, reid, eview
-        
-    
-    def force_display_link(self):
-        return (u'<span class="invisible">' 
-                u'[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
-                u'</span>' % self.req._('view all'))
-    
-    def relations_table(self, entity):
-        """yiels 3-tuples (rtype, target, related_list)
-        where <related_list> itself a list of :
-          - node_id (will be the entity element's DOM id)
-          - appropriate javascript's togglePendingDelete() function call
-          - status 'pendingdelete' or ''
-          - oneline view of related entity
-        """
-        eid = entity.eid
-        pending_deletes = self.req.get_pending_deletes(eid)
-        # XXX (adim) : quick fix to get Folder relations
-        for label, rschema, target in entity.srelations_by_category(('generic', 'metadata'), 'add'):
-            if rschema in self.skip_relations:
-                continue
-            relatedrset = entity.related(rschema, target, limit=self.limit)
-            toggable_rel_link = self.toggable_relation_link_func(rschema)
-            related = []
-            for row in xrange(relatedrset.rowcount):
-                nodeid = relation_id(eid, rschema, target, relatedrset[row][0])
-                if nodeid in pending_deletes:
-                    status = u'pendingDelete'
-                    label = '+'
-                else:
-                    status = u''
-                    label = 'x'
-                dellink = toggable_rel_link(eid, nodeid, label)
-                eview = self.view('oneline', relatedrset, row=row)
-                related.append((nodeid, dellink, status, eview))
-            yield (rschema, target, related)
-        
-    def toggable_relation_link_func(self, rschema):
-        if not rschema.has_perm(self.req, 'delete'):
-            return lambda x, y, z: u''
-        return toggable_relation_link
 
 
-    def redirect_url(self, entity=None):
-        """return a url to use as next direction if there are some information
-        specified in current form params, else return the result the reset_url
-        method which should be defined in concrete classes
-        """
-        rparams = redirect_params(self.req.form)
-        if rparams:
-            return self.build_url('view', **rparams)
-        return self.reset_url(entity)
+###############################################################################
+
+class metafieldsform(type):
+    def __new__(mcs, name, bases, classdict):
+        allfields = []
+        for base in bases:
+            if hasattr(base, '_fields_'):
+                allfields += base._fields_
+        clsfields = (item for item in classdict.items()
+                     if isinstance(item[1], Field))
+        for fieldname, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
+            if not field.name:
+                field.set_name(fieldname)
+            allfields.append(field)
+        classdict['_fields_'] = allfields
+        return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
+    
+
+class FieldsForm(FormMixIn, AppRsetObject):
+    __metaclass__ = metafieldsform
+    __registry__ = 'forms'
+    __select__ = yes()
+    
+    is_subform = False
+    
+    # attributes overrideable through __init__
+    internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
+    needs_js = ('cubicweb.edition.js',)
+    needs_css = ('cubicweb.form.css',)
+    domid = 'form'
+    title = None
+    action = None
+    onsubmit = "return freezeFormButtons('%(domid)s');"
+    cssclass = None
+    cssstyle = None
+    cwtarget = None
+    buttons = None
+    redirect_path = None
+    set_error_url = True
+    copy_nav_params = False
+                 
+    def __init__(self, req, rset=None, row=None, col=None, **kwargs):
+        super(FieldsForm, self).__init__(req, rset, row=row, col=col)
+        self.buttons = kwargs.pop('buttons', [])
+        for key, val in kwargs.items():
+            assert hasattr(self.__class__, key) and not key[0] == '_', key
+            setattr(self, key, val)
+        self.fields = list(self.__class__._fields_)
+        if self.set_error_url:
+            self.form_add_hidden('__errorurl', req.url())
+        if self.copy_nav_params:
+            for param in NAV_FORM_PARAMETERS:
+                value = kwargs.get(param, req.form.get(param))
+                if value:
+                    self.form_add_hidden(param, initial=value)
+        self.context = None
+
+    @iclassmethod
+    def field_by_name(cls_or_self, name, role='subject'):
+        if isinstance(cls_or_self, type):
+            fields = cls_or_self._fields_
+        else:
+            fields = cls_or_self.fields
+        for field in fields:
+            if field.name == name and field.role == role:
+                return field
+        raise Exception('field %s not found' % name)
+    
+    @iclassmethod
+    def remove_field(cls_or_self, field):
+        if isinstance(cls_or_self, type):
+            fields = cls_or_self._fields_
+        else:
+            fields = cls_or_self.fields
+        fields.remove(field)
+    
+    @property
+    def form_needs_multipart(self):
+        return any(field.needs_multipart for field in self.fields) 
 
-    def reset_url(self, entity):
-        raise NotImplementedError('implement me in concrete classes')
+    def form_add_hidden(self, name, value=None, **kwargs):
+        field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
+                            **kwargs)
+        self.fields.append(field)
+        return field
+    
+    def add_media(self):
+        """adds media (CSS & JS) required by this widget"""
+        if self.needs_js:
+            self.req.add_js(self.needs_js)
+        if self.needs_css:
+            self.req.add_css(self.needs_css)
+
+    def form_render(self, **values):
+        renderer = values.pop('renderer', FormRenderer())
+        return renderer.render(self, values)
+
+    def form_build_context(self, values):
+        self.context = context = {}
+        # on validation error, we get a dictionnary of previously submitted values
+        previous_values = self.req.data.get('formvalues')
+        if previous_values:
+            values.update(previous_values)
+        for field in self.fields:
+            for field in field.actual_fields(self):
+                value = self.form_field_value(field, values)
+                context[field] = {'value': field.format_value(self.req, value),
+                                  'rawvalue': value,
+                                  'name': self.form_field_name(field),
+                                  'id': self.form_field_id(field),
+                                  }
 
-    BUTTON_STR = u'<input class="validateButton" type="submit" name="%s" value="%s" tabindex="%s"/>'
-    ACTION_SUBMIT_STR = u'<input class="validateButton" type="button" onclick="postForm(\'%s\', \'%s\', \'%s\')" value="%s" tabindex="%s"/>'
+    def form_field_value(self, field, values, load_bytes=False):
+        """looks for field's value in
+        1. kw args given to render_form (including previously submitted form
+           values if any)
+        2. req.form
+        3. field's initial value
+        """
+        if field.name in values:
+            value = values[field.name]
+        elif field.name in self.req.form:
+            value = self.req.form[field.name]
+        else:
+            value = field.initial
+        return value
+    
+    def form_field_error(self, field):
+        """return validation error for widget's field, if any"""
+        errex = self.req.data.get('formerrors')
+        if errex and field.name in errex.errors:
+            self.req.data['displayederrors'].add(field.name)
+            return u'<span class="error">%s</span>' % errex.errors[field.name]
+        return u''
 
-    def button_ok(self, label=None, tabindex=None):
-        label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
-        return self.BUTTON_STR % ('defaultsubmit', label, tabindex or 2)
+    def form_field_format(self, field):
+        return self.req.property_value('ui.default-text-format')
+    
+    def form_field_encoding(self, field):
+        return self.req.encoding
+    
+    def form_field_name(self, field):
+        return field.name
+
+    def form_field_id(self, field):
+        return field.id
+   
+    def form_field_vocabulary(self, field, limit=None):
+        raise NotImplementedError
+
+    def form_buttons(self):
+        return self.buttons
+
+   
+class EntityFieldsForm(FieldsForm):
+    __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
+    
+    internal_fields = FieldsForm.internal_fields + ('__type', 'eid')
+    domid = 'entityForm'
     
-    def button_apply(self, label=None, tabindex=None):
-        label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
-        return self.ACTION_SUBMIT_STR % ('__action_apply', label, self.domid, label, tabindex or 3)
+    def __init__(self, *args, **kwargs):
+        self.edited_entity = kwargs.pop('entity', None)
+        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)
+        self.form_add_hidden('__type', eidparam=True)
+        self.form_add_hidden('eid')
+        if msg is not None:
+            # If we need to directly attach the new object to another one
+            for linkto in self.req.list_form_param('__linkto'):
+                self.form_add_hidden('__linkto', linkto)
+                msg = '%s %s' % (msg, self.req._('and linked'))
+            self.form_add_hidden('__message', msg)
+        
+    def form_render(self, **values):
+        self.form_add_entity_hiddens(self.edited_entity.e_schema)
+        return super(EntityFieldsForm, self).form_render(**values)
+
+    def form_add_entity_hiddens(self, eschema):
+        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(self.form_entity_hidden_field(field))
 
-    def button_delete(self, label=None, tabindex=None):
-        label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
-        return self.ACTION_SUBMIT_STR % ('__action_delete', label, self.domid, label, tabindex or 3)
+    def form_entity_hidden_field(self, field):
+        """returns the hidden field which will indicate the value
+        before the modification
+        """
+        # Only RelationField has a `role` attribute, others are used
+        # to describe attribute fields => role is 'subject'
+        if getattr(field, 'role', 'subject') == 'subject':
+            name = 'edits-%s' % field.name
+        else:
+            name = 'edito-%s' % field.name
+        return HiddenInitialValueField(field, name=name)
+        
+    def form_field_value(self, field, values, load_bytes=False):
+        """look for field's value with the following rules:
+        1. handle special __type and eid fields
+        2. looks in kw args given to render_form (including previously submitted
+           form values if any)
+        3. looks in req.form
+        4. if entity has an eid:
+             1. looks for an associated attribute / method
+             2. use field's initial value
+           else:
+             1. looks for a default_<fieldname> attribute / method on the form
+             2. use field's initial value
+             
+        values found in step 4 may be a callable which'll then be called.
+        """
+        fieldname = field.name
+        if fieldname.startswith('edits-') or fieldname.startswith('edito-'):
+            # edit[s|o]- fieds must have the actual value stored on the entity
+            if hasattr(field, 'visible_field'):
+                if self.edited_entity.has_eid():
+                    value = self._form_field_entity_value(field.visible_field,
+                                                          default_initial=False)
+                else:
+                    value = INTERNAL_FIELD_VALUE
+            else:
+                value = field.initial
+        elif fieldname == '__type':
+            value = self.edited_entity.id
+        elif fieldname == 'eid':
+            value = self.edited_entity.eid
+        elif fieldname in values:
+            value = values[fieldname]
+        elif fieldname in self.req.form:
+            value = self.req.form[fieldname]
+        else:
+            if self.edited_entity.has_eid() and field.eidparam:
+                # use value found on the entity or field's initial value if it's
+                # not an attribute of the entity (XXX may conflicts and get
+                # undesired value)
+                value = self._form_field_entity_value(field, default_initial=True,
+                                                      load_bytes=load_bytes)
+            else:
+                defaultattr = 'default_%s' % fieldname
+                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)
+                    value = getattr(self.edited_entity, defaultattr)
+                elif hasattr(self, defaultattr):
+                    # search for default_<field name> on the form instance
+                    value = getattr(self, defaultattr)
+                else:
+                    # use field's initial value
+                    value = field.initial
+            if callable(value):
+                value = value()
+        return value
+    
+    def form_field_format(self, field):
+        entity = self.edited_entity
+        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.attribute_metadata(field.name, 'format')
+        return self.req.property_value('ui.default-text-format')
+
+    def form_field_encoding(self, field):
+        entity = self.edited_entity
+        if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
+            entity.has_eid() or '%s_encoding' % field.name in entity):
+            return self.edited_entity.attribute_metadata(field.name, 'encoding')
+        return super(EntityFieldsForm, self).form_field_encoding(field)
     
-    def button_cancel(self, label=None, tabindex=None):
-        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
-        return self.ACTION_SUBMIT_STR % ('__action_cancel', label, self.domid, label, tabindex or 4)
+    def form_field_error(self, field):
+        """return validation error for widget's field, if any"""
+        errex = self.req.data.get('formerrors')
+        if errex and errex.eid == self.edited_entity.eid and field.name in errex.errors:
+            self.req.data['displayederrors'].add(field.name)
+            return u'<span class="error">%s</span>' % errex.errors[field.name]
+        return u''
+
+    def _form_field_entity_value(self, field, default_initial=True, load_bytes=False):
+        attr = field.name
+        entity = self.edited_entity
+        if field.role == 'object':
+            attr = 'reverse_' + attr
+        elif entity.e_schema.subject_relation(attr).is_final():
+            attrtype = entity.e_schema.destination(attr)
+            if attrtype == 'Password':
+                return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
+            if attrtype == 'Bytes':
+                if entity.has_eid():
+                    if load_bytes:
+                        return getattr(entity, attr)
+                    # XXX value should reflect if some file is already attached
+                    return True
+                return False
+        if default_initial:
+            value = getattr(entity, attr, field.initial)
+        else:
+            value = getattr(entity, attr)
+        if isinstance(field, RelationField):
+            # in this case, value is the list of related entities
+            value = [ent.eid for ent in value]
+        return value
     
-    def button_reset(self, label=None, tabindex=None):
-        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
-        return u'<input class="validateButton" type="reset" value="%s" tabindex="%s"/>' % (
-            label, tabindex or 4)
+    def form_field_name(self, field):
+        if field.eidparam:
+            return eid_param(field.name, self.edited_entity.eid)
+        return field.name
+
+    def form_field_id(self, field):
+        if field.eidparam:
+            return eid_param(field.id, self.edited_entity.eid)
+        return field.id
         
-def toggable_relation_link(eid, nodeid, label='x'):
-    js = u"javascript: togglePendingDelete('%s', %s);" % (nodeid, html_escape(dumps(eid)))
-    return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (js, nodeid, label)
+    def form_field_vocabulary(self, field, limit=None):
+        role, rtype = field.role, field.name
+        try:
+            vocabfunc = getattr(self.edited_entity, '%s_%s_vocabulary' % (role, rtype))
+        except AttributeError:
+            vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+        else:
+            # XXX bw compat, default_<field name> on the entity
+            warn('found %s_%s_vocabulary on %s, should be set on a specific form'
+                 % (role, rtype, self.edited_entity.id), 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
+        #       couples (label, None) which act as separators. In these
+        #       cases, it doesn't make sense to sort results afterwards.
+        return vocabfunc(rtype, limit)
+## XXX BACKPORT ME
+##         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)
+
+    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)
+        done = None
+        assert not rtype.is_final(), rtype
+        if entity.has_eid():
+            done = set(e.eid for e in getattr(entity, str(rtype)))
+        result = []
+        rsetsize = None
+        for objtype in rtype.objects(entity.e_schema):
+            if limit is not None:
+                rsetsize = limit - len(result)
+            result += self.relation_vocabulary(rtype, objtype, 'subject',
+                                               rsetsize, done)
+            if limit is not None and len(result) >= limit:
+                break
+        return result
+
+    def object_relation_vocabulary(self, rtype, limit=None):
+        """defaut vocabulary method for the given relation, looking for
+        relation's subject entities (i.e. self is the object)
+        """
+        entity = self.edited_entity
+        if isinstance(rtype, basestring):
+            rtype = entity.schema.rschema(rtype)
+        done = None
+        if entity.has_eid():
+            done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype))
+        result = []
+        rsetsize = None
+        for subjtype in rtype.subjects(entity.e_schema):
+            if limit is not None:
+                rsetsize = limit - len(result)
+            result += self.relation_vocabulary(rtype, subjtype, 'object',
+                                               rsetsize, done)
+            if limit is not None and len(result) >= limit:
+                break
+        return result
+
+    def relation_vocabulary(self, rtype, targettype, role,
+                            limit=None, done=None):
+        if done is None:
+            done = set()
+        rset = self.edited_entity.unrelated(rtype, targettype, role, limit)
+        res = []
+        for entity in rset.entities():
+            if entity.eid in done:
+                continue
+            done.add(entity.eid)
+            res.append((entity.view('combobox'), entity.eid))
+        return res
+
+    def subject_in_state_vocabulary(self, rschema, limit=None):
+        """vocabulary method for the in_state relation, looking for
+        relation's object entities (i.e. self is the subject) according
+        to initial_state, state_of and next_state relation
+        """
+        entity = self.edited_entity
+        if not entity.has_eid() or not entity.in_state:
+            # get the initial state
+            rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
+            rset = self.req.execute(rql, {'etype': str(entity.e_schema)})
+            if rset:
+                return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])]
+            return []
+        results = []
+        for tr in entity.in_state[0].transitions(entity):
+            state = tr.destination_state[0]
+            results.append((state.view('combobox'), state.eid))
+        return sorted(results)
 
 
-class Form(FormMixIn, View):
-    """base class for forms. Apply by default according to request form
-    parameters specified using the `form_params` class attribute which
-    should list necessary parameters in the form to be accepted.
-    """
-    __registerer__ = accepts_registerer
-    __select__ = classmethod(match_form_params)
+class CompositeForm(FieldsForm):
+    """form composed for sub-forms"""
+    
+    def __init__(self, *args, **kwargs):
+        super(CompositeForm, self).__init__(*args, **kwargs)
+        self.forms = []
 
-    form_params = ()
-
-class EntityForm(FormMixIn, EntityView):
-    """base class for forms applying on an entity (i.e. uniform result set)
-    """
-
-class AnyRsetForm(FormMixIn, AnyRsetView):
-    """base class for forms applying on any empty result sets
-    """
-
+    def form_add_subform(self, subform):
+        subform.is_subform = True
+        self.forms.append(subform)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formfields.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,421 @@
+"""field classes for form construction
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import datetime
+
+from logilab.mtconverter import html_escape
+from yams.constraints import SizeConstraint, StaticVocabularyConstraint
+
+from cubicweb.schema import FormatConstraint
+from cubicweb.utils import ustrftime
+from cubicweb.common import tags, uilib
+from cubicweb.web import INTERNAL_FIELD_VALUE
+from cubicweb.web.formwidgets import (HiddenInput, TextInput, FileInput, PasswordInput,
+                                      TextArea, FCKEditor, Radio, Select,
+                                      DateTimePicker) 
+
+class Field(object):
+    """field class is introduced to control what's displayed in edition form
+    """
+    widget = TextInput
+    needs_multipart = False
+    creation_rank = 0
+
+    def __init__(self, name=None, id=None, label=None,
+                 widget=None, required=False, initial=None,
+                 choices=None, help=None, eidparam=False, role='subject'):
+        self.required = required
+        if widget is not None:
+            self.widget = widget
+        if isinstance(self.widget, type):
+            self.widget = self.widget()
+        self.name = name
+        self.label = label or name
+        self.id = id or name
+        self.initial = initial
+        self.choices = choices
+        self.help = help
+        self.eidparam = eidparam
+        self.role = role
+        # global fields ordering in forms
+        self.creation_rank = Field.creation_rank
+        Field.creation_rank += 1
+    
+    def __unicode__(self):
+        return u'<%s name=%r label=%r id=%r initial=%r @%x>' % (
+            self.__class__.__name__, self.name, self.label,
+            self.id, self.initial, id(self))
+
+    def __repr__(self):
+        return self.__unicode__().encode('utf-8')
+
+    def set_name(self, name):
+        assert name
+        self.name = name
+        if not self.id:
+            self.id = name
+        if not self.label:
+            self.label = name
+            
+    def is_visible(self):
+        return not isinstance(self.widget, HiddenInput)
+    
+    def actual_fields(self, form):
+        yield self
+    
+    def format_value(self, req, value):
+        if isinstance(value, (list, tuple)):
+            return [self.format_single_value(req, val) for val in value]
+        return self.format_single_value(req, value)
+    
+    def format_single_value(self, req, value):
+        if value is None:
+            return u''
+        return unicode(value)
+
+    def get_widget(self, form):
+        return self.widget
+    
+    def example_format(self, req):
+        return u''
+
+    def render(self, form, renderer):
+        return self.get_widget(form).render(form, self)
+
+    def vocabulary(self, form):
+        if self.choices is not None:
+            if callable(self.choices):
+                vocab = self.choices(req=form.req)
+            else:
+                vocab = self.choices
+            if vocab and not isinstance(vocab[0], (list, tuple)):
+                vocab = [(x, x) for x in vocab]
+            return vocab
+        return form.form_field_vocabulary(self)
+
+    
+class StringField(Field):
+    def __init__(self, max_length=None, **kwargs):
+        super(StringField, self).__init__(**kwargs)
+        self.max_length = max_length
+
+
+class TextField(Field):
+    widget = TextArea
+    def __init__(self, rows=10, cols=80, **kwargs):
+        super(TextField, self).__init__(**kwargs)
+        self.rows = rows
+        self.cols = cols
+
+
+class RichTextField(TextField):
+    widget = None
+    def __init__(self, format_field=None, **kwargs):
+        super(RichTextField, self).__init__(**kwargs)
+        self.format_field = format_field
+
+    def get_widget(self, form):
+        if self.widget is None:
+            if self.use_fckeditor(form):
+                return FCKEditor()
+            return TextArea()
+        return self.widget
+
+    def get_format_field(self, form):
+        if self.format_field:
+            return self.format_field
+        # we have to cache generated field since it's use as key in the
+        # context dictionnary
+        req = form.req
+        try:
+            return req.data[self]
+        except KeyError:
+            if self.use_fckeditor(form):
+                # if fckeditor is used and format field isn't explicitly
+                # deactivated, we want an hidden field for the format
+                widget = HiddenInput()
+                choices = None
+            else:
+                # else we want a format selector
+                # XXX compute vocabulary
+                widget = Select
+                fcstr = FormatConstraint()
+                choices = [(req._(fmt), fmt) for fmt in fcstr.vocabulary(req=req)]
+            field = StringField(name=self.name + '_format', widget=widget,
+                                choices=choices)
+            req.data[self] = field
+            return field
+    
+    def actual_fields(self, form):
+        yield self
+        format_field = self.get_format_field(form)
+        if format_field:
+            yield format_field
+            
+    def use_fckeditor(self, form):
+        """return True if fckeditor should be used to edit entity's attribute named
+        `attr`, according to user preferences
+        """
+        if form.req.use_fckeditor():
+            return form.form_field_format(self) == 'text/html'
+        return False
+
+    def render(self, form, renderer):
+        format_field = self.get_format_field(form)
+        if format_field:
+            result = format_field.render(form, renderer)
+        else:
+            result = u''
+        return result + self.get_widget(form).render(form, self)
+
+    
+class FileField(StringField):
+    widget = FileInput
+    needs_multipart = True
+    
+    def __init__(self, format_field=None, encoding_field=None, **kwargs):
+        super(FileField, self).__init__(**kwargs)
+        self.format_field = format_field
+        self.encoding_field = encoding_field
+        
+    def actual_fields(self, form):
+        yield self
+        if self.format_field:
+            yield self.format_field
+        if self.encoding_field:
+            yield self.encoding_field
+
+    def render(self, form, renderer):
+        wdgs = [self.get_widget(form).render(form, self)]
+        if self.format_field or self.encoding_field:
+            divid = '%s-advanced' % form.context[self]['name']
+            wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
+                        (html_escape(uilib.toggle_action(divid)),
+                         form.req._('show advanced fields'),
+                         html_escape(form.req.build_url('data/puce_down.png')),
+                         form.req._('show advanced fields')))
+            wdgs.append(u'<div id="%s" class="hidden">' % divid)
+            if self.format_field:
+                wdgs.append(self.render_subfield(form, self.format_field, renderer))
+            if self.encoding_field:
+                wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
+            wdgs.append(u'</div>')            
+        if not self.required and form.context[self]['value']:
+            # trick to be able to delete an uploaded file
+            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'))
+        return u'\n'.join(wdgs)
+
+    def render_subfield(self, form, field, renderer):
+        return (renderer.render_label(form, field)
+                + field.render(form, renderer)
+                + renderer.render_help(form, field)
+                + u'<br/>')
+
+        
+class EditableFileField(FileField):
+    editable_formats = ('text/plain', 'text/html', 'text/rest')
+    
+    def render(self, form, renderer):
+        wdgs = [super(EditableFileField, self).render(form, renderer)]
+        if form.form_field_format(self) in self.editable_formats:
+            data = form.form_field_value(self, {}, load_bytes=True)
+            if data:
+                encoding = form.form_field_encoding(self)
+                try:
+                    form.context[self]['value'] = unicode(data.getvalue(), encoding)
+                except UnicodeError:
+                    pass
+                else:
+                    if not self.required:
+                        msg = form.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 = form.req._(
+                            '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)
+                    wdgs.append(TextArea(setdomid=False).render(form, self))
+                    # XXX restore form context?
+        return '\n'.join(wdgs)
+
+        
+class IntField(Field):
+    def __init__(self, min=None, max=None, **kwargs):
+        super(IntField, self).__init__(**kwargs)
+        self.min = min
+        self.max = max
+
+
+class BooleanField(Field):
+    widget = Radio
+        
+    def vocabulary(self, form):
+        if self.choices:
+            return self.choices
+        return [(form.req._('yes'), '1'), (form.req._('no'), '')]
+
+
+class FloatField(IntField):    
+    def format_single_value(self, req, value):
+        formatstr = req.property_value('ui.float-format')
+        if value is None:
+            return u''
+        return formatstr % float(value)
+
+    def render_example(self, req):
+        return self.format_value(req, 1.234)
+
+
+class DateField(StringField):
+    format_prop = 'ui.date-format'
+    widget = DateTimePicker
+    
+    def format_single_value(self, req, value):
+        return value and ustrftime(value, req.property_value(self.format_prop)) or u''
+
+    def render_example(self, req):
+        return self.format_value(req, datetime.now())
+
+
+class DateTimeField(DateField):
+    format_prop = 'ui.datetime-format'
+
+
+class TimeField(DateField):
+    format_prop = 'ui.datetime-format'
+
+
+class HiddenInitialValueField(Field):
+    def __init__(self, visible_field, name):
+        super(HiddenInitialValueField, self).__init__(name=name,
+                                                      widget=HiddenInput,
+                                                      eidparam=True)
+        self.visible_field = visible_field
+    
+                 
+class RelationField(Field):
+    def __init__(self, **kwargs):
+        super(RelationField, self).__init__(**kwargs)
+
+    @staticmethod
+    def fromcardinality(card, **kwargs):
+        return RelationField(widget=Select(multiple=card in '*+'), **kwargs)
+        
+    def vocabulary(self, form):
+        entity = form.edited_entity
+        req = entity.req
+        # first see if its specified by __linkto form parameters
+        linkedto = entity.linked_to(self.name, self.role)
+        if linkedto:
+            entities = (req.eid_rset(eid).get_entity(0, 0) 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
+        res = []
+        if not self.required:
+            res.append(('', INTERNAL_FIELD_VALUE))
+        # 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 + form.form_field_vocabulary(self) + relatedvocab
+    
+    def format_single_value(self, req, value):
+        return value
+
+
+def stringfield_from_constraints(constraints, **kwargs):
+    field = None
+    for cstr in constraints:
+        if isinstance(cstr, StaticVocabularyConstraint):
+            kwargs.setdefault('widget', Select())
+            return StringField(choices=cstr.vocabulary, **kwargs)
+        if isinstance(cstr, SizeConstraint) and cstr.max is not None:
+            if cstr.max > 257:
+                rows_cols_from_constraint(cstr, kwargs)
+                field = TextField(**kwargs)
+            else:
+                field = StringField(max_length=cstr.max, **kwargs)
+    return field or TextField(**kwargs)
+
+
+def rows_cols_from_constraint(constraint, kwargs):
+    if constraint.max < 513:
+        rows, cols = 5, 60
+    else:
+        rows, cols = 10, 80
+    kwargs.setdefault('rows', rows)
+    kwargs.setdefault('cols', cols)
+
+
+def guess_field(eclass, rschema, role='subject', skip_meta_attr=True, **kwargs):
+    """return the most adapated widget to edit the relation
+    'subjschema rschema objschema' according to information found in the schema
+    """
+    fieldclass = None
+    eschema = eclass.e_schema
+    if role == 'subject':
+        targetschema = rschema.objects(eschema)[0]
+        card = rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+    else:
+        targetschema = rschema.subjects(eschema)[0]
+        card = rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+    kwargs['required'] = card in '1+'
+    kwargs['name'] = rschema.type
+    if rschema.is_final():
+        if skip_meta_attr and rschema in eschema.meta_attributes():
+            return None
+        fieldclass = FIELDS[targetschema]
+        if fieldclass is StringField:
+            if targetschema == 'Password':
+                # special case for Password field: specific PasswordInput widget
+                kwargs.setdefault('widget', PasswordInput())
+                return StringField(**kwargs)
+            if eschema.has_metadata(rschema, 'format'):
+                # use RichTextField instead of StringField if the attribute has
+                # a "format" metadata. But getting information from constraints
+                # may be useful anyway...
+                constraints = rschema.rproperty(eschema, targetschema, 'constraints')
+                for cstr in constraints:
+                    if isinstance(cstr, StaticVocabularyConstraint):
+                        raise Exception('rich text field with static vocabulary')
+                    if isinstance(cstr, SizeConstraint) and cstr.max is not None:
+                        rows_cols_from_constraint(cstr, kwargs)
+                return RichTextField(**kwargs)
+            # return StringField or TextField according to constraints
+            constraints = rschema.rproperty(eschema, targetschema, 'constraints')
+            return stringfield_from_constraints(constraints, **kwargs)
+        if fieldclass is FileField:
+            for metadata in ('format', 'encoding'):
+                metaschema = eschema.has_metadata(rschema, metadata)
+                if metaschema is not None:
+                    kwargs['%s_field' % metadata] = guess_field(eclass, metaschema,
+                                                                skip_meta_attr=False)
+        return fieldclass(**kwargs)
+    kwargs['role'] = role
+    return RelationField.fromcardinality(card, **kwargs)
+
+FIELDS = {
+    'Boolean':  BooleanField,
+    'Bytes':    FileField,
+    'Date':     DateField,
+    'Datetime': DateTimeField,
+    'Int':      IntField,
+    'Float':    FloatField,
+    'Decimal':  StringField,
+    'Password': StringField,
+    'String' :  StringField,
+    'Time':     TimeField,
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formrenderers.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,402 @@
+"""form renderers, responsible to layout a form to html
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common import dictattr
+from logilab.mtconverter import html_escape
+
+from simplejson import dumps
+
+from cubicweb.common import tags
+from cubicweb.web import eid_param
+from cubicweb.web import formwidgets as fwdgs
+from cubicweb.web.widgets import checkbox
+
+class FormRenderer(object):
+    """basic renderer displaying fields in a two columns table label | value
+    """
+    button_bar_class = u'formButtonBar'
+    
+    def __init__(self, display_fields=None, display_label=True,
+                 display_help=True, button_bar_class=None):
+        self.display_fields = display_fields # None -> all fields
+        self.display_label = display_label
+        self.display_help = display_help
+        if button_bar_class is not None:
+            self.button_bar_class = button_bar_class
+            
+    # renderer interface ######################################################
+    
+    def render(self, form, values):
+        form.add_media()
+        data = []
+        w = data.append
+        w(self.open_form(form, values))
+        w(u'<div id="progress">%s</div>' % form.req._('validating...'))
+        w(u'<fieldset>')
+        w(tags.input(type=u'hidden', name=u'__form_id',
+                     value=values.get('formvid', form.id)))
+        if form.redirect_path:
+            w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
+        self.render_fields(w, form, values)
+        self.render_buttons(w, form)
+        w(u'</fieldset>')
+        w(u'</form>')
+        errormsg = self.error_message(form)
+        if errormsg:
+            data.insert(0, errormsg)          
+        return '\n'.join(data)
+
+    def render_label(self, form, field):
+        label = form.req._(field.label)
+        attrs = {'for': form.context[field]['id']}
+        if field.required:
+            attrs['class'] = 'required'
+        return tags.label(label, **attrs)
+
+    def render_help(self, form, field):
+        help = [ u'<br/>' ]
+        descr = field.help
+        if descr:
+            help.append('<span class="helper">%s</span>' % form.req._(descr))
+        example = field.example_format(form.req)
+        if example:
+            help.append('<span class="helper">(%s: %s)</span>'
+                        % (form.req._('sample format'), example))
+        return u'&nbsp;'.join(help)
+
+    # specific methods (mostly to ease overriding) #############################
+
+    def error_message(self, form):
+        """return formatted error message
+
+        This method should be called once inlined field errors has been consumed
+        """
+        req = form.req
+        errex = req.data.get('formerrors')
+        # get extra errors
+        if errex is not None:
+            errormsg = req._('please correct the following errors:')
+            displayed = req.data['displayederrors']
+            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 = '&nbsp;%s\n'
+                for field, err in errors:
+                    if field is None:
+                        errormsg += templstr % err
+                    else:
+                        errormsg += templstr % '%s: %s' % (req._(field), err)
+                if len(errors) > 1:
+                    errormsg = '<ul>%s</ul>' % errormsg
+            return u'<div class="errorMessage">%s</div>' % errormsg
+        return u''
+    
+    def open_form(self, form, values):
+        if form.form_needs_multipart:
+            enctype = 'multipart/form-data'
+        else:
+            enctype = 'application/x-www-form-urlencoded'
+        if form.action is None:
+            action = form.req.build_url('edit')
+        else:
+            action = form.action
+        tag = ('<form action="%s" method="post" id="%s" enctype="%s"' % (
+            html_escape(action or '#'), form.domid, enctype))
+        if form.onsubmit:
+            tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
+        if form.cssstyle:
+            tag += ' style="%s"' % html_escape(form.cssstyle)
+        if form.cssclass:
+            tag += ' class="%s"' % html_escape(form.cssclass)
+        if form.cwtarget:
+            tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+        return tag + '>'
+    
+    def display_field(self, form, field):
+        return (self.display_fields is None
+                or field.name in self.display_fields
+                or field.name in form.internal_fields)
+    
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
+        fields = self._render_hidden_fields(w, form)
+        if fields:
+            self._render_fields(fields, w, form, values)
+        self.render_child_forms(w, form, values)
+        
+    def render_child_forms(self, w, form, values):
+        # render 
+        for childform in getattr(form, 'forms', []):
+            self.render_fields(w, childform, values)
+
+    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():
+                w(field.render(form, self))
+                fields.remove(field)
+        return fields
+    
+    def _render_fields(self, fields, w, form, values):
+        w(u'<table id="%s" class="attributeForm" style="width:100%%;">')
+        for field in fields:
+            w(u'<tr>')
+            if self.display_label:
+                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+            error = form.form_field_error(field)
+            if error:
+                w(u'<td class="error" style="width:100%;">')
+                w(error)
+            else:
+                w(u'<td style="width:100%;">')
+            w(field.render(form, self))
+            if self.display_help:
+                w(self.render_help(form, field))
+            w(u'</td></tr>')
+        w(u'</table>')
+
+    def render_buttons(self, w, form):
+        w(u'<table class="%s">\n<tr>\n' % self.button_bar_class)
+        for button in form.form_buttons():
+            w(u'<td>%s</td>\n' % button)
+        w(u'</tr></table>')
+
+
+    
+class EntityCompositeFormRenderer(FormRenderer):
+    """specific renderer for multiple entities edition form (muledit)"""
+    def render_fields(self, w, form, values):
+        if not form.is_subform:
+            w(u'<table class="listing">')
+        super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
+        if not form.is_subform:
+            w(u'</table>')
+        
+    def _render_fields(self, fields, w, form, values):
+        if form.is_subform:
+            entity = form.edited_entity
+            values = form.req.data.get('formvalues', ())
+            qeid = eid_param('eid', entity.eid)
+            cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid))
+            w(u'<tr class="%s">' % (entity.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))
+            for field in fields:
+                error = form.form_field_error(field)
+                if error:
+                    w(u'<td class="error">')
+                    w(error)
+                else:
+                    w(u'<td>')
+                if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox, fwdgs.Radio)):
+                    field.widget.attrs['onchange'] = cbsetstate
+                elif isinstance(field.widget, fwdgs.Input):
+                    field.widget.attrs['onkeypress'] = cbsetstate
+                w(u'<div>%s</div>' % field.render(form, self))
+                w(u'/<td>')
+        else:
+            # main form, display table headers
+            w(u'<tr class="header">')
+            w(u'<th align="left">%s</th>'
+              % tags.input(type='checkbox', title=form.req._('toggle check boxes'),
+                           onclick="setCheckboxesState('eid', this.checked)"))
+            for field in self.forms[0].fields:
+                if self.display_field(form, field) and field.is_visible():
+                    w(u'<th>%s</th>' % form.req._(field.label))
+        w(u'</tr>')
+            
+
+            
+class EntityFormRenderer(FormRenderer):
+    """specific renderer for entity edition form (edition)"""
+        
+    def render(self, form, values):
+        rendered = super(EntityFormRenderer, self).render(form, values)
+        return rendered + u'</div>' # close extra div introducted by open_form
+        
+    def open_form(self, form, values):
+        attrs_fs_label = ('<div class="iformTitle"><span>%s</span></div>'
+                          % form.req._('main informations'))
+        attrs_fs_label += '<div class="formBody">'
+        return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
+
+    def render_fields(self, w, form, values):
+        super(EntityFormRenderer, self).render_fields(w, form, values)
+        self.inline_entities_form(w, form)
+        if form.edited_entity.has_eid():
+            self.relations_form(w, form)
+
+    def _render_fields(self, fields, w, form, values):
+        if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
+            super(EntityFormRenderer, self)._render_fields(fields, w, form, values)
+            
+    def render_buttons(self, w, form):
+        buttons = form.form_buttons()
+        if len(buttons) == 3:
+            w("""<table width="100%%">
+  <tbody>
+   <tr><td align="center">
+     %s
+   </td><td style="align: right; width: 50%%;">
+     %s
+     %s
+   </td></tr>
+  </tbody>
+ </table>""" % tuple(buttons))
+        else:
+            super(EntityFormRenderer, self).render_buttons(w, form)
+        
+    def relations_form(self, w, form):
+        srels_by_cat = form.srelations_by_category(('generic', 'metadata'), 'add')
+        if not srels_by_cat:
+            return u''
+        req = form.req
+        _ = req._
+        label = u'%s :' % _('This %s' % form.edited_entity.e_schema).capitalize()
+        eid = form.edited_entity.eid
+        w(u'<fieldset class="subentity">')
+        w(u'<legend class="iformTitle">%s</legend>' % label)
+        w(u'<table id="relatedEntities">')
+        for rschema, target, related in form.relations_table():
+            # already linked entities
+            if related:
+                w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
+                w(u'<td>')
+                w(u'<ul>')
+                for viewparams in related:
+                    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 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>' % form.req._('view all'))
+                    w(u'<li class="invisible">%s</li>' % link)
+                w(u'</ul>')
+                w(u'</td>')
+                w(u'</tr>')
+        pendings = list(form.restore_pending_inserts())
+        if not pendings:
+            w(u'<tr><th>&nbsp;</th><td>&nbsp;</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], html_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(), html_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>')
+        
+    def inline_entities_form(self, w, form):
+        """create a form to edit entity's inlined relations"""
+        entity = form.edited_entity
+        __ = form.req.__
+        for rschema, targettypes, role in form.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 form.should_inline_relation_form(rschema, targettype, role):
+                w(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
+                    w(self.view('inline-edition', existant, rtype=rschema, role=role, 
+                                ptype=entity.e_schema, peid=entity.eid,
+                                **kwargs))
+                if role == '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 form.should_display_inline_creation_form(rschema, existant, card):
+                    w(self.view('inline-creation', None, etype=targettype,
+                                peid=entity.eid, ptype=entity.e_schema,
+                                rtype=rschema, role=role, **kwargs))
+                # we can create more than one related entity, we thus display a link
+                # to add new related entities
+                if form.should_display_add_new_relation_link(rschema, existant, card):
+                    divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid)
+                    w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+                      % divid)
+                    js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
+                        entity.eid, entity.e_schema, targettype, rschema, role)
+                    if card in '1?':
+                        js = "toggleVisibility('%s'); %s" % (divid, js)
+                    w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+                      % (rschema, entity.eid, js, __('add a %s' % targettype)))
+                    w(u'</div>')
+                    w(u'<div class="trame_grise">&nbsp;</div>')
+                w(u'</div>')
+
+    
+class EntityInlinedFormRenderer(EntityFormRenderer):
+    """specific renderer for entity inlined edition form
+    (inline-[creation|edition])
+    """
+    def render(self, form, values):
+        form.add_media()
+        data = []
+        w = data.append
+        try:
+            w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
+        except KeyError:
+            w(u'<div id="div-%(divid)s">' % values)
+        else:
+            w(u'<div id="notice-%s" class="notice">%s</div>' % (
+                values['divid'], form.req._('click on the box to cancel the deletion')))
+        w(u'<div class="iformBody">')
+        values['removemsg'] = form.req.__('remove this %s' % form.edited_entity.e_schema)
+        w(u'<div class="iformTitle"><span>%(title)s</span> '
+          '#<span class="icounter">1</span> '
+          '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
+          % values)
+        self.render_fields(w, form, values)
+        w(u'</div>')
+        return '\n'.join(data)
+    
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
+        w(u'<fieldset id="fs-%(divid)s">' % values)
+        fields = self._render_hidden_fields(w, form)
+        w(u'</fieldset>')
+        w(u'<fieldset class="subentity">')
+        if fields:
+            self._render_fields(fields, w, form, values)
+        self.render_child_forms(w, form, values)
+        self.inline_entities_form(w, form)
+        w(u'</fieldset>')
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formwidgets.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,230 @@
+"""widget classes for form construction
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import date
+
+from cubicweb.common import tags
+
+class FieldWidget(object):
+    needs_js = ()
+    needs_css = ()
+    setdomid = True
+    settabindex = True
+    
+    def __init__(self, attrs=None, setdomid=None, settabindex=None):
+        self.attrs = attrs or {}
+        if setdomid is not None:
+            # override class's default value
+            self.setdomid = setdomid
+        if settabindex is not None:
+            # override class's default value
+            self.settabindex = settabindex
+
+    def add_media(self, form):
+        """adds media (CSS & JS) required by this widget"""
+        if self.needs_js:
+            form.req.add_js(self.needs_js)
+        if self.needs_css:
+            form.req.add_css(self.needs_css)
+        
+    def render(self, form, field):
+        raise NotImplementedError
+
+    def _render_attrs(self, form, field):
+        name = form.context[field]['name']
+        values = form.context[field]['value']
+        if not isinstance(values, (tuple, list)):
+            values = (values,)
+        attrs = dict(self.attrs)
+        if self.setdomid:
+            attrs['id'] = form.context[field]['id']
+        if self.settabindex:
+            attrs['tabindex'] = form.req.next_tabindex()
+        return name, values, attrs
+
+
+class Input(FieldWidget):
+    type = None
+    
+    def render(self, form, field):
+        self.add_media(form)
+        name, values, attrs = self._render_attrs(form, field)
+        inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
+                  for value in values]
+        return u'\n'.join(inputs)
+
+
+class TextInput(Input):
+    type = 'text'
+
+
+class PasswordInput(Input):
+    type = 'password'
+    
+    def render(self, form, field):
+        self.add_media(form)
+        name, values, attrs = self._render_attrs(form, field)
+        assert len(values) == 1
+        id = attrs.pop('id')
+        confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
+        inputs = [tags.input(name=name, value=values[0], type=self.type, id=id, **attrs),
+                  '<br/>',
+                  tags.input(name=confirmname, type=self.type, **attrs),
+                  '&nbsp;', tags.span(form.req._('confirm password'),
+                                      **{'class': 'emphasis'})]
+        return u'\n'.join(inputs)
+
+
+class FileInput(Input):
+    type = 'file'
+    
+    def _render_attrs(self, form, field):
+        # ignore value which makes no sense here (XXX even on form validation error?)
+        name, values, attrs = super(FileInput, self)._render_attrs(form, field)
+        return name, ('',), attrs
+
+        
+class HiddenInput(Input):
+    type = 'hidden'
+    setdomid = False # by default, don't set id attribute on hidden input
+    settabindex = False
+
+    
+class ButtonInput(Input):
+    type = 'button'
+
+
+class TextArea(FieldWidget):
+    def render(self, form, field):
+        name, values, attrs = self._render_attrs(form, field)
+        attrs.setdefault('onkeypress', 'autogrow(this)')
+        if not values:
+            value = u''
+        elif len(values) == 1:
+            value = values[0]
+        else:
+            raise ValueError('a textarea is not supposed to be multivalued')
+        return tags.textarea(value, name=name, **attrs)
+
+
+class FCKEditor(TextArea):
+    def __init__(self, *args, **kwargs):
+        super(FCKEditor, self).__init__(*args, **kwargs)
+        self.attrs['cubicweb:type'] = 'wysiwyg'
+    
+    def render(self, form, field):
+        form.req.fckeditor_config()
+        return super(FCKEditor, self).render(form, field)
+
+
+class Select(FieldWidget):
+    def __init__(self, attrs=None, multiple=False):
+        super(Select, self).__init__(attrs)
+        self.multiple = multiple
+        
+    def render(self, form, field):
+        name, curvalues, attrs = self._render_attrs(form, field)
+        options = []
+        for label, value in field.vocabulary(form):
+            if value in curvalues:
+                options.append(tags.option(label, value=value, selected='selected'))
+            else:
+                options.append(tags.option(label, value=value))
+        return tags.select(name=name, multiple=self.multiple,
+                           options=options, **attrs)
+
+
+class CheckBox(Input):
+    type = 'checkbox'
+    
+    def render(self, form, field):
+        name, curvalues, attrs = self._render_attrs(form, field)
+        options = []
+        for label, value in field.vocabulary(form):
+            if value in curvalues:
+                tag = tags.input(name=name, value=value, type=self.type,
+                                 checked='checked', **attrs)
+            else:
+                tag = tags.input(name=name, value=value, type=self.type,
+                                 **attrs)
+            options.append(tag + label)
+        return '<br/>\n'.join(options)
+
+        
+class Radio(Input):
+    type = 'radio'
+    setdomid = False
+    
+    def render(self, form, field):
+        name, curvalues, attrs = self._render_attrs(form, field)
+        options = []
+        for label, value in field.vocabulary(form):
+            if value in curvalues:
+                options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs))
+            else:
+                options.append(tags.option(name=name, type=self.type, value=value, **attrs))
+            options[-1] += label + '<br/>'
+        return '\n'.join(options)
+
+
+class DateTimePicker(TextInput):
+    monthnames = ('january', 'february', 'march', 'april',
+                  'may', 'june', 'july', 'august',
+                  'september', 'october', 'november', 'december')
+    daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
+                'friday', 'saturday', 'sunday')
+
+    needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js')
+    needs_css = ('cubicweb.calendar_popup.css',)
+    
+    @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 render(self, form, field):
+        txtwidget = super(DateTimePicker, self).render(form, field)
+        self.add_localized_infos(form.req)
+        cal_button = self._render_calendar_popup(form, field)
+        return txtwidget + cal_button
+    
+    def _render_calendar_popup(self, form, field):
+        req = form.req
+        value = form.context[field]['rawvalue']
+        inputid = form.context[field]['id']
+        helperid = '%shelper' % inputid
+        if not value:
+            value = date.today()
+        year, month = value.year, value.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 AjaxWidget(FieldWidget):
+    def __init__(self, wdgtype, inputid=None, **kwargs):
+        super(AjaxWidget, self).__init__(**kwargs)
+        self.attrs.setdefault('class', 'widget')
+        self.attrs.setdefault('cubicweb:loadtype', 'auto')
+        self.attrs['cubicweb:wdgtype'] = wdgtype
+        if inputid is not None:
+            self.attrs['cubicweb:inputid'] = inputid
+            
+    def render(self, form, field):
+        self.add_media(form)
+        attrs = self._render_attrs(form, field)[-1]
+        return tags.div(**attrs)
+
+# XXX backport PropertyKeyWidget and PropertyValueWidget
--- a/web/htmlwidgets.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/htmlwidgets.py	Tue Apr 07 09:30:23 2009 +0200
@@ -10,7 +10,7 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.utils import UStringIO
+from cubicweb.utils import UStringIO
 from cubicweb.common.uilib import toggle_action
 
 # XXX HTMLWidgets should have access to req (for datadir / static urls,
--- a/web/httpcache.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/httpcache.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,15 +2,15 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from mx.DateTime import DateTimeFromTicks, now, gmtime
+from datetime import datetime
 
 # time delta usable to convert localized time to GMT time
-GMTOFFSET = - (now() - gmtime())
+GMTOFFSET = - (datetime.now() - datetime.utcnow())
 
 class NoHTTPCacheManager(object):
     """default cache manager: set no-cache cache control policy"""
@@ -92,11 +92,11 @@
 # monkey patching, so view doesn't depends on this module and we have all
 # http cache related logic here
 
-from cubicweb.common import view
+from cubicweb import view as viewmod
 
 def set_http_cache_headers(self):
     self.http_cache_manager(self).set_headers()
-view.View.set_http_cache_headers = set_http_cache_headers
+viewmod.View.set_http_cache_headers = set_http_cache_headers
 
 def last_modified(self):
     """return the date/time where this view should be considered as
@@ -105,11 +105,12 @@
     /!\ must return GMT time /!\
     """
     # XXX check view module's file modification time in dev mod ?
-    ctime = gmtime()
+    ctime = datetime.utcnow()
     if self.cache_max_age:
         mtime = self.req.header_if_modified_since()
         if mtime:
-            if (ctime - mtime).seconds > self.cache_max_age:
+            tdelta = (ctime - mtime)
+            if tdelta.days * 24*60*60 + tdelta.seconds > self.cache_max_age:
                 mtime = ctime
         else:
             mtime = ctime
@@ -117,15 +118,15 @@
         mtime = ctime
     # mtime = ctime will force page rerendering
     return mtime
-view.View.last_modified = last_modified
+viewmod.View.last_modified = last_modified
 
 # configure default caching
-view.View.http_cache_manager = NoHTTPCacheManager
+viewmod.View.http_cache_manager = NoHTTPCacheManager
 # max-age=0 to actually force revalidation when needed
-view.View.cache_max_age = 0
+viewmod.View.cache_max_age = 0
 
 
-view.EntityView.http_cache_manager = EntityHTTPCacheManager
+viewmod.EntityView.http_cache_manager = EntityHTTPCacheManager
 
-view.StartupView.http_cache_manager = MaxAgeHTTPCacheManager
-view.StartupView.cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+viewmod.StartupView.http_cache_manager = MaxAgeHTTPCacheManager
+viewmod.StartupView.cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
--- a/web/request.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/request.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """abstract class for http request
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -18,15 +18,15 @@
 
 from logilab.common.decorators import cached
 
-# XXX move _MARKER here once AppObject.external_resource has been removed
 from cubicweb.dbapi import DBAPIRequest
-from cubicweb.common.appobject import _MARKER 
 from cubicweb.common.mail import header
 from cubicweb.common.uilib import remove_html_tags
-from cubicweb.common.utils import SizeConstrainedList, HTMLHead
+from cubicweb.utils import SizeConstrainedList, HTMLHead
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, RequestError,
                        StatusResponse)
 
+_MARKER = object()
+
 
 def list_form_param(form, param, pop=False):
     """get param from form parameters and return its value as a list,
@@ -215,6 +215,19 @@
         if self.cnx is not None:
             self.set_session_data('search_state', searchstate)
 
+    def match_search_state(self, rset):
+        """when searching an entity to create a relation, return True if entities in
+        the given rset may be used as relation end
+        """
+        try:
+            searchedtype = self.search_state[1][-1]
+        except IndexError:
+            return False # no searching for association
+        for etype in rset.column_types(0):
+            if etype != searchedtype:
+                return False
+        return True
+
     def update_breadcrumbs(self):
         """stores the last visisted page in session data"""
         searchstate = self.get_session_data('search_state')
@@ -265,9 +278,12 @@
     
     @cached # so it's writed only once
     def fckeditor_config(self):
+        self.add_js('fckeditor/fckeditor.js')
         self.html_headers.define_var('fcklang', self.lang)
         self.html_headers.define_var('fckconfigpath',
-                                     self.build_url('data/fckcwconfig.js'))
+                                     self.build_url('data/cubicweb.fckcwconfig.js'))
+    def use_fckeditor(self):
+        return self.vreg.config.fckeditor_installed() and self.property_value('ui.fckeditor')
 
     def edited_eids(self, withtype=False):
         """return a list of edited eids"""
--- a/web/test/runtests.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# -*- coding: ISO-8859-1 -*-
-"""Script used to fire all tests"""
-
-__revision__ = '$Id: runtests.py,v 1.1 2005-06-17 14:09:18 adim Exp $'
-
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
-    import sys, os
-    main(os.path.dirname(sys.argv[0]) or '.')
--- a/web/test/test_views.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/test/test_views.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,9 +1,7 @@
 """automatic tests"""
 
-from mx.DateTime import now
-
 from cubicweb.devtools.testlib import WebTest, AutomaticWebTest
-from cubicweb.common.view import AnyRsetView
+from cubicweb.view import AnyRsetView
 
 AutomaticWebTest.application_rql = [
     'Any L,F WHERE E is EUser, E login L, E firstname F',
@@ -36,13 +34,7 @@
 
     def test_manual_tests(self):
         rset = self.execute('Any P,F,S WHERE P is EUser, P firstname F, P surname S')
-        self.view('table', rset, template=None, displayfilter=True, displaycols=[0,2])
-        rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
-        rset.req.form['rtype'] = 'firstname'
-        self.view('editrelation', rset, template=None)
-        rset.req.form['rtype'] = 'use_email'
-        self.view('editrelation', rset, template=None)
-        
+        self.view('table', rset, template=None, displayfilter=True, displaycols=[0,2])        
 
     def test_sortable_js_added(self):
         rset = self.execute('EUser X')
--- a/web/test/unittest_controller.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/test/unittest_controller.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,7 @@
 
 """
 
-from mx.DateTime import DateTimeType, DateTimeDeltaType
+from datetime import datetime, date, time
 
 from logilab.common.testlib import unittest_main
 
@@ -10,12 +10,12 @@
 
 class BaseControllerTC(apptest.ControllerTC):
     def test_parse_datetime(self):
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18'), DateTimeType)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24'), DateTimeType)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18', 'Datetime'), DateTimeType)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Datetime'), DateTimeType)
-        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Date'), DateTimeType)
-        self.assertIsInstance(self.ctrl.parse_datetime('12:18', 'Time'), DateTimeDeltaType)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18'), datetime)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24'), datetime)
+        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)
         self.assertRaises(ValueError,
                           self.ctrl.parse_datetime, '2006/06/24 12:188', 'Datetime')
         self.assertRaises(ValueError,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_form.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,172 @@
+from logilab.common.testlib import unittest_main, mock_object
+from cubicweb import Binary
+from cubicweb.devtools.testlib import WebTest
+from cubicweb.web.form import EntityFieldsForm, FormRenderer
+from cubicweb.web.formfields import (IntField, StringField, RichTextField,
+                                     DateTimeField, DateTimePicker,
+                                     FileField, EditableFileField)
+from cubicweb.web.formwidgets import PasswordInput
+from cubicweb.web.views.workflow import ChangeStateForm
+
+
+class EntityFieldsFormTC(WebTest):
+
+    def setUp(self):
+        super(EntityFieldsFormTC, self).setUp()
+        self.req = self.request()
+        self.entity = self.user(self.req)
+        self.renderer = FormRenderer()
+        
+    # form view tests #########################################################
+        
+    def test_delete_conf_formview(self):
+        rset = self.execute('EGroup X')
+        self.view('deleteconf', rset, template=None).source
+        
+    def test_massmailing_formview(self):
+        self.execute('INSERT EmailAddress X: X address L + "@cubicweb.org", '
+                     'U use_email X WHERE U is EUser, U login L')
+        rset = self.execute('EUser X')
+        self.view('massmailing', rset, template=None)
+        
+    def test_automatic_edition_formview(self):
+        rset = self.execute('EUser X')
+        self.view('edition', rset, row=0, template=None).source
+        
+    def test_automatic_edition_formview(self):
+        rset = self.execute('EUser X')
+        self.view('copy', rset, row=0, template=None).source
+        
+    def test_automatic_creation_formview(self):
+        self.view('creation', None, etype='EUser', template=None).source
+        
+    def test_automatic_muledit_formview(self):
+        rset = self.execute('EUser X')
+        self.view('muledit', rset, template=None).source
+        
+    def test_automatic_reledit_formview(self):
+        rset = self.execute('EUser X')
+        self.view('reledit', rset, row=0, rtype='login', template=None).source
+        
+    def test_automatic_inline_edit_formview(self):
+        geid = self.execute('EGroup X LIMIT 1')[0][0]
+        rset = self.execute('EUser X LIMIT 1')
+        self.view('inline-edition', rset, row=0, rtype='in_group', peid=geid, template=None).source
+                              
+    def test_automatic_inline_creation_formview(self):
+        geid = self.execute('EGroup X LIMIT 1')[0][0]
+        self.view('inline-creation', None, etype='EUser', rtype='in_group', peid=geid, template=None).source
+
+    # form tests ##############################################################
+    
+    def test_form_inheritance(self):
+        class CustomChangeStateForm(ChangeStateForm):
+            hello = IntField(name='youlou')
+            creation_date = DateTimeField(widget=DateTimePicker)
+        form = CustomChangeStateForm(self.req, redirect_path='perdu.com',
+                                     entity=self.entity)
+        form.form_render(state=123, trcomment=u'')
+
+    def test_change_state_form(self):
+        form = ChangeStateForm(self.req, redirect_path='perdu.com',
+                               entity=self.entity)
+        form.form_render(state=123, trcomment=u'')
+        
+    def test_edition_form(self):
+        rset = self.execute('EUser X LIMIT 1')
+        form = self.vreg.select_object('forms', 'edition', rset.req, rset, row=0, col=0)
+        # should be also selectable by specifying entity
+        self.vreg.select_object('forms', 'edition', self.request(), entity=rset.get_entity(0, 0))
+        self.failIf(any(f for f in form.fields if f is None))
+        
+    # fields tests ############################################################
+
+    def _render_entity_field(self, name, form):
+        form.form_add_entity_hiddens(form.edited_entity.e_schema)
+        form.form_build_context({})
+        return form.field_by_name(name).render(form, self.renderer)
+    
+    def _test_richtextfield(self, expected):
+        class RTFForm(EntityFieldsForm):
+            content = RichTextField()
+        card = self.add_entity('Card', title=u"tls sprint fev 2009",
+                               content=u'<h1>new widgets system</h1>',
+                               content_format=u'text/html')
+        form = RTFForm(self.req, redirect_path='perdu.com', entity=card)
+        self.assertTextEquals(self._render_entity_field('content', form), expected % {'eid': card.eid})
+
+        
+    def test_richtextfield_1(self):
+        self.req.use_fckeditor = lambda: False
+        self._test_richtextfield('''<select name="content_format:%(eid)s" id="content_format:%(eid)s" tabindex="0">
+<option value="text/rest">text/rest</option>
+<option selected="selected" value="text/html">text/html</option>
+<option value="text/plain">text/plain</option>
+<option value="text/cubicweb-page-template">text/cubicweb-page-template</option>
+</select><textarea tabindex="1" id="content:%(eid)s" name="content:%(eid)s" onkeypress="autogrow(this)">&lt;h1&gt;new widgets system&lt;/h1&gt;</textarea>''')
+
+    
+    def test_richtextfield_2(self):
+        self.req.use_fckeditor = lambda: True
+        self._test_richtextfield('''<input type="hidden" name="content_format:%(eid)s" value="text/html"/><textarea tabindex="0" cubicweb:type="wysiwyg" id="content:%(eid)s" name="content:%(eid)s" onkeypress="autogrow(this)">&lt;h1&gt;new widgets system&lt;/h1&gt;</textarea>''')
+
+
+    def test_filefield(self):
+        class FFForm(EntityFieldsForm):
+            data = FileField(format_field=StringField(name='data_format'),
+                             encoding_field=StringField(name='data_encoding'))
+        file = self.add_entity('File', name=u"pouet.txt",
+                               data=Binary('new widgets system'))
+        form = FFForm(self.req, redirect_path='perdu.com', entity=file)
+        self.assertTextEquals(self._render_entity_field('data', form),
+                              '''<input id="data:%(eid)s" type="file" name="data:%(eid)s" value="" tabindex="0"/>
+<a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
+<div id="data:%(eid)s-advanced" class="hidden">
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" type="text" name="data_format:%(eid)s" value="text/plain" tabindex="1"/><br/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" type="text" name="data_encoding:%(eid)s" value="UTF-8" tabindex="2"/><br/><br/>
+</div>
+<br/>
+<input type="checkbox" name="data:594__detach"/>
+detach attached file
+''' % {'eid': file.eid})
+
+        
+    def test_editablefilefield(self):
+        class EFFForm(EntityFieldsForm):
+            data = EditableFileField(format_field=StringField(name='data_format'),
+                                     encoding_field=StringField(name='data_encoding'))
+            def form_field_encoding(self, field):
+                return 'ascii'
+            def form_field_format(self, field):
+                return 'text/plain'
+        file = self.add_entity('File', name=u"pouet.txt",
+                               data=Binary('new widgets system'))
+        form = EFFForm(self.req, redirect_path='perdu.com', entity=file)
+        self.assertTextEquals(self._render_entity_field('data', form),
+                              '''<input id="data:%(eid)s" type="file" name="data:%(eid)s" value="" tabindex="0"/>
+<a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
+<div id="data:%(eid)s-advanced" class="hidden">
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" type="text" name="data_format:%(eid)s" value="text/plain" tabindex="1"/><br/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" type="text" name="data_encoding:%(eid)s" value="UTF-8" tabindex="2"/><br/><br/>
+</div>
+<br/>
+<input type="checkbox" name="data:594__detach"/>
+detach attached file
+<p><b>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.</b></p>
+<textarea tabindex="3" name="data:%(eid)s" onkeypress="autogrow(this)">new widgets system</textarea>''' % {'eid': file.eid})
+
+
+    def test_passwordfield(self):
+        class PFForm(EntityFieldsForm):
+            upassword = StringField(widget=PasswordInput)
+        form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity)
+        self.assertTextEquals(self._render_entity_field('upassword', form),
+                              '''<input id="upassword:%(eid)s" type="password" name="upassword:%(eid)s" value="__cubicweb_internal_field__" tabindex="0"/>
+<br/>
+<input type="password" name="upassword-confirm:%(eid)s" tabindex="0"/>
+&nbsp;
+<span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
+
+        
+if __name__ == '__main__':
+    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_formfields.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,77 @@
+"""unittests for cw.web.formfields"""
+
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.web.formwidgets import PasswordInput
+from cubicweb.web.formfields import *
+from cubicweb.entities.lib import Card
+from cubicweb.entities.authobjs import EUser
+from cubes.file.entities import File
+
+config = TestServerConfiguration('data')
+config.bootstrap_cubes()
+schema = config.load_schema()
+Card.schema = schema
+Card.__initialize__()
+EUser.schema = schema
+EUser.__initialize__()
+File.schema = schema
+File.__initialize__()
+        
+class GuessFieldTC(TestCase):
+    
+    def test_card_fields(self):
+        title_field = guess_field(Card, schema['title'])
+        self.assertIsInstance(title_field, StringField)
+        self.assertEquals(title_field.required, True)
+        
+        synopsis_field = guess_field(Card, schema['synopsis'])
+        self.assertIsInstance(synopsis_field, TextField)
+        self.assertEquals(synopsis_field.required, False)
+        
+        content_field = guess_field(Card, schema['content'])
+        self.assertIsInstance(content_field, RichTextField)
+        self.assertEquals(content_field.required, False)
+        
+        content_format_field = guess_field(Card, schema['content_format'])
+        self.assertEquals(content_format_field, None)
+        
+        wikiid_field = guess_field(Card, schema['wikiid'])
+        self.assertIsInstance(wikiid_field, StringField)
+        self.assertEquals(wikiid_field.required, False)
+
+        
+    def test_euser_fields(self):
+        upassword_field = guess_field(EUser, schema['upassword'])
+        self.assertIsInstance(upassword_field, StringField)
+        self.assertIsInstance(upassword_field.widget, PasswordInput)
+        self.assertEquals(upassword_field.required, True)
+
+        last_login_time_field = guess_field(EUser, schema['last_login_time'])
+        self.assertIsInstance(last_login_time_field, DateTimeField)
+        self.assertEquals(last_login_time_field.required, False)
+
+        in_group_field = guess_field(EUser, schema['in_group'])
+        self.assertIsInstance(in_group_field, RelationField)
+        self.assertEquals(in_group_field.required, True)
+        self.assertEquals(in_group_field.role, 'subject')
+
+        owned_by_field = guess_field(EUser, schema['owned_by'], 'object')
+        self.assertIsInstance(owned_by_field, RelationField)
+        self.assertEquals(owned_by_field.required, False)
+        self.assertEquals(owned_by_field.role, 'object')
+
+    def test_file_fields(self):
+        data_format_field = guess_field(File, schema['data_format'])
+        self.assertEquals(data_format_field, None)
+        data_encoding_field = guess_field(File, schema['data_encoding'])
+        self.assertEquals(data_encoding_field, None)
+
+        data_field = guess_field(File, schema['data'])
+        self.assertIsInstance(data_field, FileField)
+        self.assertEquals(data_field.required, True)
+        self.assertIsInstance(data_field.format_field, StringField)
+        self.assertIsInstance(data_field.encoding_field, StringField)
+        
+if __name__ == '__main__':
+    unittest_main()
--- a/web/test/unittest_owl.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4075 +0,0 @@
-"""unittests for schema2dot"""
-
-import os
-
-from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.compat import set
-from cubicweb.devtools.testlib import WebTest
-
-from lxml import etree
-from StringIO import StringIO
-
-       
-class OWLTC(WebTest):
-    
-    def test_schema2owl(self):
-
-        parser = etree.XMLParser(dtd_validation=True)
-
-        owl= StringIO('''<xsd:schema 
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:owl="http://www.w3.org/2002/07/owl#"
- targetNamespace="http://www.w3.org/2002/07/owl#"
- elementFormDefault="qualified" attributeFormDefault="unqualified">
-
-<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
-
-<!-- The ontology -->
-  
-<xsd:element name="Import">
-  <xsd:complexType>
-    <xsd:simpleContent>
-      <xsd:extension base="xsd:anyURI">
-        <xsd:attributeGroup ref="xml:specialAttrs"/>
-      </xsd:extension>
-    </xsd:simpleContent>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Ontology">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:element ref="owl:Import" minOccurs="0" maxOccurs="unbounded"/>
-      <xsd:group ref="owl:ontologyAnnotations"/>
-      <xsd:group ref="owl:Axiom" minOccurs="0" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attribute name="ontologyIRI" type="xsd:anyURI" use="optional"/>
-    <xsd:attribute name="versionIRI" type="xsd:anyURI" use="optional"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Entities, anonymous individuals, and literals -->
-
-<xsd:group name="Entity">
-  <xsd:choice>
-    <xsd:element ref="owl:Class"/>
-    <xsd:element ref="owl:Datatype"/>
-    <xsd:element ref="owl:ObjectProperty"/>
-    <xsd:element ref="owl:DataProperty"/>
-    <xsd:element ref="owl:AnnotationProperty"/>
-    <xsd:element ref="owl:NamedIndividual"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="Class">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Datatype">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
- 
-<xsd:element name="ObjectProperty">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataProperty">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationProperty">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="Individual">
-  <xsd:choice>
-    <xsd:element ref="owl:NamedIndividual"/>
-    <xsd:element ref="owl:AnonymousIndividual"/>
-  </xsd:choice>
-</xsd:group>
-  
-<xsd:element name="NamedIndividual">
-  <xsd:complexType>
-    <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnonymousIndividual">
-  <xsd:complexType>
-    <xsd:attribute name="nodeID" type="xsd:NCName" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Literal">
- <xsd:complexType>
-   <xsd:simpleContent>
-     <xsd:extension base="xsd:string">
-       <xsd:attribute name="datatypeIRI" type="xsd:anyURI"/>
-       <xsd:attributeGroup ref="xml:specialAttrs"/>
-     </xsd:extension>
-   </xsd:simpleContent>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Declarations -->
-
-<xsd:element name="Declaration">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:Entity"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-  
-<!-- Object property expressions -->
-
-<xsd:group name="ObjectPropertyExpression">
-  <xsd:choice>
-    <xsd:element ref="owl:ObjectProperty"/>
-    <xsd:element ref="owl:InverseObjectProperty"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="InverseObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:element ref="owl:ObjectProperty"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Data property expressions -->
-
-<xsd:group name="DataPropertyExpression">
-  <xsd:sequence>
-    <xsd:element ref="owl:DataProperty"/>
-  </xsd:sequence>
-</xsd:group>
-
-<!-- Data ranges -->
-
-<xsd:group name="DataRange">
-  <xsd:choice>
-    <xsd:element ref="owl:Datatype"/>
-    <xsd:element ref="owl:DataIntersectionOf"/>
-    <xsd:element ref="owl:DataUnionOf"/>
-    <xsd:element ref="owl:DataComplementOf"/>
-    <xsd:element ref="owl:DataOneOf"/>
-    <xsd:element ref="owl:DatatypeRestriction"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="DataIntersectionOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataRange" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataUnionOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataRange" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataComplementOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataRange"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataOneOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:element ref="owl:Literal" minOccurs="1" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DatatypeRestriction">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:element ref="owl:Datatype"/>
-      <xsd:element name="FacetRestriction" minOccurs="1" maxOccurs="unbounded">
-        <xsd:complexType>
-          <xsd:sequence>
-            <xsd:element ref="owl:Literal"/>
-          </xsd:sequence>
-          <xsd:attribute name="facet" type="xsd:anyURI" use="required"/>
-        </xsd:complexType>
-      </xsd:element>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Class expressions -->
-
-<xsd:group name="ClassExpression">
-  <xsd:choice>
-    <xsd:element ref="owl:Class"/>
-    <xsd:element ref="owl:ObjectIntersectionOf"/>
-    <xsd:element ref="owl:ObjectUnionOf"/>
-    <xsd:element ref="owl:ObjectComplementOf"/>
-    <xsd:element ref="owl:ObjectOneOf"/>
-    <xsd:element ref="owl:ObjectSomeValuesFrom"/>
-    <xsd:element ref="owl:ObjectAllValuesFrom"/>
-    <xsd:element ref="owl:ObjectHasValue"/>
-    <xsd:element ref="owl:ObjectHasSelf"/>
-    <xsd:element ref="owl:ObjectMinCardinality"/>
-    <xsd:element ref="owl:ObjectMaxCardinality"/>
-    <xsd:element ref="owl:ObjectExactCardinality"/>
-    <xsd:element ref="owl:DataSomeValuesFrom"/>
-    <xsd:element ref="owl:DataAllValuesFrom"/>
-    <xsd:element ref="owl:DataHasValue"/>
-    <xsd:element ref="owl:DataMinCardinality"/>
-    <xsd:element ref="owl:DataMaxCardinality"/>
-    <xsd:element ref="owl:DataExactCardinality"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="ObjectIntersectionOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectUnionOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectComplementOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectOneOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:Individual" minOccurs="1" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectSomeValuesFrom">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectAllValuesFrom">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectHasValue">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:Individual"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectHasSelf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectMinCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectMaxCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectExactCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataSomeValuesFrom">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression" minOccurs="1" maxOccurs="unbounded"/>
-      <xsd:group ref="owl:DataRange"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataAllValuesFrom">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression" minOccurs="1" maxOccurs="unbounded"/>
-      <xsd:group ref="owl:DataRange"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataHasValue">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:element ref="owl:Literal"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataMinCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataMaxCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataExactCardinality">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
-    </xsd:sequence>
-    <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Axioms -->
-
-<xsd:group name="Axiom">
-  <xsd:choice>
-    <xsd:element ref="owl:Declaration"/>
-    <xsd:group ref="owl:ClassAxiom"/>
-    <xsd:group ref="owl:ObjectPropertyAxiom"/>
-    <xsd:group ref="owl:DataPropertyAxiom"/>
-    <xsd:element ref="owl:HasKey"/>
-    <xsd:group ref="owl:Assertion"/>
-    <xsd:group ref="owl:AnnotationAxiom"/>
-  </xsd:choice>
-</xsd:group>
-
-<!-- Class expression axioms -->
-
-<xsd:group name="ClassAxiom">
-  <xsd:choice>
-    <xsd:element ref="owl:SubClassOf"/>
-    <xsd:element ref="owl:EquivalentClasses"/>
-    <xsd:element ref="owl:DisjointClasses"/>
-    <xsd:element ref="owl:DisjointUnion"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubClassOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ClassExpression"/> <!-- This is the subexpression -->
-      <xsd:group ref="owl:ClassExpression"/> <!-- This is the superexpression -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentClasses">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointClasses">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointUnion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:element ref="owl:Class"/>
-      <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Object property axioms -->
-
-<xsd:group name="ObjectPropertyAxiom">
-  <xsd:choice>
-    <xsd:element ref="owl:SubObjectPropertyOf"/>
-    <xsd:element ref="owl:EquivalentObjectProperties"/>
-    <xsd:element ref="owl:DisjointObjectProperties"/>
-    <xsd:element ref="owl:InverseObjectProperties"/>
-    <xsd:element ref="owl:ObjectPropertyDomain"/>
-    <xsd:element ref="owl:ObjectPropertyRange"/>
-    <xsd:element ref="owl:FunctionalObjectProperty"/>
-    <xsd:element ref="owl:InverseFunctionalObjectProperty"/>
-    <xsd:element ref="owl:ReflexiveObjectProperty"/>
-    <xsd:element ref="owl:IrreflexiveObjectProperty"/>
-    <xsd:element ref="owl:SymmetricObjectProperty"/>
-    <xsd:element ref="owl:AsymmetricObjectProperty"/>
-    <xsd:element ref="owl:TransitiveObjectProperty"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubObjectPropertyOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:choice> <!-- This is the subproperty expression or the property chain -->
-        <xsd:group ref="owl:ObjectPropertyExpression"/>
-        <xsd:element name="PropertyChain">
-          <xsd:complexType>
-            <xsd:sequence>
-              <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
-            </xsd:sequence>
-            <xsd:attributeGroup ref="xml:specialAttrs"/>
-          </xsd:complexType>
-        </xsd:element>
-      </xsd:choice>
-      <xsd:group ref="owl:ObjectPropertyExpression"/> <!-- This is the superproperty expression -->  
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentObjectProperties">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointObjectProperties">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyDomain">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyRange">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="InverseObjectProperties">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="2"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="FunctionalObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="InverseFunctionalObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ReflexiveObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="IrreflexiveObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="SymmetricObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AsymmetricObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
- 
-<xsd:element name="TransitiveObjectProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Data property axioms -->
-
-<xsd:group name="DataPropertyAxiom">
-  <xsd:choice>
-    <xsd:element ref="owl:SubDataPropertyOf"/>
-    <xsd:element ref="owl:EquivalentDataProperties"/>
-    <xsd:element ref="owl:DisjointDataProperties"/>
-    <xsd:element ref="owl:DataPropertyDomain"/>
-    <xsd:element ref="owl:DataPropertyRange"/>
-    <xsd:element ref="owl:FunctionalDataProperty"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubDataPropertyOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/> <!-- This is the subproperty expression -->
-      <xsd:group ref="owl:DataPropertyExpression"/> <!-- This is the superproperty expression -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentDataProperties">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointDataProperties">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyDomain">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:ClassExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyRange">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:DataRange"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="FunctionalDataProperty">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Key axioms -->
-
-<xsd:element name="HasKey">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ClassExpression"/>
-      <xsd:choice minOccurs="1" maxOccurs="unbounded">
-        <xsd:group ref="owl:ObjectPropertyExpression"/>
-        <xsd:group ref="owl:DataPropertyExpression"/>
-      </xsd:choice>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Assertions -->
-
-<xsd:group name="Assertion">
-  <xsd:choice>
-    <xsd:element ref="owl:SameIndividual"/>
-    <xsd:element ref="owl:DifferentIndividuals"/>
-    <xsd:element ref="owl:ClassAssertion"/>
-    <xsd:element ref="owl:ObjectPropertyAssertion"/>
-    <xsd:element ref="owl:NegativeObjectPropertyAssertion"/>
-    <xsd:element ref="owl:DataPropertyAssertion"/>
-    <xsd:element ref="owl:NegativeDataPropertyAssertion"/>
-  </xsd:choice>
-</xsd:group> 
-
-<xsd:element name="SameIndividual">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:Individual" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DifferentIndividuals">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:Individual" minOccurs="2" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ClassAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ClassExpression"/>
-      <xsd:group ref="owl:Individual"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:Individual"/> <!-- This is the source invididual  -->
-      <xsd:group ref="owl:Individual"/> <!-- This is the target individual -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="NegativeObjectPropertyAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:ObjectPropertyExpression"/>
-      <xsd:group ref="owl:Individual"/> <!-- This is the source invididual  -->
-      <xsd:group ref="owl:Individual"/> <!-- This is the target individual -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:Individual"/> <!-- This is the source invididual  -->
-      <xsd:element ref="owl:Literal"/> <!-- This is the target value -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="NegativeDataPropertyAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:group ref="owl:DataPropertyExpression"/>
-      <xsd:group ref="owl:Individual"/> <!-- This is the source invididual  -->
-      <xsd:element ref="owl:Literal"/> <!-- This is the target value -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<!-- Annotations  -->
-
-<xsd:element name="IRI">
-  <xsd:complexType>
-    <xsd:simpleContent>
-      <xsd:extension base="xsd:anyURI">
-        <xsd:attributeGroup ref="xml:specialAttrs"/>
-      </xsd:extension>
-    </xsd:simpleContent>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="AnnotationSubject">
-  <xsd:choice>
-    <xsd:element ref="owl:IRI"/>
-    <xsd:element ref="owl:AnonymousIndividual"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:group name="AnnotationValue">
-  <xsd:choice>
-    <xsd:element ref="owl:IRI"/>
-    <xsd:element ref="owl:AnonymousIndividual"/>
-    <xsd:element ref="owl:Literal"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="Annotation">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:annotationAnnotations"/>
-      <xsd:element ref="owl:AnnotationProperty"/>
-      <xsd:group ref="owl:AnnotationValue"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="axiomAnnotations">
-  <xsd:sequence>
-    <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
-  </xsd:sequence>
-</xsd:group>
-
-<xsd:group name="ontologyAnnotations">
-  <xsd:sequence>
-    <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
-  </xsd:sequence>
-</xsd:group>
-
-<xsd:group name="annotationAnnotations">
-  <xsd:sequence>
-    <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
-  </xsd:sequence>
-</xsd:group>
-
-<!-- Annotation axioms -->
-
-<xsd:group name="AnnotationAxiom">
-  <xsd:choice>
-    <xsd:element ref="owl:AnnotationAssertion"/>
-    <xsd:element ref="owl:SubAnnotationPropertyOf"/>
-    <xsd:element ref="owl:AnnotationPropertyDomain"/>
-    <xsd:element ref="owl:AnnotationPropertyRange"/>
-  </xsd:choice>
-</xsd:group>
-
-<xsd:element name="AnnotationAssertion">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:element ref="owl:AnnotationProperty"/>
-      <xsd:group ref="owl:AnnotationSubject"/>
-      <xsd:group ref="owl:AnnotationValue"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="SubAnnotationPropertyOf">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:element ref="owl:AnnotationProperty"/> <!-- This is the subproperty -->
-      <xsd:element ref="owl:AnnotationProperty"/> <!-- This is the superproperty -->
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationPropertyDomain">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:element ref="owl:AnnotationProperty"/>
-      <xsd:element ref="owl:IRI"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationPropertyRange">
-  <xsd:complexType>
-    <xsd:sequence>
-      <xsd:group ref="owl:axiomAnnotations"/>
-      <xsd:element ref="owl:AnnotationProperty"/>
-      <xsd:element ref="owl:IRI"/>
-    </xsd:sequence>
-    <xsd:attributeGroup ref="xml:specialAttrs"/>
-  </xsd:complexType>
-</xsd:element>
-
-</xsd:schema>
-
-''')
-
-        rdf = StringIO('''<xsd:schema
-        xmlns:xsd="http://www.w3.org/1999/XMLSchema"
-        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-        targetNamespace="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-      
-        <xsd:element name="RDF">
-                <xsd:complexType  content="elementOnly" >
-                        <xsd:sequence  maxOccurs="*" >
-                                <xsd:choice>
-                                        <xsd:element ref="rdf:TypedNode"   /><!-- abstract !-->
-                                        <xsd:element ref="rdf:Bag" />
-                                        <xsd:element ref="rdf:Seq" />
-                                        <xsd:element ref="rdf:Alt" />
-                                </xsd:choice>
-                        </xsd:sequence>
-                </xsd:complexType>
-        </xsd:element>
-
-        <!-- RDF Typed nodes -->
-       <xsd:complexType   name="TypedNodeType" content="elementOnly" >
-                <xsd:sequence maxOccurs="*" >
-                        <xsd:element ref="rdf:PropertyElt"   /><!--abstract !-->
-                </xsd:sequence>
-                <xsd:attribute  name="id" minOccurs="0" type="ID"  />
-                <xsd:attribute  name="type" minOccurs="0" type="string" />
-                <xsd:attribute name="about" minOccurs="0" type="string" />
-                <xsd:attribute  name="aboutEach" minOccurs="0" type="string" />
-                <xsd:attribute   name="aboutEachPrefix" minOccurs="0" type="string" />
-                <xsd:attribute  name="badID" minOccurs="0" type="ID" />
-        </xsd:complexType>
-        <xsd:element name="TypedNode"  abstract="true"  type="rdf:TypedNodeType" />
-
-        <xsd:element name="Description"
-                type="rdf:TypedNodeType" equivClass="rdf:TypedNode" />
-
-
-        <!-- RDF Property Elements -->
-        <xsd:complexType  name="PropertyEltType" >
-                <xsd:any minOccurs="0" />
-                <xsd:attribute name="id"  minOccurs="0"  type="ID" />
-                <xsd:attribute  name="resource" minOccurs="0"  type="string" />
-                <xsd:attribute  name="value" minOccurs="0"  type="string" />
-                <xsd:attribute  name="badID" minOccurs="0" type="ID"  />
-                <xsd:attribute name="parseType"  minOccurs="0" >
-                        <xsd:simpleType base="NMTOKEN">
-                                 <xsd:enumeration value="Resource"/>
-                                 <xsd:enumeration value="Literal" />
-                       </xsd:simpleType>
-                </xsd:attribute>
-                <xsd:anyAttribute  />
-        </xsd:complexType>
-
-        <xsd:element name="PropertyElt"  abstract="true" type="rdf:PropertyEltType" />
-
-        <xsd:element   name="subject"   equivClass="rdf:PropertyElt"  />
-        <xsd:element name="predicate"   equivClass="rdf:PropertyElt" />
-        <xsd:element name="object"  equivClass="rdf:PropertyElt" />
-        <xsd:element   name="type"  equivClass="rdf:PropertyElt" />
-
-        <xsd:element name="value">
-                <xsd:complexType>
-                        <xsd:any />
-                        <xsd:anyAttribute />
-                </xsd:complexType>
-        </xsd:element>
-
-
-        <!-- RDF Containers -->
-        <xsd:complexType name="Container" abstract="true" content="elementOnly" >
-                <xsd:sequence maxOccurs="*">
-                        <xsd:element name="li">
-                                <xsd:complexType>
-                                        <xsd:any/>
-                                        <xsd:attribute name="id"  minOccurs="0" type="ID" />
-                                        <xsd:attribute name="parseType" minOccurs="0" >
-                                                <xsd:simpleType base="NMTOKEN">
-                                                     <xsd:enumeration value="Resource"/>
-                                                     <xsd:enumeration value="Literal" />
-                                                </xsd:simpleType>
-                                        </xsd:attribute>
-                                        <xsd:anyAttribute />
-                                </xsd:complexType>
-                        </xsd:element>
-                </xsd:sequence>
-                <xsd:attribute name="id" type="ID" />
-                <xsd:anyAttribute />
-        </xsd:complexType>
-
-        <xsd:element name="Alt" type="rdf:Container" />
-        <xsd:element name="Bag" type="rdf:Container" />
-        <xsd:element name="Seq" type="rdf:Container" />
-
-</xsd:schema>
-
- ''')
-        
-        
-        xmlschema_rdf = etree.parse(rdf)
-        xmlschema_owl = etree.parse(owl)
-        
-        owlschema = etree.XMLSchema(xmlschema_owl)
-        valid = StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE rdf:RDF [
-        <!ENTITY owl "http://www.w3.org/2002/07/owl#" >
-        <!ENTITY xsd "http://www.w3.org/2001/XMLSchema#" >
-        <!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#" >
-        <!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
-        <!ENTITY inst_jplorg2 "http://logilab.org/owl/ontologies/inst_jplorg2#" >
-        
-        ]>
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:xsd="http://www.w3.org/2001/XMLSchema#" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns="http://logilab.org/owl/ontologies/inst_jplorg2#" xmlns:inst_jplorg2="http://logilab.org/owl/ontologies/inst_jplorg2#" xml:base="http://logilab.org/owl/ontologies/inst_jplorg2#">
-
-    <owl:Ontology rdf:about="">
-        <rdfs:comment>
-        inst_jplorg2 Cubicweb OWL Ontology                           
-                                        
-        </rdfs:comment>
-        <!-- classes definition --><owl:Class rdf:ID="Blog"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#interested_in"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#entry_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="BlogEntry"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#entry_of"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#interested_in"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#content_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#content"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Card"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#test_case_for"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#test_case_of"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#documented_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#instance_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#synopsis"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#content_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#content"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#wikiid"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Email"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#sent_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#sender"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#recipients"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#cc"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#parts"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#reply_to"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#cites"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_thread"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#generated_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#generated_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#reply_to"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#cites"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#subject"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#messageid"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#headers"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="EmailThread"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#forked_from"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_thread"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#forked_from"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="ExtProject"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#recommends"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#uses"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#url"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="File"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#documented_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data_encoding"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Image"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#screenshot"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#data_encoding"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="License"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#license_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#shortdesc"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#longdesc_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#longdesc"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#url"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Link"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#url"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#embed"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="MailingList"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#use_email"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#mailinglist_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#sent_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#mlid"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#archive"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#homepage"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Project"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#uses"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#uses"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#recommends"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#recommends"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#documented_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#documented_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#screenshot"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_state"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#filed_under"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#recommends"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#concerns"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#test_case_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#mailinglist_of"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#uses"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#interested_in"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#license_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#version_of"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#wf_info_for"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#summary"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#url"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#vcsurl"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#reporturl"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#downloadurl"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#debian_source_package"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="TestInstance"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#instance_of"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#for_version"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#generate_bug"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_state"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#wf_info_for"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#name"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Ticket"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#see_also"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#concerns"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#appeared_in"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#done_in"/>
-                                <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_state"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#attachment"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#identical_to"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#depends_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#depends_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#comments"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#generate_bug"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#wf_info_for"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#test_case_for"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#title"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#type"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#priority"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#load"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#load_left"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#debian_bug_number"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Version"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
-                                <!-- relations --><rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_basket"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#version_of"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#todo_by"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#in_state"/>
-                                <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
-                        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#conflicts"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#depends_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#require_permission"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#done_in"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#tags"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#depends_on"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#for_version"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#wf_info_for"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <rdfs:subClassOf>
-                              <owl:Restriction>
-                              <owl:onProperty rdf:resource="#appeared_in"/>
-                                <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
-                              </owl:Restriction>
-                           </rdfs:subClassOf>
-                                <!-- attributes --><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#num"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description_format"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#description"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#starting_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#prevision_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#publication_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#creation_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf><rdfs:subClassOf>
-                              <owl:Restriction>
-                                  <owl:onProperty rdf:resource="#modification_date"/>
-                                  <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
-                              </owl:Restriction>
-                        </rdfs:subClassOf></owl:Class><!-- property definition --><!-- object property --><owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Blog"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="interested_in">
-                              <rdfs:domain rdf:resource="#Blog"/>
-                              <rdfs:range rdf:resource="#EUser"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="entry_of">
-                              <rdfs:domain rdf:resource="#Blog"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="entry_of">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Blog"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="interested_in">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#EUser"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#BlogEntry"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="test_case_for">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="test_case_of">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="documented_by">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="instance_of">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#TestInstance"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Card"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="sent_on">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#MailingList"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="sender">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#EmailAddress"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="recipients">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#EmailAddress"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="cc">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#EmailAddress"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="parts">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#EmailPart"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="reply_to">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="cites">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_thread">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#EmailThread"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="generated_by">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#TrInfo"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="generated_by">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="reply_to">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="cites">
-                              <rdfs:domain rdf:resource="#Email"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#EmailThread"/>
-                              <rdfs:range rdf:resource="#EmailThread"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="forked_from">
-                              <rdfs:domain rdf:resource="#EmailThread"/>
-                              <rdfs:range rdf:resource="#EmailThread"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#EmailThread"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_thread">
-                              <rdfs:domain rdf:resource="#EmailThread"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="forked_from">
-                              <rdfs:domain rdf:resource="#EmailThread"/>
-                              <rdfs:range rdf:resource="#EmailThread"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="recommends">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="uses">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#ExtProject"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="documented_by">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#File"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="screenshot">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Image"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#License"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="license_of">
-                              <rdfs:domain rdf:resource="#License"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#License"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Link"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#MailingList"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="use_email">
-                              <rdfs:domain rdf:resource="#MailingList"/>
-                              <rdfs:range rdf:resource="#EmailAddress"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="mailinglist_of">
-                              <rdfs:domain rdf:resource="#MailingList"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="sent_on">
-                              <rdfs:domain rdf:resource="#MailingList"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#MailingList"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="uses">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="uses">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="recommends">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="recommends">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="documented_by">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="documented_by">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="screenshot">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_state">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#State"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="filed_under">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Folder"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="recommends">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="concerns">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="test_case_of">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="mailinglist_of">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#MailingList"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="uses">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="interested_in">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#EUser"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="license_of">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#License"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="version_of">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="wf_info_for">
-                              <rdfs:domain rdf:resource="#Project"/>
-                              <rdfs:range rdf:resource="#TrInfo"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="instance_of">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="for_version">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="generate_bug">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_state">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#State"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="wf_info_for">
-                              <rdfs:domain rdf:resource="#TestInstance"/>
-                              <rdfs:range rdf:resource="#TrInfo"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#ExtProject"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#BlogEntry"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Link"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Email"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="see_also">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="concerns">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="appeared_in">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="done_in">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_state">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#State"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Image"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="attachment">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#File"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="identical_to">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="depends_on">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="depends_on">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="comments">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Comment"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="generate_bug">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#TestInstance"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="wf_info_for">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#TrInfo"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="test_case_for">
-                              <rdfs:domain rdf:resource="#Ticket"/>
-                              <rdfs:range rdf:resource="#Card"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_basket">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Basket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="version_of">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Project"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="todo_by">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#EUser"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="in_state">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#State"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="conflicts">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="depends_on">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="require_permission">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#EPermission"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="done_in">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="tags">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Tag"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="depends_on">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Version"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="for_version">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#TestInstance"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="wf_info_for">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#TrInfo"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <owl:ObjectProperty rdf:ID="appeared_in">
-                              <rdfs:domain rdf:resource="#Version"/>
-                              <rdfs:range rdf:resource="#Ticket"/>
-                           </owl:ObjectProperty>                   
-                             
-                                <!-- datatype property --><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#Blog"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Blog"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Blog"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Blog"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#BlogEntry"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content_format">
-                          <rdfs:domain rdf:resource="#BlogEntry"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content">
-                          <rdfs:domain rdf:resource="#BlogEntry"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#BlogEntry"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#BlogEntry"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="synopsis">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content_format">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="wikiid">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Card"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="subject">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="date">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="messageid">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="headers">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Email"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#EmailThread"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#EmailThread"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#EmailThread"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#ExtProject"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:byte"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_format">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_encoding">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#File"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:byte"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_format">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_encoding">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Image"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="shortdesc">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="longdesc_format">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="longdesc">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#License"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="embed">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:boolean"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Link"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="mlid">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="archive">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="homepage">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#MailingList"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="summary">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="vcsurl">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="reporturl">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="downloadurl">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="debian_source_package">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Project"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
-                          <rdfs:domain rdf:resource="#TestInstance"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#TestInstance"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#TestInstance"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="type">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="priority">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="load">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:float"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="load_left">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:float"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="debian_bug_number">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:int"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Ticket"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="num">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:string"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="starting_date">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:date"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="prevision_date">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:date"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="publication_date">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:date"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
-                          <rdfs:domain rdf:resource="#Version"/>
-                          <rdfs:range rdf:resource="xsd:dateTime"/>
-                       </owl:DatatypeProperty> </owl:Ontology></rdf:RDF> ''')
-        doc = etree.parse(valid)
-        owlschema.validate(doc)
-
-if __name__ == '__main__':
-    unittest_main()
-
--- a/web/test/unittest_views_baseforms.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/test/unittest_views_baseforms.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,26 +1,25 @@
 """cubicweb.web.views.baseforms unit tests"""
 
 from StringIO import StringIO
+from datetime import date
 import re
 
+
 from logilab.common.testlib import unittest_main
+from logilab.common.decorators import clear_cache
 from cubicweb.devtools.apptest import EnvBasedTC
-
 from cubicweb.entities import AnyEntity
+from cubicweb.web import widgets
 
-from mx.DateTime import DateTime
-from cubicweb.web import widgets
-orig_today = widgets.today
-orig_now = widgets.now
+orig_now = widgets.datetime.now
 
 def setup_module(options):
     def _today():
-        return DateTime(0000, 1, 1)
-    widgets.today = widgets.now = _today
+        return date(0000, 1, 1)
+    widgets.datetime.now = _today
 
 def teardown_module(options, results):
-    widgets.today = orig_today
-    widgets.now = orig_now
+    widgets.datetime.now = orig_now
 
 
 def cleanup_text(text):
@@ -207,6 +206,7 @@
         class BlogEntryPlus(BlogEntry):
             __rtags__ = {'checked_by': 'primary'}
         self.vreg.register_vobject_class(BlogEntryPlus)
+        clear_cache(self.vreg, 'etype_class')
         # an admin should be able to edit the checked_by relation
         html = self._build_creation_form('BlogEntry')
         self.failUnless('name="edits-checked_by:A"' in html)
--- a/web/test/unittest_viewselector.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/test/unittest_viewselector.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,25 +1,21 @@
 # -*- coding: iso-8859-1 -*-
 """XXX rename, split, reorganize this
 """
-
-import os.path as osp
+from __future__ import with_statement
 
-from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from logilab.common.testlib import unittest_main
 
-
+from cubicweb.devtools.apptest import EnvBasedTC
 from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
-from cubicweb.common.selectors import match_user_group
-
-from cubicweb.web._exceptions import NoSelectableObject
+from cubicweb.selectors import (match_user_groups, implements,
+                                specified_etype_implements, rql_condition,
+                                traced_selection)
+from cubicweb.web import NoSelectableObject
 from cubicweb.web.action import Action
 from cubicweb.web.views import (baseviews, tableview, baseforms, calendar, 
                                 management, embedding, actions, startup, 
-                                euser, schemaentities, xbel, vcard,
-                                treeview, idownloadable, wdoc, debug)
-from cubicweb.entities.lib import Card
-from cubicweb.interfaces import IMileStone
-from cubicweb.web.views import owl
+                                euser, schemaentities, xbel, vcard, owl,
+                                treeview, idownloadable, wdoc, debug, eproperties)
 
 USERACTIONS = [('myprefs', actions.UserPreferencesAction),
                ('myinfos', actions.UserInfoAction),
@@ -65,31 +61,25 @@
             raise
         
     
-    def test_possible_views(self):
-        # no entity
+    def test_possible_views_none_rset(self):
         req = self.request()
         self.assertListEqual(self.pviews(req, None),
                              [('changelog', wdoc.ChangeLogView),
                               ('debug', debug.DebugView),
-                              ('epropertiesform', management.EpropertiesForm),
+                              ('epropertiesform', eproperties.EPropertiesForm),
                               ('index', startup.IndexView),
                               ('info', management.ProcessInformationView),
                               ('manage', startup.ManageView),
                               ('owl', owl.OWLView),
                               ('schema', startup.SchemaView),
-                              ('systemepropertiesform', management.SystemEpropertiesForm)])
-        # no entity but etype
+                              ('systemepropertiesform', eproperties.SystemEPropertiesForm)])
+        
+    def test_possible_views_noresult(self):
         rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
         self.assertListEqual(self.pviews(req, rset),
-                             [#('changelog', wdoc.ChangeLogView),
-                              #('epropertiesform', management.EpropertiesForm),
-                              #('index', startup.IndexView),
-                              #('info', management.ProcessInformationView),
-                              #('manage', startup.ManageView),
-                              #('schema', startup.SchemaView),
-                              #('systemepropertiesform', management.SystemEpropertiesForm)
-                                 ])
-        # one entity
+                             [])
+        
+    def test_possible_views_one_egroup(self):
         rset, req = self.env.get_rset_and_req('EGroup X WHERE X name "managers"')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', baseviews.CSVRsetView),
@@ -110,7 +100,8 @@
                               ('xbel', xbel.XbelView),
                               ('xml', baseviews.XmlView),
                               ])
-        # list of entities of the same type
+            
+    def test_possible_views_multiple_egroups(self):
         rset, req = self.env.get_rset_and_req('EGroup X')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', baseviews.CSVRsetView),
@@ -131,7 +122,8 @@
                               ('xbel', xbel.XbelView),
                               ('xml', baseviews.XmlView),
                               ])
-        # list of entities of different types
+        
+    def test_possible_views_multiple_different_types(self):
         rset, req = self.env.get_rset_and_req('Any X')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', baseviews.CSVRsetView),
@@ -152,7 +144,8 @@
                               ('xbel', xbel.XbelView),
                               ('xml', baseviews.XmlView),
                               ])
-        # whatever
+        
+    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')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', baseviews.CSVRsetView),
@@ -160,7 +153,8 @@
                               ('rsetxml', baseviews.XMLRsetView),
                               ('table', tableview.TableView),
                               ])
-        # list of euser entities
+
+    def test_possible_views_multiple_eusers(self):
         rset, req = self.env.get_rset_and_req('EUser X')
         self.assertListEqual(self.pviews(req, rset),
                              [('csvexport', baseviews.CSVRsetView),
@@ -189,6 +183,7 @@
         self.assertDictEqual(self.pactions(req, None),
                              {'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')
@@ -196,6 +191,7 @@
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               })
+        
     def test_possible_actions_same_type_entities(self):
         rset, req = self.env.get_rset_and_req('EGroup X')
         self.assertDictEqual(self.pactions(req, rset),
@@ -205,6 +201,7 @@
                               'moreactions': [('delete', actions.DeleteAction),
                                               ('addentity', 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),
@@ -212,6 +209,7 @@
                               'siteactions': SITEACTIONS,
                               'moreactions': [('delete', 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),
@@ -226,21 +224,9 @@
                               'mainactions': [('edit', actions.ModifyAction),
                                               ('workflow', schemaentities.ViewWorkflowAction),],
                               'moreactions': [('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction)],
+                                              ('copy', actions.CopyAction),
+                                              ('managepermission', actions.ManagePermissionsAction)],
                               })
-
-    def test_load_subinterface_based_vojects(self):
-        self.vreg._lastmodifs = {} # clear cache
-        self.vreg.register_objects([osp.join(BASE, 'web', 'views', 'iprogress.py')])
-        # check progressbar was kicked
-        self.failIf('progressbar' in self.vreg['views'])
-        class MyCard(Card):
-            __implements__ = (IMileStone,)
-        self.vreg.register_vobject_class(MyCard)
-        self.vreg._lastmodifs = {} # clear cache
-        self.vreg.register_objects([osp.join(BASE, 'web', 'views', 'iprogress.py')])
-        # check progressbar isn't kicked
-        self.assertEquals(len(self.vreg['views']['progressbar']), 1)
         
 
     def test_select_creation_form(self):
@@ -253,7 +239,7 @@
         del req.form['etype']
         # custom creation form
         class EUserCreationForm(baseforms.CreationForm):
-            accepts = ('EUser',)
+            __select__ = specified_etype_implements('EUser')
         self.vreg.register_vobject_class(EUserCreationForm)
         req.form['etype'] = 'EUser'
         self.assertIsInstance(self.vreg.select_view('creation', req, rset),
@@ -411,8 +397,7 @@
         class SomeAction(Action):
             id = 'yo'
             category = 'foo'
-            __selectors__ = (match_user_group,)
-            require_groups = ('owners', )            
+            __select__ = match_user_groups('owners')
         self.vreg.register_vobject_class(SomeAction)
         self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
         try:
@@ -435,15 +420,9 @@
             del self.vreg[SomeAction.__registry__][SomeAction.id]
 
 
-        
-
-
-from cubicweb.web.action import EntityAction
-
-class EETypeRQLAction(EntityAction):
+class EETypeRQLAction(Action):
     id = 'testaction'
-    accepts = ('EEType',)
-    condition = 'X name "EEType"'
+    __select__ = implements('EEType') & rql_condition('X name "EEType"')
     title = 'bla'
 
 class RQLActionTC(ViewSelectorTC):
@@ -464,7 +443,8 @@
                               'mainactions': [('edit', actions.ModifyAction)],
                               'moreactions': [('delete', actions.DeleteAction),
                                               ('copy', actions.CopyAction),
-                                              ('testaction', EETypeRQLAction)],
+                                              ('testaction', EETypeRQLAction),
+                                              ('managepermission', actions.ManagePermissionsAction)],
                               })
         rset, req = self.env.get_rset_and_req('EEType X WHERE X name "ERType"')
         self.assertDictEqual(self.pactions(req, rset),
@@ -472,7 +452,8 @@
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction)],
                               'moreactions': [('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction)],
+                                              ('copy', actions.CopyAction),
+                                              ('managepermission', actions.ManagePermissionsAction)],
                               })
         
 
--- a/web/test/unittest_widgets.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/test/unittest_widgets.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,11 +2,13 @@
 
 """
 
-from mx.DateTime import now
-NOW = now()
+from datetime import datetime
+NOW = datetime.now()
+
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.apptest import EnvBasedTC
 
+from cubicweb.common.mttransforms import HAS_TAL
 from cubicweb.web.widgets import widget, AutoCompletionWidget
 
 
@@ -41,6 +43,10 @@
         entity
         self.assertEquals(w.required(entity), False)
         self.assertEquals(w.render(entity), '')
+        if HAS_TAL:
+            tal_format = u'\n<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>'
+        else:
+            tal_format = u''
         self.assertTextEquals(w.edit_render(entity),
                            u'''<input type="hidden" name="edits-description:X" value="__cubicweb_internal_field__"/>
 <input type="hidden" name="edits-description_format:X" value="__cubicweb_internal_field__"/>
@@ -48,9 +54,8 @@
 <select name="description_format:X" id="description_format:X" tabindex="0">
 <option value="text/rest" >text/rest</option>
 <option value="text/html" selected="selected">text/html</option>
-<option value="text/plain" >text/plain</option>
-<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>
-</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1"></textarea>''')
+<option value="text/plain" >text/plain</option>%s
+</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1"></textarea>''' % tal_format)
 
     def test_textarea_widget_previous_value(self):
         self.add_entity('EProperty', pkey=u'ui.fckeditor', value=u'')
@@ -62,6 +67,10 @@
         entity.eid = 'X'
         self.assertEquals(w.required(entity), False)
         self.assertEquals(w.render(entity), '')
+        if HAS_TAL:
+            tal_format = u'\n<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>'
+        else:
+            tal_format = u''
         self.assertTextEquals(w.edit_render(entity),
                            u'''<input type="hidden" name="edits-description:X" value="__cubicweb_internal_field__"/>
 <input type="hidden" name="edits-description_format:X" value="__cubicweb_internal_field__"/>
@@ -69,9 +78,8 @@
 <select name="description_format:X" id="description_format:X" tabindex="0">
 <option value="text/rest" >text/rest</option>
 <option value="text/html" selected="selected">text/html</option>
-<option value="text/plain" >text/plain</option>
-<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>
-</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1">a description</textarea>''')
+<option value="text/plain" >text/plain</option>%s
+</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1">a description</textarea>''' % tal_format)
 
     def test_fckeditor_widget(self):
         w = self.get_widget('State', 'description', 'String')
@@ -179,10 +187,9 @@
     def test_datetime_widget(self):
         w = self.get_widget('Personne', 'datenaiss', 'Datetime')
         self.assertEquals(w.name, 'datenaiss')
-        now_ = now()
         example = '%s, or without time: %s' % (        
-            now_.strftime(self.vreg.property_value('ui.datetime-format')),
-            now_.strftime(self.vreg.property_value('ui.date-format')))
+            NOW.strftime(self.vreg.property_value('ui.datetime-format')),
+            NOW.strftime(self.vreg.property_value('ui.date-format')))
         self.assertEquals(w.render_example(self.request()), example)
         self.assertDictEquals(w.attrs, {'accesskey': 'd', 'maxlength': 16, 'size': 16})
         entity = self.etype_instance('Personne')
@@ -214,7 +221,7 @@
     def test_float_widget(self):
         w = self.get_widget('Personne', 'salary', 'Float')
         self.assertEquals(w.name, 'salary')
-        format = now().strftime(self.vreg.property_value('ui.float-format'))
+        format = self.vreg.property_value('ui.float-format')
         self.assertEquals(w.render_example(self.request()), format % 1.23)
         self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
         entity = self.etype_instance('Personne')
@@ -229,7 +236,7 @@
     def test_float_widget_previous_value(self):
         w = self.get_widget('Personne', 'salary', 'Float')
         self.assertEquals(w.name, 'salary')
-        format = now().strftime(self.vreg.property_value('ui.float-format'))
+        format = self.vreg.property_value('ui.float-format')
         self.assertEquals(w.render_example(self.request()), format % 1.23)
         self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
         req = self.request()
@@ -338,7 +345,7 @@
     def test_nonregr_float_widget_with_none(self):
         w = self.get_widget('Personne', 'salary', 'Float')
         self.assertEquals(w.name, 'salary')
-        format = now().strftime(self.vreg.property_value('ui.float-format'))
+        format = self.vreg.property_value('ui.float-format')
         self.assertEquals(w.render_example(self.request()), format % 1.23)
         self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
         req = self.request()
--- a/web/views/__init__.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/__init__.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,13 +1,17 @@
-"""Views/forms and actions for the CubicWeb web client
+"""Views, forms, actions... for the CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
-    
+
+import os
+from tempfile import mktemp
+
 from rql import nodes
 
+
 def need_table_view(rset, schema):
     """return True if we think that a table view is more appropriate than a
     list or primary view to display the given result set
@@ -68,29 +72,34 @@
         return 'list'
     return 'table'
 
-def linksearch_match(req, rset):
-    """when searching an entity to create a relation, return True if entities in
-    the given rset may be used as relation end
-    """
-    try:
-        searchedtype = req.search_state[1][-1]
-    except IndexError:
-        return 0 # no searching for association
-    for etype in rset.column_types(0):
-        if etype != searchedtype:
-            return 0
-    return 1
     
 def linksearch_select_url(req, rset):
     """when searching an entity to create a relation, return an url to select
     entities in the given rset
     """
     req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-    target, link_eid, r_type, searchedtype = req.search_state[1]
+    target, eid, r_type, searchedtype = req.search_state[1]
     if target == 'subject':
-        id_fmt = '%s:%s:%%s' % (link_eid, r_type)
+        id_fmt = '%s:%s:%%s' % (eid, r_type)
     else:
-        id_fmt = '%%s:%s:%s' % (r_type, link_eid)
+        id_fmt = '%%s:%s:%s' % (r_type, eid)
     triplets = '-'.join(id_fmt % row[0] for row in rset.rows)
-    return "javascript: selectForAssociation('%s', '%s');" % (triplets,
-                                                              link_eid)
+    return "javascript: selectForAssociation('%s', '%s');" % (triplets, eid)
+                
+        
+class TmpFileViewMixin(object):
+    binary = True
+    content_type = 'application/octet-stream'
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+    
+    def call(self):
+        self.cell_call()
+        
+    def cell_call(self, row=0, col=0):
+        self.row, self.col = row, col # in case one need it
+        tmpfile = mktemp('.png')
+        try:
+            self._generate(tmpfile)
+            self.w(open(tmpfile).read())
+        finally:
+            os.unlink(tmpfile)
--- a/web/views/actions.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/actions.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,70 +6,108 @@
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.selectors import (searchstate_accept, match_user_group, yes,
-                                       one_line_rset, two_lines_rset, one_etype_rset,
-                                       authenticated_user, none_rset,
-                                       match_search_state, chainfirst, chainall)
-
-from cubicweb.web.action import Action, EntityAction,  LinkToEntityAction
-from cubicweb.web.views import linksearch_select_url, linksearch_match
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import (EntitySelector, 
+    one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
+    non_final_entity,
+    authenticated_user, match_user_groups, match_search_state,
+    has_permission, has_add_permission,
+    )
+from cubicweb.web.action import Action
+from cubicweb.web.views import linksearch_select_url, vid_from_rset
+from cubicweb.web.views.editforms import AutomaticEntityForm
 
 _ = unicode
 
+
+class has_editable_relation(EntitySelector):
+    """accept if some relations for an entity found in the result set is
+    editable by the logged user.
+
+    See `EntitySelector` documentation for behaviour when row is not specified.
+    """
+        
+    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'):
+            return 1
+        for rschema, targetschemas, role in AutomaticEntityForm.erelations_by_category(
+            entity, ('primary', 'secondary'), 'add'):
+            if not rschema.is_final():
+                return 1
+        return 0
+
+@objectify_selector
+def match_searched_etype(cls, req, rset, **kwargs):
+    return req.match_search_state(rset)
+
+@objectify_selector
+def view_is_not_default_view(cls, req, rset, **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):
+        return 1
+    return 0
+
+@objectify_selector
+def addable_etype_empty_rset(cls, req, rset, **kwargs):
+    if rset is not None and not rset.rowcount:
+        rqlst = rset.syntax_tree()
+        if len(rqlst.children) > 1:
+            return 0
+        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)
+            if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+                   and eschema.has_perm(req, 'add'):
+                return 1
+    return 0
+
 # generic primary actions #####################################################
 
-class SelectAction(EntityAction):
+class SelectAction(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.
     """
-    category = 'mainactions'    
-    __selectors__ = (searchstate_accept,)
-    search_states = ('linksearch',)
-    order = 0
+    id = 'select'
+    __select__ = match_search_state('linksearch') & match_searched_etype()
     
-    id = 'select'
     title = _('select')
-    
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        return linksearch_match(req, rset)
+    category = 'mainactions'    
+    order = 0
     
     def url(self):
         return linksearch_select_url(self.req, self.rset)
 
 
 class CancelSelectAction(Action):
+    id = 'cancel'
+    __select__ = match_search_state('linksearch')
+    
+    title = _('cancel select')
     category = 'mainactions'
-    search_states = ('linksearch',)
     order = 10
     
-    id = 'cancel'
-    title = _('cancel select')
-    
     def url(self):
-        target, link_eid, r_type, searched_type = self.req.search_state[1]
-        return self.build_url(rql="Any X WHERE X eid %s" % link_eid,
+        target, eid, r_type, searched_type = self.req.search_state[1]
+        return self.build_url(str(eid),
                               vid='edition', __mode='normal')
 
 
 class ViewAction(Action):
-    category = 'mainactions'    
-    __selectors__ = (match_user_group, searchstate_accept)
-    require_groups = ('users', 'managers')
-    order = 0
-    
     id = 'view'
-    title = _('view')
+    __select__ = (match_search_state('normal') &
+                  match_user_groups('users', 'managers') &
+                  view_is_not_default_view() & 
+                  non_final_entity())
     
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        # 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):
-            return 1
-        return 0
+    title = _('view')
+    category = 'mainactions'    
+    order = 0
     
     def url(self):
         params = self.req.form.copy()
@@ -79,75 +117,67 @@
                               **params)
 
 
-class ModifyAction(EntityAction):
-    category = 'mainactions'
-    __selectors__ = (one_line_rset, searchstate_accept)
-    schema_action = 'update'
-    order = 10
-    
+class ModifyAction(Action):
     id = 'edit'
-    title = _('modify')
+    __select__ = (match_search_state('normal') &
+                  one_line_rset() & 
+                  (has_permission('update') | has_editable_relation('add')))
     
-    @classmethod
-    def has_permission(cls, entity, action):
-        if entity.has_perm(action):
-            return True
-        # if user has no update right but it can modify some relation,
-        # display action anyway
-        for dummy in entity.srelations_by_category(('generic', 'metadata'),
-                                                   'add'):
-            return True
-        for rschema, targetschemas, role in entity.relations_by_category(
-            ('primary', 'secondary'), 'add'):
-            if not rschema.is_final():
-                return True
-        return False
+    title = _('modify')
+    category = 'mainactions'
+    order = 10
 
     def url(self):
         entity = self.rset.get_entity(self.row or 0, self.col or 0)
         return entity.absolute_url(vid='edition')
         
 
-class MultipleEditAction(EntityAction):
+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'))
+
+    title = _('modify')
     category = 'mainactions'
-    __selectors__ = (two_lines_rset, one_etype_rset,
-                     searchstate_accept)
-    schema_action = 'update'
     order = 10
     
-    id = 'muledit' # XXX get strange conflicts if id='edit'
-    title = _('modify')
-    
     def url(self):
         return self.build_url('view', rql=self.rset.rql, vid='muledit')
 
 
 # generic secondary actions ###################################################
 
-class ManagePermissions(LinkToEntityAction):
-    accepts = ('Any',)
+class ManagePermissionsAction(Action):
+    id = 'managepermission'
+    __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
+
+    title = _('manage permissions')
     category = 'moreactions'
-    id = 'addpermission'
-    title = _('manage permissions')
     order = 100
 
-    etype = 'EPermission'
-    rtype = 'require_permission'
-    target = 'object'
+    @classmethod
+    def registered(cls, vreg):
+        super(ManagePermissionsAction, cls).registered(vreg)
+        if 'require_permission' in vreg.schema:
+            cls.__select__ = (one_line_rset() & non_final_entity() &
+                              (match_user_groups('managers')
+                               | relation_possible('require_permission', 'subject', 'EPermission',
+                                                   action='add')))
+        return super(ManagePermissionsAction, cls).registered(vreg)
     
     def url(self):
         return self.rset.get_entity(0, 0).absolute_url(vid='security')
 
     
-class DeleteAction(EntityAction):
+class DeleteAction(Action):
+    id = 'delete'
+    __select__ = has_permission('delete')
+    
+    title = _('delete')
     category = 'moreactions' 
-    __selectors__ = (searchstate_accept,)
-    schema_action = 'delete'
     order = 20
     
-    id = 'delete'
-    title = _('delete')
-    
     def url(self):
         if len(self.rset) == 1:
             entity = self.rset.get_entity(0, 0)
@@ -155,14 +185,14 @@
         return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
     
         
-class CopyAction(EntityAction):
+class CopyAction(Action):
+    id = 'copy'
+    __select__ = one_line_rset() & has_permission('add')
+    
+    title = _('copy')
     category = 'moreactions'
-    schema_action = 'add'
     order = 30
     
-    id = 'copy'
-    title = _('copy')
-    
     def url(self):
         entity = self.rset.get_entity(self.row or 0, self.col or 0)
         return entity.absolute_url(vid='copy')
@@ -172,35 +202,15 @@
     """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') &
+                  (addable_etype_empty_rset()
+                   | (two_lines_rset() & one_etype_rset & has_add_permission()))
+                  )
+
     category = 'moreactions'
-    id = 'addentity'
     order = 40
     
-    def etype_rset_selector(cls, req, rset, **kwargs):
-        if rset is not None and not rset.rowcount:
-            rqlst = rset.syntax_tree()
-            if len(rqlst.children) > 1:
-                return 0
-            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)
-                if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
-                       and eschema.has_perm(req, 'add'):
-                    return 1
-        return 0
-
-    def has_add_perm_selector(cls, req, rset, **kwargs):
-        eschema = cls.schema.eschema(rset.description[0][0])
-        if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
-               and eschema.has_perm(req, 'add'):
-            return 1
-        return 0
-    __selectors__ = (match_search_state,
-                     chainfirst(etype_rset_selector,
-                                chainall(two_lines_rset, one_etype_rset,
-                                         has_add_perm_selector)))
-
     @property
     def rsettype(self):
         if self.rset:
@@ -218,36 +228,36 @@
 # logged user actions #########################################################
 
 class UserPreferencesAction(Action):
+    id = 'myprefs'
+    __select__ = authenticated_user()
+    
+    title = _('user preferences')
     category = 'useractions'
-    __selectors__ = authenticated_user,
     order = 10
-    
-    id = 'myprefs'
-    title = _('user preferences')
 
     def url(self):
         return self.build_url(self.id)
 
 
 class UserInfoAction(Action):
+    id = 'myinfos'
+    __select__ = authenticated_user()
+    
+    title = _('personnal informations')
     category = 'useractions'
-    __selectors__ = authenticated_user,
     order = 20
-    
-    id = 'myinfos'
-    title = _('personnal informations')
 
     def url(self):
         return self.build_url('euser/%s'%self.req.user.login, vid='edition')
 
 
 class LogoutAction(Action):
+    id = 'logout'
+    __select__ = authenticated_user()
+    
+    title = _('logout')
     category = 'useractions'
-    __selectors__ = authenticated_user,
     order = 30
-    
-    id = 'logout'
-    title = _('logout')
 
     def url(self):
         return self.build_url(self.id)
@@ -256,76 +266,28 @@
 # site actions ################################################################
 
 class ManagersAction(Action):
+    __abstract__ = True
+    __select__ = match_user_groups('managers')
+
     category = 'siteactions'
-    __abstract__ = True
-    __selectors__ = match_user_group,
-    require_groups = ('managers',)
 
     def url(self):
         return self.build_url(self.id)
 
     
 class SiteConfigurationAction(ManagersAction):
-    order = 10
     id = 'siteconfig'
     title = _('site configuration')
+    order = 10
 
     
 class ManageAction(ManagersAction):
-    order = 20
     id = 'manage'
     title = _('manage')
-
-
-class ViewSchemaAction(Action):
-    category = 'siteactions'
-    id = 'schema'
-    title = _("site schema")
-    __selectors__ = yes,
-    order = 30
-    
-    def url(self):
-        return self.build_url(self.id)
+    order = 20
 
 
-# content type specific actions ###############################################
-
-class FollowAction(EntityAction):
-    category = 'mainactions'
-    accepts = ('Bookmark',)
-    
-    id = 'follow'
-    title = _('follow')
-    
-    def url(self):
-        return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views.bookmark import FollowAction
+FollowAction = class_moved(FollowAction)
 
-class UserPreferencesEntityAction(EntityAction):
-    __selectors__ = EntityAction.__selectors__ + (one_line_rset, match_user_group,)
-    require_groups = ('owners', 'managers')
-    category = 'mainactions'
-    accepts = ('EUser',)
-    
-    id = 'prefs'
-    title = _('preferences')
-    
-    def url(self):
-        login = self.rset.get_entity(self.row or 0, self.col or 0).login
-        return self.build_url('euser/%s'%login, vid='epropertiesform')
-
-# schema view action
-def schema_view(cls, req, rset, row=None, col=None, view=None,
-                **kwargs):
-    if view is None or not view.id == 'schema':
-        return 0
-    return 1
-
-class DownloadOWLSchemaAction(Action):
-    category = 'mainactions'
-    id = 'download_as_owl'
-    title = _('download schema as owl')
-    __selectors__ = none_rset, schema_view
-   
-    def url(self):
-        return self.build_url('view', vid='owl')
-
--- a/web/views/ajaxedit.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/ajaxedit.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,13 +1,12 @@
 """Set of views allowing edition of entities/relations using ajax
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.selectors import (chainfirst, match_form_params,
-                                    match_kwargs)
+from cubicweb.selectors import match_form_params, match_kwargs
 from cubicweb.web.box import EditRelationBoxTemplate
 
 class AddRelationView(EditRelationBoxTemplate):
@@ -18,7 +17,8 @@
     class attributes.
     """
     __registry__ = 'views'
-    __selectors__ = (chainfirst(match_form_params, match_kwargs),)
+    __select__ = (match_form_params('rtype', 'target')
+                  | match_kwargs('rtype', 'target'))
     property_defs = {} # don't want to inherit this from Box
     id = 'xaddrelation'
     expected_kwargs = form_params = ('rtype', 'target')
--- a/web/views/apacherewrite.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/apacherewrite.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,7 @@
 are much more limited for the moment)
 
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -11,7 +11,7 @@
 from re import compile
 
 from cubicweb.web import Redirect
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
 
 class RewriteCond(object):
     def __init__(self, condition, match='host', rules=(), action='rewrite'):
@@ -46,7 +46,7 @@
         return path
 
     
-class ApacheURLRewrite(SingletonComponent):
+class ApacheURLRewrite(Component):
     """inherit from this class with actual rules to activate apache style rewriting
 
     rules should have the form :
--- a/web/views/basecomponents.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/basecomponents.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,31 +2,26 @@
 
 * the rql input form
 * the logged user link
-* the workflow history section for workflowable objects
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from rql import parse
 
-from cubicweb import Unauthorized
+from cubicweb.selectors import yes, two_etypes_rset, match_form_params
 from cubicweb.common.uilib import html_escape, toggle_action
-from cubicweb.common.selectors import yes, non_final_entity, one_line_rset
 from cubicweb.schema import display_name
-from cubicweb.common.selectors import (chainfirst, two_etypes_rset,
-                                    match_form_params)
 
 from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink
-from cubicweb.web.component import (VComponent, SingletonVComponent, EntityVComponent, 
-                                    RelatedObjectsVComponent)
+from cubicweb.web.component import Component, RelatedObjectsVComponent
 
 _ = unicode
 
 
-class RQLInputForm(SingletonVComponent):
+class RQLInputForm(Component):
     """build the rql input form, usually displayed in the header"""
     id = 'rqlinput'
     visible = False
@@ -55,7 +50,7 @@
         self.w(u'</form></div>')
 
 
-class ApplLogo(SingletonVComponent):
+class ApplLogo(Component):
     """build the application logo, usually displayed in the header"""
     id = 'logo'
     site_wide = True # don't want user to hide this component using an eproperty
@@ -64,7 +59,7 @@
                % (self.req.base_url(), self.req.external_resource('LOGO')))
 
 
-class ApplHelp(SingletonVComponent):
+class ApplHelp(Component):
     """build the help button, usually displayed in the header"""
     id = 'help'
     def call(self):
@@ -73,7 +68,7 @@
                   self.req._(u'help'),))
 
 
-class UserLink(SingletonVComponent):
+class UserLink(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
     """
@@ -104,17 +99,22 @@
             self.w(self.req._('anonymous'))
             self.w(u'''&nbsp;[<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
                    % (self.req._('i18n_login_popup')))
+            # FIXME maybe have an other option to explicitely authorise registration
+            #       also provide a working register view
+#             if self.config['anonymous-user']:
+#                 self.w(u'''&nbsp;[<a class="logout" href="?vid=register">%s</a>]'''
+#                        % (self.req._('i18n_register_user')))
         else:
             self.w(self.req._('anonymous'))
             self.w(u'&nbsp;[<a class="logout" href="%s">%s</a>]'
                    % (self.build_url('login'), self.req._('login')))
 
 
-class ApplicationMessage(SingletonVComponent):
+class ApplicationMessage(Component):
     """display application's messages given using the __message parameter
     into a special div section
     """
-    __selectors__ = yes,
+    __select__ = yes()
     id = 'applmessages'
     site_wide = True # don't want user to hide this component using an eproperty
 
@@ -129,43 +129,7 @@
         self.w(u'</div>')
 
 
-class WFHistoryVComponent(EntityVComponent):
-    """display the workflow history for entities supporting it"""
-    id = 'wfhistory'
-    accepts = ('Any',)
-    context = 'navcontentbottom'
-    rtype = 'wf_info_for'
-    target = 'subject'
-    title = _('Workflow history')
-
-    def cell_call(self, row, col, view=None):
-        _ = self.req._
-        eid = self.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('EUser').has_perm(self.req, 'read'):
-            sel += ',U,C'
-            rql += ', WF owned_by U?'
-            displaycols = range(5)
-            headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
-                       _('EUser'))            
-        else:
-            sel += ',C'
-            displaycols = range(4)
-            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')
-        except Unauthorized:
-            return
-        if rset:
-            self.wview('table', rset, title=_(self.title), displayactions=False,
-                       displaycols=displaycols, headers=headers)
-
-
-class ApplicationName(SingletonVComponent):
+class ApplicationName(Component):
     """display the application name"""
     id = 'appliname'
 
@@ -186,13 +150,13 @@
     help = _('contentnavigation_seealso_description')
 
     
-class EtypeRestrictionComponent(SingletonVComponent):
+class EtypeRestrictionComponent(Component):
     """displays the list of entity types contained in the resultset
     to be able to filter accordingly.
     """
     id = 'etypenavigation'
-    __select__ = classmethod(chainfirst(two_etypes_rset, match_form_params))
-    form_params = ('__restrtype', '__restrtypes', '__restrrql')
+    __select__ = two_etypes_rset() | match_form_params('__restrtype', '__restrtypes',
+                                                       '__restrrql')
     visible = False # disabled by default
     
     def call(self):
@@ -235,18 +199,7 @@
         self.w(u'</div>')
         
 
-
-class RSSFeedURL(VComponent):
-    id = 'rss_feed_url'
-    __selectors__ = (non_final_entity,)
-    
-    def feed_url(self):
-        return self.build_url(rql=self.limited_rql(), vid='rss')
-
-class RSSEntityFeedURL(VComponent):
-    id = 'rss_feed_url'
-    __selectors__ = (non_final_entity, one_line_rset)
-    
-    def feed_url(self):
-        return self.entity(0, 0).rss_feed_url()
-
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+    if 'see_also' in vreg.schema:
+        vreg.register(SeeAlsoVComponent)
--- a/web/views/basecontrollers.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/basecontrollers.py	Tue Apr 07 09:30:23 2009 +0200
@@ -13,15 +13,13 @@
 
 import simplejson
 
-from mx.DateTime.Parser import DateFromString
-
 from logilab.common.decorators import cached
 
-from cubicweb import NoSelectableObject, ValidationError, typed_eid
-from cubicweb.common.selectors import yes
+from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
+from cubicweb.utils import strptime
+from cubicweb.selectors import yes, match_user_groups
+from cubicweb.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
 from cubicweb.common.mail import format_mail
-from cubicweb.common.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
-
 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset
@@ -55,15 +53,62 @@
 
 
 class ViewController(Controller):
+    """standard entry point :
+    - build result set
+    - select and call main template
+    """
     id = 'view'
-    template = 'main'
+    template = 'main-template'
     
     def publish(self, rset=None):
         """publish a request, returning an encoded string"""
-        template = self.req.property_value('ui.main-template')
-        if template not in self.vreg.registry('templates') :
-            template = self.template
-        return self.vreg.main_template(self.req, template, rset=rset)
+        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.main_template(self.req, template, rset=rset, view=view)
+
+    def _select_view_and_rset(self, rset):
+        req = self.req
+        if rset is None and not hasattr(req, '_rql_processed'):
+            req._rql_processed = True
+            rset = self.process_rql(req.form.get('rql'))
+        if rset and rset.rowcount == 1 and '__method' in req.form:
+            entity = rset.get_entity(0, 0)
+            try:
+                method = getattr(entity, req.form.pop('__method'))
+                method()
+            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)
+        try:
+            view = self.vreg.select_view(vid, req, 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.select_view(vid, req, rset)
+        except NoSelectableObject:
+            if rset:
+                req.set_message(req._("The view %s can not be applied to this query") % vid)
+            else:
+                req.set_message(req._("You have no access to this view or it's not applyable to 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.select_view(vid, req, rset)
+        return view, rset
+
+    def add_to_breadcrumbs(self, view):
+        # update breadcrumps **before** validating cache, unless the view
+        # 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()
+
+    def validate_cache(self, view):
+        view.set_http_cache_headers()
+        self.req.validate_cache()
 
     def execute_linkto(self, eid=None):
         """XXX __linkto parameter may cause security issue
@@ -211,14 +256,14 @@
             self.req.set_content_type(content_type)
             return xmlize(data)
         return data
-
+    
     def html_exec(self, rset=None):
-        """html mode: execute query and return the view as HTML"""
+        # XXX try to use the page-content template
         req = self.req
         rql = req.form.get('rql')
         if rset is None and rql:
             rset = self._exec(rql)
-            
+        
         vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
         try:
             view = self.vreg.select_view(vid, req, rset)
@@ -239,6 +284,11 @@
             if divid == 'pageContent':
                 stream.write(u'<div id="contentmain">')
         view.dispatch()
+        extresources = req.html_headers.getvalue(skiphead=True)
+        if extresources:
+            stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ?
+            stream.write(extresources)
+            stream.write(u'</div>\n')
         if req.form.get('paginate') and divid == 'pageContent':
             stream.write(u'</div></div>')
         source = stream.getvalue()
@@ -328,7 +378,7 @@
 
     def js_format_date(self, strdate):
         """returns the formatted date for `msgid`"""
-        date = DateFromString(strdate)
+        date = strptime(strdate)
         return self.format_date(date)
 
     def js_external_resource(self, resource):
@@ -462,7 +512,7 @@
 
 class SendMailController(Controller):
     id = 'sendmail'
-    require_groups = ('managers', 'users')
+    __select__ = match_user_groups('managers', 'users')
 
     def recipients(self):
         """returns an iterator on email's recipients as entities"""
@@ -510,7 +560,7 @@
 
 class MailBugReportController(SendMailController):
     id = 'reportbug'
-    __selectors__ = (yes,)
+    __select__ = yes()
 
     def publish(self, rset=None):
         body = self.req.form['description']
--- a/web/views/baseforms.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/baseforms.py	Tue Apr 07 09:30:23 2009 +0200
@@ -14,205 +14,19 @@
 from logilab.mtconverter import html_escape
 from logilab.common.decorators import cached
 
-from cubicweb.interfaces import IWorkflowable
-from cubicweb.common.utils import make_uid
-from cubicweb.common.uilib import cut
-from cubicweb.common.selectors import (accept_etype, match_kwargs,
-                                    one_line_rset, implement_interface,
-                                    match_form_params, accept)
-from cubicweb.common.view import EntityView
-from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
+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 EntityForm, relation_id
+from cubicweb.web.form import FormMixIn
+from cubicweb.web.views.editforms import AutomaticEntityForm
 
 _ = unicode
-
-class DeleteConfForm(EntityForm):
-    id = 'deleteconf'
-    title = _('delete')
-    domid = 'deleteconf'
-    onsubmit = None
-    # don't use navigation, all entities asked to be deleted should be displayed
-    # else we will only delete the displayed page
-    need_navigation = False
+    
     
-    def call(self):
-        """ask for confirmation before real deletion"""
-        _ = self.req._
-        self.req.add_css('cubicweb.form.css')
-        self.req.add_js('cubicweb.edition.js')
-        self.w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n' % _('this action is not reversible!'))
-        # XXX above message should have style of a warning
-        self.w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
-        if self.onsubmit:
-            self.w(u'<form id="deleteconf" action="%s" onsubmit="%s" method="post">'
-                   % (self.build_url(), self.onsubmit))
-        else:
-            self.w(u'<form id="deleteconf" action="%s" method="post">'
-                   % (self.build_url()))
-            
-        self.w(u'<fieldset>\n')
-        self.display_rset()
-        #self.w(u'<input type="hidden" name="rql" value="%s"/>' % self.req.form['rql'])
-        self.w(u'<input type="hidden" name="__form_id" value="%s"/>' % self.id)
-        self.w(self.button_delete(label=stdmsgs.YES))
-        self.w(self.button_cancel(label=stdmsgs.NO))
-        for param in NAV_FORM_PARAMETERS:
-            value = self.req.form.get(param)
-            if value:
-                self.w(u'<input type="hidden" name="%s" value="%s"/>' % (param, value))
-        self.w(u'</fieldset></form>\n')
-
-    def display_rset(self):
-        self.w(u'<ul>\n')
-        done = set()
-        for i in xrange(self.rset.rowcount):
-            if self.rset[i][0] in done:
-                continue
-            done.add(self.rset[i][0])
-            self.cell_call(i, 0)
-        self.w(u'</ul>\n')
-        
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        self.w(u'<li>')
-        self.w(u'<input type="hidden" name="eid" value="%s" />' % entity.eid)
-        self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-               % (eid_param('__type', entity.eid), self.rset.description[row][0]))
-        self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
-        # don't use outofcontext view or any other that may contain inline edition form
-        self.w(html_escape(entity.view('textoutofcontext')))
-        self.w(u'</a>')
-        self.w(u'</li>')
-
-
-class ChangeStateForm(EntityForm):
-    id = 'statuschange'
-    title = _('status change')
-
-    __selectors__ = (implement_interface, match_form_params)
-    accepts_interfaces = (IWorkflowable,)
-    form_params = ('treid',)
-
-    def cell_call(self, row, col, vid='secondary'):
-        entity = self.entity(row, col)
-        eid = entity.eid
-        state = entity.in_state[0]
-        transition = self.req.eid_rset(self.req.form['treid']).get_entity(0, 0)
-        dest = transition.destination()
-        self.req.add_js('cubicweb.edition.js')
-        self.req.add_css('cubicweb.form.css')
-        _ = self.req._
-        self.w(self.error_message())
-        self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
-        msg = _('status will change from %(st1)s to %(st2)s') % {
-            'st1': _(state.name),
-            'st2': _(dest.name)}
-        self.w(u'<p>%s</p>\n' % msg)
-        self.w(u'<form action="%s" onsubmit="return freezeFormButtons(\'entityForm\');" method="post" id="entityForm">\n'
-               % self.build_url('edit'))
-        self.w(u'<div id="progress">%s</div>' % _('validating...'))
-        self.w(u'<fieldset>\n')
-        #self.w(u'<input id="errorurl" type="hidden" name="__errorurl" value="%s"/>\n'
-        #       % html_escape(self.req.url()))
-        self.w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
-        self.w(u'<input type="hidden" name="eid" value="%s" />' % eid)
-        self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-               % (eid_param('__type', eid), entity.e_schema))
-        self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-               % (eid_param('state', eid), dest.eid))
-        self.w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n'
-               % html_escape(self.redirectpath(entity)))
-        self.fill_form(entity, state, dest)
-        self.w(u'<input type="hidden" name="__method" value="set_state"/>\n')
-        self.w(self.button_ok(label=stdmsgs.YES, tabindex=self.req.next_tabindex()))
-        self.w(self.button_cancel(label=stdmsgs.NO, tabindex=self.req.next_tabindex()))
-        self.w(u'</fieldset>\n')
-        self.w(u'</form>')
-        
-    def fill_form(self, entity, state, dest):
-        # hack to use the widget for comment_format
-        trinfo = self.vreg.etype_class('TrInfo')(self.req, None)
-        # widget are cached, copy it since we want to modify its name attribute
-        wdg = trinfo.get_widget('comment_format')
-        wdg.name = 'trcommentformat'
-        # set a value in entity to avoid lookup for a non existant attribute...
-        trinfo['trcommentformat'] = u''
-        # comment format/content have to be grouped using the original entity eid
-        wdg.rname = eid_param('trcommentformat', entity.eid)
-        self.w(wdg.render_label(trinfo))
-        self.w(wdg._edit_render(trinfo))
-        self.w(u'<br/>\n')
-        cformname = eid_param('trcomment', entity.eid)
-        self.w(u'<label for="%s">%s</label>\n' % (cformname, self.req._('comment:')))
-        self.w(u'<textarea rows="10" cols="80" name="%s" tabindex="%s"></textarea><br/>\n'
-               % (cformname, self.req.next_tabindex()))
-
-    def redirectpath(self, entity):
-        return entity.rest_path()
-
-
-class ClickAndEditForm(EntityForm):
-    id = 'reledit'
-    __selectors__ = (match_kwargs, )
-    expected_kwargs = ('rtype',)
-
-    #FIXME editableField class could be toggleable from userprefs
-
-    EDITION_BODY = '''
-<div class="editableField" id="%(divid)s"
-      ondblclick="showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')">%(value)s</div>
-<form style="display: none;" onsubmit="return inlineValidateForm('%(divid)s-form', '%(rtype)s', '%(eid)s', '%(divid)s', %(reload)s);" id="%(divid)s-form" action="#">
-<fieldset>
-<input type="hidden" name="eid" value="%(eid)s" />
-<input type="hidden" name="__maineid" value="%(eid)s" />
-<input type="hidden" name="__type:%(eid)s" value="%(etype)s" />
-%(attrform)s
-</fieldset>
-<div class="buttonbar">
-%(ok)s
-%(cancel)s
-</div>
-</form>
-'''
-    def cell_call(self, row, col, rtype=None, role='subject', reload=False):
-        entity = self.entity(row, col)
-        if getattr(entity, rtype) is None:
-            value = self.req._('not specified')
-        else:
-            value = entity.printable_value(rtype)
-        if not entity.has_perm('update'):
-            self.w(value)
-            return
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        eid = entity.eid
-        edit_key = make_uid('%s-%s' % (rtype, eid))
-        divid = 'd%s' % edit_key
-        widget = entity.get_widget(rtype, 'subject')
-        eschema = entity.e_schema
-        attrform = widget.edit_render(entity, useid='i%s' % edit_key)
-        ok = (u'<input class="validateButton" type="submit" name="__action_apply" value="%s" tabindex="%s" />'
-              % (self.req._(stdmsgs.BUTTON_OK), self.req.next_tabindex()))
-        cancel = (u'<input class="validateButton" type="button" '
-                  'value="%s" onclick="cancelInlineEdit(%s, \'%s\', \'%s\')"  tabindex="%s" />'
-                  % (self.req._(stdmsgs.BUTTON_CANCEL), eid, rtype, divid,
-                     self.req.next_tabindex()))
-        self.w(self.EDITION_BODY % {
-                'eid': eid,
-                'rtype': rtype,
-                'etype': entity.e_schema,
-                'attrform': attrform,
-                'action' : self.build_url('edit'), # NOTE: actually never gets called
-                'ok': ok,
-                'cancel': cancel,
-                'value': value,
-                'reload': dumps(reload),
-                'divid': divid,
-                })
-
-
-class EditionForm(EntityForm):
+class EditionForm(FormMixIn, EntityView):
     """primary entity edition form
 
     When generating a new attribute_input, the editor will look for a method
@@ -221,12 +35,12 @@
     dynamic default values such as the 'tomorrow' date or the user's login
     being connected
     """    
-    __selectors__ = (one_line_rset, accept)
+    id = 'edition'
+    __select__ = one_line_rset() & non_final_entity()
 
-    id = 'edition'
     title = _('edition')
     controller = 'edit'
-    skip_relations = EntityForm.skip_relations.copy()
+    skip_relations = FormMixIn.skip_relations.copy()
     
     EDITION_BODY = u'''\
  %(errormsg)s
@@ -255,9 +69,15 @@
 </form>
 '''
 
+    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
+
     def cell_call(self, row, col, **kwargs):
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        self.req.add_css('cubicweb.form.css')
+        self.req.add_js( ('cubicweb.ajax.js', ) )
         entity = self.complete_entity(row, col)
         self.edit_form(entity, kwargs)
 
@@ -507,7 +327,7 @@
     # should_* method extracted to allow overriding
     
     def should_inline_relation_form(self, entity, rschema, targettype, role):
-        return entity.rtags.is_inlined(rschema, targettype, role)
+        return AutomaticForm.rinlined.etype_rtag(entity.id, rschema, role, targettype)
 
     def should_display_inline_relation_form(self, rschema, existant, card):
         return not existant and card in '1+'
@@ -527,20 +347,24 @@
 
     
 class CreationForm(EditionForm):
-    __selectors__ = (accept_etype, )
+    __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', 'cubicweb.edition.js') )
-        self.req.add_css('cubicweb.form.css')
+        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):
@@ -605,7 +429,7 @@
     def should_inline_relation_form(self, entity, rschema, targettype, role):
         if rschema == self.rschema:
             return False
-        return entity.rtags.is_inlined(rschema, targettype, role)
+        return AutomaticForm.rinlined.etype_rtag(entity.id, rschema, role, targettype)
 
     @cached
     def keep_entity(self, entity):
@@ -640,8 +464,8 @@
 
 class InlineEntityCreationForm(InlineFormMixIn, CreationForm):
     id = 'inline-creation'
-    __selectors__ = (match_kwargs, accept_etype)
-    expected_kwargs = ('ptype', 'peid', 'rtype')
+    __select__ = (match_kwargs('ptype', 'peid', 'rtype')
+                  & specified_etype_implements('Any'))
     
     EDITION_BODY = u'''\
 <div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
@@ -666,21 +490,17 @@
         :param rtype: the relation bridging `etype` and `parent`
         :param role: the role played by the `parent` in the relation
         """
-        self.req.add_css('cubicweb.form.css')
         try:
             entity = self.vreg.etype_class(etype)(self.req, None, None)
         except:
             self.w(self.req._('no such entity type %s') % etype)
             return
         self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
-    
-    
 
 
 class InlineEntityEditionForm(InlineFormMixIn, EditionForm):
     id = 'inline-edition'
-    __selectors__ = (accept, match_kwargs)
-    expected_kwargs = ('ptype', 'peid', 'rtype')
+    __select__ = non_final_entity() & match_kwargs('ptype', 'peid', 'rtype')
     
     EDITION_BODY = u'''\
 <div onclick="restoreInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s')" id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">   
@@ -706,7 +526,6 @@
         insertions of <div class="section"> between each row of
         the resultset
         """
-        self.req.add_css('cubicweb.form.css')
         rset = self.rset
         for i in xrange(len(rset)):
             self.wview(self.id, rset, row=i, **kwargs)
@@ -732,15 +551,13 @@
         ctx['count'] = entity.row + 1
         return ctx
     
-    
 
 class CopyEditionForm(EditionForm):
     id = 'copy'
     title = _('copy edition')
 
     def cell_call(self, row, col, **kwargs):
-        self.req.add_js(('cubicweb.ajax.js', 'cubicweb.edition.js'))
-        self.req.add_css('cubicweb.form.css')
+        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. 
@@ -777,8 +594,7 @@
         return self.req._('element copied')
        
     
-
-class TableEditForm(EntityForm):
+class TableEditForm(FormMixIn, EntityView):
     id = 'muledit'
     title = _('multiple edit')
 
@@ -821,8 +637,6 @@
         """
         req = self.req
         form = req.form
-        req.add_js('cubicweb.edition.js')
-        req.add_css('cubicweb.form.css')
         _ = req._
         sampleentity = self.complete_entity(0)
         attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
@@ -878,128 +692,10 @@
                                   'widget': wobj.edit_render(entity)})
         w(u'</tr>')
         return '\n'.join(html)
-        
-
-class UnrelatedDivs(EntityView):
-    id = 'unrelateddivs'
-    __selectors__ = (match_form_params,)
-    form_params = ('relation',)
-
-    @property
-    def limit(self):
-        if self.req.form.get('__force_display'):
-            return None
-        return self.req.property_value('navigation.related-limit') + 1
-
-    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
-        self.w(self.build_unrelated_select_div(entity, rschema, target,
-                                               is_cell=is_cell, hidden=hidden))
-
-    def build_unrelated_select_div(self, entity, rschema, target,
-                                   is_cell=False, hidden=True):
-        options = []
-        divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
-        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))
-        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))
-        options += self._get_select_options(entity, rschema, target)
-        options += self._get_search_options(entity, rschema, target, targettypes)
-        if 'Basket' in self.schema: # XXX
-            options += self._get_basket_options(entity, rschema, target, targettypes)
-        relname, target = self.req.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');">
-    %s
-  </select>
-</div>
-""" % (hidden and 'hidden' or '', divid, selectid, html_escape(dumps(entity.eid)),
-       is_cell and 'true' or 'null', relname, '\n'.join(options))
-
-    def _get_select_options(self, entity, rschema, target):
-        """add options to search among all entities of each possible type"""
-        options = []
-        eid = entity.eid
-        pending_inserts = self.req.get_pending_inserts(eid)
-        rtype = rschema.type
-        for eview, reid in entity.vocabulary(rschema, target, self.limit):
-            if reid is None:
-                options.append('<option class="separator">-- %s --</option>' % html_escape(eview))
-            else:
-                optionid = relation_id(eid, rtype, target, reid)
-                if optionid not in pending_inserts:
-                    # prefix option's id with letters to make valid XHTML wise
-                    options.append('<option id="id%s" value="%s">%s</option>' %
-                                   (optionid, reid, html_escape(eview)))
-        return options
-
-    def _get_search_options(self, entity, rschema, target, targettypes):
-        """add options to search among all entities of each possible type"""
-        options = []
-        _ = self.req._
-        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',
-                                 __mode=mode)
-            options.append((eschema.display_name(self.req),
-                            '<option value="%s">%s %s</option>' % (
-                html_escape(url), _('Search for'), eschema.display_name(self.req))))
-        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,
-                                                            target, targettypes):
-            optionid = relation_id(entity.eid, rtype, target, basketeid)
-            options.append('<option id="%s" value="%s">%s %s</option>' % (
-                optionid, basketeid, _('link to each item in'), html_escape(basketname)))
-        return options
-
-    def _get_basket_links(self, ueid, target, targettypes):
-        targettypes = set(targettypes)
-        for basketeid, basketname, elements in self._get_basket_info(ueid):
-            baskettypes = elements.column_types(0)
-            # if every elements in the basket can be attached to the
-            # edited entity
-            if baskettypes & targettypes:
-                yield basketeid, basketname
-            
-    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')
-        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')
-            basketref.append((result[0], result[1], rset))
-        return basketref
 
 
-class ComboboxView(EntityView):
-    """the view used in combobox (unrelated entities)
+# XXX bw compat
 
-    THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
-    """
-    id = 'combobox'
-    accepts = ('Any',)
-    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)
-
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views import editviews
+ComboboxView = class_moved(editviews.ComboboxView)
--- a/web/views/basetemplates.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/basetemplates.py	Tue Apr 07 09:30:23 2009 +0200
@@ -10,23 +10,19 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb import NoSelectableObject, ObjectNotFound
-from cubicweb.common.view import Template, MainTemplate,  NOINDEX, NOFOLLOW
-from cubicweb.common.utils import make_uid
-from cubicweb.common.utils import UStringIO
-
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import match_kwargs
+from cubicweb.view import View, MainTemplate,  NOINDEX, NOFOLLOW
+from cubicweb.utils import make_uid, UStringIO
 
 # main templates ##############################################################
 
+class LogInOutTemplate(MainTemplate):
 
-class LogInOutTemplate(MainTemplate):
-    
     def call(self):
         self.set_request_content_type()
         w = self.w
         self.write_doctype()
-        lang = self.req.lang
         self.template_header('text/html', self.req._('login_action'))
         w(u'<body>\n')
         self.content(w)
@@ -41,17 +37,17 @@
         w(NOINDEX)
         w(NOFOLLOW)
         w(u'\n'.join(additional_headers) + u'\n')
-        self.template('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.rset)
         w(u'<title>%s</title>\n' % html_escape(page_title))
-        
+
 
 class LogInTemplate(LogInOutTemplate):
     id = 'login'
     title = 'log in'
 
     def content(self, w):
-        self.template('logform', rset=self.rset, id='loginBox', klass='')
-        
+        self.wview('logform', rset=self.rset, id='loginBox', klass='')
+
 
 class LoggedOutTemplate(LogInOutTemplate):
     id = 'loggedout'
@@ -67,110 +63,70 @@
                 html_escape(indexurl),
                 self.req._('go back to the index page')))
 
-        
+@objectify_selector
+def templatable_view(cls, req, rset, *args, **kwargs):
+    view = kwargs.pop('view', None)
+    if view is None:
+        return 1
+    if view.binary:
+        return 0
+    if req.form.has_key('__notemplate'):
+        return 0
+    return view.templatable
+
+
+class NonTemplatableViewTemplate(MainTemplate):
+    """main template for any non templatable views (xml, binaries, etc.)"""
+    id = 'main-template'
+    __select__ = ~ templatable_view()
+    
+    def call(self, view):
+        view.set_request_content_type()
+        self.set_stream(templatable=False)
+        # have to replace our unicode stream using view's binary stream
+        view.dispatch()
+        assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
+        self._stream = view._stream
+
+
 class TheMainTemplate(MainTemplate):
     """default main template :
-    
+
     - call header / footer templates
-    - build result set
-    - guess and call an appropriate view through the view manager
     """
-    id = 'main'
-
-    def _select_view_and_rset(self):
-        req = self.req
-        if self.rset is None and not hasattr(req, '_rql_processed'):
-            req._rql_processed = True
-            rset = self.process_rql(req.form.get('rql'))
-        else:
-            rset = self.rset
-        # handle special "method" param when necessary
-        # XXX this should probably not be in the template (controller ?), however:
-        #     * we need to have the displayed rset
-        #     * we don't want to handle it in each view
-        if rset and rset.rowcount == 1 and '__method' in req.form:
-            entity = rset.get_entity(0, 0)
-            try:
-                method = getattr(entity, req.form.pop('__method'))
-                method()
-            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)
-        try:
-            view = self.vreg.select_view(vid, req, 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.select_view(vid, req, rset)
-        except NoSelectableObject:
-            if rset:
-                req.set_message(req._("The view %s can not be applied to this query") % vid)
-            else:
-                req.set_message(req._("You have no access to this view or it's not applyable to 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.select_view(vid, req, rset)
-        return view, rset
+    id = 'main-template'
+    __select__ = templatable_view()
     
-    def call(self):
-        view, rset = self._select_view_and_rset()
-        req = self.req
-        # update breadcrumps **before** validating cache, unless the view
-        # 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:
-            req.update_breadcrumbs()
-        view.set_http_cache_headers()
-        req.validate_cache()
-        with_templates = not view.binary and view.templatable and \
-                         not req.form.has_key('__notemplate')
-        if not with_templates:
-            view.set_request_content_type()
-            self.set_stream(templatable=False)
-        else:
-            self.set_request_content_type()
-            content_type = self.content_type
-            self.template_header(content_type, view)
-        if view.binary:
-            # have to replace our unicode stream using view's binary stream
-            view.dispatch()
-            assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
-            self._stream = view._stream
-        else:
-            view.dispatch(w=self.w)
-        if with_templates:
-            self.template_footer(view)
-
-            
-    def process_rql(self, rql):
-        """execute rql if specified"""
-        if rql:
-            self.ensure_ro_rql(rql)
-            if not isinstance(rql, unicode):
-                rql = unicode(rql, self.req.encoding)
-            pp = self.vreg.select_component('magicsearch', self.req)
-            self.rset = pp.process_query(rql, self.req)
-            return self.rset
-        return None
+    def call(self, view):
+        self.set_request_content_type()
+        self.template_header(self.content_type, view)
+        w = self.w
+        w(u'<div id="pageContent">\n')
+        vtitle = self.req.form.get('vtitle')
+        if vtitle:
+            w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
+        # display entity type restriction component
+        etypefilter = self.vreg.select_component('etypenavigation',
+                                                 self.req, self.rset)
+        if etypefilter and etypefilter.propval('visible'):
+            etypefilter.dispatch(w=w)
+        self.nav_html = UStringIO()
+        if view and view.need_navigation:
+            view.paginate(w=self.nav_html.write)
+        w(_(self.nav_html.getvalue()))
+        w(u'<div id="contentmain">\n')
+        view.dispatch(w=w)
+        w(u'</div>\n') # close id=contentmain
+        w(_(self.nav_html.getvalue()))
+        w(u'</div>\n') # closes id=pageContent
+        self.template_footer(view)
 
     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()
         self.template_html_header(content_type, page_title, additional_headers)
         self.template_body_header(view)
-        # display entity type restriction component
-        etypefilter = self.vreg.select_component('etypenavigation',
-                                                 self.req, self.rset)
-        if etypefilter and etypefilter.propval('visible'):
-            etypefilter.dispatch(w=self.w)
-        self.nav_html = UStringIO()
-        self.pagination(self.req, self.rset, self.nav_html.write,
-                        not (view and view.need_navigation))
-        self.w(_(self.nav_html.getvalue()))
-        self.w(u'<div id="contentmain">\n')
-    
+
     def template_html_header(self, content_type, page_title, additional_headers=()):
         w = self.whead
         lang = self.req.lang
@@ -179,14 +135,14 @@
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self.req.encoding))
         w(u'\n'.join(additional_headers) + u'\n')
-        self.template('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.rset)
         if page_title:
             w(u'<title>%s</title>\n' % html_escape(page_title))
 
     def template_body_header(self, view):
         w = self.w
         w(u'<body>\n')
-        self.template('header', rset=self.rset, view=view)
+        self.wview('header', rset=self.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')
@@ -197,20 +153,13 @@
         if msgcomp:
             msgcomp.dispatch(w=self.w)
         self.content_header(view)
-        w(u'<div id="pageContent">\n')
-        vtitle = self.req.form.get('vtitle')
-        if vtitle:
-            w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
-            
+
     def template_footer(self, view=None):
-        self.w(u'</div>\n') # close id=contentmain
-        self.w(_(self.nav_html.getvalue()))
-        self.w(u'</div>\n') # closes id=pageContent
         self.content_footer(view)
         self.w(u'</td>\n')
         self.nav_column(view, 'right')
         self.w(u'</tr></table></div>\n')
-        self.template('footer', rset=self.rset)
+        self.wview('footer', rset=self.rset)
         self.w(u'</body>')
 
     def nav_column(self, view, context):
@@ -224,10 +173,10 @@
 
     def content_header(self, view=None):
         """by default, display informal messages in content header"""
-        self.template('contentheader', rset=self.rset, view=view)
-            
+        self.wview('contentheader', rset=self.rset, view=view)
+
     def content_footer(self, view=None):
-        self.template('contentfooter', rset=self.rset, view=view)
+        self.wview('contentfooter', rset=self.rset, view=view)
 
 
 class ErrorTemplate(TheMainTemplate):
@@ -235,8 +184,8 @@
     main template. This template may be called for authentication error,
     which means that req.cnx and req.user may not be set.
     """
-    id = 'error'
-    
+    id = 'error-template'
+
     def call(self):
         """display an unexpected error"""
         self.set_request_content_type()
@@ -246,7 +195,7 @@
                              [NOINDEX, NOFOLLOW])
         view.dispatch(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
@@ -254,7 +203,7 @@
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self.req.encoding))
         w(u'\n'.join(additional_headers))
-        self.template('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.rset)
         w(u'<title>%s</title>\n' % html_escape(page_title))
         self.w(u'<body>\n')
 
@@ -265,7 +214,7 @@
 class SimpleMainTemplate(TheMainTemplate):
 
     id = '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()
@@ -275,7 +224,7 @@
         whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
               % (content_type, self.req.encoding))
         whead(u'\n'.join(additional_headers) + u'\n')
-        self.template('htmlheader', rset=self.rset)
+        self.wview('htmlheader', rset=self.rset)
         w = self.w
         w(u'<title>%s</title>\n' % html_escape(page_title))
         w(u'<body>\n')
@@ -296,7 +245,7 @@
         vtitle = self.req.form.get('vtitle')
         if vtitle:
             w(u'<h1 class="vtitle">%s</h1>' % html_escape(vtitle))
-            
+
     def topleft_header(self):
         self.w(u'<table id="header"><tr>\n')
         self.w(u'<td>')
@@ -306,10 +255,10 @@
 
 # page parts templates ########################################################
 
-class HTMLHeader(Template):
+class HTMLHeader(View):
     """default html headers"""
     id = 'htmlheader'
-    
+
     def call(self, **kwargs):
         self.favicon()
         self.stylesheets()
@@ -321,7 +270,7 @@
         favicon = self.req.external_resource('FAVICON', None)
         if favicon:
             self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
-            
+
     def stylesheets(self):
         req = self.req
         add_css = req.add_css
@@ -331,11 +280,11 @@
             add_css(css, u'print', localfile=False)
         for css in req.external_resource('IE_STYLESHEETS'):
             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)
-            
+
     def alternates(self):
         urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
         if urlgetter is not None:
@@ -347,13 +296,13 @@
         req = self.req
         pid = make_uid(id(req))
         req.pageid = pid
-        req.html_headers.define_var('pageid', pid);
+        req.html_headers.define_var('pageid', pid)
 
 
-class HTMLPageHeader(Template):
+class HTMLPageHeader(View):
     """default html page header"""
     id = 'header'
-    
+
     def call(self, view, **kwargs):
         self.main_header(view)
         self.w(u'''
@@ -362,7 +311,7 @@
         self.w(u'''
   </div>
   ''')
-        
+
     def main_header(self, view):
         """build the top menu with authentification info and the rql box"""
         self.w(u'<table id="header"><tr>\n')
@@ -391,9 +340,9 @@
         self.w(u'<td id="lastcolumn">')
         self.w(u'</td>\n')
         self.w(u'</tr></table>\n')
-        self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
-                      title=False, message=False)
-        
+        self.wview('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+                   title=False, message=False)
+
     def state_header(self):
         state = self.req.search_state
         if state[0] == 'normal':
@@ -403,18 +352,18 @@
         msg = ' '.join((_("searching for"),
                         display_name(self.req, state[1][3]),
                         _("to associate with"), value,
-                        _("by relation"), '"', 
+                        _("by relation"), '"',
                         display_name(self.req, state[1][2], state[1][0]),
                         '"'))
         return self.w(u'<div class="stateMessage">%s</div>' % msg)
 
 
 
-class HTMLPageFooter(Template):
+class HTMLPageFooter(View):
     """default html page footer: include logo if any, and close the HTML body
     """
     id = 'footer'
-    
+
     def call(self, **kwargs):
         req = self.req
         self.w(u'<div class="footer">')
@@ -429,13 +378,13 @@
         self.w(u'</div>')
 
 
-class HTMLContentHeader(Template):
+class HTMLContentHeader(View):
     """default html page content header:
     * include message component if selectable for this request
     * include selectable content navigation components
     """
     id = 'contentheader'
-    
+
     def call(self, view, **kwargs):
         """by default, display informal messages in content header"""
         components = self.vreg.possible_vobjects('contentnavigation',
@@ -448,12 +397,12 @@
             self.w(u'</div><div class="clear"></div>')
 
 
-class HTMLContentFooter(Template):
+class HTMLContentFooter(View):
     """default html page content footer: include selectable content navigation
     components
     """
     id = 'contentfooter'
-    
+
     def call(self, view, **kwargs):
         components = self.vreg.possible_vobjects('contentnavigation',
                                                  self.req, self.rset,
@@ -465,17 +414,19 @@
             self.w(u'</div>')
 
 
-class LogFormTemplate(Template):
+class LogFormTemplate(View):
     id = '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.w(u'<div id="%s" class="%s">' % (id, klass))
         if title:
             self.w(u'<div id="loginTitle">%s</div>'
                    % self.req.property_value('ui.site-title'))
-        self.w(u'<div id="loginContent">\n')        
+        self.w(u'<div id="loginContent">\n')
 
         if message:
             self.display_message()
@@ -491,14 +442,14 @@
         message = self.req.message
         if message:
             self.w(u'<div class="simpleMessage">%s</div>\n' % message)
-                     
+
     def login_form(self, id):
         _ = self.req._
         self.w(u'<form method="post" action="%s" id="login_form">\n'
                % html_escape(login_form_url(self.config, self.req)))
         self.w(u'<table>\n')
         self.w(u'<tr>\n')
-        self.w(u'<td><label for="__login">%s</label></td>' % _('login'))
+        self.w(u'<td><label for="__login">%s</label></td>' % _('login or email'))
         self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
         self.w(u'</tr><tr>\n')
         self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
@@ -510,7 +461,7 @@
         self.w(u'</form>\n')
         self.req.html_headers.add_onload('jQuery("#__login:visible").focus()')
 
-    
+
 def login_form_url(config, req):
     if req.https:
         return req.url()
@@ -518,3 +469,7 @@
         return req.url().replace(req.base_url(), config['https-url'])
     return req.url()
 
+
+## vregistry registration callback ############################################
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), modname=__name__)
--- a/web/views/baseviews.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/baseviews.py	Tue Apr 07 09:30:23 2009 +0200
@@ -4,7 +4,6 @@
 * primary, sidebox
 * secondary, oneline, incontext, outofcontext, text
 * list
-* xml, rss
 
 
 :organization: Logilab
@@ -16,36 +15,30 @@
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
-from time import timezone
 
 from rql import nodes
 
-from logilab.common.decorators import cached
-from logilab.mtconverter import TransformError, html_escape, xml_escape
+from logilab.mtconverter import TransformError, html_escape
 
-from cubicweb import Unauthorized, NoSelectableObject, typed_eid
-from cubicweb.common.selectors import (yes, nonempty_rset, accept,
-                                       one_line_rset, match_search_state, 
-                                       match_form_params, accept_rset)
-from cubicweb.common.uilib import (cut, printable_value,  UnicodeCSVWriter,
-                                   ajax_replace_url, rql_for_eid, simple_sgml_tag)
-from cubicweb.common.view import EntityView, AnyRsetView, EmptyRsetView
-from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
-from cubicweb.web.views import vid_from_rset, linksearch_select_url, linksearch_match
+from cubicweb import Unauthorized, NoSelectableObject
+from cubicweb.selectors import yes, empty_rset
+from cubicweb.view import EntityView, AnyRsetView, View
+from cubicweb.common.uilib import cut, printable_value
 
 _ = unicode
 
 class NullView(AnyRsetView):
     """default view when no result has been found"""
     id = 'null'
-    __select__ = classmethod(yes)
+    __select__ = yes()
     def call(self, **kwargs):
         pass
     cell_call = call
 
 
-class NoResultView(EmptyRsetView):
+class NoResultView(View):
     """default view when no result has been found"""
+    __select__ = empty_rset()
     id = 'noresult'
     
     def call(self, **kwargs):
@@ -107,19 +100,6 @@
             return
         self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
 
-
-class EditableFinalView(FinalView):
-    """same as FinalView but enables inplace-edition when possible"""
-    id = 'editable-final'
-                
-    def cell_call(self, row, col, props=None, displaytime=False):
-        etype = self.rset.description[row][col]
-        value = self.rset.rows[row][col]
-        entity, rtype = self.rset.related_entity(row, col)
-        if entity is not None:
-            self.w(entity.view('reledit', rtype=rtype))
-        else:
-            super(EditableFinalView, self).cell_call(row, col, props, displaytime)
         
 PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity',
                          'owned_by', 'created_by', 
@@ -188,9 +168,8 @@
         
     def iter_attributes(self, entity):
         for rschema, targetschema in entity.e_schema.attribute_definitions():
-            attr = rschema.type
-            if attr in self.skip_attrs:
-               continue
+            if rschema.type in self.skip_attrs:
+                continue
             yield rschema, targetschema
             
     def iter_relations(self, entity):
@@ -319,36 +298,6 @@
         label = display_name(self.req, rschema.type, role)
         self.field(label, value, show_label=show_label, w=self.w, tr=False)
 
-
-class SideBoxView(EntityView):
-    """side box usually displaying some related entities in a primary view"""
-    id = 'sidebox'
-    
-    def call(self, boxclass='sideBox', title=u''):
-        """display a list of entities by calling their <item_vid> view
-        """
-        if title:
-            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)
-            else:
-                self.wview('simplelist', self.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.w(u'</div>\n</div>\n')
-
-
  
 class SecondaryView(EntityView):
     id = 'secondary'
@@ -362,6 +311,7 @@
         self.w(u'&nbsp;')
         self.wview('oneline', self.rset, row=row, col=col)
 
+
 class OneLineView(EntityView):
     id = 'oneline'
     title = _('oneline') 
@@ -374,12 +324,13 @@
         self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
         self.w(u'</a>')
 
+
 class TextView(EntityView):
     """the simplest text view for an entity"""
     id = 'text'
     title = _('text')
     content_type = 'text/plain'
-    accepts = 'Any',
+
     def call(self, **kwargs):
         """the view is called for an entire result set, by default loop
         other rows of the result set and call the same view on the
@@ -404,7 +355,6 @@
 class MetaDataView(EntityView):
     """paragraph view of some metadata"""
     id = 'metadata'
-    accepts = 'Any',
     show_eid = True
     
     def cell_call(self, row, col):
@@ -437,7 +387,8 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         self.w(entity.dc_title())
-        
+
+
 class OutOfContextTextView(InContextTextView):
     id = 'textoutofcontext'
 
@@ -466,20 +417,8 @@
         self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
         self.w(u'</a>')
 
-class NotClickableInContextView(EntityView):
-    id = 'incontext'
-    accepts = ('State',)
-    def cell_call(self, row, col):
-        self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
-
-## class NotClickableOutOfContextView(EntityView):
-##     id = 'outofcontext'
-##     accepts = ('State',)
-##     def cell_call(self, row, col):
-##         self.w(html_escape(self.view('textoutofcontext', self.rset, row=row)))
-
             
-# list and table related views ################################################
+# list views ##################################################################
     
 class ListView(EntityView):
     id = 'list'
@@ -575,399 +514,12 @@
 
 
 class TreeItemView(ListItemView):
-    accepts = ('Any',)
     id = 'treeitem'
     
     def cell_call(self, row, col):
         self.wview('incontext', self.rset, row=row, col=col)
 
 
-# xml and xml/rss views #######################################################
-    
-class XmlView(EntityView):
-    id = 'xml'
-    title = _('xml')
-    templatable = False
-    content_type = 'text/xml'
-    xml_root = 'rset'
-    item_vid = 'xmlitem'
-    
-    def cell_call(self, row, col):
-        self.wview(self.item_vid, self.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.cell_call(i, 0)
-        self.w(u'</%s>\n' % self.xml_root)
-
-
-class XmlItemView(EntityView):
-    id = 'xmlitem'
-
-    def cell_call(self, row, col):
-        """ element as an item for an xml feed """
-        entity = self.complete_entity(row, col)
-        self.w(u'<%s>\n' % (entity.e_schema))
-        for rschema, attrschema in entity.e_schema.attribute_definitions():
-            attr = rschema.type
-            try:
-                value = entity[attr]
-            except KeyError:
-                # Bytes
-                continue
-            if value is not None:
-                if attrschema == 'Bytes':
-                    from base64 import b64encode
-                    value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
-                elif isinstance(value, basestring):
-                    value = xml_escape(value)
-                self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
-        self.w(u'</%s>\n' % (entity.e_schema))
-
-
-    
-class XMLRsetView(AnyRsetView):
-    """dumps xml in CSV"""
-    id = 'rsetxml'
-    title = _('xml export')
-    templatable = False
-    content_type = 'text/xml'
-    xml_root = 'rset'
-        
-    def call(self):
-        w = self.w
-        rset, descr = self.rset, self.rset.description
-        eschema = self.schema.eschema
-        labels = self.columns_labels(False)
-        w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
-        w(u'<%s query="%s">\n' % (self.xml_root, html_escape(rset.printable_rql())))
-        for rowindex, row in enumerate(self.rset):
-            w(u' <row>\n')
-            for colindex, val in enumerate(row):
-                etype = descr[rowindex][colindex]
-                tag = labels[colindex]
-                attrs = {}
-                if '(' in tag:
-                    attrs['expr'] = tag
-                    tag = 'funccall'
-                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)
-                else:
-                    val = self.view('final', rset, displaytime=True,
-                                    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)
-    
-
-class RssView(XmlView):
-    id = 'rss'
-    title = _('rss')
-    templatable = False
-    content_type = 'text/xml'
-    http_cache_manager = MaxAgeHTTPCacheManager
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
-
-    def _open(self):
-        req = self.req
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
-        self.w(u'''<rss version="2.0"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
->''')
-        self.w(u'  <channel rdf:about="%s">\n' % html_escape(req.url()))
-        self.w(u'    <title>%s RSS Feed</title>\n' % html_escape(self.page_title()))
-        self.w(u'    <description>%s</description>\n' % html_escape(req.form.get('vtitle', '')))
-        params = req.form.copy()
-        params.pop('vid', None)
-        self.w(u'    <link>%s</link>\n' % html_escape(self.build_url(**params)))
-
-    def _close(self):
-        self.w(u'  </channel>\n')
-        self.w(u'</rss>')       
-        
-    def call(self):
-        """display a list of entities by calling their <item_vid> view"""
-        self._open()
-        for i in xrange(self.rset.rowcount):
-            self.cell_call(i, 0)
-        self._close()
-
-    def cell_call(self, row, col):
-        self.wview('rssitem', self.rset, row=row, col=col)
-
-class RssItemView(EntityView):
-    id = '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)
-        self.w(u'<item rdf:about="%s">\n' % html_escape(entity.absolute_url()))
-        self.render_title_link(entity)
-        self._marker('description', html_escape(entity.dc_description()))
-        self._marker('dc:date', entity.dc_date(self.date_format))
-        self.render_entity_creator(entity)
-        self.w(u'</item>\n')
-
-    def render_title_link(self, entity):
-        self._marker('title', entity.dc_long_title())
-        self._marker('link', entity.absolute_url())
-           
-    def render_entity_creator(self, entity):
-        if entity.creator:
-            self._marker('dc:creator', entity.creator.name())
-            email = entity.creator.get_email()
-            if email:
-                self.w(u'<author>')
-                self.w(email)
-                self.w(u'</author>')       
-        
-    def _marker(self, marker, value):
-        if value:
-            self.w(u'  <%s>%s</%s>\n' % (marker, html_escape(value), marker))
-
-class CSVMixIn(object):
-    """mixin class for CSV views"""
-    templatable = False
-    content_type = "text/comma-separated-values"    
-    binary = True # avoid unicode assertion
-    csv_params = {'dialect': 'excel',
-                  'quotechar': '"',
-                  'delimiter': ';',
-                  'lineterminator': '\n'}
-    
-    def set_request_content_type(self):
-        """overriden to set a .csv filename"""
-        self.req.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)
-
-    
-class CSVRsetView(CSVMixIn, AnyRsetView):
-    """dumps rset in CSV"""
-    id = '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
-        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)
-                else:
-                    content = self.view('final', rset,
-                                        displaytime=True, format='text/plain',
-                                        row=rowindex, col=colindex)
-                csvrow.append(content)                    
-            writer.writerow(csvrow)
-    
-    
-class CSVEntityView(CSVMixIn, EntityView):
-    """dumps rset's entities (with full set of attributes) in CSV"""
-    id = 'ecsvexport'
-    title = _('csv entities export')
-
-    def call(self):
-        """
-        the generated CSV file will have a table per entity type
-        found in the resultset. ('table' here only means empty
-        lines separation between table contents)
-        """
-        req = self.req
-        rows_by_type = {}
-        writer = self.csvwriter()
-        rowdef_by_type = {}
-        for index in xrange(len(self.rset)):
-            entity = self.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']
-                rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
-                                                  for rschema in rowdef_by_type[entity.e_schema]]]
-            rows = rows_by_type[entity.e_schema]
-            rows.append([entity.printable_value(rs.type, format='text/plain')
-                         for rs in rowdef_by_type[entity.e_schema]])
-        for etype, rows in rows_by_type.items():
-            writer.writerows(rows)
-            # use two empty lines as separator
-            writer.writerows([[], []])        
-    
-
-## Work in progress ###########################################################
-
-class SearchForAssociationView(EntityView):
-    """view called by the edition view when the user asks
-    to search for something to link to the edited eid
-    """
-    id = 'search-associate'
-    title = _('search for association')
-    __selectors__ = (one_line_rset, match_search_state, accept)
-    accepts = ('Any',)
-    search_states = ('linksearch',)
-
-    def cell_call(self, row, col):
-        rset, vid, divid, paginate = self.filter_box_context_info()
-        self.w(u'<div id="%s">' % divid)
-        self.pagination(self.req, rset, w=self.w)
-        self.wview(vid, rset, 'noresult')
-        self.w(u'</div>')
-
-    @cached
-    def filter_box_context_info(self):
-        entity = self.entity(0, 0)
-        role, eid, rtype, etype = self.req.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
-        # since the latter is mainly there to select relevant items in the combo
-        # box, it doesn't give interesting result in this context
-        rql = entity.unrelated_rql(rtype, etype, role,
-                                   ordermethod='fetch_order',
-                                   vocabconstraints=False)
-        rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
-        #vid = vid_from_rset(self.req, rset, self.schema)
-        return rset, 'list', "search-associate-content", True
-
-
-class OutOfContextSearch(EntityView):
-    id = 'outofcontext-search'
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        erset = entity.as_rset()
-        if linksearch_match(self.req, erset):
-            self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
-                html_escape(linksearch_select_url(self.req, erset)),
-                self.req._('select this entity'),
-                html_escape(entity.view('textoutofcontext')),
-                html_escape(entity.absolute_url(vid='primary')),
-                self.req._('view detail for this entity')))
-        else:
-            entity.view('outofcontext', w=self.w)
-            
-            
-class EditRelationView(EntityView):
-    """Note: This is work in progress
-
-    This view is part of the edition view refactoring.
-    It is still too big and cluttered with strange logic, but it's a start
-
-    The main idea is to be able to call an edition view for a specific
-    relation. For example :
-       self.wview('editrelation', person_rset, rtype='firstname')
-       self.wview('editrelation', person_rset, rtype='works_for')
-    """
-    id = 'editrelation'
-
-    __selectors__ = (match_form_params,)
-    form_params = ('rtype',)
-    
-    # TODO: inlineview, multiple edit, (widget view ?)
-    def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
-                 showlabel=True):
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        entity = self.entity(row, col)
-        rtype = self.req.form.get('rtype', rtype)
-        showlabel = self.req.form.get('showlabel', showlabel)
-        assert rtype is not None, "rtype is mandatory for 'edirelation' view"
-        targettype = self.req.form.get('targettype', targettype)
-        role = self.req.form.get('role', role)
-        category = entity.rtags.get_category(rtype, targettype, role)
-        if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
-            if hasattr(entity, '%s_format' % rtype):
-                formatwdg = entity.get_widget('%s_format' % rtype, role)
-                self.w(formatwdg.edit_render(entity))
-                self.w(u'<br/>')
-            wdg = entity.get_widget(rtype, role)
-            if showlabel:
-                self.w(u'%s' % wdg.render_label(entity))
-            self.w(u'%s %s %s' %
-                   (wdg.render_error(entity), wdg.edit_render(entity),
-                    wdg.render_help(entity),))
-        else:
-            self._render_generic_relation(entity, rtype, role)
-
-    def _render_generic_relation(self, entity, relname, role):
-        text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
-        # pending operations
-        operations = self.req.get_pending_operations(entity, relname, role)
-        if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
-            self.w(u'<h3>%s</h3>' % text)
-            self._render_generic_relation_form(operations, entity, relname, role)
-        else:
-            divid = "%s%sreledit" % (relname, role)
-            url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
-                                   {'unfold' : 1, 'relname' : relname, 'role' : role})
-            self.w(u'<a href="%s">%s</a>' % (url, text))
-            self.w(u'<div id="%s"></div>' % divid)
-        
-
-    def _build_opvalue(self, entity, relname, target, role):
-        if role == 'subject':
-            return '%s:%s:%s' % (entity.eid, relname, target)
-        else:
-            return '%s:%s:%s' % (target, relname, entity.eid)
-        
-    
-    def _render_generic_relation_form(self, operations, entity, relname, role):
-        rqlexec = self.req.execute
-        for optype, targets in operations.items():
-            for target in targets:
-                self._render_pending(optype, entity, relname, target, role)
-                opvalue = self._build_opvalue(entity, relname, target, role)
-                self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
-                       % (opvalue, entity.eid))
-                rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
-                self.wview('oneline', rset)
-        # now, unrelated ones
-        self._render_unrelated_selection(entity, relname, role)
-
-    def _render_pending(self, optype, entity, relname, target, role):
-        opvalue = self._build_opvalue(entity, relname, target, role)
-        self.w(u'<input type="hidden" name="__%s" value="%s" />'
-               % (optype, opvalue))
-        if optype == 'insert':
-            checktext = '-'
-        else:
-            checktext = '+'
-        rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
-        self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
-               % (optype.capitalize(), relname, target, role,
-                  self.view('oneline', rset)))
-
-    def _render_unrelated_selection(self, entity, relname, role):
-        rschema = self.schema.rschema(relname)
-        if role == 'subject':
-            targettypes = rschema.objects(entity.e_schema)
-        else:
-            targettypes = rschema.subjects(entity.e_schema)
-        self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
-        for targettype in targettypes:
-            unrelated = entity.unrelated(relname, targettype, role) # XXX limit
-            for rowindex, row in enumerate(unrelated):
-                teid = row[0]
-                opvalue = self._build_opvalue(entity, relname, teid, role)
-                self.w(u'<option name="__insert" value="%s>%s</option>'
-                       % (opvalue, self.view('text', unrelated, row=rowindex)))
-        self.w(u'</select>')
-
-
 class TextSearchResultView(EntityView):
     """this view is used to display full-text search
 
@@ -977,7 +529,6 @@
     """
     id = 'tsearch'
 
-
     def cell_call(self, row, col, **kwargs):
         entity = self.complete_entity(row, col)
         self.w(entity.view('incontext'))
@@ -1004,33 +555,28 @@
                 self.w(value.replace('\n', '<br/>'))            
 
 
-class EntityRelationView(EntityView):
-    accepts = ()
-    vid = 'list'
-    
-    def cell_call(self, row, col):
-        if self.target == 'object':
-            role = 'subject'
-        else:
-            role = 'object'
-        rset = self.rset.get_entity(row, col).related(self.rtype, role)
-        if self.title:
-            self.w(u'<h1>%s</h1>' % self.req._(self.title).capitalize())
-        self.w(u'<div class="mainInfo">')
-        self.wview(self.vid, rset, 'noresult')
-        self.w(u'</div>')
-
-
-class TooltipView(OneLineView):
+class TooltipView(EntityView):
     """A entity view used in a tooltip"""
     id = 'tooltip'
-    title = None # don't display in possible views
     def cell_call(self, row, col):
         self.wview('oneline', self.rset, row=row, col=col)
 
+
+# XXX bw compat
+
+from logilab.common.deprecation import class_moved
+
 try:
     from cubicweb.web.views.tableview import TableView
-    from logilab.common.deprecation import class_moved
     TableView = class_moved(TableView)
 except ImportError:
     pass # gae has no tableview module (yet)
+
+from cubicweb.web.views import boxes, xmlrss
+SideBoxView = class_moved(boxes.SideBoxView)
+XmlView = class_moved(xmlrss.XmlView)
+XmlItemView = class_moved(xmlrss.XmlItemView)
+XmlRsetView = class_moved(xmlrss.XmlRsetView)
+RssView = class_moved(xmlrss.RssView)
+RssItemView = class_moved(xmlrss.RssItemView)
+            
--- a/web/views/bookmark.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/bookmark.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """Primary view for bookmarks + user's bookmarks box
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -9,13 +9,30 @@
 from logilab.mtconverter import html_escape
 
 from cubicweb import Unauthorized
+from cubicweb.selectors import implements
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
+from cubicweb.web.action import Action
 from cubicweb.web.box import UserRQLBoxTemplate
 from cubicweb.web.views.baseviews import PrimaryView
+from cubicweb.web.views.editforms import AutomaticEntityForm
+
+AutomaticEntityForm.rcategories.set_rtag('primary', 'path', 'subject', 'Bookmark')
+AutomaticEntityForm.rwidgets.set_rtag('StringWidget', 'path', 'subject', 'Bookmark')
+
+
+class FollowAction(Action):
+    id = '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()
 
 
 class BookmarkPrimaryView(PrimaryView):
-    accepts = ('Bookmark',)
+    __select__ = implements('Bookmark')
         
     def cell_call(self, row, col):
         """the primary view for bookmark entity"""
--- a/web/views/boxes.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/boxes.py	Tue Apr 07 09:30:23 2009 +0200
@@ -3,7 +3,6 @@
 
 * actions box
 * possible views box
-* rss icon
 
 additional (disabled by default) boxes
 * schema box
@@ -16,9 +15,12 @@
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import html_escape
-from cubicweb.common.selectors import any_rset, appobject_selectable
+
+from cubicweb.rtags import RelationTags
+from cubicweb.selectors import match_user_groups, non_final_entity
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web.box import BoxTemplate, ExtResourcesBoxTemplate
+from cubicweb.view import EntityView
+from cubicweb.web.box import BoxTemplate
 
 _ = unicode
     
@@ -28,8 +30,65 @@
     change state, add related entities
     """
     id = 'edit_box'
+    __select__ = BoxTemplate.__select__ & non_final_entity()
+
     title = _('actions')
     order = 2
+    # 'link' / 'create' relation tags, used to control the "add entity" submenu
+    rmode = RelationTags() 
+    rmode.set_rtag('link', 'is', 'subject')
+    rmode.set_rtag('link', 'is', 'object')
+    rmode.set_rtag('link', 'is_instance_of', 'subject')
+    rmode.set_rtag('link', 'is_instance_of', 'object')
+    rmode.set_rtag('link', 'identity', 'subject')
+    rmode.set_rtag('link', 'identity', 'object')
+    rmode.set_rtag('link', 'owned_by', 'subject')
+    rmode.set_rtag('link', 'created_by', 'subject')
+    rmode.set_rtag('link', 'require_permission', 'subject')
+    rmode.set_rtag('link', 'wf_info_for', 'subject')
+    rmode.set_rtag('link', 'wf_info_for', 'subject')
+
+    @classmethod
+    def registered(cls, registry):
+        """build class using descriptor at registration time"""
+        super(EditBox, cls).registered(registry)
+        cls.init_rtags_mode()
+        return cls
+        
+    @classmethod
+    def init_rtags_mode(cls):
+        """set default category tags for relations where it's not yet defined in
+        the category relation tags
+        """
+        for eschema in cls.schema.entities():
+            for rschema, tschemas, role in eschema.relation_definitions(True):
+                for tschema in tschemas:
+                    if role == 'subject':
+                        X, Y = eschema, tschema
+                        card = rschema.rproperty(X, Y, 'cardinality')[0]
+                    else:
+                        X, Y = tschema, eschema
+                        card = rschema.rproperty(X, Y, 'cardinality')[1]
+                    if not cls.rmode.rtag(rschema, role, X, Y):
+                        if card in '?1':
+                            # by default, suppose link mode if cardinality doesn't allow
+                            # more than one relation
+                            mode = 'link'
+                        elif rschema.rproperty(X, Y, 'composite') == role:
+                            # if self is composed of the target type, create mode
+                            mode = 'create'
+                        else:
+                            # link mode by default
+                            mode = 'link'
+                        cls.rmode.set_rtag(mode, rschema, role, X, Y)
+
+    @classmethod
+    def relation_mode(cls, rtype, etype, targettype, role='subject'):
+        """return a string telling if the given relation is usually created
+        to a new entity ('create' mode) or to an existant entity ('link' mode)
+        """
+        return cls.rmode.rtag(rtype, role, etype, targettype)
+
 
     def call(self, view=None, **kwargs):
         _ = self.req._
@@ -89,7 +148,7 @@
         actions = []
         _ = self.req._
         eschema = entity.e_schema
-        for rschema, teschema, x in entity.add_related_schemas():
+        for rschema, teschema, x in self.add_related_schemas(entity):
             if x == 'subject':
                 label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'object')
@@ -99,6 +158,33 @@
             actions.append(self.mk_action(_(label), url))
         return actions
 
+    def add_related_schemas(self, entity):
+        """this is actually used ui method to generate 'addrelated' actions from
+        the schema.
+
+        If you're using explicit 'addrelated' actions for an entity types, you
+        should probably overrides this method to return an empty list else you
+        may get some unexpected actions.
+        """
+        req = self.req
+        eschema = entity.e_schema
+        for role, rschemas in (('subject', eschema.subject_relations()),
+                               ('object', eschema.object_relations())):
+            for rschema in rschemas:
+                if rschema.is_final():
+                    continue
+                # check the relation can be added as well
+                if role == 'subject'and not rschema.has_perm(req, 'add', fromeid=entity.eid):
+                    continue
+                if role == 'object'and not rschema.has_perm(req, 'add', toeid=entity.eid):
+                    continue
+                # check the target types can be added as well
+                for teschema in rschema.targets(eschema, role):
+                    if not self.relation_mode(rschema, eschema, teschema, role) == 'create':
+                        continue
+                    if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
+                        yield rschema, teschema, role
+
 
     def workflow_actions(self, entity, box):
         if 'in_state' in entity.e_schema.subject_relations() and entity.in_state:
@@ -130,10 +216,10 @@
 class SearchBox(BoxTemplate):
     """display a box with a simple search form"""
     id = 'search_box'
+
     visible = True # enabled by default
     title = _('search')
     order = 0
-    need_resources = 'SEARCH_GO'
     formdef = u"""<form action="%s">
 <table id="tsearch"><tr><td>
 <input id="norql" type="text" accesskey="q" tabindex="%s" title="search text" value="%s" name="rql" />
@@ -144,7 +230,6 @@
 </td></tr></table>
 </form>"""
 
-
     def call(self, view=None, **kwargs):
         req = self.req
         if req.form.pop('__fromsearchbox', None):
@@ -164,11 +249,11 @@
 class PossibleViewsBox(BoxTemplate):
     """display a box containing links to all possible views"""
     id = 'possible_views_box'
+    __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
     
+    visible = False
     title = _('possible views')
     order = 10
-    require_groups = ('users', 'managers')
-    visible = False
 
     def call(self, **kwargs):
         box = BoxWidget(self.req._(self.title), self.id)
@@ -182,27 +267,11 @@
         if not box.is_empty():
             box.render(self.w)
 
-        
-class RSSIconBox(ExtResourcesBoxTemplate):
-    """just display the RSS icon on uniform result set"""
-    __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (appobject_selectable('components', 'rss_feed_url'),)
-    
-    id = 'rss'
-    order = 999
-    need_resources = 'RSS_LOGO',
-    visible = False
-    
-    def call(self, **kwargs):
-        urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
-        url = urlgetter.feed_url()
-        rss = self.req.external_resource('RSS_LOGO')
-        self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (html_escape(url), rss))
-
 
 class StartupViewsBox(BoxTemplate):
     """display a box containing links to all startup views"""
     id = 'startup_views_box'
-    visible = False# disabled by default
+    visible = False # disabled by default
     title = _('startup views')
     order = 70
 
@@ -215,3 +284,32 @@
         if not box.is_empty():
             box.render(self.w)
 
+
+# helper classes ##############################################################
+
+class SideBoxView(EntityView):
+    """helper view class to display some entities in a sidebox"""
+    id = 'sidebox'
+    
+    def call(self, boxclass='sideBox', title=u''):
+        """display a list of entities by calling their <item_vid> view"""
+        if title:
+            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)
+            else:
+                self.wview('simplelist', self.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.w(u'</div>\n</div>\n')
--- a/web/views/calendar.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/calendar.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,90 +1,44 @@
 """html calendar views
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
-from mx.DateTime import DateTime, RelativeDateTime, today, ISO
-from datetime import datetime
+from datetime import datetime, date, timedelta
 
-from vobject import iCalendar, icalendar
+from vobject import iCalendar
 
 from logilab.mtconverter import html_escape
 
 from cubicweb.interfaces import ICalendarable
-from cubicweb.common.utils import date_range
+from cubicweb.selectors import implements
+from cubicweb.utils import strptime, date_range, todate
+from cubicweb.view import EntityView
 from cubicweb.common.uilib import ajax_replace_url
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.view import EntityView
-
-
-# For backward compatibility
-from cubicweb.interfaces import ICalendarViews, ITimetableViews
-try:
-    from cubicweb.web.views.old_calendar import _CalendarView, AMPMWeekCalendarView
-except ImportError:
-    import logging
-    logger = logging.getLogger('cubicweb.registry')
-    logger.info("old calendar views could not be found and won't be registered")
 
 _ = unicode
 
-# useful constants & functions
-def mkdt(mxdate):
-    """
-    Build a stdlib datetime date from a mx.datetime 
-    """
-    d = mxdate
-    return datetime(d.year, d.month, d.day, d.hour, d.minute,
-                    tzinfo=icalendar.utc)
-def iso(mxdate):
-    """
-    Format a ms datetime in ISO 8601 string 
-    """
-    # XXX What about timezone?
-    return ISO.str(mxdate)
+# useful constants & functions ################################################
 
-# mx.DateTime and ustrftime could be used to build WEEKDAYS
+ONEDAY = timedelta(1)
+
 WEEKDAYS = (_("monday"), _("tuesday"), _("wednesday"), _("thursday"),
             _("friday"), _("saturday"), _("sunday"))
-
-# used by i18n tools
 MONTHNAMES = ( _('january'), _('february'), _('march'), _('april'), _('may'),
                _('june'), _('july'), _('august'), _('september'), _('october'),
                _('november'), _('december')
                )
-
-#################
-# In calendar views (views used as calendar cell item) 
-
-
-class CalendarItemView(EntityView):
-    id = 'calendaritem'
+        
+# Calendar views ##############################################################
 
-    def cell_call(self, row, col, dates=False):
-        task = self.complete_entity(row)
-        task.view('oneline', w=self.w)
-        if dates:
-            if task.start and task.stop:
-                self.w('<br/>from %s'%self.format_date(task.start))
-                self.w('<br/>to %s'%self.format_date(task.stop))
-                
-class CalendarLargeItemView(CalendarItemView):
-    id = 'calendarlargeitem'
-        
-#################
-# Calendar views
 
 class iCalView(EntityView):
     """A calendar view that generates a iCalendar file (RFC 2445)
 
     Does apply to ICalendarable compatible entities
     """
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
+    __select__ = implements(ICalendarable)
     need_navigation = False
     content_type = 'text/calendar'
     title = _('iCalendar')
@@ -99,9 +53,9 @@
             event.add('summary').value = task.dc_title()
             event.add('description').value = task.dc_description()
             if task.start:
-                event.add('dtstart').value = mkdt(task.start)
+                event.add('dtstart').value = task.start
             if task.stop:
-                event.add('dtend').value = mkdt(task.stop)
+                event.add('dtend').value = task.stop
 
         buff = ical.serialize()
         if not isinstance(buff, unicode):
@@ -113,13 +67,11 @@
 
     Does apply to ICalendarable compatible entities
     """
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
+    id = 'hcal'
+    __select__ = implements(ICalendarable)
     need_navigation = False
     title = _('hCalendar')
     #templatable = False
-    id = 'hcal'
 
     def call(self):
         self.w(u'<div class="hcalendar">')
@@ -129,12 +81,28 @@
             self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
             self.w(u'<div class="description">%s</div>' % html_escape(task.dc_description()))
             if task.start:
-                self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (iso(task.start), self.format_date(task.start)))
+                self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self.format_date(task.start)))
             if task.stop:
-                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (iso(task.stop), self.format_date(task.stop)))
+                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self.format_date(task.stop)))
             self.w(u'</div>')
         self.w(u'</div>')
 
+
+class CalendarItemView(EntityView):
+    id = 'calendaritem'
+
+    def cell_call(self, row, col, dates=False):
+        task = self.complete_entity(row)
+        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))
+                
+class CalendarLargeItemView(CalendarItemView):
+    id = 'calendarlargeitem'
+
     
 class _TaskEntry(object):
     def __init__(self, task, color, index=0):
@@ -143,20 +111,28 @@
         self.index = index
         self.length = 1
 
+    def in_working_hours(self):
+        """predicate returning True is the task is in working hours"""
+        if self.task.start.hour > 7 and self.task.stop.hour < 20:
+            return True
+        return False
+    
+    def is_one_day_task(self):
+        task = self.task
+        return task.start and task.stop and task.start.isocalendar() ==  task.stop.isocalendar()
+        
 class OneMonthCal(EntityView):
     """At some point, this view will probably replace ampm calendars"""
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface, )
-    accepts_interfaces = (ICalendarable,)
+    id = 'onemonthcal'
+    __select__ = implements(ICalendarable)
     need_navigation = False
-    id = 'onemonthcal'
     title = _('one month')
 
     def call(self):
         self.req.add_js('cubicweb.ajax.js')
         self.req.add_css('cubicweb.calendar.css')
         # XXX: restrict courses directy with RQL
-        _today =  today()
+        _today =  datetime.today()
 
         if 'year' in self.req.form:
             year = int(self.req.form['year'])
@@ -167,56 +143,60 @@
         else:
             month = _today.month
 
-        first_day_of_month = DateTime(year, month, 1)
-        lastday = first_day_of_month + RelativeDateTime(months=1,weekday=(6,1))
-        firstday= first_day_of_month + RelativeDateTime(months=-1,weekday=(0,-1))
+        first_day_of_month = date(year, month, 1)
+        firstday = first_day_of_month - timedelta(first_day_of_month.weekday())
+        if month >= 12:
+            last_day_of_month = date(year + 1, 1, 1) - timedelta(1)
+        else:
+            last_day_of_month = date(year, month + 1, 1) - timedelta(1)
+        lastday = last_day_of_month + timedelta(6 - last_day_of_month.weekday())
         month_dates = list(date_range(firstday, lastday))
         dates = {}
-        users = []
         task_max = 0
         for row in xrange(self.rset.rowcount):
-            task = self.rset.get_entity(row,0)
+            task = self.rset.get_entity(row, 0)
             if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'EUser':
-                user = self.rset.get_entity(row,1)
+                user = self.rset.get_entity(row, 1)
             else:
                 user = None
             the_dates = []
-            if task.start:
-                if task.start > lastday:
+            tstart = todate(task.start)
+            if tstart:
+                if tstart > lastday:
                     continue
-                the_dates = [task.start]
-            if task.stop:
-                if task.stop < firstday:
+                the_dates = [tstart]
+            tstop = todate(task.start)
+            if tstop:
+                if tstop < firstday:
                     continue
-                the_dates = [task.stop]
-            if task.start and task.stop:
-                if task.start.absdate == task.stop.absdate:
-                    date = task.start
-                    if firstday<= date <= lastday:
-                        the_dates = [date]
+                the_dates = [tstop]
+            if tstart and tstop:
+                if tstart.isocalendar() == tstop.isocalendar():
+                    if firstday <= tstart <= lastday:
+                        the_dates = [tstart]
                 else:
-                    the_dates = date_range(max(task.start,firstday),
-                                           min(task.stop,lastday))
+                    the_dates = date_range(max(tstart, firstday),
+                                           min(tstop, lastday))
             if not the_dates:
                 continue
             
             for d in the_dates:
                 d_tasks = dates.setdefault((d.year, d.month, d.day), {})
-                t_users = d_tasks.setdefault(task,set())
+                t_users = d_tasks.setdefault(task, set())
                 t_users.add( user )
-                if len(d_tasks)>task_max:
+                if len(d_tasks) > task_max:
                     task_max = len(d_tasks)
 
         days = []
-        nrows = max(3,task_max)
+        nrows = max(3, task_max)
         # colors here are class names defined in cubicweb.css
-        colors = [ "col%x"%i for i in range(12) ]
+        colors = [ "col%x" % i for i in range(12) ]
         next_color_index = 0
 
         visited_tasks = {} # holds a description of a task
         task_colors = {}   # remember a color assigned to a task
-        for date in month_dates:
-            d_tasks = dates.get((date.year, date.month, date.day), {})
+        for mdate in month_dates:
+            d_tasks = dates.get((mdate.year, mdate.month, mdate.day), {})
             rows = [None] * nrows
             # every task that is "visited" for the first time
             # require a special treatment, so we put them in
@@ -232,7 +212,7 @@
                 # to every 'new' task we must affect a color
                 # (which must be the same for every user concerned
                 # by the task)
-                for i,t in enumerate(rows):
+                for i, t in enumerate(rows):
                     if t is None:
                         if task in task_colors:
                             color = task_colors[task]
@@ -264,41 +244,41 @@
                tuple(self.req._(day) for day in WEEKDAYS))
         
         # build calendar
-        for date, task_rows in zip(month_dates, days):
-            if date.day_of_week == 0:
+        for mdate, task_rows in zip(month_dates, days):
+            if mdate.weekday() == 0:
                 self.w(u'<tr>')
-            self._build_calendar_cell(date, task_rows, curdate)
-            if date.day_of_week == 6:
+            self._build_calendar_cell(mdate, task_rows, curdate)
+            if mdate.weekday() == 6:
                 self.w(u'</tr>')
         self.w(u'</table></div>')
 
     def _prevnext_links(self, curdate):
-        prevdate = curdate - RelativeDateTime(months=1)
-        nextdate = curdate + RelativeDateTime(months=1)
-        rql = self.rset.rql
+        prevdate = curdate - timedelta(31)
+        nextdate = curdate + timedelta(31)
+        rql = self.rset.printable_rql()
         prevlink = ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
                                     year=prevdate.year, month=prevdate.month)
         nextlink = ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
                                     year=nextdate.year, month=nextdate.month)
         return prevlink, nextlink
 
-    def _build_calendar_cell(self, date, rows, curdate):
+    def _build_calendar_cell(self, celldate, rows, curdate):
         curmonth = curdate.month
         classes = ""
-        if date.month != curmonth:
+        if celldate.month != curmonth:
             classes += " outOfRange"
-        if date == today():
+        if celldate == date.today():
             classes += " today"
         self.w(u'<td class="cell%s">' % classes)
         self.w(u'<div class="calCellTitle%s">' % classes)
-        self.w(u'<div class="day">%s</div>' % date.day)
+        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(date), stop=self.format_date(date),
-                                 __redirectrql=self.rset.rql,
+                                 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
                                  )
@@ -312,7 +292,7 @@
                 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.rql,
+                                        __redirectrql=self.rset.printable_rql(),
                                         __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
                                         __redirectvid=self.id
                                         )
@@ -330,19 +310,16 @@
 
 class OneWeekCal(EntityView):
     """At some point, this view will probably replace ampm calendars"""
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface, )
-    accepts_interfaces = (ICalendarable,)
+    id = 'oneweekcal'
+    __select__ = implements(ICalendarable)
     need_navigation = False
-    id = 'oneweekcal'
     title = _('one week')
     
     def call(self):
         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
         self.req.add_css('cubicweb.calendar.css')
-        # XXX: restrict courses directy with RQL
-        _today =  today()
-
+        # XXX: restrict directly with RQL
+        _today =  datetime.today()
         if 'year' in self.req.form:
             year = int(self.req.form['year'])
         else:
@@ -350,44 +327,45 @@
         if 'week' in self.req.form:
             week = int(self.req.form['week'])
         else:
-            week = _today.iso_week[1]        
-
-        first_day_of_week = ISO.ParseWeek("%s-W%s-1"%(year, week))
-        lastday = first_day_of_week + RelativeDateTime(days=6)
-        firstday= first_day_of_week
+            week = _today.isocalendar()[1]        
+        # week - 1 since we get week number > 0 while we want it to start from 0
+        first_day_of_week = todate(strptime('%s-%s-1' % (year, week - 1), '%Y-%U-%w'))
+        lastday = first_day_of_week + timedelta(6)
+        firstday = first_day_of_week
         dates = [[] for i in range(7)]
-        task_max = 0
         task_colors = {}   # remember a color assigned to a task
         # colors here are class names defined in cubicweb.css
-        colors = [ "col%x"%i for i in range(12) ]
+        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)
+            task = self.rset.get_entity(row, 0)
             if task in done_tasks:
                 continue
             done_tasks.append(task)
             the_dates = []
-            if task.start:
-                if task.start > lastday:
+            tstart = todate(task.start)
+            tstop = todate(task.stop)
+            if tstart:
+                if tstart > lastday:
                     continue
-                the_dates = [task.start]
-            if task.stop:
-                if task.stop < firstday:
+                the_dates = [tstart]
+            if tstop:
+                if tstop < firstday:
                     continue
-                the_dates = [task.stop]
-            if task.start and task.stop:
-                the_dates = date_range(max(task.start,firstday),
-                                       min(task.stop,lastday))
+                the_dates = [tstop]
+            if tstart and tstop:
+                the_dates = date_range(max(tstart, firstday),
+                                       min(tstop, lastday))
             if not the_dates:
                 continue
                 
             if task not in task_colors:
                 task_colors[task] = colors[next_color_index]
-                next_color_index = (next_color_index+1)%len(colors)
+                next_color_index = (next_color_index+1) % len(colors)
             
             for d in the_dates:
-                day = d.day_of_week
+                day = d.weekday()
                 task_descr = _TaskEntry(task, task_colors[task])  
                 dates[day].append(task_descr)
             
@@ -399,21 +377,20 @@
         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>' %
                (html_escape(prevlink), first_day_of_week.year,
-                self.req._(u'week'), first_day_of_week.iso_week[1],
+                self.req._(u'week'), first_day_of_week.isocalendar()[1],
                 html_escape(nextlink)))
 
         # output header
         self.w(u'<tr>')
         self.w(u'<th class="transparent"></th>') # column for hours
-        _today = today()
+        _today = date.today()
         for i, day in enumerate(WEEKDAYS):
-            date = first_day_of_week + i
-            if date.absdate == _today.absdate:
-                self.w(u'<th class="today">%s<br/>%s</th>' % (self.req._(day), self.format_date(date)))
+            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)))
             else:
-                self.w(u'<th>%s<br/>%s</th>' % (self.req._(day), self.format_date(date)))
+                self.w(u'<th>%s<br/>%s</th>' % (self.req._(day), self.format_date(wdate)))
         self.w(u'</tr>')
-
         
         # build week calendar
         self.w(u'<tr>')
@@ -426,20 +403,21 @@
         self.w(u'</td>')
         
         for i, day in enumerate(WEEKDAYS):
-            date = first_day_of_week + i
+            wdate = first_day_of_week + timedelta(i)
             classes = ""
-            if date.absdate == _today.absdate:
+            if wdate.isocalendar() == _today.isocalendar():
                 classes = " today"
-            self.w(u'<td class="column %s" id="%s">'%(classes, day))
+            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.rql,
+                                     __redirectrql=self.rset.printable_rql(),
                                      __redirectparams=self.req.build_url_params(year=year, week=week),
                                      __redirectvid=self.id
                                      )
-                extra = ' ondblclick="addCalendarItem(event, hmin=%s, hmax=%s, year=%s, month=%s, day=%s, duration=%s, baseurl=\'%s\')"' % (8,20,date.year, date.month, date.day, 2, html_escape(url))
+                extra = ' ondblclick="addCalendarItem(event, hmin=8, hmax=20, year=%s, month=%s, day=%s, duration=2, baseurl=\'%s\')"' % (
+                    wdate.year, wdate.month, wdate.day, html_escape(url))
             else:
                 extra = ""
             self.w(u'<div class="columndiv"%s>'% extra)
@@ -447,7 +425,7 @@
                 self.w(u'<div class="hourline" style="top:%sex;">'%((h-7)*8))
                 self.w(u'</div>')            
             if dates[i]:
-                self._build_calendar_cell(date, dates[i])
+                self._build_calendar_cell(wdate, dates[i])
             self.w(u'</div>')
             self.w(u'</td>')
         self.w(u'</tr>')
@@ -455,19 +433,9 @@
         self.w(u'<div id="coord"></div>')
         self.w(u'<div id="debug">&nbsp;</div>')
  
-    def _one_day_task(self, task):
-        """
-        Return true if the task is a "one day" task; ie it have a start and a stop the same day
-        """
-        if task.start and task.stop:
-            if task.start.absdate ==  task.stop.absdate:
-                return True
-        return False
-        
     def _build_calendar_cell(self, date, task_descrs):
-        inday_tasks = [t for t in task_descrs if self._one_day_task(t.task) and  t.task.start.hour<20 and t.task.stop.hour>7]
-        wholeday_tasks = [t for t in task_descrs if not self._one_day_task(t.task)]
-
+        inday_tasks = [t for t in task_descrs if t.is_one_day_task() and  t.in_working_hours()]
+        wholeday_tasks = [t for t in task_descrs if not t.is_one_day_task()]
         inday_tasks.sort(key=lambda t:t.task.start)
         sorted_tasks = []
         for i, t in enumerate(wholeday_tasks):
@@ -498,11 +466,11 @@
             stop_hour = 20
             stop_min = 0
             if task.start:
-                if date < task.start < date + 1:
+                if date < todate(task.start) < date + ONEDAY:
                     start_hour = max(8, task.start.hour)
                     start_min = task.start.minute
             if task.stop:
-                if date < task.stop < date + 1:
+                if date < todate(task.stop) < date + ONEDAY:
                     stop_hour = min(20, task.stop.hour)
                     if stop_hour < 20:
                         stop_min = task.stop.minute
@@ -516,8 +484,8 @@
                        (task_desc.color, style))
             task.view('calendaritem', dates=False, w=self.w)
             url = task.absolute_url(vid='edition',
-                                    __redirectrql=self.rset.rql,
-                                    __redirectparams=self.req.build_url_params(year=date.year, week=date.iso_week[1]),
+                                    __redirectrql=self.rset.printable_rql(),
+                                    __redirectparams=self.req.build_url_params(year=date.year, week=date.isocalendar()[1]),
                                     __redirectvid=self.id
                                  )
 
@@ -538,12 +506,12 @@
 
             
     def _prevnext_links(self, curdate):
-        prevdate = curdate - RelativeDateTime(days=7)
-        nextdate = curdate + RelativeDateTime(days=7)
-        rql = self.rset.rql
+        prevdate = curdate - timedelta(7)
+        nextdate = curdate + timedelta(7)
+        rql = self.rset.printable_rql()
         prevlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
-                                    year=prevdate.year, week=prevdate.iso_week[1])
+                                    year=prevdate.year, week=prevdate.isocalendar()[1])
         nextlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
-                                    year=nextdate.year, week=nextdate.iso_week[1])
+                                    year=nextdate.year, week=nextdate.isocalendar()[1])
         return prevlink, nextlink
 
--- a/web/views/card.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/card.py	Tue Apr 07 09:30:23 2009 +0200
@@ -6,13 +6,14 @@
 """
 __docformat__ = "restructuredtext en"
 
+from cubicweb.selectors import implements
 from cubicweb.web.views import baseviews
 from logilab.mtconverter import html_escape
 
 _ = unicode
 
 class CardPrimaryView(baseviews.PrimaryView):
-    accepts = ('Card',)
+    __select__ = implements('Card')
     skip_attrs = baseviews.PrimaryView.skip_attrs + ('title', 'synopsis', 'wikiid')
     show_attr_label = False
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/csvexport.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,87 @@
+"""csv export views
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import UnicodeCSVWriter
+from cubicweb.view import EntityView, AnyRsetView
+
+class CSVMixIn(object):
+    """mixin class for CSV views"""
+    templatable = False
+    content_type = "text/comma-separated-values"    
+    binary = True # avoid unicode assertion
+    csv_params = {'dialect': 'excel',
+                  'quotechar': '"',
+                  'delimiter': ';',
+                  'lineterminator': '\n'}
+    
+    def set_request_content_type(self):
+        """overriden to set a .csv filename"""
+        self.req.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)
+
+    
+class CSVRsetView(CSVMixIn, AnyRsetView):
+    """dumps raw result set in CSV"""
+    id = '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
+        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)
+                else:
+                    content = self.view('final', rset,
+                                        displaytime=True, format='text/plain',
+                                        row=rowindex, col=colindex)
+                csvrow.append(content)                    
+            writer.writerow(csvrow)
+    
+    
+class CSVEntityView(CSVMixIn, EntityView):
+    """dumps rset's entities (with full set of attributes) in CSV
+
+    the generated CSV file will have a table per entity type found in the
+    resultset. ('table' here only means empty lines separation between table
+    contents)
+    """
+    id = 'ecsvexport'
+    title = _('csv entities export')
+
+    def call(self):
+        req = self.req
+        rows_by_type = {}
+        writer = self.csvwriter()
+        rowdef_by_type = {}
+        for index in xrange(len(self.rset)):
+            entity = self.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']
+                rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
+                                                  for rschema in rowdef_by_type[entity.e_schema]]]
+            rows = rows_by_type[entity.e_schema]
+            rows.append([entity.printable_value(rs.type, format='text/plain')
+                         for rs in rowdef_by_type[entity.e_schema]])
+        for rows in rows_by_type.itervalues():
+            writer.writerows(rows)
+            # use two empty lines as separator
+            writer.writerows([[], []])        
+    
--- a/web/views/debug.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/debug.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -11,7 +11,8 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.view import StartupView
+from cubicweb.selectors import none_rset, match_user_groups
+from cubicweb.view import StartupView
 
 def dict_to_html(w, dict):
     # XHTML doesn't allow emtpy <ul> nodes
@@ -21,11 +22,12 @@
             w(u'<li><span class="label">%s</span>: <span>%s</span></li>' % (
                 html_escape(str(key)), html_escape(repr(dict[key]))))
         w(u'</ul>')
+
     
 class DebugView(StartupView):
     id = 'debug'
+    __select__ = none_rset() & match_user_groups('managers')
     title = _('server debug information')
-    require_groups = ('managers',)
 
     def call(self, **kwargs):
         """display server information"""
--- a/web/views/dynimages.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-"""dynamically generated image views
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-import os
-from tempfile import mktemp
-from itertools import cycle
-
-from logilab.common.graph import escape, GraphGenerator, DotBackend
-from yams import schema2dot as s2d
-
-from cubicweb.common.view import EntityView, StartupView
-
-
-class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
-    def __init__(self, req):
-        # FIXME: colors are arbitrary
-        self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
-                                 '#000000', '#888888') ).next
-        self.req = req
-        
-    def display_attr(self, rschema):
-        return not rschema.meta and (rschema.has_local_role('read')
-                                     or rschema.has_perm(self.req, 'read'))
-    
-    # XXX remove this method once yams > 0.20 is out
-    def node_properties(self, eschema):
-        """return default DOT drawing options for an entity schema"""
-        label = ['{',eschema.type,'|']
-        label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
-                                if rel.final and self.display_attr(rel)))
-        label.append(r'\l}') # trailing \l ensure alignement of the last one
-        return {'label' : ''.join(label), 'shape' : "record",
-                'fontname' : "Courier", 'style' : "filled"}
-
-    def edge_properties(self, rschema, subjnode, objnode):
-        kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
-        # symetric rels are handled differently, let yams decide what's best
-        if not rschema.symetric:
-            kwargs['color'] = self.nextcolor()
-        kwargs['fontcolor'] = kwargs['color']
-        # dot label decoration is just awful (1 line underlining the label
-        # + 1 line going to the closest edge spline point)
-        kwargs['decorate'] = 'false'
-        return kwargs
-    
-
-class RestrictedSchemaVisitorMiIn:
-    def __init__(self, req, *args, **kwargs):
-        # hack hack hack
-        assert len(self.__class__.__bases__) == 2
-        self.__parent = self.__class__.__bases__[1]
-        self.__parent.__init__(self, *args, **kwargs)
-        self.req = req
-        
-    def nodes(self):
-        for etype, eschema in self.__parent.nodes(self):
-            if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
-                yield eschema.type, eschema
-            
-    def edges(self):
-        for setype, oetype, rschema in self.__parent.edges(self):
-            if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
-                yield setype, oetype, rschema
-
-class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
-    pass
-
-class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
-    pass
-
-class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
-    pass
-                
-        
-class TmpFileViewMixin(object):
-    binary = True
-    content_type = 'application/octet-stream'
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
-    
-    def call(self):
-        self.cell_call()
-        
-    def cell_call(self, row=0, col=0):
-        self.row, self.col = row, col # in case one need it
-        tmpfile = mktemp('.png')
-        try:
-            self._generate(tmpfile)
-            self.w(open(tmpfile).read())
-        finally:
-            os.unlink(tmpfile)
-    
-class SchemaImageView(TmpFileViewMixin, StartupView):
-    id = 'schemagraph'
-    content_type = 'image/png'
-    skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
-    def _generate(self, tmpfile):
-        """display global schema information"""
-        skipmeta = not int(self.req.form.get('withmeta', 0))
-        visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
-        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
-                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
-    id = 'eschemagraph'
-    content_type = 'image/png'
-    accepts = ('EEType',)
-    skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
-    
-    def _generate(self, tmpfile):
-        """display schema information for an entity"""
-        entity = self.entity(self.row, self.col)
-        eschema = self.vreg.schema.eschema(entity.name)
-        visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
-        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
-                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-class ERTypeSchemaImageView(EETypeSchemaImageView):
-    accepts = ('ERType',)
-    
-    def _generate(self, tmpfile):
-        """display schema information for an entity"""
-        entity = self.entity(self.row, self.col)
-        rschema = self.vreg.schema.rschema(entity.name)
-        visitor = OneHopRSchemaVisitor(self.req, rschema)
-        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
-                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-
-
-class WorkflowDotPropsHandler(object):
-    def __init__(self, req):
-        self._ = req._
-        
-    def node_properties(self, stateortransition):
-        """return default DOT drawing options for a state or transition"""
-        props = {'label': stateortransition.name, 
-                 'fontname': 'Courier'}
-        if hasattr(stateortransition, 'state_of'):
-            props['shape'] = 'box'
-            props['style'] = 'filled'
-            if stateortransition.reverse_initial_state:
-                props['color'] = '#88CC88'
-        else:
-            props['shape'] = 'ellipse'
-            descr = []
-            tr = stateortransition
-            if tr.require_group:
-                descr.append('%s %s'% (
-                    self._('groups:'),
-                    ','.join(g.name for g in tr.require_group)))
-            if tr.condition:
-                descr.append('%s %s'% (self._('condition:'), tr.condition))
-            if descr:
-                props['label'] += escape('\n'.join(descr))
-        return props
-    
-    def edge_properties(self, transition, fromstate, tostate):
-        return {'label': '', 'dir': 'forward',
-                'color': 'black', 'style': 'filled'}
-
-class WorkflowVisitor:
-    def __init__(self, entity):
-        self.entity = entity
-
-    def nodes(self):
-        for state in self.entity.reverse_state_of:
-            state.complete()
-            yield state.eid, state
-            
-        for transition in self.entity.reverse_transition_of:
-            transition.complete()
-            yield transition.eid, transition
-            
-    def edges(self):
-        for transition in self.entity.reverse_transition_of:
-            for incomingstate in transition.reverse_allowed_transition:
-                yield incomingstate.eid, transition.eid, transition
-            yield transition.eid, transition.destination().eid, transition
-
-
-class EETypeWorkflowImageView(TmpFileViewMixin, EntityView):
-    id = 'ewfgraph'
-    content_type = 'image/png'
-    accepts = ('EEType',)
-    
-    def _generate(self, tmpfile):
-        """display schema information for an entity"""
-        entity = self.entity(self.row, self.col)
-        visitor = WorkflowVisitor(entity)
-        prophdlr = WorkflowDotPropsHandler(self.req)
-        generator = GraphGenerator(DotBackend('workflow', 'LR',
-                                              ratio='compress', size='30,12'))
-        return generator.generate(visitor, prophdlr, tmpfile)
--- a/web/views/edit_multiple.pt	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-<!-- rows are precomputed first to consume error messages if necessary -->
-<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);"
-      action="%(action)s"
-      tal:define="rows python:[self.edit_form(e) for e in rset.entities()]"
-      >
-  <div tal:replace="structure self/error_message"/>
-  <div id="progress" tal:content="progress">validating changes...</div>
-  <fieldset>
-  <input type="hidden" name="__errorurl" value="#"
-         tal:attributes="value req/url;" />
-  <input type="hidden" name="__form_id" value="#"
-	 tal:attributes="value python:self.id"/>
-  <input type="hidden" name="__redirectvid" value="primary"
-	 tal:attributes="value python:req.form.get('__redirectvid', 'list');"/>
-  <input type="hidden" name="__redirectrql" value="#"
-	 tal:attributes="value python:req.form.get('__redirectrql', rset.printable_rql());"/>
-  <table class="listing">
-    <tr class="header">
-      <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
-      <tal:th tal:iter="rdef python:sampleentity.relations_by_category('primary', 'add')">
-	<th tal:condition="python: rdef[0].type != 'eid'"
-            tal:content="python: rdef[0].display_name(req, rdef[-1])"/>
-      </tal:th>
-    </tr>
-    <tr tal:iter="row rows" tal:attributes="class python: repeat['row'].getOdd() and 'even' or 'odd'" tal:content="structure row"/>
-  </table>
-  <table width="100%%">
-    <tr>
-      <td align="left">
-	<input class="validateButton" type="submit"  value="#"
-	       tal:attributes="value okbuttonmsg; title okbuttontitle;"/>
-	<input class="validateButton" type="reset" name="__action_cancel" value="#"
-	       tal:attributes="value  cancelbuttonmsg; title cancelbuttontitle;"/>
-      </td>
-    </tr>
-  </table>
-  </fieldset>
-</form>
--- a/web/views/edit_relations.pt	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-<fieldset class="subentity">
-<legend class="iformTitle" tal:content="python: label">relations</legend>
-<table id="relatedEntities"
-       tal:define="pendings python: list(self.restore_pending_inserts(entity))">
-  <span tal:iter="row python: self.relations_table(entity)" tal:omit-tag="python: True">
-    <tr tal:condition="python: row[2]">
-      <th class="labelCol" tal:content="python: display_name(req, row[0].type, row[1])">relation name</th>
-      <td>
-	<ul>
-	  <li tal:iter="viewparams python: row[2]" class="invisible">
-	    <span tal:replace="structure python:viewparams[1]">[del it if you can]</span>
-	    <div tal:attributes="id python: 'span'+viewparams[0]; class python: viewparams[2]"
-                 tal:content="structure python: viewparams[3]">related entity view</div>
-	  </li>
-	  <li class="invisible"
-	      tal:condition="python: not self.force_display and self.maxrelitems &lt; len(row[2])"
-	      tal:content="structure python:self.force_display_link()"/>
-	</ul>
-      </td>
-    </tr>
-  </span>
-  <tr tal:iter="row pendings"
-      tal:attributes="id python: 'tr' + row[1]">
-    <!-- row: (relname, nodeid, js, url, eview) -->
-    <th tal:content="python: row[3]">relation name</th>
-    <td>
-      <a class="handle" title="cancel this insert"
-	 tal:attributes="href python: row[2]">[x]</a>
-      <a class="editionPending"
-	 tal:attributes="href python: row[4]; id python: 'a' + row[1]"
-	 tal:content="python: row[5]">entity\'s text_view</a>
-    </td>
-  </tr>
-  <tr tal:condition="not:pendings"><th>&nbsp;</th><td>&nbsp;</td></tr>
-  <tr class="separator" tal:attributes="id string: relationSelectorRow_$eid;">
-    <th class="labelCol">
-      <span i18n:content="add relation"></span>
-      <select tal:attributes="id string: relationSelector_${eid};
-                              tabindex req/next_tabindex;
-			      onchange string: javascript:showMatchingSelect(this.options[this.selectedIndex].value,${eid});">
-	<option value="" i18n:content="select a relation">select a relation</option>
-	<option tal:iter="rel python: entity.srelations_by_category(('generic', 'metadata'), 'add')" 
-                tal:attributes="value python: '%s_%s' % (rel[1], rel[2])"
-		tal:content="python: rel[0]">rel</option>
-      </select>
-    </th>
-    <td tal:attributes="id string: unrelatedDivs_$eid">
-    </td>
-  </tr>
-</table>
-</fieldset>
--- a/web/views/editcontroller.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/editcontroller.py	Tue Apr 07 09:30:23 2009 +0200
@@ -26,17 +26,14 @@
     def publish(self, rset=None, fromjson=False):
         """edit / create / copy / delete entity / relations"""
         self.fromjson = fromjson
-        req = self.req
-        form = req.form
-        for key in form:
+        for key in self.req.form:
             # There should be 0 or 1 action
             if key.startswith('__action_'):
                 cbname = key[1:]
                 try:
                     callback = getattr(self, cbname)
                 except AttributeError:
-                    raise ValidationError(None,
-                                          {None: req._('invalid action %r' % key)})
+                    raise RequestError(self.req._('invalid action %r' % key))
                 else:
                     return callback()
         self._default_publish()
@@ -199,27 +196,29 @@
             value = Decimal(value)
         elif attrtype == 'Bytes':
             # if it is a file, transport it using a Binary (StringIO)
-            if formparams.has_key('__%s_detach' % attr):
+            # 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 attribut is not modified
+            # 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
-                value = Binary(value.encode(entity.text_encoding(attr)))
+                value = Binary(value.encode(entity.attribute_metadata(attr, 'encoding')))
             else:
                 # (filename, mimetype, stream)
                 val = Binary(value[2].read())
                 if not val.getvalue(): # usually an unexistant file
                     value = None
                 else:
-                    # XXX suppose a File compatible schema
                     val.filename = value[0]
-                    if entity.has_format(attr):
+                    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]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editforms.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,679 @@
+"""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), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from copy import copy
+
+from simplejson import dumps
+
+from logilab.common.decorators import iclassmethod
+from logilab.mtconverter import html_escape
+
+from cubicweb import typed_eid
+from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
+                                specified_etype_implements, yes)
+from cubicweb.rtags import RelationTags
+from cubicweb.utils import make_uid
+from cubicweb.view import EntityView
+from cubicweb.common import tags
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, formwidgets
+from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormMixIn
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer,
+                                        EntityCompositeFormRenderer,
+                                        EntityInlinedFormRenderer)
+_ = unicode
+
+def relation_id(eid, rtype, role, reid):
+    """return an identifier for a relation between two entities"""
+    if role == 'subject':
+        return u'%s:%s:%s' % (eid, rtype, reid)
+    return u'%s:%s:%s' % (reid, rtype, eid)
+        
+def toggable_relation_link(eid, nodeid, label='x'):
+    """return javascript snippet to delete/undelete a relation between two
+    entities
+    """
+    js = u"javascript: togglePendingDelete('%s', %s);" % (nodeid, html_escape(dumps(eid)))
+    return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (js, nodeid, label)
+
+
+class DeleteConfForm(EntityView):
+    """form used to confirm deletion of some entities"""
+    id = 'deleteconf'
+    title = _('delete')
+    domid = 'deleteconf'
+    onsubmit = None
+    # don't use navigation, all entities asked to be deleted should be displayed
+    # else we will only delete the displayed page
+    need_navigation = False
+    
+    def call(self):
+        """ask for confirmation before real deletion"""
+        req, w = self.req, 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 = CompositeForm(req, domid='deleteconf', action=self.build_url('edit'),
+                                  onsubmit=self.onsubmit, copy_nav_params=True)
+        # XXX tabindex
+        form.buttons.append(form.button_delete(label=stdmsgs.YES))
+        form.buttons.append(form.button_cancel(label=stdmsgs.NO))
+        done = set()
+        w(u'<ul>\n')
+        for entity in self.rset.entities():
+            if entity.eid in done:
+                continue
+            done.add(entity.eid)
+            subform = EntityFieldsForm(req, entity=entity, set_error_url=False)
+            form.form_add_subform(subform)
+            # 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')
+        w(form.form_render())
+
+
+class ClickAndEditForm(FormMixIn, EntityView):
+    """form used to permit ajax edition of an attribute of an entity in a view
+    
+    (double-click on the field to see an appropriate edition widget)
+    """
+    id = 'reledit'
+    __select__ = non_final_entity() & match_kwargs('rtype')
+    
+    # FIXME editableField class could be toggleable from userprefs
+    
+    onsubmit = ("return inlineValidateForm('%(divid)s-form', '%(rtype)s', "
+                "'%(eid)s', '%(divid)s', %(reload)s);")
+    ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+    
+    def cell_call(self, row, col, rtype=None, role='subject', reload=False):
+        """display field to edit entity's `rtype` relation on double-click"""
+        entity = self.entity(row, col)
+        if getattr(entity, rtype) is None:
+            value = self.req._('not specified')
+        else:
+            value = entity.printable_value(rtype)
+        if not entity.has_perm('update'):
+            self.w(value)
+            return
+        self.req.add_js( ('cubicweb.ajax.js',) )
+        eid = entity.eid
+        edit_key = make_uid('%s-%s' % (rtype, eid))
+        divid = 'd%s' % edit_key
+        reload = dumps(reload)
+        # XXX tab index
+        buttons = [tags.input(klass="validateButton", type="submit",
+                              name="__action_apply",
+                              value=self.req._(stdmsgs.BUTTON_OK),
+                              tabindex=self.req.next_tabindex()),
+                   tags.input(klass="validateButton", type="button",
+                              value=self.req._(stdmsgs.BUTTON_CANCEL),
+                              onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid),
+                              tabindex=self.req.next_tabindex())]
+        form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                       row=row, col=col, buttons=buttons,
+                                       domid='%s-form' % divid, action='#',
+                                       cssstyle='display: none',
+                                       onsubmit=self.onsubmit % locals())
+        renderer = FormRenderer(display_label=False, display_help=False,
+                                display_fields=(rtype,), button_bar_class='buttonbar')
+        self.w(tags.div(value, klass='editableField', id=divid,
+                        ondblclick=self.ondblclick % locals()))
+        self.w(form.form_render(renderer=renderer))
+
+
+class AutomaticEntityForm(EntityFieldsForm):
+    """base automatic form to edit any entity
+
+    Designed to be flly generated from schema but highly configurable through:
+    * rtags (rcategories, rwidgets, inlined, rpermissions)
+    * various standard form parameters
+
+    You can also easily customise it by adding/removing fields in
+    AutomaticEntityForm instances.
+    """
+    id = 'edition'
+    
+    needs_js = EntityFieldsForm.needs_js + ('cubicweb.ajax.js',)
+    cwtarget = 'eformframe'
+    cssclass = 'entityForm'
+    copy_nav_params = True
+    attrcategories = ('primary', 'secondary')
+    
+    # relations'category (eg primary/secondary/generic/metadata/generated)
+    rcategories = RelationTags()
+    # use primary and not generated for eid since it has to be an hidden
+    rcategories.set_rtag('primary', 'eid', 'subject')
+    rcategories.set_rtag('metadata', 'creation_date', 'subject')
+    rcategories.set_rtag('metadata', 'modification_date', 'subject')
+    rcategories.set_rtag('generated', 'has_text', 'subject')
+    rcategories.set_rtag('metadata', 'owned_by', 'subject')
+    rcategories.set_rtag('metadata', 'created_by', 'subject')
+    rcategories.set_rtag('generated', 'is', 'subject')
+    rcategories.set_rtag('generated', 'is', 'object')
+    rcategories.set_rtag('generated', 'is_instance_of', 'subject')
+    rcategories.set_rtag('generated', 'is_instance_of', 'object')
+    rcategories.set_rtag('generated', 'identity', 'subject')
+    rcategories.set_rtag('generated', 'identity', 'object')
+    rcategories.set_rtag('generated', 'require_permission', 'subject')
+    rcategories.set_rtag('primary', 'in_state', 'subject')
+    rcategories.set_rtag('generated', 'wf_info_for', 'subject')
+    rcategories.set_rtag('generated', 'wf_info_for', 'subject')
+    rcategories.set_rtag('secondary', 'description', 'subject')
+
+    # relations'widget (eg one of available class name in cubicweb.web.formwidgets)
+    rwidgets = RelationTags()
+    # 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
+    rinlined = RelationTags()
+    # set of tags of the form <action>_on_new on relations. <action> is a
+    # schema action (add/update/delete/read), and when such a tag is found
+    # permissions checking is by-passed and supposed to be ok
+    rpermissions_overrides = RelationTags(use_set=True)
+
+    @classmethod
+    def registered(cls, registry):
+        """build class using descriptor at registration time"""
+        super(AutomaticEntityForm, cls).registered(registry)
+        cls.init_rtags_category()
+        return cls
+        
+    @classmethod
+    def init_rtags_category(cls):
+        """set default category tags for relations where it's not yet defined in
+        the category relation tags
+        """
+        for eschema in cls.schema.entities():
+            for rschema, tschemas, role in eschema.relation_definitions(True):
+                for tschema in tschemas:
+                    if role == 'subject':
+                        X, Y = eschema, tschema
+                        card = rschema.rproperty(X, Y, 'cardinality')[0]
+                        composed = rschema.rproperty(X, Y, 'composite') == 'object'
+                    else:
+                        X, Y = tschema, eschema
+                        card = rschema.rproperty(X, Y, 'cardinality')[1]
+                        composed = rschema.rproperty(X, Y, 'composite') == 'subject'
+                    if not cls.rcategories.rtag(rschema, role, X, Y):
+                        if card in '1+':
+                            if not rschema.is_final() and composed:
+                                category = 'generated'
+                            else:
+                                category = 'primary'
+                        elif rschema.is_final():
+                            category = 'secondary'
+                        else: 
+                            category = 'generic'
+                        cls.rcategories.set_rtag(category, rschema, role, X, Y)
+
+    @classmethod
+    def erelations_by_category(cls, entity, categories=None, permission=None):
+        """return a list of (relation schema, target schemas, role) matching
+        categories and permission
+        """
+        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
+        rtags = cls.rcategories
+        permsoverrides = cls.rpermissions_overrides
+        if entity.has_eid():
+            eid = entity.eid
+        else:
+            eid = None
+        for rschema, targetschemas, role in eschema.relation_definitions(True):
+            if rschema in ('identity', 'has_text'):
+                continue
+            # 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_rtag(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_rtags(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 ((eid is None 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 ((eid is None 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):
+        """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):
+            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', eclass=None):
+        try:
+            return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role)
+        except Exception: # XXX should raise more explicit exception
+            if eclass is None:
+                raise
+            field = guess_field(eclass, cls_or_self.schema.rschema(name), role,
+                                eidparam=True)
+            if field is None:
+                raise
+            raise
+            
+    
+    def __init__(self, *args, **kwargs):
+        super(AutomaticEntityForm, self).__init__(*args, **kwargs)
+        if self.edited_entity.has_eid():
+            self.edited_entity.complete()
+        for rschema, role in self.editable_attributes():
+            wdgname = self.rwidgets.etype_rtag(self.edited_entity.id, rschema,
+                                               role)
+            if wdgname:
+                widget = getattr(formwidgets, wdgname)
+                field = guess_field(self.edited_entity.__class__, rschema, role,
+                                    eidparam=True, widget=widget)
+            else:
+                field = guess_field(self.edited_entity.__class__, rschema, role,
+                                    eidparam=True)
+            if field is not None:
+                self.fields.append(field)
+    
+    def relations_by_category(self, categories=None, permission=None):
+        """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)
+    
+    def srelations_by_category(self, categories=None, permission=None):
+        """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)
+        
+    def action(self):
+        """return the form's action attribute. Default to validateform if not
+        explicitly overriden.
+        """
+        try:
+            return self._action
+        except AttributeError:
+            return self.build_url('validateform')
+        
+    def set_action(self, value):
+        """override default action"""
+        self._action = value
+        
+    action = property(action, set_action)
+    
+    def form_buttons(self):
+        """return the form's buttons (as string)"""
+        return [self.button_ok(tabindex=self.req.next_tabindex()),
+                self.button_apply(tabindex=self.req.next_tabindex()),
+                self.button_cancel(tabindex=self.req.next_tabindex())]
+
+    def editable_attributes(self):
+        """return a list of (relation schema, role) to edit for the entity"""
+        return [(rschema, x) for rschema, _, x in self.relations_by_category(
+            self.attrcategories, 'add') if rschema != 'eid']
+        
+    def relations_table(self):
+        """yiels 3-tuples (rtype, target, related_list)
+        where <related_list> itself a list of :
+          - node_id (will be the entity element's DOM id)
+          - appropriate javascript's togglePendingDelete() function call
+          - status 'pendingdelete' or ''
+          - 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'):
+            relatedrset = entity.related(rschema, role, limit=self.limit)
+            if rschema.has_perm(self.req, 'delete'):
+                toggable_rel_link_func = toggable_relation_link
+            else:
+                toggable_rel_link_func = lambda x, y, z: u''
+            related = []
+            for row in xrange(relatedrset.rowcount):
+                nodeid = relation_id(entity.eid, rschema, role,
+                                     relatedrset[row][0])
+                if nodeid in pending_deletes:
+                    status = u'pendingDelete'
+                    label = '+'
+                else:
+                    status = u''
+                    label = 'x'
+                dellink = toggable_rel_link_func(entity.eid, nodeid, label)
+                eview = self.view('oneline', relatedrset, row=row)
+                related.append((nodeid, dellink, status, eview))
+            yield (rschema, role, related)
+            
+    def restore_pending_inserts(self, cell=False):
+        """used to restore edition page as it was before clicking on
+        'search for <some entity type>'
+        """
+        eid = self.edited_entity.eid
+        cell = cell and "div_insert_" or "tr"
+        pending_inserts = set(self.req.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')
+                reid = eidto
+            else:
+                label = display_name(self.req, rtype, 'object')
+                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)
+            # XXX find a clean way to handle baskets
+            if rset.description[0][0] == 'Basket':
+                eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
+            yield rtype, pendingid, jscall, label, reid, eview
+            
+    # should_* method extracted to allow overriding
+    
+    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
+        """
+        return self.rinlined.etype_rtag(self.edited_entity.id, rschema, role, targettype)
+
+    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+'
+
+    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
+        (through ajax call)
+
+        by default true if there is no related entity or if the relation has
+        multiple cardinality
+        """
+        return not existant or card in '+*'
+
+    
+class EditionFormView(EntityView):
+    """display primary entity edition form"""    
+    id = '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()
+
+    title = _('edition')
+    controller = 'edit'
+    
+    def cell_call(self, row, col, **kwargs):
+        entity = self.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.select_object('forms', 'edition', self.req, self.rset,
+                                       row=self.row, col=self.col, entity=entity,
+                                       domid=self.id, submitmsg=self.submited_message())
+        self.init_form(form, entity)
+        self.w(form.form_render(renderer=EntityFormRenderer(), formvid=u'edition'))
+
+    def init_form(self, form, entity):
+        """customize your form before rendering here"""
+        form.form_add_hidden(u'__maineid', entity.eid)
+        
+    def form_title(self, entity):
+        """the form view title"""
+        ptitle = self.req._(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')
+
+        
+class CreationFormView(EditionFormView):
+    """display primary entity creation form"""    
+    id = 'creation'
+    __select__ = specified_etype_implements('Any') & yes()
+    
+    title = _('creation')
+    
+    def call(self, **kwargs):
+        """creation view for an entity"""
+        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:
+            self.initialize_varmaker()
+            entity.eid = self.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):
+                # 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)}
+            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'))
+    
+    def submited_message(self):
+        """return the message that will be displayed on successful edition"""
+        return self.req._('entity created')
+
+
+class CopyFormView(EditionFormView):
+    """display primary entity creation form initialized with values from another
+    entity
+    """    
+    id = 'copy'
+    def render_form(self, entity):
+        """fetch and render the form"""
+        # 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.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
+               % self.req._('Please note that this is only a shallow copy'))
+        super(CopyFormView, self).render_form(entity)
+        del self.newentity
+    
+    def init_form(self, form, entity):
+        """customize your form before rendering here"""
+        super(CopyFormView, self).init_form(form, entity)
+        if entity.eid == self.newentity.eid:
+            form.form_add_hidden('__cloned_eid', self.copying, eidparam=True)
+    
+    def submited_message(self):
+        """return the message that will be displayed on successful edition"""
+        return self.req._('entity copied')
+
+    
+class TableEditForm(CompositeForm):
+    id = 'muledit'
+    onsubmit = "return validateForm('entityForm', null);"
+    
+    def __init__(self, *args, **kwargs):
+        super(TableEditForm, self).__init__(*args, **kwargs)
+        for row in xrange(len(self.rset)):
+            form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                           row=row, domid=self.id,
+                                           attrcategories=('primary',),
+                                           set_error_url=False)
+            # XXX rely on the MultipleEntityFormRenderer to put the eid input
+            form.remove_field(form.field_by_name('eid'))
+            self.form_add_subform(form)
+
+    def form_buttons(self):
+        """return the form's buttons (as string)"""
+        okt = self.req._('validate modifications on selected items').capitalize()
+        resett = self.req._('revert changes').capitalize()
+        return [self.button_ok(title=okt), self.button_reset(title=resett)]
+
+        
+class TableEditFormView(EntityView):
+    id = 'muledit'
+    __select__ = EntityView.__select__ & yes()
+    title = _('multiple edit')
+    
+    def call(self, **kwargs):
+        """a view to edit multiple entities of the same type the first column
+        should be the eid
+        """
+        #self.form_title(entity)
+        form = self.vreg.select_object('forms', self.id, self.req, self.rset,
+                                       domid=self.id)
+        self.w(form.form_render(renderer=EntityCompositeFormRenderer()))
+
+
+class InlineEntityEditionFormView(EntityView):
+    id = 'inline-edition'
+    __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+    removejs = "removeInlinedEntity('%s', '%s', '%s')"
+    
+    def call(self, **kwargs):
+        """redefine default call() method to avoid automatic
+        insertions of <div class="section"> between each row of
+        the resultset
+        """
+        rset = self.rset
+        for i in xrange(len(rset)):
+            self.wview(self.id, rset, row=i, **kwargs)
+
+    def cell_call(self, row, col, peid, rtype, role='subject', **kwargs):
+        """
+        :param peid: the parent entity's eid hosting the inline form
+        :param rtype: the relation bridging `etype` and `peid`
+        :param role: the role played by the `peid` in the relation
+        """
+        entity = self.entity(row, col)
+        divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (peid, rtype,
+                                                                 entity.eid)
+        self.render_form(entity, peid, rtype, role, divonclick=divonclick)
+        
+    def render_form(self, entity, peid, rtype, role, **kwargs):
+        """fetch and render the form"""
+        rschema = self.schema.rschema(rtype)
+        divid = '%s-%s-%s' % (peid, rtype, entity.eid)
+        title = rschema.display_name(self.req, role)
+        form = self.vreg.select_object('forms', 'edition', self.req,
+                                       entity=entity)
+        removejs = self.removejs % (peid, rtype,entity.eid)
+        if self.keep_entity(entity, peid, rtype):
+            if entity.has_eid():
+                rval = entity.eid
+            else:
+                rval = INTERNAL_FIELD_VALUE
+            form.form_add_hidden('edit%s-%s:%s' % (role[0], rtype, peid), rval)
+        form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid,
+                             id='rel-%s-%s-%s'  % (peid, rtype, entity.eid))
+        self.w(form.form_render(renderer=EntityInlinedFormRenderer(), divid=divid,
+                                title=title, removejs=removejs,**kwargs))
+
+    def keep_entity(self, entity, peid, rtype):
+        if not entity.has_eid():
+            return True
+        # are we regenerating form because of a validation error ?
+        erroneous_post = self.req.data.get('formvalues')
+        if erroneous_post:
+            cdvalues = self.req.list_form_param('%s:%s' % (rtype, peid),
+                                                erroneous_post)
+            if unicode(entity.eid) not in cdvalues:
+                return False
+        return True
+
+
+class InlineEntityCreationFormView(InlineEntityEditionFormView):
+    id = 'inline-creation'
+    __select__ = (match_kwargs('peid', 'rtype')
+                  & specified_etype_implements('Any'))
+    
+    def call(self, etype, peid, rtype, role='subject', **kwargs):
+        """
+        :param etype: the entity type being created in the inline form
+        :param peid: the parent entity's eid hosting the inline form
+        :param rtype: the relation bridging `etype` and `peid`
+        :param role: the role played by the `peid` in the relation
+        """
+        try:
+            entity = self.vreg.etype_class(etype)(self.req, None, None)
+        except:
+            self.w(self.req._('no such entity type %s') % etype)
+            return
+        self.initialize_varmaker()
+        entity.eid = self.varmaker.next()
+        self.render_form(entity, peid, rtype, role)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editviews.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,211 @@
+"""Some views used to help to the edition process
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from simplejson import dumps
+
+from logilab.common.decorators import cached
+from logilab.mtconverter import html_escape
+
+from cubicweb import typed_eid
+from cubicweb.view import EntityView
+from cubicweb.selectors import (one_line_rset, non_final_entity,
+                                match_search_state, match_form_params)
+from cubicweb.common.uilib import cut
+from cubicweb.web.views import linksearch_select_url
+from cubicweb.web.views.editforms import relation_id
+from cubicweb.web.views.baseviews import FinalView
+
+_ = unicode
+
+class SearchForAssociationView(EntityView):
+    """view called by the edition view when the user asks to search for
+    something to link to the edited eid
+    """
+    id = 'search-associate'
+    __select__ = (one_line_rset() & match_search_state('linksearch')
+                  & non_final_entity())
+    
+    title = _('search for association')
+
+    def cell_call(self, row, col):
+        rset, vid, divid, paginate = self.filter_box_context_info()
+        self.rset = rset
+        self.w(u'<div id="%s">' % divid)
+        self.paginate(self.req, rset, w=self.w)
+        self.wview(vid, rset, 'noresult')
+        self.w(u'</div>')
+
+    @cached
+    def filter_box_context_info(self):
+        entity = self.entity(0, 0)
+        role, eid, rtype, etype = self.req.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
+        # since the latter is mainly there to select relevant items in the combo
+        # box, it doesn't give interesting result in this context
+        rql = entity.unrelated_rql(rtype, etype, role,
+                                   ordermethod='fetch_order',
+                                   vocabconstraints=False)
+        rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
+        return rset, 'list', "search-associate-content", True
+
+
+class OutOfContextSearch(EntityView):
+    id = 'outofcontext-search'
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+        erset = entity.as_rset()
+        if self.req.match_search_state(erset):
+            self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
+                html_escape(linksearch_select_url(self.req, erset)),
+                self.req._('select this entity'),
+                html_escape(entity.view('textoutofcontext')),
+                html_escape(entity.absolute_url(vid='primary')),
+                self.req._('view detail for this entity')))
+        else:
+            entity.view('outofcontext', w=self.w)
+
+        
+class UnrelatedDivs(EntityView):
+    id = 'unrelateddivs'
+    __select__ = match_form_params('relation')
+
+    @property
+    def limit(self):
+        if self.req.form.get('__force_display'):
+            return None
+        return self.req.property_value('navigation.related-limit') + 1
+
+    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
+        self.w(self.build_unrelated_select_div(entity, rschema, target,
+                                               is_cell=is_cell, hidden=hidden))
+
+    def build_unrelated_select_div(self, entity, rschema, target,
+                                   is_cell=False, hidden=True):
+        options = []
+        divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
+        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))
+        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))
+        options += self._get_select_options(entity, rschema, target)
+        options += self._get_search_options(entity, rschema, target, targettypes)
+        if 'Basket' in self.schema: # XXX
+            options += self._get_basket_options(entity, rschema, target, targettypes)
+        relname, target = self.req.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');">
+    %s
+  </select>
+</div>
+""" % (hidden and 'hidden' or '', divid, selectid,
+       html_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+       '\n'.join(options))
+
+    def _get_select_options(self, entity, rschema, target):
+        """add options to search among all entities of each possible type"""
+        options = []
+        eid = entity.eid
+        pending_inserts = self.req.get_pending_inserts(eid)
+        rtype = rschema.type
+        form = self.vreg.select_object('forms', 'edition', self.req,
+                                       entity=entity)
+        field = form.field_by_name(rschema, target, entity.__class__)
+        for eview, reid in form.form_field_vocabulary(field, self.limit):
+            if reid is None:
+                options.append('<option class="separator">-- %s --</option>'
+                               % html_escape(eview))
+            else:
+                optionid = relation_id(eid, rtype, target, reid)
+                if optionid not in pending_inserts:
+                    # prefix option's id with letters to make valid XHTML wise
+                    options.append('<option id="id%s" value="%s">%s</option>' %
+                                   (optionid, reid, html_escape(eview)))
+        return options
+
+    def _get_search_options(self, entity, rschema, target, targettypes):
+        """add options to search among all entities of each possible type"""
+        options = []
+        _ = self.req._
+        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',
+                                 __mode=mode)
+            options.append((eschema.display_name(self.req),
+                            '<option value="%s">%s %s</option>' % (
+                html_escape(url), _('Search for'), eschema.display_name(self.req))))
+        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,
+                                                            target, targettypes):
+            optionid = relation_id(entity.eid, rtype, target, basketeid)
+            options.append('<option id="%s" value="%s">%s %s</option>' % (
+                optionid, basketeid, _('link to each item in'), html_escape(basketname)))
+        return options
+
+    def _get_basket_links(self, ueid, target, targettypes):
+        targettypes = set(targettypes)
+        for basketeid, basketname, elements in self._get_basket_info(ueid):
+            baskettypes = elements.column_types(0)
+            # if every elements in the basket can be attached to the
+            # edited entity
+            if baskettypes & targettypes:
+                yield basketeid, basketname
+            
+    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')
+        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')
+            basketref.append((result[0], result[1], rset))
+        return basketref
+
+
+class ComboboxView(EntityView):
+    """the view used in combobox (unrelated entities)
+
+    THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
+    """
+    id = '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)
+        
+
+class EditableFinalView(FinalView):
+    """same as FinalView but enables inplace-edition when possible"""
+    id = 'editable-final'
+                
+    def cell_call(self, row, col, props=None, displaytime=False):
+        entity, rtype = self.rset.related_entity(row, col)
+        if entity is not None:
+            self.w(entity.view('reledit', rtype=rtype))
+        else:
+            super(EditableFinalView, self).cell_call(row, col, props, displaytime)
--- a/web/views/emailaddress.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/emailaddress.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,18 +1,21 @@
 """Specific views for email addresses entities
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import html_escape
 
+from cubicweb.selectors import implements
 from cubicweb.common import Unauthorized
 from cubicweb.web.views import baseviews
+from cubicweb.web.views.editforms import AutomaticEntityForm
+
 
 class EmailAddressPrimaryView(baseviews.PrimaryView):
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     
     def cell_call(self, row, col, skipeids=None):
         self.skipeids = skipeids
@@ -59,7 +62,7 @@
 
 
 class EmailAddressShortPrimaryView(EmailAddressPrimaryView):
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     id = 'shortprimary'
     title = None # hidden view
     def render_entity_attributes(self, entity, siderelations):
@@ -69,7 +72,7 @@
 
     
 class EmailAddressOneLineView(baseviews.OneLineView):
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
@@ -89,7 +92,7 @@
     'mailto:'"""
 
     id = 'mailto'
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
@@ -113,7 +116,7 @@
 
     
 class EmailAddressTextView(baseviews.TextView):
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     
     def cell_call(self, row, col, **kwargs):
         self.w(self.entity(row, col).display_address())
--- a/web/views/embedding.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/embedding.py	Tue Apr 07 09:30:23 2009 +0200
@@ -3,7 +3,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -15,11 +15,11 @@
 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
+from cubicweb.view import NOINDEX, NOFOLLOW
 from cubicweb.common.uilib import soup2xhtml
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
-                                    match_search_state, implement_interface)
-from cubicweb.common.view import NOINDEX, NOFOLLOW
 from cubicweb.web.controller import Controller
 from cubicweb.web.action import Action
 from cubicweb.web.views import basetemplates
@@ -33,7 +33,6 @@
     def call(self, body):
         # XXX fallback to HTML 4 mode when embeding ?
         self.set_request_content_type()
-        self.process_rql(self.req.form.get('rql'))
         self.req.search_state = ('normal',)
         self.template_header(self.content_type, None, self.req._('external page'),
                              [NOINDEX, NOFOLLOW])
@@ -72,7 +71,19 @@
             except HTTPError, err:
                 body = '<h2>%s</h2><h3>%s</h3>' % (
                     _('error while embedding page'), err)
-        return self.vreg.main_template(req, self.template, body=body)
+        self.process_rql(req.form.get('rql'))
+        return self.vreg.main_template(req, self.template, rset=self.rset, body=body)
+
+
+def entity_has_embedable_url(entity):
+    """return 1 if the entity provides an allowed embedable url"""
+    url = entity.embeded_url()
+    if not url or not url.strip():
+        return 0
+    allowed = entity.config['embed-allowed']
+    if allowed is None or not allowed.match(url):
+        return 0
+    return 1
 
 
 class EmbedAction(Action):
@@ -80,25 +91,12 @@
     if the returned url match embeding configuration
     """
     id = 'embed'
-    controller = 'embed'
-    __selectors__ = (one_line_rset, match_search_state,
-                     implement_interface, score_entity_selector)
-    accepts_interfaces = (IEmbedable,)
+    __select__ = (one_line_rset() & match_search_state('normal')
+                  & implements(IEmbedable) 
+                  & score_entity(entity_has_embedable_url))
     
     title = _('embed')
-        
-    @classmethod
-    def score_entity(cls, entity):
-        """return a score telling how well I can display the given 
-        entity instance (required by the value_selector)
-        """
-        url = entity.embeded_url()
-        if not url or not url.strip():
-            return 0
-        allowed = cls.config['embed-allowed']
-        if allowed is None or not allowed.match(url):
-            return 0
-        return 1
+    controller = 'embed'
     
     def url(self, row=0):
         entity = self.rset.get_entity(row, 0)
@@ -132,6 +130,7 @@
                 url = '%s?custom_css=%s' % (url, self.custom_css)
         return '<a href="%s"' % url
 
+
 class absolutize_links:
     def __init__(self, embedded_url, tag, custom_css=None):
         self.embedded_url = embedded_url
@@ -152,12 +151,13 @@
     for rgx, repl in filters:
         body = rgx.sub(repl, body)
     return body
-    
+
+
 def embed_external_page(url, prefix, headers=None, custom_css=None):
     req = Request(url, headers=(headers or {}))
     content = urlopen(req).read()
     page_source = unicode(content, guess_encoding(content), 'replace')
-    page_source =page_source
+    page_source = page_source
     match = BODY_RGX.search(page_source)
     if match is None:
         return page_source
--- a/web/views/eproperties.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/eproperties.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,14 +1,256 @@
 """Specific views for EProperty
 
-
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.mtconverter import html_escape
+
+from logilab.common.decorators import cached
+
+from cubicweb.selectors import (one_line_rset, none_rset, implements,
+                                match_user_groups)
+from cubicweb.utils import UStringIO
+from cubicweb.view import StartupView
+from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
 from cubicweb.web.views import baseviews
+from cubicweb.web.form import FormMixIn
+from cubicweb.web.views.editforms import AutomaticEntityForm
+
+AutomaticEntityForm.rwidgets.set_rtag('PropertyKeyWidget', 'pkey', 'subject', 'EProperty')
+AutomaticEntityForm.rwidgets.set_rtag('PropertyValueWidget', 'value', 'subject', 'EProperty')
+
+_ = unicode
+
+# some string we want to be internationalizable for nicer display of eproperty
+# groups
+_('navigation')
+_('ui')
+_('actions')
+_('boxes')
+_('components')
+_('contentnavigation')
 
 class EPropertyPrimaryView(baseviews.PrimaryView):
-    accepts = ('EProperty',)
+    __select__ = implements('EProperty')
     skip_none = False
+
+
+def make_togglable_link(nodeid, label, cookiename):
+    """builds a HTML link that switches the visibility & remembers it"""
+    action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \
+        (nodeid, cookiename)
+    return u'<a href="%s">%s</a>' % (action, label)
+
+def css_class(someclass):
+    return someclass and 'class="%s"' % someclass or ''
+
+class SystemEPropertiesForm(FormMixIn, StartupView):
+    id = 'systemepropertiesform'
+    __select__ = none_rset & match_user_groups('managers')
+
+    title = _('site configuration')
+    controller = 'edit'
+    category = 'startupview'
+
+    def linkable(self):
+        return True
+
+    def url(self):
+        """return the url associated with this view. We can omit rql here"""
+        return self.build_url('view', vid=self.id)
+
+    def _cookie_name(self, somestr):
+        return str('%s_property_%s' % (self.config.appid, somestr))
+
+    def _group_status(self, group, default=u'hidden'):
+        cookies = self.req.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)
+            status = default
+        else:
+            status = cookie.value
+        return status
+
+    def call(self, **kwargs):
+        """The default view representing the application's index"""
+        self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js'))
+        self.req.add_css('cubicweb.preferences.css')
+        vreg = self.vreg
+        values = self.defined_keys
+        groupedopts = {}
+        mainopts = {}
+        # "self.id=='systemepropertiesform'" to skip site wide properties on
+        # user's preference but not site's configuration
+        for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
+            parts = key.split('.')
+            if parts[0] in vreg:
+                # appobject configuration
+                reg, oid, propid = parts
+                groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key)
+            else:
+                mainopts.setdefault(parts[0], []).append(key)
+        # precompute form to consume error message
+        for group, keys in mainopts.items():
+            mainopts[group] = self.form(keys, False)
+        for group, objects in groupedopts.items():
+            for oid, keys in objects.items():
+                groupedopts[group][oid] = self.form(keys, True)
+
+        w = self.w
+        req = self.req
+        _ = req._
+        w(u'<h1>%s</h1>\n' % _(self.title))
+        w(self.error_message())
+        for label, group, form in sorted((_(g), g, f)
+                                         for g, f in mainopts.iteritems()):
+            status = css_class(self._group_status(group)) #'hidden' (collapsed), or '' (open) ?
+            w(u'<h2 class="propertiesform">%s</h2>\n' %
+              (make_togglable_link('fieldset_' + group, label,
+                                   self._cookie_name(group))))
+            w(u'<div id="fieldset_%s" %s>' % (group, status))
+            w(u'<fieldset class="subentity">')
+            w(form)
+            w(u'</fieldset></div>')
+        for label, group, objects in sorted((_(g), g, o)
+                                            for g, o in groupedopts.iteritems()):
+            status = css_class(self._group_status(group))
+            w(u'<h2 class="propertiesform">%s</h2>\n' %
+              (make_togglable_link('fieldset_' + group, label,
+                                   self._cookie_name(group))))
+            w(u'<div id="fieldset_%s" %s>' % (group, status))
+            for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
+                                           for o, f in objects.iteritems()):
+                w(u'<fieldset class="subentity">')
+                w(u'<legend class="componentTitle">%s</legend>\n' % label)
+                docmsgid = '%s_%s_description' % (group, oid)
+                doc = _(docmsgid)
+                if doc != docmsgid:
+                    w(u'<p class="description">%s</p>' % html_escape(doc))
+                w(form)
+                w(u'</fieldset>')
+            w(u'</div>')
+
+    @property
+    @cached
+    def eprops_rset(self):
+        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U')
+
+    @property
+    def defined_keys(self):
+        values = {}
+        for i, entity in enumerate(self.eprops_rset.entities()):
+            values[entity.pkey] = i
+        return values
+
+    def entity_for_key(self, key):
+        values = self.defined_keys
+        if key in values:
+            entity = self.eprops_rset.get_entity(values[key], 0)
+        else:
+            entity = self.vreg.etype_class('EProperty')(self.req, None, None)
+            entity.eid = self.req.varmaker.next()
+            entity['value'] = self.vreg.property_value(key)
+        return entity
+
+    def form(self, keys, splitlabel=False):
+        stream = UStringIO()
+        w = stream.write
+        w(u'<form action="%s" method="post">\n' % self.build_url())
+        w(u'<fieldset>\n')
+        w(u'<input type="hidden" name="__errorurl" value="%s"/>\n'
+          % html_escape(self.req.url()))
+        w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
+        path = self.req.relative_path()
+        if '?' in path:
+            path, params = path.split('?', 1)
+            w(u'<input type="hidden" name="__redirectparams" value="%s"/>\n'
+              % html_escape(params))
+        w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % path)
+        #w(u'<input type="hidden" name="__redirectrql" value=""/>\n')
+        w(u'<input type="hidden" name="__message" value="%s"/>\n'
+          % self.req._('changes applied'))
+        w(u'<table><tr><td>\n')
+
+        w(u'<table>\n')
+        for key in keys:
+            w(u'<tr>\n')
+            self.form_row(w, key, splitlabel)
+            w(u'</tr>\n')
+        w(u'</table>\n')
+        w(u'</td></tr><tr><td>\n')
+        w(self.button_ok())
+        w(self.button_cancel())
+        w(u'</td></tr></table>\n')
+        w(u'</fieldset>\n')
+        w(u'</form>\n')
+        return stream.getvalue()
+
+    def form_row(self, w, key, splitlabel):
+        entity = self.entity_for_key(key)
+        if splitlabel:
+            w(u'<td class="label">%s</td>' % self.req._(key.split('.')[-1]))
+        else:
+            w(u'<td class="label">%s</td>' % self.req._(key))
+        wdg = self.vreg.property_value_widget(key, req=self.req)
+        error = wdg.render_error(entity)
+        w(u'<td class="%s">' % (error and 'error' or ''))
+        w(error)
+        self.form_row_hiddens(w, entity, key)
+        w(wdg.edit_render(entity))
+        w(u'</td>\n')
+        w(u'<td>%s</td>' % wdg.render_help(entity))
+        return entity
+
+    def form_row_hiddens(self, w, entity, key):
+        eid = entity.eid
+        w(u'<input type="hidden" name="eid" value="%s"/>' % eid)
+        w(u'<input type="hidden" name="%s" value="EProperty"/>' % eid_param('__type', eid))
+        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('pkey', eid), key))
+        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
+
+
+
+def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
+    return req.user.eid == rset[row or 0][col]
+
+
+class EPropertiesForm(SystemEPropertiesForm):
+    id = 'epropertiesform'
+    __select__ = (
+        # we don't want guests to be able to come here
+        match_user_groups('users', 'managers') &
+        (none_rset | ((one_line_rset() & is_user_prefs) &
+                      (one_line_rset() & match_user_groups('managers'))))
+        )
+    
+    title = _('preferences')
+
+    @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)
+
+    @property
+    @cached
+    def eprops_rset(self):
+        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,'
+                                'P for_user U, U eid %(x)s', {'x': self.user.eid})
+
+    def form_row_hiddens(self, w, entity, key):
+        super(EPropertiesForm, self).form_row_hiddens(w, entity, key)
+        # if user is in the managers group and the property is being created,
+        # we have to set for_user explicitly
+        if not entity.has_eid() and self.user.matching_groups('managers'):
+            eid = entity.eid
+            w(u'<input type="hidden" name="%s" value="%s"/>'
+              % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE))
+            w(u'<input type="hidden" name="%s" value="%s"/>'
+              % (eid_param('for_user', eid), self.user.eid))
+
--- a/web/views/error.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/error.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,12 +2,12 @@
 as startup views and are used for standard error pages (404, 500, etc.)
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
 
 class FourOhFour(StartupView):
     id = '404'
--- a/web/views/euser.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/euser.py	Tue Apr 07 09:30:23 2009 +0200
@@ -10,12 +10,47 @@
 from logilab.mtconverter import html_escape
 
 from cubicweb.schema import display_name
+from cubicweb.selectors import one_line_rset, implements, match_user_groups
+from cubicweb.view import EntityView
 from cubicweb.web import INTERNAL_FIELD_VALUE
-from cubicweb.web.form import EntityForm
+from cubicweb.web.form import FormMixIn
+from cubicweb.web.action import Action
 from cubicweb.web.views.baseviews import PrimaryView, EntityView
+from cubicweb.web.views.editforms import AutomaticEntityForm
+from cubicweb.web.views.boxes import EditBox
+
+
+AutomaticEntityForm.rcategories.set_rtag('secondary', 'firstname', 'subject', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('secondary', 'surname', 'subject', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('metadata', 'last_login_time', 'subject', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('primary', 'in_group', 'subject', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('generated', 'owned_by', 'object', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('metadata', 'created_by', 'object', 'EUser')
+AutomaticEntityForm.rcategories.set_rtag('metadata', 'bookmarked_by', 'object', 'EUser')
+AutomaticEntityForm.rinlined.set_rtag(True, 'use_email', 'subject', 'EUser')
+
+EditBox.rmode.set_rtag('create', 'in_group', 'subject', 'EGroup')
+EditBox.rmode.set_rtag('link', 'owned_by', 'object', 'EUser')
+EditBox.rmode.set_rtag('link', 'created_by', 'object', 'EUser')
+EditBox.rmode.set_rtag('create', 'bookmarked_by', 'object', 'EUser')
+    
+
+class UserPreferencesEntityAction(Action):
+    id = 'prefs'
+    __select__ = (one_line_rset() & implements('EUser') &
+                  match_user_groups('owners', 'managers'))
+    
+    title = _('preferences')
+    category = 'mainactions'
+    
+    def url(self):
+        login = self.rset.get_entity(self.row or 0, self.col or 0).login
+        return self.build_url('euser/%s'%login, vid='epropertiesform')
+
 
 class EUserPrimaryView(PrimaryView):
-    accepts = ('EUser',)
+    __select__ = implements('EUser')
+    
     skip_attrs = ('firstname', 'surname')
     
     def iter_relations(self, entity):
@@ -34,7 +69,8 @@
                                  ]
 class FoafView(EntityView):
     id = 'foaf'
-    accepts = ('EUser',)
+    __select__ = implements('EUser')
+    
     title = _('foaf')
     templatable = False
     content_type = 'text/xml'
@@ -54,7 +90,6 @@
                       <foaf:maker rdf:resource="%s"/>
                       <foaf:primaryTopic rdf:resource="%s"/>
                    </foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
-                      
         self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
         self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
         if entity.surname:
@@ -67,12 +102,13 @@
         if emailaddr:
             self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
         self.w(u'</foaf:Person>\n')
-                   
-class EditGroups(EntityForm):
+
+
+class EditGroups(FormMixIn, EntityView):
     """displays a simple euser / egroups editable table"""
     
     id = 'editgroups'
-    accepts = ('EUser',)
+    __select__ = implements('EUser')
     
     def call(self):
         self.req.add_css('cubicweb.acl.css')            
--- a/web/views/facets.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/facets.py	Tue Apr 07 09:30:23 2009 +0200
@@ -10,13 +10,14 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.selectors import (chainfirst, chainall, non_final_entity,
-                                    two_lines_rset, match_context_prop,
-                                    yes, one_has_relation)
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import (non_final_entity, two_lines_rset,
+                                match_context_prop, yes, relation_possible)
 from cubicweb.web.box import BoxTemplate
-from cubicweb.web.facet import (AbstractFacet, VocabularyFacet, FacetStringWidget,
-                             RelationFacet, prepare_facets_rqlst, filter_hiddens)
+from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
+                                prepare_facets_rqlst, filter_hiddens)
 
+@objectify_selector
 def contextview_selector(cls, req, rset, row=None, col=None, view=None,
                          **kwargs):
     if view and getattr(view, 'filter_box_context_info', lambda: None)():
@@ -27,9 +28,9 @@
 class FilterBox(BoxTemplate):
     """filter results of a query"""
     id = 'filter_box'
-    __selectors__ = (chainfirst(contextview_selector,
-                                chainall(non_final_entity, two_lines_rset)),
-                     match_context_prop)
+    __select__ = (((non_final_entity() & two_lines_rset())
+                   | contextview_selector()
+                   ) & match_context_prop())
     context = 'left'
     title = _('boxes_filter_box')
     visible = True # functionality provided by the search box by default
@@ -58,7 +59,7 @@
         req.add_css('cubicweb.facets.css')
         if self.roundcorners:
             req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");')
-        rset, vid, divid, paginate=self._get_context(view)
+        rset, vid, divid, paginate = self._get_context(view)
         if rset.rowcount < 2: # XXX done by selectors, though maybe necessary when rset has been hijacked
             return
         if vid is None:
@@ -129,7 +130,7 @@
 # inherit from RelationFacet to benefit from its possible_values implementation
 class ETypeFacet(RelationFacet):
     id = 'etype-facet'
-    __selectors__ = (yes,)
+    __select__ = yes()
     order = 1
     rtype = 'is'
     target_attr = 'name'
@@ -153,7 +154,7 @@
 
 
 class HasTextFacet(AbstractFacet):
-    __selectors__ = (one_has_relation, match_context_prop)
+    __select__ = relation_possible('has_text', 'subject') & match_context_prop()
     id = 'has_text-facet'
     rtype = 'has_text'
     role = 'subject'
--- a/web/views/ibreadcrumbs.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/ibreadcrumbs.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,20 +1,19 @@
 """navigation components definition for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import html_escape
 
+# don't use AnyEntity since this may cause bug with isinstance() due to reloading
 from cubicweb.interfaces import IBreadCrumbs
-from cubicweb.common.selectors import (match_context_prop, one_line_rset, 
-                                    implement_interface)
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import match_context_prop, one_line_rset, implements
+from cubicweb.entity import Entity
+from cubicweb.view import EntityView
 from cubicweb.common.uilib import cut
-# don't use AnyEntity since this may cause bug with isinstance() due to reloading
-from cubicweb.common.entity import Entity
 from cubicweb.web.component import EntityVComponent
 
 _ = unicode
@@ -29,8 +28,7 @@
     # register msg not generated since no entity implements IPrevNext in cubicweb itself
     title = _('contentnavigation_breadcrumbs')
     help = _('contentnavigation_breadcrumbs_description')
-    __selectors__ = (one_line_rset, match_context_prop, implement_interface)
-    accepts_interfaces = (IBreadCrumbs,)
+    __select__ = (one_line_rset() & match_context_prop() & implements(IBreadCrumbs))
     context = 'navtop'
     order = 5
     visible = False
@@ -73,7 +71,7 @@
 
 class BreadCrumbComponent(BreadCrumbEntityVComponent):
     __registry__ = 'components'
-    __selectors__ = (one_line_rset, implement_interface)
+    __select__ = (one_line_rset() & implements(IBreadCrumbs))
     visible = True
 
 
--- a/web/views/idownloadable.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/idownloadable.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,22 +1,28 @@
 """Specific views for entities implementing IDownloadable
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape
 
+from cubicweb.selectors import (one_line_rset, score_entity,
+                                implements, match_context_prop)
 from cubicweb.interfaces import IDownloadable
 from cubicweb.common.mttransforms import ENGINE
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
-                                       implement_interface, match_context_prop)
 from cubicweb.web.box import EntityBoxTemplate
 from cubicweb.web.views import baseviews
 
 _ = unicode
 
+def is_image(entity):
+    mt = entity.download_content_type()
+    if not (mt and mt.startswith('image/')):
+        return 0
+    return 1
+
 def download_box(w, entity, title=None, label=None):
     req = entity.req
     w(u'<div class="sideRelated">')
@@ -32,21 +38,13 @@
     w(u'</div>')
     w(u'</div>\n</div>\n')
 
-    
+
 class DownloadBox(EntityBoxTemplate):
     id = 'download_box'
+    # no download box for images
     # XXX primary_view selector ?
-    __selectors__ = (one_line_rset, implement_interface, match_context_prop, score_entity_selector)
-    accepts_interfaces = (IDownloadable,)
+    __select__ = (one_line_rset() & implements(IDownloadable) & match_context_prop() & ~score_entity(is_image))
     order = 10
-
-    @classmethod
-    def score_entity(cls, entity):
-        mt = entity.download_content_type()
-        # no download box for images
-        if mt and mt.startswith('image/'):
-            return 0
-        return 1
     
     def cell_call(self, row, col, title=None, label=None, **kwargs):
         entity = self.entity(row, col)
@@ -54,12 +52,11 @@
 
 
 class DownloadView(baseviews.EntityView):
-    """this view is replacing the deprecated 'download' controller and allow downloading
-    of entities providing the necessary interface
+    """this view is replacing the deprecated 'download' controller and allow
+    downloading of entities providing the necessary interface
     """
     id = 'download'
-    __selectors__ = (one_line_rset, implement_interface)
-    accepts_interfaces = (IDownloadable,)
+    __select__ = one_line_rset() & implements(IDownloadable)
 
     templatable = False
     content_type = 'application/octet-stream'
@@ -86,10 +83,9 @@
 class DownloadLinkView(baseviews.EntityView):
     """view displaying a link to download the file"""
     id = 'downloadlink'
+    __select__ = implements(IDownloadable)
     title = None # should not be listed in possible views
-    __selectors__ = (implement_interface,)
 
-    accepts_interfaces = (IDownloadable,)
     
     def cell_call(self, row, col, title=None, **kwargs):
         entity = self.entity(row, col)
@@ -99,8 +95,7 @@
 
                                                                                 
 class IDownloadablePrimaryView(baseviews.PrimaryView):
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (IDownloadable,)
+    __select__ = implements(IDownloadable)
     # XXX File/Image attributes but this is not specified in the IDownloadable interface
     skip_attrs = baseviews.PrimaryView.skip_attrs + ('data', 'name')
 
@@ -133,10 +128,7 @@
 
 
 class IDownloadableLineView(baseviews.OneLineView):
-    __selectors__ = (implement_interface,)
-    # don't kick default oneline view
-    accepts_interfaces = (IDownloadable,)
-    
+    __select__ = implements(IDownloadable)
 
     def cell_call(self, row, col, title=None, **kwargs):
         """the secondary view is a link to download the file"""
@@ -149,10 +141,10 @@
 
 
 class ImageView(baseviews.EntityView):
-    __selectors__ = (implement_interface, score_entity_selector)
     id = 'image'
+    __select__ = implements(IDownloadable) & score_entity(is_image)
+    
     title = _('image')
-    accepts_interfaces = (IDownloadable,)
     
     def call(self):
         rset = self.rset
@@ -160,13 +152,6 @@
             self.w(u'<div class="efile">')
             self.wview(self.id, rset, row=i, col=0)
             self.w(u'</div>')
-
-    @classmethod
-    def score_entity(cls, entity):
-        mt = entity.download_content_type()
-        if not (mt and mt.startswith('image/')):
-            return 0
-        return 1
     
     def cell_call(self, row, col):
         entity = self.entity(row, col)
--- a/web/views/igeocodable.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/igeocodable.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,10 +1,16 @@
-# -*- coding: utf-8 -*-
+"""Specific views for entities implementing IGeocodable
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
 
 import simplejson
 
 from cubicweb.interfaces import IGeocodable
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface
+from cubicweb.view import EntityView
+from cubicweb.selectors import implements
 
 class GeocodingJsonView(EntityView):
     id = 'geocoding-json'
@@ -12,8 +18,7 @@
     templatable = False
     content_type = 'application/json'
 
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (IGeocodable,)
+    __select__ = implements(IGeocodable)
 
     def call(self):
         zoomlevel = self.req.form.pop('zoomlevel', 8)
@@ -46,8 +51,7 @@
 class GoogleMapBubbleView(EntityView):
     id = 'gmap-bubble'
 
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (IGeocodable,)
+    __select__ = implements(IGeocodable)
 
     def cell_call(self, row, col):
         entity = self.entity(row, col)
@@ -58,13 +62,12 @@
 class GoogleMapsView(EntityView):
     id = 'gmap-view'
 
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (IGeocodable,)
+    __select__ = implements(IGeocodable)
     need_navigation = False
 
     def call(self, gmap_key, width=400, height=400, uselabel=True, urlparams=None):
         self.req.add_js('http://maps.google.com/maps?file=api&amp;v=2&amp;key=%s' % gmap_key,
-                        localfile=False);
+                        localfile=False)
         self.req.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
         rql = self.rset.printable_rql()
         if urlparams is None:
--- a/web/views/iprogress.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/iprogress.py	Tue Apr 07 09:30:23 2009 +0200
@@ -9,10 +9,10 @@
 
 from logilab.mtconverter import html_escape
 
+from cubicweb.selectors import implements
 from cubicweb.interfaces import IProgress, IMileStone
 from cubicweb.schema import display_name
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface, accept
+from cubicweb.view import EntityView
 from cubicweb.web.htmlwidgets import ProgressBarWidget
 
 
@@ -35,9 +35,7 @@
     
     id = 'progress_table_view'
     title = _('task progression')
-    __selectors__ = (accept, implement_interface)
-
-    accepts_interfaces = (IMileStone,)
+    __select__ = implements(IMileStone)
 
     # default columns of the table
     columns = (_('project'), _('milestone'), _('state'), _('eta_date'),
@@ -182,9 +180,7 @@
     """displays a progress bar"""
     id = 'progressbar'
     title = _('progress bar')
-    __selectors__ = (accept, implement_interface)
-
-    accepts_interfaces = (IProgress,)
+    __select__ = implements(IProgress)
 
     def cell_call(self, row, col):
         self.req.add_css('cubicweb.iprogress.css')
--- a/web/views/magicsearch.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/magicsearch.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,7 +2,7 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -15,7 +15,7 @@
 from rql.nodes import Relation
 
 from cubicweb import Unauthorized
-from cubicweb.common.appobject import Component, SingletonComponent
+from cubicweb.view import Component
 
 LOGGER = getLogger('cubicweb.magicsearch')
 
@@ -187,7 +187,7 @@
     priority = 4
     
     def preprocess_query(self, uquery, req):
-        """"""
+        """try to get rql from an unicode query string"""
         args = None
         self.req = req
         try:
@@ -349,7 +349,7 @@
 
 
 
-class MagicSearchComponent(SingletonComponent):
+class MagicSearchComponent(Component):
     id  = 'magicsearch'
     def __init__(self, req, rset=None):
         super(MagicSearchComponent, self).__init__(req, rset)
@@ -392,33 +392,3 @@
             # let exception propagate
             return proc.process_query(uquery, req)
         raise BadRQLQuery(req._('sorry, the server is unable to handle this query'))
-
-
-# Do not make a strong dependency on NlpTools
-try:
-    from NlpTools.rqltools.client import RQLClient
-except ImportError:
-    LOGGER.info('could not import RQLClient (NlpTools)')
-else:
-    try:
-        from Pyro.errors import NamingError
-    except ImportError:
-        LOGGER.warning("pyro is not installed, can't try to connect to nlp server")
-    else:
-        try:
-            class NLPProcessor(BaseQueryProcessor):
-                priority = 8
-                nlp_agent = RQLClient('ivan')
-                def preprocess_query(self, uquery, req):
-                    try:
-                        answer = self.nlp_agent.get_translation(uquery)
-                        if not answer:
-                            raise BadRQLQuery(uquery)
-                        return answer or uquery,
-                    except Exception, ex:
-                        LOGGER.exception(str(ex))
-                        return uquery,
-
-        except NamingError: # NlpTools available but no server registered
-            LOGGER.warning('could not find any RQLServer object named "ivan"')
-
--- a/web/views/management.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/management.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,24 +2,18 @@
 
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import html_escape
 
-from logilab.common.decorators import cached
-
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.view import AnyRsetView, StartupView, EntityView
+from cubicweb.selectors import yes, none_rset, match_user_groups
+from cubicweb.view import AnyRsetView, StartupView, EntityView
 from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.common.selectors import (yes, one_line_rset,
-                                       accept_rset, none_rset,
-                                       chainfirst, chainall)
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
 from cubicweb.web.widgets import StaticComboBoxWidget
-from cubicweb.web.form import FormMixIn
 
 _ = unicode
 
@@ -151,8 +145,8 @@
         newperm.eid = self.req.varmaker.next()
         w(u'<p>%s</p>' % _('add a new permission'))
         begin_form(w, newperm, 'security', entity.rest_path())
-        w(u'<input type="hidden" name="%s" value="__cubicweb_internal_field__"/>'
-          % eid_param('edito-require_permission', newperm.eid))
+        w(u'<input type="hidden" name="%s" value="%s"/>'
+          % (eid_param('edito-require_permission', newperm.eid), INTERNAL_FIELD_VALUE))
         w(u'<input type="hidden" name="%s" value="%s"/>'
           % (eid_param('require_permission', newperm.eid), entity.eid))
         w(u'<table border="0">\n')
@@ -180,7 +174,7 @@
 
 class ErrorView(AnyRsetView):
     """default view when no result has been found"""
-    __selectors__ = (yes,)
+    __select__ = yes()
     id = 'error'
 
     def page_title(self):
@@ -274,242 +268,11 @@
     binfo += '\n'
     return binfo
 
-# some string we want to be internationalizable for nicer display of eproperty
-# groups
-_('navigation')
-_('ui')
-_('actions')
-_('boxes')
-_('components')
-_('contentnavigation')
-
-
-def make_togglable_link(nodeid, label, cookiename):
-    """builds a HTML link that switches the visibility & remembers it"""
-    action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \
-        (nodeid, cookiename)
-    return u'<a href="%s">%s</a>' % (action, label)
-
-def css_class(someclass):
-    return someclass and 'class="%s"' % someclass or ''
-
-class SystemEpropertiesForm(FormMixIn, StartupView):
-    controller = 'edit'
-    id = 'systemepropertiesform'
-    title = _('site configuration')
-    require_groups = ('managers',)
-    category = 'startupview'
-
-    def linkable(self):
-        return True
-
-    def url(self):
-        """return the url associated with this view. We can omit rql here"""
-        return self.build_url('view', vid=self.id)
-
-    def _cookie_name(self, somestr):
-        return str('%s_property_%s' % (self.config.appid, somestr))
-
-    def _group_status(self, group, default=u'hidden'):
-        cookies = self.req.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)
-            status = default
-        else:
-            status = cookie.value
-        return status
-
-    def call(self, **kwargs):
-        """The default view representing the application's index"""
-        self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js'))
-        self.req.add_css('cubicweb.preferences.css')
-        vreg = self.vreg
-        values = self.defined_keys
-        groupedopts = {}
-        mainopts = {}
-        # "self.id=='systemepropertiesform'" to skip site wide properties on
-        # user's preference but not site's configuration
-        for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
-            parts = key.split('.')
-            if parts[0] in vreg:
-                # appobject configuration
-                reg, oid, propid = parts
-                groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key)
-            else:
-                mainopts.setdefault(parts[0], []).append(key)
-        # precompute form to consume error message
-        for group, keys in mainopts.items():
-            mainopts[group] = self.form(keys, False)
-        for group, objects in groupedopts.items():
-            for oid, keys in objects.items():
-                groupedopts[group][oid] = self.form(keys, True)
-
-        w = self.w
-        req = self.req
-        _ = req._
-        w(u'<h1>%s</h1>\n' % _(self.title))
-        w(self.error_message())
-        for label, group, form in sorted((_(g), g, f)
-                                         for g, f in mainopts.iteritems()):
-            status = css_class(self._group_status(group)) #'hidden' (collapsed), or '' (open) ?
-            w(u'<h2 class="propertiesform">%s</h2>\n' %
-              (make_togglable_link('fieldset_' + group, label,
-                                   self._cookie_name(group))))
-            w(u'<div id="fieldset_%s" %s>' % (group, status))
-            w(u'<fieldset class="subentity">')
-            w(form)
-            w(u'</fieldset></div>')
-        for label, group, objects in sorted((_(g), g, o)
-                                            for g, o in groupedopts.iteritems()):
-            status = css_class(self._group_status(group))
-            w(u'<h2 class="propertiesform">%s</h2>\n' %
-              (make_togglable_link('fieldset_' + group, label,
-                                   self._cookie_name(group))))
-            w(u'<div id="fieldset_%s" %s>' % (group, status))
-            for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
-                                           for o, f in objects.iteritems()):
-                w(u'<fieldset class="subentity">')
-                w(u'<legend class="componentTitle">%s</legend>\n' % label)
-                docmsgid = '%s_%s_description' % (group, oid)
-                doc = _(docmsgid)
-                if doc != docmsgid:
-                    w(u'<p class="description">%s</p>' % html_escape(doc))
-                w(form)
-                w(u'</fieldset>')
-            w(u'</div>')
-
-    @property
-    @cached
-    def eprops_rset(self):
-        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U')
-
-    @property
-    def defined_keys(self):
-        values = {}
-        for i, entity in enumerate(self.eprops_rset.entities()):
-            values[entity.pkey] = i
-        return values
-
-    def entity_for_key(self, key):
-        values = self.defined_keys
-        if key in values:
-            entity = self.eprops_rset.get_entity(values[key], 0)
-        else:
-            entity = self.vreg.etype_class('EProperty')(self.req, None, None)
-            entity.eid = self.req.varmaker.next()
-            entity['value'] = self.vreg.property_value(key)
-        return entity
-
-    def form(self, keys, splitlabel=False):
-        stream = UStringIO()
-        w = stream.write
-        w(u'<form action="%s" method="post">\n' % self.build_url())
-        w(u'<fieldset>\n')
-        w(u'<input type="hidden" name="__errorurl" value="%s"/>\n'
-          % html_escape(self.req.url()))
-        w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
-        path = self.req.relative_path()
-        if '?' in path:
-            path, params = path.split('?', 1)
-            w(u'<input type="hidden" name="__redirectparams" value="%s"/>\n'
-              % html_escape(params))
-        w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % path)
-        #w(u'<input type="hidden" name="__redirectrql" value=""/>\n')
-        w(u'<input type="hidden" name="__message" value="%s"/>\n'
-          % self.req._('changes applied'))
-        w(u'<table><tr><td>\n')
-
-        w(u'<table>\n')
-        for key in keys:
-            w(u'<tr>\n')
-            self.form_row(w, key, splitlabel)
-            w(u'</tr>\n')
-        w(u'</table>\n')
-        w(u'</td></tr><tr><td>\n')
-        w(self.button_ok())
-        w(self.button_cancel())
-        w(u'</td></tr></table>\n')
-        w(u'</fieldset>\n')
-        w(u'</form>\n')
-        return stream.getvalue()
-
-    def form_row(self, w, key, splitlabel):
-        entity = self.entity_for_key(key)
-        eid = entity.eid
-        if splitlabel:
-            w(u'<td class="label">%s</td>' % self.req._(key.split('.')[-1]))
-        else:
-            w(u'<td class="label">%s</td>' % self.req._(key))
-        wdg = self.vreg.property_value_widget(key, req=self.req)
-        error = wdg.render_error(entity)
-        w(u'<td class="%s">' % (error and 'error' or ''))
-        w(error)
-        self.form_row_hiddens(w, entity, key)
-        w(wdg.edit_render(entity))
-        w(u'</td>\n')
-        w(u'<td>%s</td>' % wdg.render_help(entity))
-        return entity
-
-    def form_row_hiddens(self, w, entity, key):
-        eid = entity.eid
-        w(u'<input type="hidden" name="eid" value="%s"/>' % eid)
-        w(u'<input type="hidden" name="%s" value="EProperty"/>' % eid_param('__type', eid))
-        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('pkey', eid), key))
-        w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
-
-
-class EpropertiesForm(SystemEpropertiesForm):
-    id = 'epropertiesform'
-    title = _('preferences')
-    require_groups = ('users', 'managers') # we don't want guests to be able to come here
-    __selectors__ = chainfirst(none_rset,
-                               chainall(one_line_rset, accept_rset)),
-    accepts = ('EUser',)
-
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        if row is None:
-            row = 0
-        score = super(EpropertiesForm, cls).accept_rset(req, rset, row, col)
-        # check current user is the rset user or he is in the managers group
-        if score and (req.user.eid == rset[row][col or 0]
-                      or req.user.matching_groups('managers')):
-            return score
-        return 0
-
-    @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)
-
-    @property
-    @cached
-    def eprops_rset(self):
-        return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,'
-                                'P for_user U, U eid %(x)s', {'x': self.user.eid})
-
-    def form_row_hiddens(self, w, entity, key):
-        super(EpropertiesForm, self).form_row_hiddens(w, entity, key)
-        # if user is in the managers group and the property is being created,
-        # we have to set for_user explicitly
-        if not entity.has_eid() and self.user.matching_groups('managers'):
-            eid = entity.eid
-            w(u'<input type="hidden" name="%s" value="%s"/>'
-              % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE))
-            w(u'<input type="hidden" name="%s" value="%s"/>'
-              % (eid_param('for_user', eid), self.user.eid))
-
-
-
-
 class ProcessInformationView(StartupView):
     id = 'info'
+    __select__ = none_rset() & match_user_groups('managers')
+    
     title = _('server information')
-    require_groups = ('managers',)
 
     def call(self, **kwargs):
         """display server information"""
--- a/web/views/massmailing.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/massmailing.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,29 +1,30 @@
 """Mass mailing form views
 
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
+__docformat__ = "restructuredtext en"
 
 import operator
 
-from logilab.mtconverter import html_escape
-
 from cubicweb.interfaces import IEmailable
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface, match_user_group
-from cubicweb.web.action import EntityAction
+from cubicweb.selectors import implements, match_user_groups
+from cubicweb.view import EntityView
 from cubicweb.web import stdmsgs
+from cubicweb.web.action import Action
+from cubicweb.web.form import FieldsForm, FormRenderer
+from cubicweb.web.formfields import StringField
+from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget
 
 
-class SendEmailAction(EntityAction):
+class SendEmailAction(Action):
+    id = 'sendemail'
+    # XXX should check email is set as well
+    __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
+
+    title = _('send email')
     category = 'mainactions'
-    __selectors__ = (implement_interface, match_user_group)
-    accepts_interfaces = (IEmailable,) # XXX should check email is set as well
-    require_groups = ('managers', 'users')
-
-    id = 'sendemail'
-    title = _('send email')
 
     def url(self):
         params = {'vid': 'massmailing', '__force_display': 1}
@@ -32,98 +33,94 @@
         return self.build_url(self.req.relative_path(includeparams=False),
                               **params)
 
-
-class MassMailingForm(EntityView):
+            
+class MassMailingForm(FieldsForm):
     id = 'massmailing'
-    __selectors__ = (implement_interface, match_user_group)
-    accepts_interfaces = (IEmailable,)
-    require_groups = ('managers', 'users')
     
+    sender = StringField(widget=TextInput({'disabled': 'disabled'}), label=_('From:'))
+    recipient = StringField(widget=CheckBox(), label=_('Recipients:'))
+    subject = StringField(label=_('Subject:'))
+    mailbody = StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
+                                             inputid='mailarea'))
+
+    def form_field_vocabulary(self, field):
+        if field.name == 'recipient':
+            vocab = [(entity.get_email(), entity.eid) for entity in self.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()]
+        elif field.name == 'mailbody':
+            field.widget.attrs['cubicweb:variables'] = self.get_allowed_substitutions()
+        return super(MassMailingForm, self).form_field_value(field, values)
 
-    form_template = u"""
-<div id="compose">
-<form id="sendemail" action="sendmail" method="post">
-<table class="headersform">
-<tr>
-  <td class="hlabel">%(from_header)s</td>
-  <td class="hvalue">%(from)s</td>
-</tr>
-<tr>
-  <td class="hlabel">%(recipients_header)s</td>
-  <td class="hvalue">%(recipients)s</td>
-</tr>
-<tr>
-  <td class="hlabel">%(subject)s</td>
-  <td class="hvalue"><input id="mailsubj" name="mailsubject" value="" /></td>
-</tr>
-</table>
-<div id="toolbar">
-<ul>
-<li><a id="sendbutton" href="javascript: $('sendemail').submit()">
-    <img src="%(sendimgpath)s" alt="%(send)s"/>%(send)s</a></li>
-<li><a id="cancelbutton" href="javascript: history.back()">
-    <img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a></li>
- </ul>
-</div>
-<table>
-<tr>
-  <td>
-    <div>
-      <div id="emailbody" class="widget" cubicweb:loadtype="auto" cubicweb:wdgtype="TemplateTextField"
-           cubicweb:inputid="emailarea" cubicweb:inputname="mailbody" cubicweb:variables="%(variables)s"/>
-    </div>
-  </td>
-  <td>%(substitutions)s</td>
-</tr>
-</table>
-</form>
-</div>
-    """    
+    def form_buttons(self):
+        context = {'domid': self.domid,
+                   'cancel' : self.req._(stdmsgs.BUTTON_CANCEL),
+                   'cancelimgpath' : self.req.external_resource('CANCEL_EMAIL_ICON'),
+                   'send' : self.req._('send email'),
+                   'sendimgpath' : self.req.external_resource('SEND_EMAIL_ICON'),
+                   }
+        return ['''<a id="sendbutton" href="javascript: $('%(domid)s').submit()">
+<img src="%(sendimgpath)s" alt="%(send)s"/>%(send)s</a>''' % context,
+                '''<a id="cancelbutton" href="javascript: history.back()">
+<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context,
+                ]
+    
+    def get_allowed_substitutions(self):
+        attrs = []
+        for coltype in self.rset.column_types(0):
+            eclass = self.vreg.etype_class(coltype)
+            attrs.append(eclass.allowed_massmail_keys())
+        return sorted(reduce(operator.and_, attrs))
+
+    def build_substitutions_help(self):
+        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')
+        return u'<div id="substitutions"><span>%s</span>%s</div>' % (
+            helpmsg, u'\n'.join(substs))
+
+
+class MassMailingFormRenderer(FormRenderer):
+    button_bar_class = u'toolbar'
+    
+    def _render_fields(self, fields, w, form, values):
+        w(u'<table class="headersform">')
+        for field in fields:
+            if field.name == 'mailbody':
+                w(u'</table>')
+                w(u'<table>')
+                w(u'<tr><td><div>')
+            else:
+                w(u'<tr>')
+                w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
+                w(u'<td class="hvalue">')
+            w(field.render(form, self))
+            if field.name == 'mailbody':
+                w(u'</div></td>')
+                w(u'<td>%s</td>' % form.build_substitutions_help())
+                w(u'</tr>')
+            else:
+                w(u'</td></tr>')
+        w(u'</table>')
+
+    
+class MassMailingFormView(EntityView):
+    id = 'massmailing'
+    __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
 
     def call(self):
         req = self.req
         req.add_js('cubicweb.widgets.js')
         req.add_css('cubicweb.mailform.css')
         from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
-        ctx = {
-            'from_header' : req._('From: '),
-            'from' : html_escape(from_addr),
-            'substitutions' : self._build_substitutions_help(),
-            'recipients_header' : req._('Recipients: '),
-            'subject' : req._('Subject: '),
-            'body' : req._('Email body: '),
-            'variables' : ','.join(self._get_allowed_substitutions()),
-            'recipients' : self._build_recipients_list(),
-            'cancel' : req._(stdmsgs.BUTTON_CANCEL),
-            'cancelimgpath' : req.external_resource('CANCEL_EMAIL_ICON'),
-            'send' : req._('send email'),
-            'sendimgpath' : req.external_resource('SEND_EMAIL_ICON'),
-            }
-        self.w(self.form_template % ctx)
+        form = self.vreg.select_object('forms', 'massmailing', self.req, self.rset,
+                                       action='sendmail', domid='sendmail')
+        self.w(form.form_render(sender=from_addr, renderer=MassMailingFormRenderer()))
 
 
-    def _get_allowed_substitutions(self):
-        coltypes = self.rset.column_types(0)
-        attrs = []
-        for coltype in coltypes:
-            eclass = self.vreg.etype_class(coltype)
-            attrs.append(eclass.allowed_massmail_keys())
-        return sorted(reduce(operator.and_, attrs))
-            
-    def _build_recipients_list(self):
-        emails = ((entity.eid, entity.get_email()) for entity in self.rset.entities())
-        checkboxes = (u'<input name="recipient" type="checkbox" value="%s" checked="checked" />%s'
-                      % (eid, html_escape(email)) for eid, email in emails if email)
-        boxes = (u'<div class="recipient">%s</div>' % cbox for cbox in checkboxes)
-        return u'<div id="recipients">%s</div>' % u'\n'.join(boxes)
-            
-
-    def _build_substitutions_help(self):
-        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')
-        return u'<div id="substitutions"><span>%s</span>%s</div>' % (
-            helpmsg, u'\n'.join(substs))
-
     
--- a/web/views/navigation.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/navigation.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """navigation components definition for CubicWeb web client
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -9,11 +9,12 @@
 from rql.nodes import VariableRef, Constant
 
 from logilab.mtconverter import html_escape
+from logilab.common.deprecation import obsolete
 
 from cubicweb.interfaces import IPrevNext
-from cubicweb.common.selectors import (paginated_rset, sorted_rset,
-                                       primary_view, match_context_prop,
-                                       one_line_rset, implement_interface)
+from cubicweb.selectors import (paginated_rset, sorted_rset,
+                                primary_view, match_context_prop,
+                                one_line_rset, implements)
 from cubicweb.common.uilib import cut
 from cubicweb.web.component import EntityVComponent, NavigationComponent
 
@@ -51,7 +52,7 @@
     """sorted navigation apply if navigation is needed (according to page size)
     and if the result set is sorted
     """
-    __selectors__ = (paginated_rset, sorted_rset)
+    __select__ = paginated_rset() & sorted_rset()
     
     # number of considered chars to build page links
     nb_chars = 5
@@ -145,7 +146,7 @@
 
 
 def limit_rset_using_paged_nav(self, req, rset, w, forcedisplay=False,
-                               show_all_option=True, page_size = None):
+                               show_all_option=True, page_size=None):
     showall = forcedisplay or req.form.get('__force_display') is not None
     nav = not showall and self.vreg.select_component('navigation', req, rset,
                                                      page_size=page_size)
@@ -165,9 +166,9 @@
 
 # monkey patch base View class to add a .pagination(req, rset, w, forcedisplay)
 # method to be called on view's result set and printing pages index in the view
-from cubicweb.common.view import View
+from cubicweb.view import View
 # XXX deprecated, use paginate
-View.pagination = limit_rset_using_paged_nav
+View.pagination = obsolete('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
 
 def paginate(view, show_all_option=True, w=None):
     limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w,
@@ -180,9 +181,8 @@
     # itself
     title = _('contentnavigation_prevnext')
     help = _('contentnavigation_prevnext_description')
-    __selectors__ = (one_line_rset, primary_view,
-                     match_context_prop, implement_interface)
-    accepts_interfaces = (IPrevNext,)
+    __select__ = (one_line_rset() & primary_view()
+                  & match_context_prop() & implements(IPrevNext))
     context = 'navbottom'
     order = 10
     def call(self, view=None):
--- a/web/views/old_calendar.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,544 +0,0 @@
-"""html calendar views
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-from mx.DateTime import DateTime, RelativeDateTime, Date, Time, today, Sunday
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.interfaces import ICalendarViews
-from cubicweb.common.utils import date_range
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.view import EntityView
-
-# Define some useful constants
-ONE_MONTH = RelativeDateTime(months=1)
-TODAY = today()
-THIS_MONTH = TODAY.month
-THIS_YEAR = TODAY.year
-# mx.DateTime and ustrftime could be used to build WEEKDAYS
-WEEKDAYS = [_("monday"), _("tuesday"), _("wednesday"), _("thursday"),
-            _("friday"), _("saturday"), _("sunday")]
-
-# used by i18n tools
-MONTHNAMES = [ _('january'), _('february'), _('march'), _('april'), _('may'),
-               _('june'), _('july'), _('august'), _('september'), _('october'),
-               _('november'), _('december')
-               ]
-
-class _CalendarView(EntityView):
-    """base calendar view containing helpful methods to build calendar views"""
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarViews,)
-    need_navigation = False
-
-    # Navigation building methods / views ####################################
-
-    PREV = u'<a href="%s">&lt;&lt;</a>&nbsp;&nbsp;<a href="%s">&lt;</a>'
-    NEXT = u'<a href="%s">&gt;</a>&nbsp;&nbsp;<a href="%s">&gt;&gt;</a>'
-    NAV_HEADER = u"""<table class="calendarPageHeader">
-<tr><td class="prev">%s</td><td class="next">%s</td></tr>
-</table>
-""" % (PREV, NEXT)
-    
-    def nav_header(self, date, smallshift=3, bigshift=9):
-        """prints shortcut links to go to previous/next steps (month|week)"""
-        prev1 = date - RelativeDateTime(months=smallshift)
-        prev2 = date - RelativeDateTime(months=bigshift)
-        next1 = date + RelativeDateTime(months=smallshift)
-        next2 = date + RelativeDateTime(months=bigshift)
-        rql, vid = self.rset.printable_rql(), self.id
-        return self.NAV_HEADER % (
-            html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, month=prev2.month)),
-            html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, month=prev1.month)),
-            html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, month=next1.month)),
-            html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, month=next2.month)))
-        
-    
-    # Calendar building methods ##############################################
-    
-    def build_calendars(self, schedule, begin, end):
-        """build several HTML calendars at once, one for each month
-        between begin and end
-        """
-        return [self.build_calendar(schedule, date)
-                for date in date_range(begin, end, incr=ONE_MONTH)]
-    
-    def build_calendar(self, schedule, first_day):
-        """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
-        rows = []
-        current_row = [NO_CELL] * first_day.day_of_week
-        for daynum in xrange(0, first_day.days_in_month):
-            # build cell day
-            day = first_day + daynum
-            events = schedule.get(day)
-            if events:
-                events = [u'\n'.join(event) for event in events.values()]
-                current_row.append(CELL % (daynum+1, '\n'.join(events)))
-            else:
-                current_row.append(EMPTY_CELL % (daynum+1))
-            # store & reset current row on Sundays
-            if day.day_of_week == Sunday:
-                rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.iso_week[1], ''.join(current_row)))
-                current_row = []
-        current_row.extend([NO_CELL] * (Sunday-day.day_of_week))
-        rql = self.rset.printable_rql()
-        if day.day_of_week != Sunday:
-            rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.iso_week[1], ''.join(current_row)))
-        url = self.build_url(rql=rql, vid='calendarmonth',
-                             year=first_day.year, month=first_day.month)
-        monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
-
-    def _mk_schedule(self, begin, end, itemvid='calendaritem'):
-        """private method that gathers information from resultset
-        and builds calendars according to it
-
-        :param begin: begin of date range
-        :param end: end of date rangs
-        :param itemvid: which view to call to render elements in cells
-
-        returns { day1 : { hour : [views] },
-                  day2 : { hour : [views] } ... }
-        """
-        # put this here since all sub views are calling this method        
-        self.req.add_css('cubicweb.calendar.css') 
-        schedule = {}
-        for row in xrange(len(self.rset.rows)):
-            entity = self.entity(row)
-            infos = u'<div class="event">'
-            infos += self.view(itemvid, self.rset, row=row)
-            infos += u'</div>'
-            for date in entity.matching_dates(begin, end):
-                day = Date(date.year, date.month, date.day)
-                time = Time(date.hour, date.minute, date.second) 
-                schedule.setdefault(day, {})
-                schedule[day].setdefault(time, []).append(infos)
-        return schedule
-        
-
-    @staticmethod
-    def get_date_range(day=TODAY, shift=4):
-        """returns a couple (begin, end)
-
-        <begin> is the first day of current_month - shift
-        <end> is the last day of current_month + (shift+1)
-        """
-        first_day_in_month = DateTime(day.year, day.month, 1)
-        begin = first_day_in_month - RelativeDateTime(months=shift)
-        end = (first_day_in_month + RelativeDateTime(months=shift+1)) - 1
-        return begin, end
-
-
-    def _build_ampm_cells(self, daynum, events):
-        """create a view without any hourly details.
-
-        :param daynum: day of the built cell
-        :param events: dictionnary with all events classified by hours"""
-        # split events according am/pm
-        am_events = [event for e_time, e_list in events.iteritems()
-                     if 0 <= e_time.hour < 12
-                     for event in e_list]
-        pm_events = [event for e_time, e_list in events.iteritems()
-                     if 12 <= e_time.hour < 24
-                     for event in e_list]
-        # format each am/pm cell
-        if am_events:
-            am_content = AMPM_CONTENT % ("amCell", "am", '\n'.join(am_events))
-        else:
-            am_content = AMPM_EMPTY % ("amCell", "am")
-        if pm_events:
-            pm_content = AMPM_CONTENT % ("pmCell", "pm", '\n'.join(pm_events))
-        else:
-            pm_content = AMPM_EMPTY % ("pmCell", "pm")
-        return am_content, pm_content
-
-
-
-class YearCalendarView(_CalendarView):
-    id = 'calendaryear'
-    title = _('calendar (year)')
-
-    def call(self, year=THIS_YEAR, month=THIS_MONTH):
-        """this view renders a 3x3 calendars' table"""
-        year = int(self.req.form.get('year', year))
-        month = int(self.req.form.get('month', month))
-        center_date = DateTime(year, month)
-        begin, end = self.get_date_range(day=center_date)
-        schedule = self._mk_schedule(begin, end)
-        self.w(self.nav_header(center_date))
-        calendars = tuple(self.build_calendars(schedule, begin, end))
-        self.w(SMALL_CALENDARS_PAGE % calendars)
-
-
-class SemesterCalendarView(_CalendarView):
-    """this view renders three semesters as three rows of six columns,
-    one column per month
-    """
-    id = 'calendarsemester'
-    title = _('calendar (semester)')
-
-    def call(self, year=THIS_YEAR, month=THIS_MONTH):
-        year = int(self.req.form.get('year', year))
-        month = int(self.req.form.get('month', month))
-        begin = DateTime(year, month) - RelativeDateTime(months=2)
-        end = DateTime(year, month) + RelativeDateTime(months=3)
-        schedule = self._mk_schedule(begin, end)
-        self.w(self.nav_header(DateTime(year, month), 1, 6))
-        self.w(u'<table class="semesterCalendar">')
-        self.build_calendars(schedule, begin, end)
-        self.w(u'</table>')
-        self.w(self.nav_header(DateTime(year, month), 1, 6))
-
-    def build_calendars(self, schedule, begin, end):
-        self.w(u'<tr>')
-        rql = self.rset.printable_rql()
-        for cur_month in date_range(begin, end, incr=ONE_MONTH):
-            umonth = u'%s&nbsp;%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)
-            self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (html_escape(url),
-                                                                  umonth))
-        self.w(u'</tr>')
-        _ = self.req._
-        for day_num in xrange(31):
-            self.w(u'<tr>')
-            for cur_month in date_range(begin, end, incr=ONE_MONTH):
-                if day_num >= cur_month.days_in_month:
-                    self.w(u'%s%s' % (NO_CELL, NO_CELL))
-                else:
-                    day = DateTime(cur_month.year, cur_month.month, day_num+1)
-                    events = schedule.get(day)
-                    self.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(), day_num+1))
-                    self.format_day_events(day, events)
-            self.w(u'</tr>')
-            
-    def format_day_events(self, day, events):
-        if events:
-            events = ['\n'.join(event) for event in events.values()]
-            self.w(WEEK_CELL % '\n'.join(events))
-        else:
-            self.w(WEEK_EMPTY_CELL)
-        
-
-class MonthCalendarView(_CalendarView):
-    """this view renders a 3x1 calendars' table"""
-    id = 'calendarmonth'
-    title = _('calendar (month)')
-    
-    def call(self, year=THIS_YEAR, month=THIS_MONTH):
-        year = int(self.req.form.get('year', year))
-        month = int(self.req.form.get('month', month))
-        center_date = DateTime(year, month)
-        begin, end = self.get_date_range(day=center_date, shift=1)
-        schedule = self._mk_schedule(begin, end)
-        calendars = self.build_calendars(schedule, begin, end)
-        self.w(self.nav_header(center_date, 1, 3))
-        self.w(BIG_CALENDARS_PAGE % tuple(calendars))
-        self.w(self.nav_header(center_date, 1, 3))
-
-        
-class WeekCalendarView(_CalendarView):
-    """this view renders a calendar for week events"""
-    id = 'calendarweek'
-    title = _('calendar (week)')
-    
-    def call(self, year=THIS_YEAR, week=TODAY.iso_week[1]):
-        year = int(self.req.form.get('year', year))
-        week = int(self.req.form.get('week', week))
-        day0 = DateTime(year)
-        first_day_of_week = (day0-day0.day_of_week) + 7*week
-        begin, end = first_day_of_week-7, first_day_of_week+14
-        schedule = self._mk_schedule(begin, end, itemvid='calendarlargeitem')
-        self.w(self.nav_header(first_day_of_week))
-        self.w(u'<table class="weekCalendar">')
-        _weeks = [(first_day_of_week-7, first_day_of_week-1),
-                  (first_day_of_week, first_day_of_week+6),
-                  (first_day_of_week+7, first_day_of_week+13)]
-        self.build_calendar(schedule, _weeks)
-        self.w(u'</table>')
-        self.w(self.nav_header(first_day_of_week))
- 
-    def build_calendar(self, schedule, weeks):
-        rql = self.rset.printable_rql()
-        _ = self.req._
-        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)
-            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
-            self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
-                  % (_('week'), monday.iso_week[1], monthlink))
-            for day in date_range(monday, sunday):
-                self.w(u'<tr>')
-                self.w(u'<td>%s</td>' % _(WEEKDAYS[day.day_of_week]))
-                self.w(u'<td>%s</td>' % (day.strftime('%Y-%m-%d')))
-                events = schedule.get(day)
-                if events:
-                    events = ['\n'.join(event) for event in events.values()]
-                    self.w(WEEK_CELL % '\n'.join(events))
-                else:
-                    self.w(WEEK_EMPTY_CELL)
-                self.w(u'</tr>')
-        
-    def nav_header(self, date, smallshift=1, bigshift=3):
-        """prints shortcut links to go to previous/next steps (month|week)"""
-        prev1 = date - RelativeDateTime(weeks=smallshift)
-        prev2 = date - RelativeDateTime(weeks=bigshift)
-        next1 = date + RelativeDateTime(weeks=smallshift)
-        next2 = date + RelativeDateTime(weeks=bigshift)
-        rql, vid = self.rset.printable_rql(), self.id
-        return self.NAV_HEADER % (
-            html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, week=prev2.iso_week[1])),
-            html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, week=prev1.iso_week[1])),
-            html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, week=next1.iso_week[1])),
-            html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, week=next2.iso_week[1])))
-
-
-        
-class AMPMYearCalendarView(YearCalendarView):
-    id = '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
-        rows = [] # each row is: (am,pm), (am,pm) ... week_title
-        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
-        rql = self.rset.printable_rql()
-        for daynum in xrange(0, first_day.days_in_month):
-            # build cells day
-            day = first_day + daynum
-            events = schedule.get(day)
-            if events:
-                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
-            else:
-                current_row.append((AMPM_DAY % (daynum+1),
-                                    AMPM_EMPTY % ("amCell", "am"),
-                                    AMPM_EMPTY % ("pmCell", "pm")))
-            # store & reset current row on Sundays
-            if day.day_of_week == Sunday:
-                url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                                     year=day.year, week=day.iso_week[1])
-                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                                    day.iso_week[1])
-                current_row.append(WEEKNUM_CELL % weeklink)
-                rows.append(current_row)
-                current_row = []
-        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
-        url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                             year=day.year, week=day.iso_week[1])
-        weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.iso_week[1])
-        current_row.append(WEEKNUM_CELL % weeklink)
-        rows.append(current_row)
-        # build two rows for each week: am & pm
-        formatted_rows = []
-        for row in rows:
-            week_title = row.pop()
-            day_row = [day for day, am, pm in row]
-            am_row = [am for day, am, pm in row]
-            pm_row = [pm for day, am, pm in row]
-            formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
-            formatted_rows.append('<tr class="amRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(am_row))
-            formatted_rows.append('<tr class="pmRow"><td>&nbsp;</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)
-        monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
-        
-
-
-class AMPMSemesterCalendarView(SemesterCalendarView):
-    """this view renders a 3x1 calendars' table"""
-    id = 'ampmcalendarsemester'
-    title = _('am/pm calendar (semester)')
-
-    def build_calendars(self, schedule, begin, end):
-        self.w(u'<tr>')
-        rql = self.rset.printable_rql()
-        for cur_month in date_range(begin, end, incr=ONE_MONTH):
-            umonth = u'%s&nbsp;%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)
-            self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (html_escape(url),
-                                                                  umonth))
-        self.w(u'</tr>')
-        _ = self.req._
-        for day_num in xrange(31):
-            self.w(u'<tr>')
-            for cur_month in date_range(begin, end, incr=ONE_MONTH):
-                if day_num >= cur_month.days_in_month:
-                    self.w(u'%s%s%s' % (NO_CELL, NO_CELL, NO_CELL))
-                else:
-                    day = DateTime(cur_month.year, cur_month.month, day_num+1)
-                    events = schedule.get(day)
-                    self.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(),
-                                                       day_num+1))
-                    self.format_day_events(day, events)
-            self.w(u'</tr>')
-    
-    def format_day_events(self, day, events):
-        if events:
-            self.w(u'\n'.join(self._build_ampm_cells(day, events)))
-        else:
-            self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"), 
-                              AMPM_EMPTY % ("pmCell", "pm")))
-
-
-class AMPMMonthCalendarView(MonthCalendarView):
-    """this view renders a 3x1 calendars' table"""
-    id = '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
-        rows = [] # each row is: (am,pm), (am,pm) ... week_title
-        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
-        rql = self.rset.printable_rql()
-        for daynum in xrange(0, first_day.days_in_month):
-            # build cells day
-            day = first_day + daynum
-            events = schedule.get(day)
-            if events:
-                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
-            else:
-                current_row.append((AMPM_DAY % (daynum+1),
-                                    AMPM_EMPTY % ("amCell", "am"),
-                                    AMPM_EMPTY % ("pmCell", "pm")))
-            # store & reset current row on Sundays
-            if day.day_of_week == Sunday:
-                url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                                     year=day.year, week=day.iso_week[1])
-                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                                    day.iso_week[1])
-                current_row.append(WEEKNUM_CELL % weeklink)
-                rows.append(current_row)
-                current_row = []
-        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
-        url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                             year=day.year, week=day.iso_week[1])
-        weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                            day.iso_week[1])
-        current_row.append(WEEKNUM_CELL % weeklink)
-        rows.append(current_row)
-        # build two rows for each week: am & pm
-        formatted_rows = []
-        for row in rows:
-            week_title = row.pop()
-            day_row = [day for day, am, pm in row]
-            am_row = [am for day, am, pm in row]
-            pm_row = [pm for day, am, pm in row]
-            formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
-            formatted_rows.append('<tr class="amRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(am_row))
-            formatted_rows.append('<tr class="pmRow"><td>&nbsp;</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)
-        monthlink = '<a href="%s">%s</a>' % (html_escape(url),
-                                             umonth)
-        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))      
-    
-
-    
-class AMPMWeekCalendarView(WeekCalendarView):
-    """this view renders a 3x1 calendars' table"""
-    id = 'ampmcalendarweek'
-    title = _('am/pm calendar (week)')
-
-    def build_calendar(self, schedule, weeks):
-        rql = self.rset.printable_rql()
-        w = self.w
-        _ = self.req._
-        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)
-            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
-            w(u'<tr>%s</tr>' % (
-                WEEK_TITLE % (_('week'), monday.iso_week[1], monthlink)))
-            w(u'<tr><th>%s</th><th>&nbsp;</th></tr>'% _(u'Date'))
-            for day in date_range(monday, sunday):
-                events = schedule.get(day)
-                style = day.day_of_week % 2 and "even" or "odd"
-                w(u'<tr class="%s">' % style)
-                if events:
-                    hours = events.keys()
-                    hours.sort()
-                    w(AMPM_DAYWEEK % (
-                        len(hours), _(WEEKDAYS[day.day_of_week]),
-                        self.format_date(day)))
-                    w(AMPM_WEEK_CELL % (
-                        hours[0].hour, hours[0].minute,
-                        '\n'.join(events[hours[0]])))
-                    w(u'</tr>')
-                    for hour in hours[1:]:
-                        w(u'<tr class="%s">%s</tr>'% (
-                            style, AMPM_WEEK_CELL % (hour.hour, hour.minute,
-                                                     '\n'.join(events[hour]))))
-                else:
-                    w(AMPM_DAYWEEK_EMPTY % (
-                        _(WEEKDAYS[day.day_of_week]),
-                        self.format_date(day)))
-                    w(WEEK_EMPTY_CELL)
-                    w(u'</tr>')
-
-
-SMALL_CALENDARS_PAGE = u"""<table class="smallCalendars">
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-</table>
-"""
-
-BIG_CALENDARS_PAGE = u"""<table class="bigCalendars">
-<tr><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td></tr>
-</table>
-"""
-
-WEEKNUM_CELL = u'<td class="weeknum">%s</td>'
-
-def CALENDAR(req):
-    _ = req._
-    WEEKNUM_HEADER = u'<th class="weeknum">%s</th>' % _('week')
-    CAL_HEADER = WEEKNUM_HEADER + u' \n'.join([u'<th class="weekday">%s</th>' % _(day)[0].upper()
-                                               for day in WEEKDAYS])
-    return u"""<table>
-<tr><th class="month" colspan="8">%%s</th></tr>
-<tr>
-  %s
-</tr>
-%%s
-</table>
-""" % (CAL_HEADER,)
-
-
-DAY_TEMPLATE = """<tr><td class="weekday">%(daylabel)s</td><td>%(dmydate)s</td><td>%(dayschedule)s</td>
-"""
-
-NO_CELL = u'<td class="noday"></td>'
-EMPTY_CELL = u'<td class="cellEmpty"><span class="cellTitle">%s</span></td>'
-CELL = u'<td class="cell"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
-
-AMPM_DAY = u'<td class="cellDay">%d</td>'
-AMPM_EMPTY = u'<td class="%sEmpty"><span class="cellTitle">%s</span></td>'
-AMPM_CONTENT = u'<td class="%s"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
-
-WEEK_TITLE = u'<th class="weekTitle" colspan="2">%s %s (%s)</th>'
-WEEK_EMPTY_CELL = u'<td class="weekEmptyCell">&nbsp;</td>'
-WEEK_CELL = u'<td class="weekCell"><div class="cellContent">%s</div></td>'
-
-AMPM_DAYWEEK_EMPTY = u'<td>%s&nbsp;%s</td>'
-AMPM_DAYWEEK = u'<td rowspan="%d">%s&nbsp;%s</td>'
-AMPM_WEEK_CELL = u'<td class="ampmWeekCell"><div class="cellContent">%02d:%02d - %s</div></td>'
--- a/web/views/owl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/owl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,16 @@
+"""produces some Ontology Web Language schema and views
+
+:organization: Logilab
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
 from logilab.mtconverter import TransformError, xml_escape
 
-from cubicweb.common.view import StartupView
-from cubicweb.common.view import EntityView
+from cubicweb.view import StartupView, EntityView
+from cubicweb.web.action import Action
+from cubicweb.selectors import none_rset, match_view
 
 _ = unicode
 
@@ -155,7 +164,6 @@
     id = 'owlabox'
     title = _('owlabox')
     templatable = False
-    accepts = ('Any',)
     content_type = 'application/xml' # 'text/xml'
     
     def call(self):
@@ -169,11 +177,9 @@
 
         
 class OWLABOXItemView(EntityView):
-    '''This view represents a part of the ABOX for a given entity.'''
-    
+    '''This view represents a part of the ABOX for a given entity.'''    
     id = 'owlaboxitem'
     templatable = False
-    accepts = ('Any',)
     content_type = 'application/xml' # 'text/xml'
 
     def cell_call(self, row, col, skiprels=DEFAULT_SKIP_RELS):
@@ -209,3 +215,14 @@
                 self.w(u'<%s>%s %s</%s>' % (attr, x.id, x.eid, attr))
         self.w(u'</%s>'% eschema)
 
+
+class DownloadOWLSchemaAction(Action):
+    id = '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')
+
--- a/web/views/plots.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/plots.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,8 +2,10 @@
 
 from logilab.common import flatten
 
+from cubicweb.vregistry import objectify_selector
 from cubicweb.web.views import baseviews
 
+@objectify_selector
 def plot_selector(cls, req, rset, *args, **kwargs):
     """accept result set with at least one line and two columns of result
     all columns after second must be of numerical types"""
@@ -23,8 +25,7 @@
     import sys
     if 'matplotlib.backends' not in sys.modules:
         matplotlib.use('Agg')
-    from matplotlib.ticker import FormatStrFormatter
-    from pylab import figure, show
+    from pylab import figure
 except ImportError:
     pass
 else:
@@ -34,7 +35,7 @@
         binary = True
         content_type = 'image/png'
         _plot_count = 0
-        __selectors__ = (plot_selector,)
+        __select__ = plot_selector()
 
         def call(self, width=None, height=None):
             # compute dimensions
@@ -55,7 +56,7 @@
             abscisses = [row[0] for row in self.rset]
             courbes = []
             nbcols = len(self.rset.rows[0])
-            for col in range(1,nbcols):
+            for col in xrange(1, nbcols):
                 courbe = [row[col] for row in self.rset]
                 courbes.append(courbe)
             if not courbes:
@@ -70,7 +71,7 @@
             except ValueError:
                 xlabels = abscisses
                 abscisses = range(len(xlabels))
-            for idx,courbe in enumerate(courbes):
+            for idx, courbe in enumerate(courbes):
                 ax.plot(abscisses, courbe, '%sv-' % colors[idx], label=self.rset.description[0][idx+1])
             ax.autoscale_view()
             alldata = flatten(courbes)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/schema.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,236 @@
+"""Specific views for schema related entities
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from itertools import cycle
+
+from logilab.mtconverter import html_escape
+from yams import schema2dot as s2d
+
+from cubicweb.selectors import implements, yes
+from cubicweb.schemaviewer import SchemaViewer
+from cubicweb.view import EntityView, StartupView
+from cubicweb.common.uilib import ureport_as_html
+from cubicweb.web.action import Action
+from cubicweb.web.views import baseviews
+from cubicweb.web.views import TmpFileViewMixin
+from cubicweb.web.views.editforms import AutomaticEntityForm
+from cubicweb.web.views.boxes import EditBox
+
+
+AutomaticEntityForm.rcategories.set_rtag('primary', 'require_group', 'subject', 'EPermission')
+AutomaticEntityForm.rcategories.set_rtag('generated', 'final', 'subject', 'EEtype')
+AutomaticEntityForm.rcategories.set_rtag('generated', 'final', 'subject', 'ERtype')
+AutomaticEntityForm.rinlined.set_rtag(True, 'relation_type', 'subject', 'ENFRDef')
+AutomaticEntityForm.rinlined.set_rtag(True, 'from_entity', 'subject', 'ENFRDef')
+AutomaticEntityForm.rinlined.set_rtag(True, 'to_entity', 'subject', 'ENFRDef')
+AutomaticEntityForm.rwidgets.set_rtag('StringWidget', 'expression', 'subject', 'RQLExpression')
+
+EditBox.rmode.set_rtag('create', 'state_of', 'object', 'EEType')
+EditBox.rmode.set_rtag('create', 'transition_of', 'object', 'EEType')
+EditBox.rmode.set_rtag('create', 'relation_type', 'object', 'ERType')
+EditBox.rmode.set_rtag('link', 'from_entity', 'object', 'EEType')
+EditBox.rmode.set_rtag('link', 'to_entity', 'object', 'EEType')
+
+
+class ViewSchemaAction(Action):
+    id = 'schema'
+    __select__ = yes()
+    
+    title = _("site schema")
+    category = 'siteactions'
+    order = 30
+    
+    def url(self):
+        return self.build_url(self.id)
+    
+        
+# schema entity types views ###################################################
+
+class _SchemaEntityPrimaryView(baseviews.PrimaryView):
+    show_attr_label = False
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+    
+    def content_title(self, entity):
+        return html_escape(entity.dc_long_title())
+    
+class EETypePrimaryView(_SchemaEntityPrimaryView):
+    __select__ = implements('EEType')
+    skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final')
+
+class ERTypePrimaryView(_SchemaEntityPrimaryView):
+    __select__ = implements('ERType')
+    skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final',
+                                                        'symetric', 'inlined')
+
+class ErdefPrimaryView(_SchemaEntityPrimaryView):
+    __select__ = implements('EFRDef', 'ENFRDef')
+    show_attr_label = True
+
+class EETypeOneLineView(baseviews.OneLineView):
+    __select__ = implements('EEType')
+    
+    def cell_call(self, row, col, **kwargs):
+        entity = self.entity(row, col)
+        final = entity.final
+        if final:
+            self.w(u'<em class="finalentity">')
+        super(EETypeOneLineView, self).cell_call(row, col, **kwargs)
+        if final:
+            self.w(u'</em>')
+
+
+# in memory schema views (yams class instances) ###############################
+
+class EETypeSchemaView(EETypePrimaryView):
+    id = 'eschema'
+    title = _('in memory entity schema')
+    main_related_section = False
+    skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
+                 'has_text',)
+    
+    def render_entity_attributes(self, entity, siderelations):
+        super(EETypeSchemaView, self).render_entity_attributes(entity, siderelations)
+        eschema = self.vreg.schema.eschema(entity.name)
+        viewer = SchemaViewer(self.req)
+        layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels)
+        self.w(ureport_as_html(layout))
+        if not eschema.is_final():
+            self.w(u'<img src="%s" alt="%s"/>' % (
+                html_escape(entity.absolute_url(vid='eschemagraph')),
+                html_escape(self.req._('graphical schema for %s') % entity.name)))
+
+
+class ERTypeSchemaView(ERTypePrimaryView):
+    id = 'eschema'
+    title = _('in memory relation schema')
+    main_related_section = False
+
+    def render_entity_attributes(self, entity, siderelations):
+        super(ERTypeSchemaView, self).render_entity_attributes(entity, siderelations)
+        rschema = self.vreg.schema.rschema(entity.name)
+        viewer = SchemaViewer(self.req)
+        layout = viewer.visit_relationschema(rschema)
+        self.w(ureport_as_html(layout))
+        if not rschema.is_final():
+            self.w(u'<img src="%s" alt="%s"/>' % (
+                html_escape(entity.absolute_url(vid='eschemagraph')),
+                html_escape(self.req._('graphical schema for %s') % entity.name)))
+        
+
+# schema images ###############################################################
+
+class ImageView(EntityView):
+    __select__ = implements('EEType')
+    id = 'image'
+    title = _('image')
+
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+        url = entity.absolute_url(vid='eschemagraph')
+        self.w(u'<img src="%s" alt="%s"/>' % (
+            html_escape(url),
+            html_escape(self.req._('graphical schema for %s') % entity.name)))
+
+
+class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
+    def __init__(self, req):
+        # FIXME: colors are arbitrary
+        self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
+                                 '#000000', '#888888') ).next
+        self.req = req
+        
+    def display_attr(self, rschema):
+        return not rschema.meta and (rschema.has_local_role('read')
+                                     or rschema.has_perm(self.req, 'read'))
+    
+    # XXX remove this method once yams > 0.20 is out
+    def node_properties(self, eschema):
+        """return default DOT drawing options for an entity schema"""
+        label = ['{', eschema.type, '|']
+        label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
+                                if rel.final and self.display_attr(rel)))
+        label.append(r'\l}') # trailing \l ensure alignement of the last one
+        return {'label' : ''.join(label), 'shape' : "record",
+                'fontname' : "Courier", 'style' : "filled"}
+
+    def edge_properties(self, rschema, subjnode, objnode):
+        kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
+        # symetric rels are handled differently, let yams decide what's best
+        if not rschema.symetric:
+            kwargs['color'] = self.nextcolor()
+        kwargs['fontcolor'] = kwargs['color']
+        # dot label decoration is just awful (1 line underlining the label
+        # + 1 line going to the closest edge spline point)
+        kwargs['decorate'] = 'false'
+        return kwargs
+    
+
+class RestrictedSchemaVisitorMiIn:
+    def __init__(self, req, *args, **kwargs):
+        # hack hack hack
+        assert len(self.__class__.__bases__) == 2
+        self.__parent = self.__class__.__bases__[1]
+        self.__parent.__init__(self, *args, **kwargs)
+        self.req = req
+        
+    def nodes(self):
+        for etype, eschema in self.__parent.nodes(self):
+            if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
+                yield eschema.type, eschema
+            
+    def edges(self):
+        for setype, oetype, rschema in self.__parent.edges(self):
+            if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
+                yield setype, oetype, rschema
+
+
+class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
+    pass
+
+class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
+    pass
+
+class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
+    pass
+
+
+class SchemaImageView(TmpFileViewMixin, StartupView):
+    id = 'schemagraph'
+    content_type = 'image/png'
+    skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+    def _generate(self, tmpfile):
+        """display global schema information"""
+        skipmeta = not int(self.req.form.get('withmeta', 0))
+        visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
+        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
+    id = 'eschemagraph'
+    content_type = 'image/png'
+    __select__ = implements('EEType')
+    skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+    
+    def _generate(self, tmpfile):
+        """display schema information for an entity"""
+        entity = self.entity(self.row, self.col)
+        eschema = self.vreg.schema.eschema(entity.name)
+        visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
+        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class ERTypeSchemaImageView(EETypeSchemaImageView):
+    __select__ = implements('ERType')
+    
+    def _generate(self, tmpfile):
+        """display schema information for an entity"""
+        entity = self.entity(self.row, self.col)
+        rschema = self.vreg.schema.rschema(entity.name)
+        visitor = OneHopRSchemaVisitor(self.req, rschema)
+        s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+                       prophdlr=RestrictedSchemaDotPropsHandler(self.req))
--- a/web/views/schemaentities.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-"""Specific views for schema related entities
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.schemaviewer import SchemaViewer
-from cubicweb.common.uilib import ureport_as_html
-from cubicweb.common.view import EntityView
-from cubicweb.web.views import baseviews
-
-
-class ImageView(EntityView):
-    accepts = ('EEType',)
-    id = 'image'
-    title = _('image')
-
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        url = entity.absolute_url(vid='eschemagraph')
-        self.w(u'<img src="%s" alt="%s"/>' % (
-            html_escape(url),
-            html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-
-class _SchemaEntityPrimaryView(baseviews.PrimaryView):
-    show_attr_label = False
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
-    
-    def content_title(self, entity):
-        return html_escape(entity.dc_long_title())
-    
-class EETypePrimaryView(_SchemaEntityPrimaryView):
-    accepts = ('EEType',)
-    skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final')
-
-class ERTypePrimaryView(_SchemaEntityPrimaryView):
-    accepts = ('ERType',)
-    skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final',
-                                                        'symetric', 'inlined')
-
-class ErdefPrimaryView(_SchemaEntityPrimaryView):
-    accepts = ('EFRDef', 'ENFRDef')
-    show_attr_label = True
-
-class EETypeSchemaView(EETypePrimaryView):
-    id = 'eschema'
-    title = _('in memory entity schema')
-    main_related_section = False
-    skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
-                 'has_text',)
-    
-    def render_entity_attributes(self, entity, siderelations):
-        super(EETypeSchemaView, self).render_entity_attributes(entity, siderelations)
-        eschema = self.vreg.schema.eschema(entity.name)
-        viewer = SchemaViewer(self.req)
-        layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels)
-        self.w(ureport_as_html(layout))
-        if not eschema.is_final():
-            self.w(u'<img src="%s" alt="%s"/>' % (
-                html_escape(entity.absolute_url(vid='eschemagraph')),
-                html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-class ERTypeSchemaView(ERTypePrimaryView):
-    id = 'eschema'
-    title = _('in memory relation schema')
-    main_related_section = False
-
-    def render_entity_attributes(self, entity, siderelations):
-        super(ERTypeSchemaView, self).render_entity_attributes(entity, siderelations)
-        rschema = self.vreg.schema.rschema(entity.name)
-        viewer = SchemaViewer(self.req)
-        layout = viewer.visit_relationschema(rschema)
-        self.w(ureport_as_html(layout))
-        if not rschema.is_final():
-            self.w(u'<img src="%s" alt="%s"/>' % (
-                html_escape(entity.absolute_url(vid='eschemagraph')),
-                html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-        
-class EETypeWorkflowView(EntityView):
-    id = 'workflow'
-    accepts = ('EEType',)
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
-    
-    def cell_call(self, row, col, **kwargs):
-        entity = self.entity(row, col)
-        self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
-                                 % display_name(self.req, entity.name)))
-        self.w(u'<img src="%s" alt="%s"/>' % (
-            html_escape(entity.absolute_url(vid='ewfgraph')),
-            html_escape(self.req._('graphical workflow for %s') % entity.name)))
-
-
-class EETypeOneLineView(baseviews.OneLineView):
-    accepts = ('EEType',)
-    
-    def cell_call(self, row, col, **kwargs):
-        entity = self.entity(row, col)
-        final = entity.final
-        if final:
-            self.w(u'<em class="finalentity">')
-        super(EETypeOneLineView, self).cell_call(row, col, **kwargs)
-        if final:
-            self.w(u'</em>')
-        
-
-from cubicweb.web.action import EntityAction
-
-class ViewWorkflowAction(EntityAction):
-    id = 'workflow'
-    category = 'mainactions'
-    title = _('view workflow')
-    accepts = ('EEType',)
-    condition = 'S state_of X' # must have at least one state associated
-    def url(self):
-        entity = self.rset.get_entity(self.row or 0, self.col or 0)
-        return entity.absolute_url(vid='workflow')
-        
--- a/web/views/sessions.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/sessions.py	Tue Apr 07 09:30:23 2009 +0200
@@ -2,12 +2,12 @@
 object :/
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.web import ExplicitLogin, InvalidSession
+from cubicweb.web import InvalidSession
 from cubicweb.web.application import AbstractSessionManager
 
 
--- a/web/views/startup.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/startup.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,10 +7,11 @@
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.common.textutils import unormalize
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.uilib import ureport_as_html, unormalize, ajax_replace_url
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
+from cubicweb.common.uilib import ureport_as_html, ajax_replace_url
 from cubicweb.web.httpcache import EtagHTTPCacheManager
 
 _ = unicode
--- a/web/views/tableview.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/tableview.py	Tue Apr 07 09:30:23 2009 +0200
@@ -11,13 +11,12 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.utils import make_uid
+from cubicweb.selectors import nonempty_rset, match_form_params
+from cubicweb.utils import make_uid
+from cubicweb.view import EntityView, AnyRsetView
 from cubicweb.common.uilib import toggle_action, limitsize, jsonize, htmlescape
-from cubicweb.common.view import EntityView, AnyRsetView
-from cubicweb.common.selectors import (nonempty_rset,  match_form_params,
-                                    accept_rset)
 from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
-                                   PopupBoxMenu, BoxLink)
+                                      PopupBoxMenu, BoxLink)
 from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
 
 class TableView(AnyRsetView):
@@ -215,7 +214,7 @@
 
             if cellattrs and colindex in cellattrs:
                 for name, value in cellattrs[colindex].iteritems():
-                    column.add_attr(name,value)
+                    column.add_attr(name, value)
             # add column
             columns.append(column)
         return columns
@@ -246,6 +245,7 @@
         entity = self.rset.get_entity(row, col)
         return entity.sortvalue()
 
+
 class EditableTableView(TableView):
     id = 'editable-table'
     finalview = 'editable-final'
@@ -253,10 +253,9 @@
 
     
 class CellView(EntityView):
-    __selectors__ = (nonempty_rset, accept_rset)
+    __select__ = nonempty_rset()
     
     id = 'cell'
-    accepts = ('Any',)
     
     def cell_call(self, row, col, cellvid=None):
         """
@@ -289,8 +288,7 @@
       displayed with default restrictions set
     """
     id = 'initialtable'
-    __selectors__ = nonempty_rset, match_form_params
-    form_params = ('actualrql',)
+    __select__ = nonempty_rset() & match_form_params('actualrql')
     # should not be displayed in possible view since it expects some specific
     # parameters
     title = None
--- a/web/views/tabs.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/tabs.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,22 +1,18 @@
 """base classes to handle tabbed views
 
 :organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
 __docformat__ = "restructuredtext en"
 
-from logilab.common.decorators import monkeypatch
 from logilab.mtconverter import html_escape
 
 from cubicweb import NoSelectableObject, role
-from cubicweb.common.view import EntityView 
-from cubicweb.common.selectors import has_related_entities
-from cubicweb.common.utils import HTMLHead
-from cubicweb.common.uilib import rql_for_eid
-
-from cubicweb.web.views.basecontrollers import JSonController
+from cubicweb.selectors import partial_has_related_entities
+from cubicweb.view import EntityView
+from cubicweb.common import tags, uilib
 
 
 class LazyViewMixin(object):
@@ -46,7 +42,7 @@
         if rql:
             urlparams['rql'] = rql
         elif eid:
-            urlparams['rql'] = rql_for_eid(eid)
+            urlparams['rql'] = uilib.rql_for_eid(eid)
         elif rset:
             urlparams['rql'] = rset.printable_rql()
         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
@@ -142,9 +138,8 @@
         'cookiename' : self.cookie_name})
 
 
-class EntityRelatedTab(EntityView):
-    """A view you should inherit from leftmost,
-    to wrap another actual view displaying entity related stuff.
+class EntityRelationView(EntityView):
+    """view displaying entity related stuff.
     Such a view _must_ provide the rtype, target and vid attributes :
 
     Example :
@@ -152,23 +147,22 @@
     class ProjectScreenshotsView(EntityRelationView):
         '''display project's screenshots'''
         id = title = _('projectscreenshots')
-        accepts = ('Project',)
+        __select__ = EntityRelationView.__select__ & implements('Project')
         rtype = 'screenshot'
-        target = 'object'
+        role = 'subject'
         vid = 'gallery'
-        __selectors__ = EntityRelationView.__selectors__ + (one_line_rset,)
 
-    This is the view we want to have in a tab, only if there is something to show.
-    Then, just define as below, and declare this being the tab content :
-
-    class ProjectScreenshotTab(EntityRelatedTab, ProjectScreenshotsView):
-        id = 'screenshots_tab'
+    in this example, entities related to project entity by the'screenshot'
+    relation (where the project is subject of the relation) will be displayed
+    using the 'gallery' view.
     """
-    __selectors__ = EntityView.__selectors__ + (has_related_entities,)
+    __select__ = EntityView.__select__ & partial_has_related_entities()
     vid = 'list'
-
+    
     def cell_call(self, row, col):
         rset = self.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.wview(self.vid, rset, 'noresult')
         self.w(u'</div>')
--- a/web/views/timeline.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/timeline.py	Tue Apr 07 09:30:23 2009 +0200
@@ -3,7 +3,7 @@
 cf. http://code.google.com/p/simile-widgets/
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -13,11 +13,10 @@
 from logilab.mtconverter import html_escape
 
 from cubicweb.interfaces import ICalendarable
-from cubicweb.common.view import EntityView, StartupView
-from cubicweb.common.selectors import implement_interface
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView, StartupView
 
 
-# 
 class TimelineJsonView(EntityView):
     """generates a json file to feed Timeline.loadJSON()
     NOTE: work in progress (image_url, bubbleUrl and so on
@@ -28,8 +27,7 @@
     templatable = False
     content_type = 'application/json'
 
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
+    __select__ = implements(ICalendarable)
     date_fmt = '%Y/%m/%d'
     
     def call(self):
@@ -103,8 +101,7 @@
 class TimelineView(TimelineViewMixIn, EntityView):
     """builds a cubicweb timeline widget node"""
     id = 'timeline'
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
+    __select__ = implements(ICalendarable)
     need_navigation = False
     def call(self, tlunit=None):
         self.req.html_headers.define_var('Timeline_urlPrefix', self.req.datadir_url)
@@ -120,5 +117,5 @@
     id = 'static-timeline'
     
     def call(self, loadurl, tlunit=None, wdgclass=None):
-        self.widget_class = wdgclass or self.widget_clas
+        self.widget_class = wdgclass or self.widget_class
         self.render(loadurl, tlunit)
--- a/web/views/timetable.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/timetable.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,16 +1,16 @@
 """html calendar views
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
 from logilab.mtconverter import html_escape
 
 from cubicweb.interfaces import ITimetableViews
-from cubicweb.common.utils import date_range
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.view import AnyRsetView
+from cubicweb.selectors import implements
+from cubicweb.utils import date_range
+from cubicweb.view import AnyRsetView
 
 
 class _TaskEntry(object):
@@ -25,8 +25,7 @@
 class TimeTableView(AnyRsetView):
     id = 'timetable'
     title = _('timetable')
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ITimetableViews,)
+    __select__ = implements(ITimetableViews)
     need_navigation = False
 
     def call(self, title=None):
@@ -39,9 +38,9 @@
 
         # XXX: try refactoring with calendar.py:OneMonthCal
         for row in xrange(self.rset.rowcount):
-            task = self.rset.get_entity(row,0)
+            task = self.rset.get_entity(row, 0)
             if len(self.rset[row])>1:
-                user = self.rset.get_entity(row,1)
+                user = self.rset.get_entity(row, 1)
             else:
                 user = u"*"
             the_dates = []
@@ -56,9 +55,9 @@
                 the_dates.append(task.stop)
             for d in the_dates:
                 d_users = dates.setdefault(d, {})
-                u_tasks = d_users.setdefault(user,set())
+                u_tasks = d_users.setdefault(user, set())
                 u_tasks.add( task )
-                task_max = users_max.setdefault(user,0)
+                task_max = users_max.setdefault(user, 0)
                 if len(u_tasks)>task_max:
                     users_max[user] = len(u_tasks)
             if user not in users:
@@ -72,7 +71,7 @@
 
         rows = []
         # colors here are class names defined in cubicweb.css
-        colors = [ "col%x"%i for i in range(12) ]
+        colors = ["col%x" % i for i in xrange(12)]
         next_color_index = 0
 
         visited_tasks = {} # holds a description of a task for a user
@@ -93,7 +92,7 @@
                     if key in visited_tasks:
                         task_descr = visited_tasks[ key ]
                         user_columns[task_descr.column] = task_descr, False
-                        task_descr.lines+=1
+                        task_descr.lines += 1
                     else:
                         postpone.append(key)
                 for key in postpone:
@@ -101,7 +100,7 @@
                     # (which must be the same for every user concerned
                     # by the task)
                     task, user = key
-                    for i,t in enumerate(user_columns):
+                    for i, t in enumerate(user_columns):
                         if t is None:
                             if task in task_colors:
                                 color = task_colors[task]
@@ -129,16 +128,16 @@
         self.w(u'</table>')
         self.w(u'</div>\n')
 
-    def render_col_headers(self,users,widths):
+    def render_col_headers(self, users, widths):
         """ render column headers """
         self.w(u'<tr class="header">\n')
 
         self.w(u'<th class="ttdate">&nbsp;</th>\n')
         columns = []
-        for user,width in zip(users,widths):
-            self.w(u'<th colspan="%s">' % max(MIN_COLS,width))
-            if user!=u"*":
-                user.view('secondary',w=self.w)
+        for user, width in zip(users, widths):
+            self.w(u'<th colspan="%s">' % max(MIN_COLS, width))
+            if user != u"*":
+                user.view('secondary', w=self.w)
             else:
                 self.w(user)
             self.w(u'</th>')
@@ -165,7 +164,7 @@
             previous_is_empty = False
 
             klass = "even"
-            if date.day_of_week in (5,6) and not empty_line:
+            if date.day_of_week in (5, 6) and not empty_line:
                 klass = "odd"
             self.w(u'<tr class="%s">' % klass)
             odd = not odd
--- a/web/views/treeview.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/treeview.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,22 +1,24 @@
 """Set of tree-building widgets, based on jQuery treeview plugin
 
 :organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+
 from logilab.mtconverter import html_escape
+
 from cubicweb.interfaces import ITree
-from cubicweb.common.selectors import implement_interface, yes
-from cubicweb.common.utils import make_uid
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
+from cubicweb.utils import make_uid
 
 def treecookiename(treeid):
     return str('treestate-%s' % treeid)
 
+
 class TreeView(EntityView):
     id = 'treeview'
-    accepts = ('Any',)
     itemvid = 'treeitemview'
     css_classes = 'treeview widget'
     title = _('tree view')
@@ -37,13 +39,13 @@
             self.req.add_css('jquery.treeview.css')
             self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
             self.req.html_headers.add_onload(u"""
-                 jQuery("#tree-%s").treeview({toggle: toggleTree,
-                                              prerendered: true});""" % treeid)
+jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
         self.w(u'<ul id="tree-%s" class="%s">' % (treeid, 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)
+                       vid=subvid, parentvid=self.id)
         self.w(u'</ul>')
+        
 
 class FileTreeView(TreeView):
     """specific version of the treeview to display file trees
@@ -55,6 +57,8 @@
     def call(self, subvid=None, treeid=None, initial_load=True):
         super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
 
+
+
 class FileItemInnerView(EntityView):
     """inner view used by the TreeItemView instead of oneline view
 
@@ -66,20 +70,17 @@
     def cell_call(self, row, col):
         entity = self.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'))
+            self.w(u'<div class="folder">%s</div>' % entity.view('oneline'))
         else:
             # XXX define specific CSS classes according to mime types
-            self.w(u'<div class="file">%s</div>\n' % entity.view('oneline'))
+            self.w(u'<div class="file">%s</div>' % entity.view('oneline'))
 
 
 class DefaultTreeViewItemView(EntityView):
-    """default treeitem view for entities which don't implement ITree
-    """
+    """default treeitem view for entities which don't implement ITree"""
     id = 'treeitemview'
-    accepts = ('Any',)
-
-    def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
-        assert treeid is not None
+    
+    def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
         entity = self.entity(row, col)
         itemview = self.view(vid, self.rset, row=row, col=col)
         if row == len(self.rset) - 1:
@@ -91,90 +92,36 @@
 class TreeViewItemView(EntityView):
     """specific treeitem view for entities which implement ITree
 
-    (each item should be exandable if it's not a tree leaf)
+    (each item should be expandable if it's not a tree leaf)
     """
     id = 'treeitemview'
-    # XXX append yes to make sure we get an higher score than
-    #     the default treeitem view
-    __selectors__ = (implement_interface, yes)
-    accepts_interfaces = (ITree,)
-
-    def open_state(self, eeid, treeid):
-        cookies = self.req.get_cookie()
-        treestate = cookies.get(treecookiename(treeid))
-        if treestate:
-            return str(eeid) in treestate.value.split(';')
-        return False
-
-    def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview'):
-        w = self.w
+    __select__ = implements(ITree)
+    
+    def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
         entity = self.entity(row, col)
-        liclasses = []
+        cssclasses = []
         is_leaf = False
-        is_last = row == len(self.rset) - 1
-        is_open = self.open_state(entity.eid, treeid)
+        if row == len(self.rset) - 1:
+            is_leaf = True
         if not hasattr(entity, 'is_leaf') or entity.is_leaf():
-            if is_last:
-                liclasses.append('last')
-            w(u'<li class="%s">' % u' '.join(liclasses))
+            if is_leaf : cssclasses.append('last')
+            self.w(u'<li class="%s">' % u' '.join(cssclasses))
         else:
             rql = entity.children_rql() % {'x': entity.eid}
             url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
                                              pageid=self.req.pageid,
-                                             treeid=treeid,
-                                             subvid=vid))
-            divclasses = ['hitarea']
-            if is_open:
-                liclasses.append('collapsable')
-                divclasses.append('collapsable-hitarea')
-            else:
-                liclasses.append('expandable')
-                divclasses.append('closed-hitarea expandable-hitarea')
-            if is_last:
-                if is_open:
-                    liclasses.append('lastCollapsable')
-                    divclasses.append('lastCollapsable-hitarea')
-                else:
-                    liclasses.append('lastExpandable')
-                    divclasses.append('lastExpandable-hitarea')
-            if is_open:
-                w(u'<li class="%s">' % u' '.join(liclasses))
-            else:
-                w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses)))
-            if is_leaf:
-                divtail = ''
-            else:
-                divtail = ''' onclick="async_remote_exec('node_clicked', '%s', '%s')"''' % \
-                    (treeid, entity.eid)
-            w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
-
+                                             subvid=vid,
+                                             noautoload=True))
+            cssclasses.append('expandable')
+            divclasses = ['hitarea expandable-hitarea']
+            if is_leaf :
+                cssclasses.append('lastExpandable')
+                divclasses.append('lastExpandable-hitarea')
+            self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses)))
+            self.w(u'<div class="%s"> </div>' % u' '.join(divclasses))
             # add empty <ul> because jquery's treeview plugin checks for
             # sublists presence
-            if not is_open:
-                w(u'<ul class="placeholder"><li>place holder</li></ul>')
-        # the local node info
+            self.w(u'<ul class="placeholder"><li>place holder</li></ul>')
         self.wview(vid, self.rset, row=row, col=col)
-        if is_open: # recurse if needed
-            self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
-        w(u'</li>')
-
-from logilab.common.decorators import monkeypatch
-from cubicweb.web.views.basecontrollers import JSonController
+        self.w(u'</li>')
 
-@monkeypatch(JSonController)
-def js_node_clicked(self, treeid, nodeeid):
-    """add/remove eid in treestate cookie"""
-    cookies = self.req.get_cookie()
-    statename = treecookiename(treeid)
-    treestate = cookies.get(statename)
-    if treestate is None:
-        cookies[statename] = nodeeid
-        self.req.set_cookie(cookies, statename)
-    else:
-        marked = set(filter(None, treestate.value.split(';')))
-        if nodeeid in marked:
-            marked.remove(nodeeid)
-        else:
-            marked.add(nodeeid)
-        cookies[statename] = ';'.join(marked)
-        self.req.set_cookie(cookies, statename)
--- a/web/views/urlpublishing.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/urlpublishing.py	Tue Apr 07 09:30:23 2009 +0200
@@ -18,7 +18,7 @@
 because of redirecting instead of direct traversal
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -28,7 +28,7 @@
 
 from cubicweb import RegistryException, typed_eid
 from cubicweb.web import NotFound, Redirect
-from cubicweb.web.component import SingletonComponent, Component
+from cubicweb.web.component import Component, Component
 
 
 class PathDontMatch(Exception):
@@ -36,7 +36,7 @@
     a path
     """
     
-class URLPublisherComponent(SingletonComponent):
+class URLPublisherComponent(Component):
     """associate url's path to view identifier / rql queries,
     by applying a chain of urlpathevaluator components.
 
--- a/web/views/urlrewrite.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/urlrewrite.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,22 +1,20 @@
 """Rules based url rewriter component, to get configurable RESTful urls
 
 :organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 import re
 
-from cubicweb.vregistry import autoselectors
-
+from cubicweb.appobject import AppObject
 from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.appobject import AppObject
 
 
 def rgx(pattern, flags=0):
     """this is just a convenient shortcout to add the $ sign"""
     return re.compile(pattern+'$', flags)
 
-class metarewriter(autoselectors):
+class metarewriter(type):
     """auto-extend rules dictionnary"""
     def __new__(mcs, name, bases, classdict):
         # collect baseclass' rules
@@ -59,7 +57,6 @@
     __abstract__ = True
 
     id = 'urlrewriting'
-    accepts = ('Any',)
     priority = 1
 
     def rewrite(self, req, uri):
@@ -95,7 +92,6 @@
         """for each `input`, `output `in rules, if `uri` matches `input`,
         req's form is updated with `output`
         """
-        rset = None
         for data in self.rules:
             try:
                 inputurl, infos, required_groups = data
@@ -112,7 +108,7 @@
                 # XXX what about i18n ? (vtitle for instance)
                 for param, value in infos.items():
                     if isinstance(value, basestring):
-                        req.form[param]= inputurl.sub(value, uri)
+                        req.form[param] = inputurl.sub(value, uri)
                     else:
                         req.form[param] = value
                 break
--- a/web/views/vcard.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/vcard.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,12 +1,13 @@
 """vcard import / export
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
 
 _ = unicode 
 
@@ -18,8 +19,7 @@
     title = _('vcard')
     templatable = False
     content_type = 'text/x-vcard'
-    accepts = ('EUser',)
-        
+    __select__ = implements('EUser')        
 
     def set_request_content_type(self):
         """overriden to set a .vcf filename"""
--- a/web/views/wdoc.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/wdoc.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """inline help system, using ReST file in products `wdoc` directory
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -9,14 +9,14 @@
 from itertools import chain
 from os.path import join
 from bisect import bisect_right
-
-from mx.DateTime import strptime, today
+from datetime import date
 
 from logilab.common.changelog import ChangeLog
 from logilab.mtconverter import CHARSET_DECL_RGX
 
-from cubicweb.common.selectors import match_form_params
-from cubicweb.common.view import StartupView
+from cubicweb.selectors import match_form_params
+from cubicweb.view import StartupView
+from cubicweb.utils import strptime
 from cubicweb.common.uilib import rest_publish
 from cubicweb.web import NotFound
 
@@ -74,7 +74,7 @@
             build_toc_index(section, index)
     return index
     
-def title(node, lang):
+def title_for_lang(node, lang):
     for title in node.findall('title'):
         if title.attrib['{http://www.w3.org/XML/1998/namespace}lang'] == lang:
             return unicode(title.text)
@@ -85,8 +85,7 @@
 # help views ##################################################################
 
 class InlineHelpView(StartupView):
-    __selectors__ = (match_form_params,)
-    form_params = ('fid',)
+    __select__ = match_form_params('fid')
     id = 'wdoc'
     title = _('site documentation')
     
@@ -108,7 +107,7 @@
         else:
             self.navigation_links(node)
             self.w(u'<div class="hr"></div>')
-            self.w(u'<h1>%s</h1>' % (title(node, self.req.lang)))            
+            self.w(u'<h1>%s</h1>' % (title_for_lang(node, self.req.lang)))            
         data = open(join(resourcedir, rid)).read()
         self.w(rest_publish(self, data))
         if node is not None:
@@ -142,7 +141,7 @@
         self.w(u'%s : ' % self.req._(msgid))
         self.w(u'<a href="%s">%s</a>' % (
             self.req.build_url('doc/'+node.attrib['resource']),
-            title(node, self.req.lang)))
+            title_for_lang(node, self.req.lang)))
         self.w(u'</span>\n')
         
     def subsections_links(self, node, first=True):
@@ -155,7 +154,7 @@
         for child in sub:
             self.w(u'<li><a href="%s">%s</a>' % (
                 self.req.build_url('doc/'+child.attrib['resource']),
-                title(child, self.req.lang)))
+                title_for_lang(child, self.req.lang)))
             self.subsections_links(child, False)
             self.w(u'</li>')
         self.w(u'</ul>\n')
@@ -163,9 +162,8 @@
 
 
 class InlineHelpImageView(StartupView):
-    __selectors__ = (match_form_params,)
-    form_params = ('fid',)
     id = 'wdocimages'
+    __select__ = match_form_params('fid')
     binary = True
     templatable = False
     content_type = 'image/png'
@@ -207,9 +205,9 @@
                     w(unicode(line, encoding))
             for entry in cl.entries:
                 if entry.date:
-                    date = strptime(entry.date, '%Y-%m-%d')
+                    edate = strptime(entry.date, '%Y-%m-%d')
                 else:
-                    date = today()
+                    edate = date.today()
                 messages = []
                 for msglines, submsgs in entry.messages:
                     msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
@@ -218,15 +216,15 @@
                         msgstr += '     - ' + unicode(' '.join(l.strip() for l in submsglines), encoding)
                         msgstr += u'\n'
                     messages.append(msgstr)
-                entry = (date, messages)
+                entry = (edate, messages)
                 allentries.insert(bisect_right(allentries, entry), entry)
         latestdate = None
         i = 0
-        for date, messages in reversed(allentries):
-            if latestdate != date:
-                fdate = self.format_date(date)
+        for edate, messages in reversed(allentries):
+            if latestdate != edate:
+                fdate = self.format_date(edate)
                 w(u'\n%s' % fdate)
-                w('~'*len(fdate))
+                w('~' * len(fdate))
                 latestdate = date
             for msg in messages:
                 w(u'* %s' % msg)
--- a/web/views/wfentities.py	Tue Apr 07 08:55:37 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-"""html view for workflow related entities
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.common.view import EntityView
-
-class CellView(EntityView):
-    id = 'cell'
-    accepts = ('TrInfo',)
-    def cell_call(self, row, col, cellvid=None):
-        entity = self.entity(row, col)
-        self.w(entity.printable_value('comment'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/workflow.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,220 @@
+"""workflow views:
+
+* IWorkflowable views and forms
+* workflow entities views (State, Transition, TrInfo)
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.mtconverter import html_escape
+from logilab.common.graph import escape, GraphGenerator, DotBackend
+
+from cubicweb import Unauthorized, view
+from cubicweb.selectors import (implements, has_related_entities,
+                                relation_possible, match_form_params)
+from cubicweb.interfaces import IWorkflowable
+from cubicweb.web import stdmsgs, action, component, form
+from cubicweb.web.formfields import StringField,  RichTextField
+from cubicweb.web.formwidgets import HiddenInput
+from cubicweb.web.views import TmpFileViewMixin
+from cubicweb.web.views.boxes import EditBox
+
+
+EditBox.rmode.set_rtag('create', 'destination_state', 'subject', 'Transition')
+EditBox.rmode.set_rtag('create', 'allowed_transition', 'object', 'Transition')
+EditBox.rmode.set_rtag('create', 'destination_state', 'object', 'State')
+EditBox.rmode.set_rtag('create', 'allowed_transition', 'subject', 'State')
+
+
+# IWorkflowable views #########################################################
+
+class ChangeStateForm(form.EntityFieldsForm):
+    id = 'changestate'
+    
+    __method = StringField(name='__method', initial='set_state', widget=HiddenInput)
+    state = StringField(widget=HiddenInput, eidparam=True)
+    trcomment = RichTextField(eidparam=True)
+
+    def form_buttons(self):
+        return [self.button_ok(label=stdmsgs.YES,
+                               tabindex=self.req.next_tabindex()),
+                self.button_cancel(label=stdmsgs.NO,
+                                   tabindex=self.req.next_tabindex())]
+
+        
+class ChangeStateFormView(view.EntityView):
+    id = 'statuschange'
+    title = _('status change')
+    __select__ = implements(IWorkflowable) & match_form_params('treid')
+
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+        state = entity.in_state[0]
+        transition = self.req.eid_rset(self.req.form['treid']).get_entity(0, 0)
+        dest = transition.destination()
+        _ = self.req._
+        form = self.vreg.select_object('forms', 'changestate', self.req, self.rset, row=row, col=col,
+                                       entity=entity, redirect_path=self.redirectpath(entity))
+        self.w(form.error_message())
+        self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
+        msg = _('status will change from %(st1)s to %(st2)s') % {
+            'st1': _(state.name),
+            'st2': _(dest.name)}
+        self.w(u'<p>%s</p>\n' % msg)
+        self.w(form.form_render(state=dest.eid, trcomment=u''))
+
+    def redirectpath(self, entity):
+        return entity.rest_path()
+
+
+class WFHistoryVComponent(component.EntityVComponent):
+    """display the workflow history for entities supporting it"""
+    id = 'wfhistory'
+    __select__ = (component.EntityVComponent.__select__
+                  & relation_possible('wf_info_for', role='object'))
+    context = 'navcontentbottom'
+    title = _('Workflow history')
+
+    def cell_call(self, row, col, view=None):
+        _ = self.req._
+        eid = self.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('EUser').has_perm(self.req, 'read'):
+            sel += ',U,C'
+            rql += ', WF owned_by U?'
+            displaycols = range(5)
+            headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
+                       _('EUser'))            
+        else:
+            sel += ',C'
+            displaycols = range(4)
+            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')
+        except Unauthorized:
+            return
+        if rset:
+            self.wview('table', rset, title=_(self.title), displayactions=False,
+                       displaycols=displaycols, headers=headers)
+
+
+# workflow entity types views #################################################
+
+class CellView(view.EntityView):
+    id = 'cell'
+    __select__ = implements('TrInfo')
+    
+    def cell_call(self, row, col, cellvid=None):
+        self.w(self.entity(row, col).printable_value('comment'))
+
+
+class StateInContextView(view.EntityView):
+    """convenience trick, State's incontext view should not be clickable"""
+    id = 'incontext'
+    __select__ = implements('State')
+    
+    def cell_call(self, row, col):
+        self.w(html_escape(self.view('textincontext', self.rset,
+                                     row=row, col=col)))
+
+
+# workflow images #############################################################
+        
+class ViewWorkflowAction(action.Action):
+    id = 'workflow'
+    __select__ = implements('EEType') & has_related_entities('state_of', 'object')
+    
+    category = 'mainactions'
+    title = _('view workflow')
+    def url(self):
+        entity = self.rset.get_entity(self.row or 0, self.col or 0)
+        return entity.absolute_url(vid='workflow')
+
+        
+class EETypeWorkflowView(view.EntityView):
+    id = 'workflow'
+    __select__ = implements('EEType')
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+    
+    def cell_call(self, row, col, **kwargs):
+        entity = self.entity(row, col)
+        self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
+                                 % display_name(self.req, entity.name)))
+        self.w(u'<img src="%s" alt="%s"/>' % (
+            html_escape(entity.absolute_url(vid='ewfgraph')),
+            html_escape(self.req._('graphical workflow for %s') % entity.name)))
+
+
+class WorkflowDotPropsHandler(object):
+    def __init__(self, req):
+        self._ = req._
+        
+    def node_properties(self, stateortransition):
+        """return default DOT drawing options for a state or transition"""
+        props = {'label': stateortransition.name, 
+                 'fontname': 'Courier'}
+        if hasattr(stateortransition, 'state_of'):
+            props['shape'] = 'box'
+            props['style'] = 'filled'
+            if stateortransition.reverse_initial_state:
+                props['color'] = '#88CC88'
+        else:
+            props['shape'] = 'ellipse'
+            descr = []
+            tr = stateortransition
+            if tr.require_group:
+                descr.append('%s %s'% (
+                    self._('groups:'),
+                    ','.join(g.name for g in tr.require_group)))
+            if tr.condition:
+                descr.append('%s %s'% (self._('condition:'), tr.condition))
+            if descr:
+                props['label'] += escape('\n'.join(descr))
+        return props
+    
+    def edge_properties(self, transition, fromstate, tostate):
+        return {'label': '', 'dir': 'forward',
+                'color': 'black', 'style': 'filled'}
+
+
+class WorkflowVisitor:
+    def __init__(self, entity):
+        self.entity = entity
+
+    def nodes(self):
+        for state in self.entity.reverse_state_of:
+            state.complete()
+            yield state.eid, state
+            
+        for transition in self.entity.reverse_transition_of:
+            transition.complete()
+            yield transition.eid, transition
+            
+    def edges(self):
+        for transition in self.entity.reverse_transition_of:
+            for incomingstate in transition.reverse_allowed_transition:
+                yield incomingstate.eid, transition.eid, transition
+            yield transition.eid, transition.destination().eid, transition
+
+
+class EETypeWorkflowImageView(TmpFileViewMixin, view.EntityView):
+    id = 'ewfgraph'
+    content_type = 'image/png'
+    __select__ = implements('EEType')
+    
+    def _generate(self, tmpfile):
+        """display schema information for an entity"""
+        entity = self.entity(self.row, self.col)
+        visitor = WorkflowVisitor(entity)
+        prophdlr = WorkflowDotPropsHandler(self.req)
+        generator = GraphGenerator(DotBackend('workflow', 'LR',
+                                              ratio='compress', size='30,12'))
+        return generator.generate(visitor, prophdlr, tmpfile)
+
--- a/web/views/xbel.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/views/xbel.py	Tue Apr 07 09:30:23 2009 +0200
@@ -1,7 +1,7 @@
 """xbel views
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -9,7 +9,9 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.web.views.baseviews import XmlView, EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
+from cubicweb.web.views.xmlrss import XmlView
 
 
 class XbelView(XmlView):
@@ -45,9 +47,10 @@
 
     def url(self, entity):
         return entity.absolute_url()
+
         
 class XbelItemBookmarkView(XbelItemView):
-    accepts = ('Bookmark',)
+    __select__ = implements('Bookmark')
 
     def url(self, entity):
         return entity.actual_url()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/xmlrss.py	Tue Apr 07 09:30:23 2009 +0200
@@ -0,0 +1,205 @@
+"""base xml and rss views
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from time import timezone
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.selectors import non_final_entity, one_line_rset, appobject_selectable
+from cubicweb.view import EntityView, AnyRsetView
+from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
+from cubicweb.web.component import Component
+from cubicweb.web.box import BoxTemplate
+from cubicweb.common.uilib import simple_sgml_tag
+
+_ = unicode
+
+
+# base xml views ##############################################################
+
+class XmlView(EntityView):
+    """xml view for entities"""
+    id = 'xml'
+    title = _('xml')
+    templatable = False
+    content_type = 'text/xml'
+    xml_root = 'rset'
+    item_vid = 'xmlitem'
+    
+    def cell_call(self, row, col):
+        self.wview(self.item_vid, self.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.cell_call(i, 0)
+        self.w(u'</%s>\n' % self.xml_root)
+
+
+class XmlItemView(EntityView):
+    id = 'xmlitem'
+
+    def cell_call(self, row, col):
+        """ element as an item for an xml feed """
+        entity = self.complete_entity(row, col)
+        self.w(u'<%s>\n' % (entity.e_schema))
+        for rschema, attrschema in entity.e_schema.attribute_definitions():
+            attr = rschema.type
+            try:
+                value = entity[attr]
+            except KeyError:
+                # Bytes
+                continue
+            if value is not None:
+                if attrschema == 'Bytes':
+                    from base64 import b64encode
+                    value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
+                elif isinstance(value, basestring):
+                    value = xml_escape(value)
+                self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
+        self.w(u'</%s>\n' % (entity.e_schema))
+
+    
+class XmlRsetView(AnyRsetView):
+    """dumps raw rset as xml"""
+    id = 'rsetxml'
+    title = _('xml export')
+    templatable = False
+    content_type = 'text/xml'
+    xml_root = 'rset'
+        
+    def call(self):
+        w = self.w
+        rset, descr = self.rset, self.rset.description
+        eschema = self.schema.eschema
+        labels = self.columns_labels(False)
+        w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
+        for rowindex, row in enumerate(self.rset):
+            w(u' <row>\n')
+            for colindex, val in enumerate(row):
+                etype = descr[rowindex][colindex]
+                tag = labels[colindex]
+                attrs = {}
+                if '(' in tag:
+                    attrs['expr'] = tag
+                    tag = 'funccall'
+                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)
+                else:
+                    val = self.view('final', rset, displaytime=True,
+                                    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)
+
+    
+# RSS stuff ###################################################################
+
+class RSSFeedURL(Component):
+    id = 'rss_feed_url'
+    __select__ = non_final_entity()
+    
+    def feed_url(self):
+        return self.build_url(rql=self.limited_rql(), vid='rss')
+
+
+class RSSEntityFeedURL(Component):
+    id = 'rss_feed_url'
+    __select__ = non_final_entity() & one_line_rset()
+    
+    def feed_url(self):
+        return self.entity(0, 0).rss_feed_url()
+
+        
+class RSSIconBox(BoxTemplate):
+    """just display the RSS icon on uniform result set"""
+    id = 'rss'
+    __select__ = (BoxTemplate.__select__
+                  & appobject_selectable('components', 'rss_feed_url'))
+    
+    visible = False
+    order = 999
+    
+    def call(self, **kwargs):
+        try:
+            rss = self.req.external_resource('RSS_LOGO')
+        except KeyError:
+            self.error('missing RSS_LOGO external resource')
+            return
+        urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.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'
+    title = _('rss')
+    templatable = False
+    content_type = 'text/xml'
+    http_cache_manager = MaxAgeHTTPCacheManager
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+    
+    def cell_call(self, row, col):
+        self.wview('rssitem', self.rset, row=row, col=col)
+        
+    def call(self):
+        """display a list of entities by calling their <item_vid> view"""
+        req = self.req
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
+        self.w(u'''<rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns="http://purl.org/rss/1.0/"
+>''')
+        self.w(u'  <channel rdf:about="%s">\n' % xml_escape(req.url()))
+        self.w(u'    <title>%s RSS Feed</title>\n' % xml_escape(self.page_title()))
+        self.w(u'    <description>%s</description>\n' % 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'    <items>\n')
+        self.w(u'      <rdf:Seq>\n')
+        for entity in self.rset.entities():
+            self.w(u'      <rdf:li resource="%s" />\n' % xml_escape(entity.absolute_url()))
+        self.w(u'      </rdf:Seq>\n')
+        self.w(u'    </items>\n')
+        self.w(u'  </channel>\n')
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</rdf:RDF>')
+
+
+class RssItemView(EntityView):
+    id = 'rssitem'
+    date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
+
+    def cell_call(self, row, col):
+        entity = self.complete_entity(row, col)
+        self.w(u'<item rdf:about="%s">\n' % xml_escape(entity.absolute_url()))
+        self._marker('title', entity.dc_long_title())
+        self._marker('link', entity.absolute_url())
+        self._marker('description', entity.dc_description())
+        self._marker('dc:date', entity.dc_date(self.date_format))
+        if entity.creator:
+            self.w(u'<author>')
+            self._marker('name', entity.creator.name())
+            email = entity.creator.get_email()
+            if email:
+                self._marker('email', email)
+            self.w(u'</author>')
+        self.w(u'</item>\n')
+        
+    def _marker(self, marker, value):
+        if value:
+            self.w(u'  <%s>%s</%s>\n' % (marker, xml_escape(value), marker))
--- a/web/webconfig.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/webconfig.py	Tue Apr 07 09:30:23 2009 +0200
@@ -7,8 +7,7 @@
 __docformat__ = "restructuredtext en"
 
 import os
-from os.path import join, dirname, exists, split
-from urlparse import urljoin
+from os.path import join, exists, split
 
 from logilab.common.configuration import Method
 from logilab.common.decorators import cached
@@ -26,7 +25,7 @@
       'sitewide': True, 'group': 'ui', 
       }),
     ('main-template',
-     {'type' : 'string', 'default': 'main',
+     {'type' : 'string', 'default': 'main-template',
       'help': _('id of main template used to render pages'),
       'sitewide': True, 'group': 'ui',
       }),
@@ -193,12 +192,21 @@
         try:
             cube = self.cubes()[0]
             cubeeid = self.cube_pkginfo(cube).cube_eid
-        except Exception, ex:
+        except Exception:
             return None
         if cubeeid:
             return 'http://intranet.logilab.fr/jpl/view?__linkto=concerns:%s:subject&etype=Ticket&type=bug&vid=creation' % cubeeid
         return None
 
+    def fckeditor_installed(self):
+        return exists(self.ext_resources['FCKEDITOR_PATH'])
+    
+    def eproperty_definitions(self):
+        for key, pdef in super(WebConfiguration, self).eproperty_definitions():
+            if key == 'ui.fckeditor' and not self.fckeditor_installed():
+                continue
+            yield key, pdef
+                
     # method used to connect to the repository: 'inmemory' / 'pyro'
     # Pyro repository by default
     repo_method = 'pyro'
--- a/web/webctl.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/webctl.py	Tue Apr 07 09:30:23 2009 +0200
@@ -23,8 +23,8 @@
             print '-' * 72
             config.input_config('pyro-client', inputlevel)
         if confirm('allow anonymous access', False):
-           config.global_set_option('anonymous-user', 'anon') 
-           config.global_set_option('anonymous-password', 'anon') 
+            config.global_set_option('anonymous-user', 'anon') 
+            config.global_set_option('anonymous-password', 'anon') 
         
     def postcreate(self):
         """hooks called once application's initialization has been completed"""
--- a/web/widgets.py	Tue Apr 07 08:55:37 2009 +0200
+++ b/web/widgets.py	Tue Apr 07 09:30:23 2009 +0200
@@ -4,13 +4,12 @@
 serialization time
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
-from simplejson import dumps
-from mx.DateTime import now, today
+from datetime import datetime
 
 from logilab.mtconverter import html_escape
 
@@ -391,11 +390,7 @@
         if not entity.has_eid():
             return u''
         return entity.printable_value(self.name)
-    
-    def add_fckeditor_info(self, req):
-        req.add_js('fckeditor.js')
-        req.fckeditor_config()
-    
+        
     def _edit_render(self, entity, with_format=True):
         req = entity.req
         editor = self._edit_render_textarea(entity, with_format)
@@ -411,10 +406,10 @@
         if isinstance(dvalue, basestring):
             dvalue = html_escape(dvalue)
         if entity.use_fckeditor(self.name):
-            self.add_fckeditor_info(entity.req)
+            entity.req.fckeditor_config()
             if with_format:
                 if entity.has_eid():
-                    format = entity.format(self.name)
+                    format = entity.attribute_metadata(self.name, 'format')
                 else:
                     format = ''
                 frname = eid_param(self.name + '_format', entity.eid)
@@ -423,7 +418,7 @@
                     frname, format, frname)
             return u'%s<textarea cubicweb:type="wysiwyg" onkeypress="autogrow(this)" name="%s" %s>%s</textarea>' % (
                 hidden, self.rname, self.format_attrs(), dvalue)
-        if with_format and entity.has_format(self.name):
+        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()
@@ -471,7 +466,8 @@
     def _file_wdg(self, entity):
         wdgs = [u'<input type="file" name="%s" %s/>' % (self.rname, self.format_attrs())]
         req = entity.req
-        if entity.has_format(self.name) or entity.has_text_encoding(self.name):
+        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>' %
                         (html_escape(toggle_action(divid)),
@@ -517,14 +513,14 @@
     
     def _edit_render(self, entity):
         wdgs = [self._file_wdg(entity)]
-        if entity.format(self.name) in ('text/plain', 'text/html', 'text/rest'):
+        if entity.attribute_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.text_encoding(self.name)
+                encoding = entity.attribute_metadata(self.name, 'encoding')
                 try:
                     entity[self.name] = unicode(data.getvalue(), encoding)
                 except UnicodeError:
@@ -667,6 +663,7 @@
         res.append(u'<a href="javascript:noop()" id="add_newopt">&nbsp;</a></div>')
         return '\n'.join(res)
 
+
 class IntegerWidget(StringWidget):
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
         kwattrs['size'] = 5
@@ -676,7 +673,6 @@
     def render_example(self, req):
         return '23'
     
-
         
 class FloatWidget(StringWidget):
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
@@ -700,6 +696,7 @@
             return [formatstr % value]
         return ()
 
+
 class DecimalWidget(StringWidget):
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
         kwattrs['size'] = 5
@@ -708,17 +705,25 @@
         
     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")
+    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)
@@ -734,17 +739,7 @@
 
     def render_example(self, req):
         formatstr = req.property_value(self.format_key)
-        return now().strftime(formatstr)
-
-    @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)
+        return datetime.now().strftime(formatstr)
 
 
     def _edit_render(self, entity):
@@ -773,7 +768,7 @@
         req.add_css(('cubicweb.calendar_popup.css',))
         inputid = self.attrs.get('id', self.rname)
         helperid = "%shelper" % inputid
-        _today = today()
+        _today = datetime.now()
         year = int(req.form.get('year', _today.year))
         month = int(req.form.get('month', _today.month))
 
@@ -784,24 +779,21 @@
 
 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': now().strftime(formatstr1),
-            'fmt2': now().strftime(formatstr2),
+            'fmt1': datetime.now().strftime(formatstr1),
+            'fmt2': datetime.now().strftime(formatstr2),
             }
 
 
-
-
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs['size'] = 16
-        kwattrs['maxlength'] = 16
-        DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-
 class TimeWidget(StringWidget):
     format_key = 'ui.time-format'
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):