merge
authorsylvain.thenault@logilab.fr
Thu, 14 May 2009 12:50:34 +0200
changeset 1813 4e4333bb56b3
parent 1810 e95e876be17c (diff)
parent 1812 4307a461f029 (current diff)
child 1814 0c7b09121507
merge
--- a/.hgtags	Thu May 14 12:50:14 2009 +0200
+++ b/.hgtags	Thu May 14 12:50:34 2009 +0200
@@ -22,3 +22,7 @@
 dfaedb0bba88e3a4e931948bb0c6a9587269303f cubicweb-debian-version-3_1_1-1
 a9ba200ab15098704a6255387c558c02488551c6 cubicweb-version-3_1_2
 a823124b812f4fa494bfceb773f3ca1cd00407e8 cubicweb-debian-version-3_1_2-1
+a5dc91adb7c133f83f5ad9cceb07bc246d21ed01 cubicweb-version-3_1_3
+9e98dec0768b87363a7826a04636dc161ed0ec7d cubicweb-debian-version-3_1_3-1
+e0e0a1c3d80f4fbf4bbd55066278e467b75df8a4 cubicweb-version-3_1_4
+0e132fbae9cc5e004f4b79a8b842addad43519a7 cubicweb-debian-version-3_1_4-1
--- a/MANIFEST.in	Thu May 14 12:50:14 2009 +0200
+++ b/MANIFEST.in	Thu May 14 12:50:34 2009 +0200
@@ -25,6 +25,6 @@
 recursive-include web/test/data *.js *.css *.png *.gif *.jpg *.ico external_resources
 recursive-include devtools/test/data *
 
-recursive-include skeleton *.py*.css *.js *.po compat *.in *.tmpl
+recursive-include skeleton *.py *.css *.js *.po compat *.in *.tmpl
 
 prune misc/cwfs
--- a/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -22,13 +22,7 @@
 from urllib import quote as urlquote, unquote as urlunquote
 
 from logilab.common.decorators import cached
-
-def set_log_methods(cls, logger):
-    """bind standart logger's methods as static methods on the class
-    """
-    cls._logger = logger
-    for attr in ('debug', 'info', 'warning', 'error', 'critical', 'exception'):
-        setattr(cls, attr, getattr(logger, attr))
+from logilab.common.logging_ext import set_log_methods
 
 if os.environ.get('APYCOT_ROOT'):
     logging.basicConfig(level=logging.CRITICAL)
@@ -74,7 +68,7 @@
             encoding = 'utf-8'
         self.encoding = encoding
         # cache result of execution for (rql expr / eids),
-        # should be emptied on commit/rollback of the server session / web 
+        # should be emptied on commit/rollback of the server session / web
         # connection
         self.local_perm_cache = {}
 
@@ -82,14 +76,14 @@
         if self.user:
             return self.user.property_value(key)
         return self.vreg.property_value(key)
-    
+
     def etype_rset(self, etype, size=1):
         """return a fake result set for a particular entity type"""
         from cubicweb.rset import ResultSet
         rset = ResultSet([('A',)]*size, '%s X' % etype,
                          description=[(etype,)]*size)
-        def get_entity(row, col=0, etype=etype, 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)
 
@@ -114,7 +108,7 @@
             return None
 
     # url generation methods ##################################################
-    
+
     def build_url(self, method, base_url=None, **kwargs):
         """return an absolute URL using params dictionary key/values as URL
         parameters. Values are automatically URL quoted, and the
@@ -130,7 +124,7 @@
         if not kwargs:
             return u'%s%s' % (base_url, path)
         return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
-        
+
 
     def build_url_params(self, **kwargs):
         """return encoded params to incorporate them in an URL"""
@@ -154,7 +148,7 @@
 
     def url_unquote(self, quoted):
         """returns a unicode unquoted string
-        
+
         decoding is based on `self.encoding` which is the encoding
         used in `url_quote`
         """
@@ -164,10 +158,10 @@
             return unicode(urlunquote(quoted), self.encoding)
         except UnicodeDecodeError: # might occurs on manually typed URLs
             return unicode(urlunquote(quoted), 'iso-8859-1')
-    
+
 
     # session's user related methods #####################################
-    
+
     @cached
     def user_data(self):
         """returns a dictionnary with this user's information"""
@@ -197,31 +191,44 @@
         return False
 
     # abstract methods to override according to the web front-end #############
-    
+
     def base_url(self):
         """return the root url of the application"""
         raise NotImplementedError
-    
+
     def decorate_rset(self, rset):
         """add vreg/req (at least) attributes to the given result set """
         raise NotImplementedError
-    
+
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         raise NotImplementedError
-        
+
 
-# XXX 2.45 is allowing nicer entity type names, use this map for bw compat    
-ETYPE_NAME_MAP = {'Eetype': 'EEType',
-                  'Ertype': 'ERType',
-                  'Efrdef': 'EFRDef',
-                  'Enfrdef': 'ENFRDef',
-                  'Econstraint': 'EConstraint',
-                  'Econstrainttype': 'EConstraintType',
-                  'Epermission': 'EPermission',
-                  'Egroup': 'EGroup',
-                  'Euser': 'EUser',
-                  'Eproperty': 'EProperty',
+# XXX 2.45 is allowing nicer entity type names, use this map for bw compat
+ETYPE_NAME_MAP = {# 3.2 migration
+                  'ECache': 'CWCache',
+                  'EUser': 'CWUser',
+                  'EGroup': 'CWGroup',
+                  'EProperty': 'CWProperty',
+                  'EFRDef': 'CWAttribute',
+                  'ENFRDef': 'CWRelation',
+                  'ERType': 'CWRType',
+                  'EEType': 'CWEType',
+                  'EConstraintType': 'CWConstraintType',
+                  'EConstraint': 'CWConstraint',
+                  'EPermission': 'CWPermission',
+                   # 2.45 migration
+                  'Eetype': 'CWEType',
+                  'Ertype': 'CWRType',
+                  'Efrdef': 'CWAttribute',
+                  'Enfrdef': 'CWRelation',
+                  'Econstraint': 'CWConstraint',
+                  'Econstrainttype': 'CWConstraintType',
+                  'Epermission': 'CWPermission',
+                  'Egroup': 'CWGroup',
+                  'Euser': 'CWUser',
+                  'Eproperty': 'CWProperty',
                   'Emailaddress': 'EmailAddress',
                   'Rqlexpression': 'RQLExpression',
                   'Trinfo': 'TrInfo',
@@ -255,7 +262,7 @@
                     'ezone': 'zone',
                     'i18ncontent': 'i18ncontent',
                     'svnfile': 'vcsfile',
-                    
+
                     'eclassschemes': 'keyword',
                     'eclassfolders': 'folder',
                     'eclasstags': 'tag',
@@ -269,11 +276,6 @@
                     'agueol': 'agueol',
                     'docaster': 'docaster',
                     'asteretud': 'asteretud',
-                    
-                    # XXX temp
-                    'keywords': 'keyword',
-                    'folders': 'folder',
-                    'tags': 'tag',
                     }
 
 def neg_role(role):
@@ -292,4 +294,4 @@
         return obj.target
     except AttributeError:
         return neg_role(obj.role)
-        
+
--- a/__pkginfo__.py	Thu May 14 12:50:14 2009 +0200
+++ b/__pkginfo__.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 1, 2)
+numversion = (3, 2, 0)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL v2'
--- a/_exceptions.py	Thu May 14 12:50:14 2009 +0200
+++ b/_exceptions.py	Thu May 14 12:50:34 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"
@@ -26,9 +26,9 @@
     """a misconfiguration error"""
 
 class InternalError(CubicWebException):
-    """base class for exceptions which should not occurs"""    
+    """base class for exceptions which should not occurs"""
 
-class SecurityError(CubicWebException): 
+class SecurityError(CubicWebException):
     """base class for cubicweb server security exception"""
 
 class RepositoryError(CubicWebException):
@@ -39,7 +39,7 @@
 
 class CubicWebRuntimeError(CubicWebException):
     """base class for runtime exceptions"""
-    
+
 # repository exceptions #######################################################
 
 class ConnectionError(RepositoryError):
@@ -53,7 +53,7 @@
 class BadConnectionId(ConnectionError):
     """raised when a bad connection id is given or when an attempt to establish
     a connection failed"""
-    
+
 BadSessionId = BadConnectionId # XXX bw compat for pyro connections
 
 class UnknownEid(RepositoryError):
@@ -68,7 +68,7 @@
     """no source support a relation type"""
     msg = 'No source supports %r relation\'s type'
 
-    
+
 # security exceptions #########################################################
 
 class Unauthorized(SecurityError):
@@ -80,7 +80,7 @@
     var = None
     #def __init__(self, *args):
     #    self.args = args
-        
+
     def __str__(self):
         try:
             if self.args and len(self.args) == 2:
@@ -90,7 +90,7 @@
             return self.msg
         except Exception, ex:
             return str(ex)
-    
+
 # source exceptions ###########################################################
 
 class EidNotInSource(SourceException):
@@ -98,8 +98,8 @@
     source has failed
     """
     msg = 'No entity with eid %s in %s'
-    
-    
+
+
 # registry exceptions #########################################################
 
 class RegistryException(CubicWebException):
@@ -110,16 +110,16 @@
 
     this is usually a programming/typo error...
     """
-    
+
 class ObjectNotFound(RegistryException):
     """raised when an unregistered object is requested
 
     this may be a programming/typo or a misconfiguration error
     """
-    
+
 # class ViewNotFound(ObjectNotFound):
 #     """raised when an unregistered view is called"""
-    
+
 class NoSelectableObject(RegistryException):
     """some views with the given vid have been found but no
     one is applyable to the result set
@@ -144,5 +144,5 @@
     """server execution control error (already started, not running...)"""
 
 # pylint: disable-msg=W0611
-from logilab.common.clcommands import BadCommandUsage 
+from logilab.common.clcommands import BadCommandUsage
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/appobject.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,329 @@
+"""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 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 vreg_initialization_completed(cls):
+        pass
+
+    @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 CWProperty key is build using
+    #      <registry name>.<obj id>.<property id>
+    # value: tuple (property type, vocabfunc, default value, property description)
+    #        possible types are those used by `logilab.common.configuration`
+    #
+    # notice that when it exists multiple objects with the same id (adaptation,
+    # overriding) only the first encountered definition is considered, so those
+    # objects can't try to have different default values for instance.
+
+    property_defs = {}
+
+    @classmethod
+    def register_properties(cls):
+        for propid, pdef in cls.property_defs.items():
+            pdef = pdef.copy() # may be shared
+            pdef['default'] = getattr(cls, propid, pdef['default'])
+            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
+            cls.vreg.register_property(cls.propkey(propid), **pdef)
+
+    @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 CWCache, C name %(name)s, C timestamp T',
+                                      {'name':cachename}).get_entity(0,0)
+            cache.latest_cache_lookup = _now
+            if not ecache.valid(cache.cache_creation_date):
+                cache.clear()
+                cache.cache_creation_date = _now
+        return cache
+
+    def propval(self, propid):
+        assert self.req
+        return self.req.property_value(self.propkey(propid))
+
+    def limited_rql(self):
+        """return a printable rql for the result set associated to the object,
+        with limit/offset correctly set according to maximum page size and
+        currently displayed page when necessary
+        """
+        # 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.render(**kwargs)
+
+    def initialize_varmaker(self):
+        varmaker = self.req.get_page_data('rql_varmaker')
+        if varmaker is None:
+            varmaker = self.req.varmaker
+            self.req.set_page_data('rql_varmaker', varmaker)
+        self.varmaker = varmaker
+
+    # url generation methods ##################################################
+
+    controller = 'view'
+
+    def build_url(self, 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
+        """
+        from simplejson import dumps
+        self.req.add_js('cubicweb.ajax.js')
+        cbname = self.req.register_onetime_callback(cb, *args)
+        msg = dumps(msg or '')
+        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+            cbname, msg)
+
+    # formating methods #######################################################
+
+    def tal_render(self, template, variables):
+        """render a precompiled page template with variables in the given
+        dictionary as context
+        """
+        from cubicweb.ext.tal import CubicWebContext
+        context = CubicWebContext()
+        context.update({'self': self, 'rset': self.rset, '_' : self.req._,
+                        'req': self.req, 'user': self.req.user})
+        context.update(variables)
+        output = UStringIO()
+        template.expand(context, output)
+        return output.getvalue()
+
+    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/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 hg stserver side and on the client side
 
 :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,38 +15,38 @@
 class COMMA_JOIN(FunctionDescr):
     supported_backends = ('postgres', 'sqlite',)
     rtype = 'String'
-    
+
     @classmethod
     def st_description(cls, funcnode):
         return ', '.join(term.get_description()
                          for term in iter_funcnode_variables(funcnode))
-    
+
 register_function(COMMA_JOIN)  # XXX do not expose?
 
 
 class CONCAT_STRINGS(COMMA_JOIN):
     aggregat = True
-    
+
 register_function(CONCAT_STRINGS) # XXX bw compat
 
 class GROUP_CONCAT(CONCAT_STRINGS):
     supported_backends = ('mysql', 'postgres', 'sqlite',)
-    
+
 register_function(GROUP_CONCAT)
 
 
 class LIMIT_SIZE(FunctionDescr):
     supported_backends = ('postgres', 'sqlite',)
     rtype = 'String'
-    
+
     @classmethod
     def st_description(cls, funcnode):
         return funcnode.children[0].get_description()
-    
+
 register_function(LIMIT_SIZE)
 
 
 class TEXT_LIMIT_SIZE(LIMIT_SIZE):
     supported_backends = ('mysql', 'postgres', 'sqlite',)
-    
+
 register_function(TEXT_LIMIT_SIZE)
--- a/common/appobject.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/appobject.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/entity.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/i18n.py	Thu May 14 12:50:34 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
@@ -90,4 +90,3 @@
         except Exception:
             continue
     return errors
-                         
--- a/common/mail.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/mail.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Common utilies to format / semd emails.
 
 :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/common/migration.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/migration.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 version
 
 :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"
@@ -125,10 +125,10 @@
 
     def repo_connect(self):
         return self.config.repository()
-        
+
     def migrate(self, vcconf, toupgrade, options):
         """upgrade the given set of cubes
-        
+
         `cubes` is an ordered list of 3-uple:
         (cube, fromversion, toversion)
         """
@@ -149,23 +149,23 @@
                                    'versions_map': vmap})
             self.scripts_session(scripts)
         else:
-            print 'no migration script to execute'            
+            print 'no migration script to execute'
 
     def shutdown(self):
         pass
-    
+
     def __getattribute__(self, name):
         try:
             return object.__getattribute__(self, name)
         except AttributeError:
             cmd = 'cmd_%s' % name
             if hasattr(self, cmd):
-                meth = getattr(self, cmd) 
+                meth = getattr(self, cmd)
                 return lambda *args, **kwargs: self.interact(args, kwargs,
                                                              meth=meth)
             raise
         raise AttributeError(name)
-            
+
     def interact(self, args, kwargs, meth):
         """execute the given method according to user's confirmation"""
         msg = 'execute command: %s(%s) ?' % (
@@ -224,7 +224,7 @@
         except ImportError:
             # readline not available
             pass
-        else:        
+        else:
             readline.set_completer(Completer(local_ctx).complete)
             readline.parse_and_bind('tab: complete')
             histfile = os.path.join(os.environ["HOME"], ".eshellhist")
@@ -256,7 +256,7 @@
                 else:
                     context[attr[4:]] = getattr(self, attr)
         return context
-    
+
     def process_script(self, migrscript, funcname=None, *args, **kwargs):
         """execute a migration script
         in interactive mode,  display the migration script path, ask for
@@ -280,7 +280,7 @@
                     self.critical('no %s in script %s', funcname, migrscript)
                     return None
                 return func(*args, **kwargs)
-                    
+
     def scripts_session(self, migrscripts):
         """execute some scripts in a transaction"""
         try:
@@ -311,7 +311,7 @@
     def cmd_option_type_changed(self, optname, oldtype, newvalue):
         """a configuration option's type has changed"""
         self._option_changes.append(('typechanged', optname, oldtype, newvalue))
-        
+
     def cmd_add_cubes(self, cubes):
         """modify the list of used cubes in the in-memory config
         returns newly inserted cubes, including dependencies
@@ -319,7 +319,7 @@
         if isinstance(cubes, basestring):
             cubes = (cubes,)
         origcubes = self.config.cubes()
-        newcubes = [p for p in self.config.expand_cubes(cubes) 
+        newcubes = [p for p in self.config.expand_cubes(cubes)
                        if not p in origcubes]
         if newcubes:
             for cube in cubes:
@@ -340,7 +340,7 @@
         assert cube in removed, \
                "can't remove cube %s, used as a dependancy" % cube
         return removed
-    
+
     def rewrite_configuration(self):
         # import locally, show_diffs unavailable in gae environment
         from cubicweb.toolsutils import show_diffs
--- a/common/mixins.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/mixins.py	Thu May 14 12:50:34 2009 +0200
@@ -2,14 +2,16 @@
 
 
 :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.deprecation import obsolete
 from logilab.common.decorators import cached
 
-from cubicweb.common.selectors import implement_interface
+from cubicweb import typed_eid
+from cubicweb.selectors import implements
 from cubicweb.interfaces import IWorkflowable, IEmailable, ITree
 
 
@@ -24,10 +26,10 @@
     # XXX misnamed
     parent_target = 'subject'
     children_target = 'object'
-    
+
     def different_type_children(self, entities=True):
         """return children entities of different type as this entity.
-        
+
         according to the `entities` parameter, return entity objects or the
         equivalent result set
         """
@@ -39,7 +41,7 @@
 
     def same_type_children(self, entities=True):
         """return children entities of the same type as this entity.
-        
+
         according to the `entities` parameter, return entity objects or the
         equivalent result set
         """
@@ -48,7 +50,7 @@
         if entities:
             return [e for e in res if e.e_schema == self.e_schema]
         return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col)
-    
+
     def iterchildren(self, _done=None):
         if _done is None:
             _done = set()
@@ -72,7 +74,7 @@
                     yield entity
             except AttributeError:
                 pass
-    
+
     @cached
     def path(self):
         """returns the list of eids from the root object to this object"""
@@ -94,7 +96,7 @@
 
         path.reverse()
         return path
-    
+
     def iterparents(self):
         def _uptoroot(self):
             curr = self
@@ -108,7 +110,7 @@
     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
         """
@@ -140,7 +142,7 @@
 
     def children_rql(self):
         return self.related_rql(self.tree_attribute, self.children_target)
-    
+
     def __iter__(self):
         return self.iterchildren()
 
@@ -161,7 +163,7 @@
     relation (which implies supporting 'wf_info_for' as well)
     """
     __implements__ = (IWorkflowable,)
-    
+
     @property
     def state(self):
         try:
@@ -169,7 +171,7 @@
         except IndexError:
             self.warning('entity %s has no state', self)
             return None
-    
+
     @property
     def displayable_state(self):
         return self.req._(self.state)
@@ -180,14 +182,14 @@
         if rset:
             return rset.get_entity(0, 0)
         return None
-    
+
     def wf_transition(self, trname):
         rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, T transition_of E, E name %(e)s',
                                 {'n': trname, 'e': str(self.e_schema)})
         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
@@ -199,7 +201,7 @@
             self.req.set_shared_data('trcommentformat', trcommentformat)
         self.req.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                          {'x': self.eid, 's': stateeid}, 'x')
-    
+
     def can_pass_transition(self, trname):
         """return the Transition instance if the current user can pass the
         transition with the given name, else None
@@ -213,42 +215,30 @@
         for tr in rset.entities():
             if tr.may_be_passed(self.eid, stateeid):
                 return tr
-    
+
     def latest_trinfo(self):
         """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):
         """change the entity's state according to a state defined in given
         parameters, used to be called using __method controler facility
         """
         params = params or self.req.form
-        self.change_state(int(params.pop('state')), params.get('trcomment'),
+        self.change_state(typed_eid(params.pop('state')),
+                          params.get('trcomment'),
                           params.get('trcommentformat'))
         self.req.set_message(self.req._('__msg state changed'))
 
+    # specific vocabulary methods #############################################
+
+    @obsolete('use EntityFieldsForm.subject_in_state_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)
+
 
 
 class EmailableMixIn(object):
@@ -259,7 +249,7 @@
     primary_email / use_email scheme
     """
     __implements__ = (IEmailable,)
-    
+
     def get_email(self):
         if getattr(self, 'primary_email', None):
             return self.primary_email[0].address
@@ -281,14 +271,14 @@
     def as_email_context(self):
         """returns the dictionary as used by the sendmail controller to
         build email bodies.
-        
+
         NOTE: the dictionary keys should match the list returned by the
         `allowed_massmail_keys` method.
         """
         return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
 
 
-    
+
 MI_REL_TRIGGERS = {
     ('in_state',    'subject'): WorkflowableMixIn,
     ('primary_email',   'subject'): EmailableMixIn,
@@ -316,14 +306,13 @@
     """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:
             done = set()
         super(TreeViewMixIn, self).call(done=done, **kwargs)
-            
+
     def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
         done, entity = _done_init(done, self, row, col)
         if done is None:
@@ -352,7 +341,7 @@
         self.w(u'<div class="pathbar">')
         super(TreePathMixIn, self).call(**kwargs)
         self.w(u'</div>')
-        
+
     def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
         done, entity = _done_init(done, self, row, col)
         if done is None:
@@ -394,7 +383,7 @@
 
     def in_progress(self):
         raise NotImplementedError()
-    
+
     def progress(self):
         try:
             return 100. * self.done / self.revised_cost
--- a/common/mttransforms.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/mttransforms.py	Thu May 14 12:50:34 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"
@@ -11,10 +11,10 @@
 from logilab.mtconverter.engine import TransformEngine
 from logilab.mtconverter.transform import Transform
 from logilab.mtconverter import (register_base_transforms,
-                                 register_pil_transforms, 
+                                 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')
 
@@ -32,15 +32,6 @@
     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,13 +40,32 @@
 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
 else:
     HAS_PIL_TRANSFORMS = False
-    
+
 try:
     from logilab.mtconverter.transforms import pygmentstransforms
     for mt in ('text/plain',) + HTML_MIMETYPES:
@@ -74,9 +84,9 @@
             return origconvert(self, trdata)
         cls._convert = _convert
     patch_convert(pygmentstransforms.PygmentsHTMLTransform)
-    
+
     HAS_PYGMENTS_TRANSFORMS = True
 except ImportError:
     HAS_PYGMENTS_TRANSFORMS = False
-    
+
 register_base_transforms(ENGINE, verb=False)
--- a/common/registerers.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-"""This file contains some basic registerers required by application objects
-registry to handle registration at startup time.
-
-A registerer is responsible to tell if an object should be registered according
-to the application's schema or to already registered object
-
-:organization: Logilab
-:copyright: 2006-2008 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
-
-class priority_registerer(registerer):
-    """systematically kick previous registered class and register the
-    wrapped class (based on the fact that directory containing vobjects
-    are loaded from the most generic to the most specific).
-
-    This is usually for templates or startup views where we want to
-    keep only the latest in the load path
-    """
-    def do_it_yourself(self, registered):
-        if registered:
-            if len(registered) > 1:
-                self.warning('priority_registerer found more than one registered objects '
-                             '(registerer monkey patch ?)')
-            for regobj in registered[:]:
-                self.kick(registered, regobj)
-        return self.vobject
-    
-    def remove_equivalents(self, registered):
-        for _obj in registered[:]:
-            if self.equivalent(_obj):
-                self.kick(registered, _obj)
-                break
-            
-    def remove_all_equivalents(self, registered):
-        for _obj in registered[:]:
-            if _obj is self.vobject:
-                continue
-            if self.equivalent(_obj):
-                self.kick(registered, _obj)
-
-    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):
-    """register according to the .accepts attribute of the wrapped
-    class, which should be a tuple refering some entity's types
-
-    * if no type is defined the application'schema, skip the wrapped
-      class
-    * if the class defines a requires attribute, each entity type defined
-      in the requires list must be in the schema
-    * if an object previously registered has equivalent .accepts
-      attribute, kick it out
-    * register
-    """
-    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):
-            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):
-            return False
-        try:
-            newaccepts = list(other.accepts)
-            for etype in self.vobject.accepts:
-                try:
-                    newaccepts.remove(etype)
-                except ValueError:
-                    continue
-            if newaccepts:
-                other.accepts = tuple(newaccepts)
-                return False
-            return True
-        except AttributeError:
-            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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/schema.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/selectors.py	Thu May 14 12:50:34 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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,45 @@
+"""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, escapecontent=True):
+        self.name = name
+        self.escapecontent = escapecontent
+
+    def __call__(self, __content=None, **attrs):
+        attrs.setdefault('escapecontent', self.escapecontent)
+        return simple_sgml_tag(self.name, __content, **attrs)
+
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+div = tag('div', False)
+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
+    attrs['name'] = name
+    html = [u'<select %s>' % ' '.join('%s="%s"' % kv
+                                      for kv in sorted(attrs.items()))]
+    html += options
+    html.append(u'</select>')
+    return u'\n'.join(html)
+
--- a/common/tal.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/test/data/bootstrap_cubes	Thu May 14 12:50:34 2009 +0200
@@ -1,1 +1,1 @@
-file, tag
+
--- a/common/test/data/entities.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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_mail.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/test/unittest_mail.py	Thu May 14 12:50:34 2009 +0200
@@ -17,14 +17,14 @@
     Another solution would be to use $LOGNAME, $USER or $USERNAME
     """
     return pwd.getpwuid(os.getuid())[0]
-    
+
 
 class EmailTC(EnvBasedTC):
 
     def test_format_mail(self):
         self.set_option('sender-addr', 'bim@boum.fr')
         self.set_option('sender-name', 'BimBam')
-        
+
         mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
                            ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
                            config=self.config)
@@ -47,7 +47,7 @@
         self.assertEquals(msg.get('reply-to'), u'oim <oim@logilab.fr>, BimBam <bim@boum.fr>')
         self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou')
 
-        
+
     def test_format_mail_euro(self):
         mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
                            ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
@@ -92,7 +92,7 @@
         self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
         # set sender name and address as expected
         self.set_option('sender-name', 'cubicweb-test')
-        self.set_option('sender-addr', 'cubicweb-test@logilab.fr') 
+        self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
         # anonymous notification: no name and no email specified
         msg = format_mail({'name': u'', 'email': u''},
                            ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
@@ -119,4 +119,4 @@
 
 if __name__ == '__main__':
     unittest_main()
-    
+
--- a/common/test/unittest_migration.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/test/unittest_migration.py	Thu May 14 12:50:34 2009 +0200
@@ -24,13 +24,15 @@
 
     def cube_migration_scripts_dir(cls, cube):
         return TMIGRDIR
-    
+
 class MigrationToolsTC(TestCase):
     def setUp(self):
         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)),
                                                             ('TEMPLATE', (0,0,2), (0,0,3))]),
@@ -46,7 +48,7 @@
                               [SMIGRDIR+'bootstrapmigration_repository.py',
                                SMIGRDIR+'2.6.0_Any.sql',
                                TMIGRDIR+'0.0.4_Any.py'])
-        
+
 ##     def test_migration_files_overlap(self):
 ##         self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2),
 ##                                               (0,0,2), (0,1,2)),
@@ -60,7 +62,7 @@
 ##                                TMIGRDIR+'0.1.0_repository.py',
 ##                                TMIGRDIR+'0.1.2_Any.py',
 ##                                SMIGRDIR+'2.10.1_2.10.2_Any.sql'])
-        
+
     def test_migration_files_for_mode(self):
         from cubicweb.server.migractions import ServerMigrationHelper
         self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
--- a/common/test/unittest_mixins.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/test/unittest_mixins.py	Thu May 14 12:50:34 2009 +0200
@@ -7,19 +7,19 @@
         self.execute('SET X state_of ET WHERE ET name "Bookmark", X eid %(x)s',
                      {'x': s.eid})
         es = self.user().wf_state('activated')
-        self.assertEquals(es.state_of[0].name, 'EUser')
-        
+        self.assertEquals(es.state_of[0].name, 'CWUser')
+
     def test_wf_transition(self):
         t = self.add_entity('Transition', name=u'deactivate')
         self.execute('SET X transition_of ET WHERE ET name "Bookmark", X eid %(x)s',
                      {'x': t.eid})
         et = self.user().wf_transition('deactivate')
-        self.assertEquals(et.transition_of[0].name, 'EUser')
+        self.assertEquals(et.transition_of[0].name, 'CWUser')
 
     def test_change_state(self):
         user = self.user()
         user.change_state(user.wf_state('deactivated').eid)
         self.assertEquals(user.state, 'deactivated')
-    
+
 if __name__ == '__main__':
     unittest_main()
--- a/common/test/unittest_rest.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
-
-from cubicweb.common.rest import rest_publish
-        
-class RestTC(EnvBasedTC):
-    def context(self):
-        return self.execute('EUser X WHERE X login "admin"').get_entity(0, 0)
-    
-    def test_eid_role(self):
-        context = self.context()
-        self.assertEquals(rest_publish(context, ':eid:`%s`' % context.eid),
-                          '<p><a class="reference" href="http://testing.fr/cubicweb/euser/admin">#%s</a></p>\n' % context.eid)
-        self.assertEquals(rest_publish(context, ':eid:`%s:some text`' %  context.eid),
-                          '<p><a class="reference" href="http://testing.fr/cubicweb/euser/admin">some text</a></p>\n')
-        
-    def test_card_role_create(self):
-        self.assertEquals(rest_publish(self.context(), ':card:`index`'),
-                          '<p><a class="reference" href="http://testing.fr/cubicweb/view?etype=Card&amp;wikiid=index&amp;vid=creation">index</a></p>\n')
-
-    def test_card_role_link(self):
-        self.add_entity('Card', wikiid=u'index', title=u'Site index page', synopsis=u'yo')
-        self.assertEquals(rest_publish(self.context(), ':card:`index`'),
-                          '<p><a class="reference" href="http://testing.fr/cubicweb/card/index">index</a></p>\n')
-
-    def test_bad_rest_no_crash(self):
-        data = rest_publish(self.context(), '''
-| card | implication     |
---------------------------
-| 1-1  | N1 = N2         |
-| 1-?  | N1 <= N2        |
-| 1-+  | N1 >= N2        |
-| 1-*  | N1>0 => N2>0    |       
---------------------------
-| ?-?  | N1 # N2         |
-| ?-+  | N1 >= N2        |
-| ?-*  | N1 #  N2        |
---------------------------
-| +-+  | N1>0 => N2>0 et |
-|      | N2>0 => N1>0    |
-| +-*  | N1>+ => N2>0    |
---------------------------
-| *-*  | N1#N2           |
---------------------------
-
-''')
-        
-if __name__ == '__main__':
-    unittest_main()
--- a/common/test/unittest_uilib.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/test/unittest_uilib.py	Thu May 14 12:50:34 2009 +0200
@@ -21,7 +21,7 @@
         for text, expected in data:
             got = uilib.remove_html_tags(text)
             self.assertEquals(got, expected)
-       
+
     def test_fallback_safe_cut(self):
         self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 4), u'ab c...')
         self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 5), u'ab <a href="hello">cd</a>')
@@ -29,7 +29,7 @@
         self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&amp;d</a> ef', 5), u'ab &amp;d...')
         self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&igrave;d</a>', 4), u'ab ì...')
         self.assertEquals(uilib.fallback_safe_cut(u'&amp; <a href="hello">&amp;d</a> ef', 4), u'&amp; &amp;d...')
-        
+
     def test_lxml_safe_cut(self):
         self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 4), u'<p>aaa</p><div>a...</div>')
         self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 7), u'<p>aaa</p><div>aaad</div>...')
@@ -75,18 +75,6 @@
             got = uilib.text_cut(text, 30)
             self.assertEquals(got, expected)
 
-    def test_ajax_replace_url(self):
-        # NOTE: for the simplest use cases, we could use doctest
-        arurl = uilib.ajax_replace_url
-        self.assertEquals(arurl('foo', 'Person P'),
-                          "javascript: replacePageChunk('foo', 'Person%20P');")
-        self.assertEquals(arurl('foo', 'Person P', 'oneline'),
-                          "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');")
-        self.assertEquals(arurl('foo', 'Person P', 'oneline', name='bar', age=12),
-                          'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'oneline\', {"age": 12, "name": "bar"});')
-        self.assertEquals(arurl('foo', 'Person P', name='bar', age=12),
-                          'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'null\', {"age": 12, "name": "bar"});')
-
 tree = ('root', (
     ('child_1_1', (
     ('child_2_1', ()), ('child_2_2', (
@@ -116,18 +104,18 @@
     for child in tuple[1]:
         n.append(make_tree(child))
     return n
-    
+
 class UIlibHTMLGenerationTC(TestCase):
     """ a basic tree node, caracterised by an id"""
     def setUp(self):
-        """ called before each test from this class """        
+        """ called before each test from this class """
         self.o = make_tree(tree)
 
     def test_generated_html(self):
         s = uilib.render_HTML_tree(self.o, selected_node="child_2_2")
         self.assertTextEqual(s, generated_html)
-    
-    
+
+
 if __name__ == '__main__':
     unittest_main()
 
--- a/common/test/unittest_utils.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/uilib.py	Thu May 14 12:50:34 2009 +0200
@@ -10,30 +10,13 @@
 __docformat__ = "restructuredtext en"
 
 import csv
-import decimal
-import locale
 import re
 from urllib import quote as urlquote
-from cStringIO import StringIO
-from copy import deepcopy
+from StringIO import StringIO
 
-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
@@ -56,7 +39,7 @@
         # don't translate empty value if you don't want strange results
         if props is not None and value and props.get('internationalizable'):
             return req._(value)
-        
+
         return value
     if attrtype == 'Date':
         return ustrftime(value, req.property_value('ui.date-format'))
@@ -78,12 +61,12 @@
 # text publishing #############################################################
 
 try:
-    from cubicweb.common.rest import rest_publish # pylint: disable-msg=W0611
+    from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
 except ImportError:
     def rest_publish(entity, data):
         """default behaviour if docutils was not found"""
-        return data
-    
+        return html_escape(data)
+
 TAG_PROG = re.compile(r'</?.*?>', re.U)
 def remove_html_tags(text):
     """Removes HTML tags from text
@@ -180,21 +163,27 @@
             if add_ellipsis:
                 return text + u'...'
             return text
-        
-def text_cut(text, nbwords=30):
+
+def text_cut(text, nbwords=30, gotoperiod=True):
     """from the given plain text, return a text with at least <nbwords> words,
     trying to go to the end of the current sentence.
 
+    :param nbwords: the minimum number of words required
+    :param gotoperiod: specifies if the function should try to go to
+                       the first period after the cut (i.e. finish
+                       the sentence if possible)
+
     Note that spaces are normalized.
     """
     if text is None:
         return u''
     words = text.split()
     text = u' '.join(words) # normalize spaces
-    minlength = len(' '.join(words[:nbwords]))
-    textlength = text.find('.', minlength) + 1
-    if textlength == 0: # no point found
-        textlength = minlength 
+    textlength = minlength = len(' '.join(words[:nbwords]))
+    if gotoperiod:
+        textlength = text.find('.', minlength) + 1
+        if textlength == 0: # no period found
+            textlength = minlength
     return text[:textlength]
 
 def cut(text, length):
@@ -210,20 +199,27 @@
     return text[:length] + u'...'
 
 
-    
+
 # HTML generation helper functions ############################################
 
-def simple_sgml_tag(tag, content=None, **attrs):
+def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
     """generation of a simple sgml tag (eg without children tags) easier
 
     content and attributes will be escaped
     """
     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 sorted(attrs.items())
+                                  if value is not None)
     if content:
-        value += u'>%s</%s>' % (html_escape(unicode(content)), tag)
+        if escapecontent:
+            content = html_escape(unicode(content))
+        value += u'>%s</%s>' % (content, tag)
     else:
         value += u'/>'
     return value
@@ -241,30 +237,6 @@
     """builds a HTML link that uses the js toggleVisibility function"""
     return u'<a href="%s">%s</a>' % (toggle_action(nodeid), label)
 
-def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
-    """builds a replacePageChunk-like url
-    >>> ajax_replace_url('foo', 'Person P')
-    "javascript: replacePageChunk('foo', 'Person%20P');"
-    >>> ajax_replace_url('foo', 'Person P', 'oneline')
-    "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');"
-    >>> ajax_replace_url('foo', 'Person P', 'oneline', name='bar', age=12)
-    "javascript: replacePageChunk('foo', 'Person%20P', 'oneline', {'age':12, 'name':'bar'});"
-    >>> ajax_replace_url('foo', 'Person P', name='bar', age=12)
-    "javascript: replacePageChunk('foo', 'Person%20P', 'null', {'age':12, 'name':'bar'});"    
-    """
-    params = [repr(nodeid), repr(urlquote(rql))]
-    if extraparams and not vid:
-        params.append("'null'")
-    elif vid:
-        params.append(repr(vid))
-    if extraparams:
-        params.append(simplejson.dumps(extraparams))
-    if swap:
-        params.append('true')
-    return "javascript: replacePageChunk(%s);" % ', '.join(params)
-
-
-from StringIO import StringIO
 
 def ureport_as_html(layout):
     from logilab.common.ureports import HTMLWriter
@@ -318,7 +290,7 @@
         else:
             for child in path[-1].children:
                 build_matrix(path[:] + [child], matrix)
-        
+
     matrix = []
     build_matrix([tree], matrix)
 
@@ -347,12 +319,12 @@
             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)
-    
+
 
-    # We can now generate the HTML code for the <table> 
+    # We can now generate the HTML code for the <table>
     s = u'<table class="tree">\n'
     if caption:
         s += '<caption>%s</caption>\n' % caption
@@ -372,7 +344,7 @@
                 s += '<td rowspan="2">&nbsp;</td>'
             s += '<td class="tree_cell_%d_1">&nbsp;</td>' % link_cell
             s += '<td class="tree_cell_%d_2">&nbsp;</td>' % link_cell
-                
+
         cell = line[-1]
         if cell:
             if cell.id == selected_node:
@@ -462,7 +434,7 @@
                            (boxid, ''.join(html_info)))
             tcbk = tcbk.tb_next
         except Exception:
-            pass # doesn't really matter if we have no context info    
+            pass # doesn't really matter if we have no context info
     strings.append(u'</div>')
     return '\n'.join(strings)
 
@@ -470,7 +442,7 @@
 
 class UnicodeCSVWriter:
     """proxies calls to csv.writer.writerow to be able to deal with unicode"""
-    
+
     def __init__(self, wfunc, encoding, **kwargs):
         self.writer = csv.writer(self, **kwargs)
         self.wfunc = wfunc
@@ -508,23 +480,6 @@
         return newfunc
 
 
-def jsonize(function):
-    import simplejson
-    def newfunc(*args, **kwargs):
-        ret = function(*args, **kwargs)
-        if isinstance(ret, decimal.Decimal):
-            ret = float(ret)
-        elif isinstance(ret, DateTimeType):
-            ret = ret.strftime('%Y-%m-%d %H:%M')
-        elif isinstance(ret, DateTimeDeltaType):
-            ret = ret.seconds
-        try:
-            return simplejson.dumps(ret)
-        except TypeError:
-            return simplejson.dumps(repr(ret))
-    return newfunc
-
-
 def htmlescape(function):
     def newfunc(*args, **kwargs):
         ret = function(*args, **kwargs)
--- a/common/utils.py	Thu May 14 12:50:14 2009 +0200
+++ b/common/utils.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/common/view.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/cwconfig.py	Thu May 14 12:50:34 2009 +0200
@@ -2,8 +2,13 @@
 """common configuration utilities 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
+
+.. envvar:: CW_CUBES_PATH
+
+   Augments the default search path for cubes
+
 """
 __docformat__ = "restructuredtext en"
 
@@ -18,7 +23,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 +61,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 = (
@@ -75,44 +69,44 @@
       'default': 'UTF-8',
       'help': _('user interface encoding'),
       'group': 'ui', 'sitewide': True,
-      }),    
+      }),
     ('language',
      {'type' : 'string',
       'default': 'en',
       'vocabulary': Method('available_languages'),
       'help': _('language of the user interface'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('date-format',
      {'type' : 'string',
       'default': '%Y/%m/%d',
       'help': _('how to format date in the ui ("man strftime" for format description)'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('datetime-format',
      {'type' : 'string',
       'default': '%Y/%m/%d %H:%M',
       'help': _('how to format date and time in the ui ("man strftime" for format description)'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('time-format',
      {'type' : 'string',
       'default': '%H:%M',
       'help': _('how to format time in the ui ("man strftime" for format description)'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('float-format',
      {'type' : 'string',
       'default': '%.3f',
       'help': _('how to format float numbers in the ui'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('default-text-format',
      {'type' : 'choice',
       'choices': ('text/plain', 'text/rest', 'text/html'),
       'default': 'text/html', # use fckeditor in the web ui
       'help': _('default text format for rich text fields.'),
-      'group': 'ui', 
+      'group': 'ui',
       }),
     ('short-line-size',
      {'type' : 'int',
@@ -125,7 +119,7 @@
 def register_persistent_options(options):
     global PERSISTENT_OPTIONS
     PERSISTENT_OPTIONS = merge_options(PERSISTENT_OPTIONS + options)
-                
+
 CFGTYPE2ETYPE_MAP = {
     'string': 'String',
     'choice': 'String',
@@ -133,7 +127,7 @@
     'int':    'Int',
     'float' : 'Float',
     }
-    
+
 class CubicWebNoAppConfiguration(ConfigurationMixIn):
     """base class for cubicweb configuration without a specific instance directory
     """
@@ -157,7 +151,7 @@
         mode = 'installed'
         CUBES_DIR = '/usr/share/cubicweb/cubes/'
 
-    options = VREGOPTIONS + (
+    options = (
        ('log-threshold',
          {'type' : 'string', # XXX use a dedicated type?
           'default': 'ERROR',
@@ -195,6 +189,14 @@
           'help': 'web server root url',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('use-request-subdomain',
+         {'type' : 'yn',
+          'default': None,
+          'help': ('if set, base-url subdomain is replaced by the request\'s '
+                   'host, to help managing sites with several subdomains in a '
+                   'single cubicweb instance'),
+          'group': 'main', 'inputlevel': 1,
+          }),
         ('mangle-emails',
          {'type' : 'yn',
           'default': False,
@@ -202,9 +204,14 @@
 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 ##
-        
+
     @staticmethod
     def cubicweb_version():
         """return installed cubicweb version"""
@@ -213,7 +220,7 @@
         version = __pkginfo__.numversion
         assert len(version) == 3, version
         return Version(version)
-    
+
     @staticmethod
     def persistent_options_configuration():
         return Configuration(options=PERSISTENT_OPTIONS)
@@ -226,7 +233,7 @@
         if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'):
             return join(CW_SOFTWARE_ROOT, 'web')
         return cls.cube_dir('shared')
-        
+
     @classmethod
     def i18n_lib_dir(cls):
         """return application's i18n directory"""
@@ -242,7 +249,7 @@
                 if isdir(join(directory, cube)) and not cube in ('CVS', '.svn', 'shared', '.hg'):
                     cubes.add(cube)
         return sorted(cubes)
-    
+
     @classmethod
     def cubes_search_path(cls):
         """return the path of directories where cubes should be searched"""
@@ -257,7 +264,7 @@
         if not cls.CUBES_DIR in path:
             path.append(cls.CUBES_DIR)
         return path
-    
+
     @classmethod
     def cube_dir(cls, cube):
         """return the cube directory for the given cube id,
@@ -273,7 +280,7 @@
     def cube_migration_scripts_dir(cls, cube):
         """cube migration scripts directory"""
         return join(cls.cube_dir(cube), 'migration')
-    
+
     @classmethod
     def cube_pkginfo(cls, cube):
         """return the information module for the given cube"""
@@ -286,7 +293,7 @@
 
     @classmethod
     def cube_version(cls, cube):
-        """return the version of the cube located in the given directory        
+        """return the version of the cube located in the given directory
         """
         from logilab.common.changelog import Version
         version = cls.cube_pkginfo(cube).numversion
@@ -349,7 +356,7 @@
                         except KeyError:
                             continue
         return tuple(reversed(cubes))
-    
+
     @classmethod
     def cls_adjust_sys_path(cls):
         """update python path if necessary"""
@@ -387,7 +394,7 @@
                 except:
                     cls.exception('while loading cube %s', cube)
             else:
-                cls.warning('no __init__ file in cube %s', cube) 
+                cls.warning('no __init__ file in cube %s', cube)
 
     @classmethod
     def init_available_cubes(cls):
@@ -399,7 +406,7 @@
                 __import__('cubes.%s' % cube)
             except Exception, ex:
                 cls.warning("can't init cube %s: %s", cube, ex)
-        
+
     cubicweb_vobject_path = set(['entities'])
     cube_vobject_path = set(['entities'])
 
@@ -447,17 +454,17 @@
                 elif exists(path + '.py'):
                     vregpath.append(path + '.py')
         return vregpath
-        
+
     def __init__(self):
         ConfigurationMixIn.__init__(self)
         self.adjust_sys_path()
         self.load_defaults()
-        self.translations = {} 
+        self.translations = {}
 
     def adjust_sys_path(self):
         self.cls_adjust_sys_path()
-        
-    def init_log(self, logthreshold=None, debug=False, 
+
+    def init_log(self, logthreshold=None, debug=False,
                  logfile=None, syslog=False):
         """init the log service"""
         if logthreshold is None:
@@ -474,7 +481,7 @@
         for application objects. By default return nothing in NoApp config.
         """
         return []
-    
+
     def eproperty_definitions(self):
         cfg = self.persistent_options_configuration()
         for section, options in cfg.options_by_section():
@@ -487,7 +494,7 @@
                         'help': optdict['help'],
                         'sitewide': optdict.get('sitewide', False)}
                 yield key, pdef
-                
+
     def map_option(self, optdict):
         try:
             vocab = optdict['choices']
@@ -497,21 +504,20 @@
                 vocab = getattr(self, vocab.method, ())
         return CFGTYPE2ETYPE_MAP[optdict['type']], vocab
 
-    
+
 class CubicWebConfiguration(CubicWebNoAppConfiguration):
     """base class for cubicweb server and web configurations"""
-    
+
+    INSTANCE_DATA_DIR = None
     if CubicWebNoAppConfiguration.mode == 'test':
         root = os.environ['APYCOT_ROOT']
         REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
-        INSTANCE_DATA_DIR = REGISTRY_DIR
         RUNTIME_DIR = '/tmp/'
         MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
         if not exists(REGISTRY_DIR):
             os.makedirs(REGISTRY_DIR)
     elif CubicWebNoAppConfiguration.mode == 'dev':
         REGISTRY_DIR = expanduser('~/etc/cubicweb.d/')
-        INSTANCE_DATA_DIR = REGISTRY_DIR
         RUNTIME_DIR = '/tmp/'
         MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration')
     else: #mode = 'installed'
@@ -524,7 +530,7 @@
     set_language = True
     # set this to true to avoid false error message while creating an application
     creating = False
-    
+
     options = CubicWebNoAppConfiguration.options + (
         ('log-file',
          {'type' : 'string',
@@ -547,7 +553,7 @@
           }),
         ('sender-name',
          {'type' : 'string',
-          'default': Method('default_application_id'), 
+          'default': Method('default_application_id'),
           'help': 'name used as HELO name for outgoing emails from the \
 repository.',
           'group': 'email', 'inputlevel': 2,
@@ -565,7 +571,7 @@
     def runtime_dir(cls):
         """run time directory for pid file..."""
         return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time')
-    
+
     @classmethod
     def registry_dir(cls):
         """return the control directory"""
@@ -574,9 +580,10 @@
     @classmethod
     def instance_data_dir(cls):
         """return the instance data directory"""
-        return env_path('CW_INSTANCE_DATA', cls.INSTANCE_DATA_DIR,
+        return env_path('CW_INSTANCE_DATA',
+                        cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR,
                         'additional data')
-        
+
     @classmethod
     def migration_scripts_dir(cls):
         """cubicweb migration scripts directory"""
@@ -589,7 +596,7 @@
         config = config or guess_configuration(cls.application_home(appid))
         configcls = configuration_cls(config)
         return configcls(appid)
-    
+
     @classmethod
     def possible_configurations(cls, appid):
         """return the name of possible configurations for the given
@@ -597,7 +604,7 @@
         """
         home = cls.application_home(appid)
         return possible_configurations(home)
-    
+
     @classmethod
     def application_home(cls, appid):
         """return the home directory of the application with the given
@@ -616,9 +623,9 @@
     def accept_mode(cls, mode):
         #assert mode in cls.MODES, mode
         return mode in cls.MCOMPAT[cls.name]
-            
+
     # default configuration methods ###########################################
-    
+
     def default_application_id(self):
         """return the application identifier, useful for option which need this
         as default value
@@ -640,13 +647,13 @@
                     i += 1
             return path
         return '/var/log/cubicweb/%s-%s.log' % (self.appid, self.name)
-    
+
     def default_pid_file(self):
         """return default path to the pid file of the application'server"""
         return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name))
-    
+
     # instance methods used to get application specific resources #############
-    
+
     def __init__(self, appid):
         self.appid = appid
         CubicWebNoAppConfiguration.__init__(self)
@@ -664,13 +671,13 @@
     @property
     def apphome(self):
         return join(self.registry_dir(), self.appid)
-    
+
     @property
     def appdatahome(self):
         return join(self.instance_data_dir(), self.appid)
-        
+
     def init_cubes(self, cubes):
-        assert self._cubes is None
+        assert self._cubes is None, self._cubes
         self._cubes = self.reorder_cubes(cubes)
         # load cubes'__init__.py file first
         for cube in cubes:
@@ -681,7 +688,7 @@
         self.load_file_configuration(self.main_config_file())
         # configuration initialization hook
         self.load_configuration()
-        
+
     def cubes(self):
         """return the list of cubes used by this instance
 
@@ -690,7 +697,7 @@
         """
         assert self._cubes is not None
         return self._cubes
-        
+
     def cubes_path(self):
         """return the list of path to cubes used by this instance, from outer
         most to inner most cubes
@@ -702,11 +709,11 @@
         if not isinstance(cubes, list):
             cubes = list(cubes)
         self._cubes = self.reorder_cubes(list(self._cubes) + cubes)
-        
+
     def main_config_file(self):
         """return application's control configuration file"""
         return join(self.apphome, '%s.conf' % self.name)
-            
+
     def save(self):
         """write down current configuration"""
         self.generate_config(open(self.main_config_file(), 'w'))
@@ -719,7 +726,7 @@
             version = self.cube_version(pkg)
             infos.append('%s-%s' % (pkg, version))
         return md5.new(';'.join(infos)).hexdigest()
-                
+
     def load_site_cubicweb(self):
         """load (web?) application's specific site_cubicweb file"""
         for path in reversed([self.apphome] + self.cubes_path()):
@@ -733,7 +740,7 @@
                     self._load_site_cubicweb(sitefile)
                     self._site_loaded.add(sitefile)
                     self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
-                
+
     def _load_site_cubicweb(self, sitefile):
         context = {}
         execfile(sitefile, context, context)
@@ -742,14 +749,14 @@
         if context.get('options'):
             self.register_options(context['options'])
             self.load_defaults()
-                
+
     def load_configuration(self):
         """load application's configuration files"""
         super(CubicWebConfiguration, self).load_configuration()
         if self.apphome and self.set_language:
             # init gettext
             self._set_language()
-            
+
     def init_log(self, logthreshold=None, debug=False, force=False):
         """init the log service"""
         if not force and hasattr(self, '_logging_initialized'):
@@ -775,7 +782,7 @@
             lang = path.split(os.sep)[-3]
             if lang != 'en':
                 yield lang
-        
+
     def _set_language(self):
         """set language for gettext"""
         from gettext import translation
@@ -787,8 +794,8 @@
                 self.translations[language] = tr.ugettext
             except (ImportError, AttributeError, IOError):
                 self.exception('localisation support error for language %s',
-                               language)            
-    
+                               language)
+
     def vregistry_path(self):
         """return a list of files or directories where the registry will look
         for application objects
@@ -802,7 +809,7 @@
         if not 'all' in sources:
             print 'warning: ignoring specified sources, requires a repository '\
                   'configuration'
-        
+
     def migration_handler(self):
         """return a migration handler instance"""
         from cubicweb.common.migration import MigrationHelper
@@ -820,7 +827,7 @@
         return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs)
 
 set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
-        
+
 # alias to get a configuration instance from an application id
-application_configuration = CubicWebConfiguration.config_for        
+application_configuration = CubicWebConfiguration.config_for
 
--- a/cwctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/cwctl.py	Thu May 14 12:50:34 2009 +0200
@@ -1,17 +1,18 @@
 """%%prog %s [options] %s
 
-CubicWeb main applications controller. 
+CubicWeb main applications controller.
 %s"""
 
 import sys
-from os import remove, listdir, system, kill, getpgid
+from os import remove, listdir, system, kill, getpgid, pathsep
 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"""
     import signal
@@ -41,13 +42,13 @@
             modes.append('web ui')
             break
     return modes
-    
-    
+
+
 class ApplicationCommand(Command):
     """base class for command taking 0 to n application id as arguments
     (0 meaning all registered applications)
     """
-    arguments = '[<application>...]'    
+    arguments = '[<application>...]'
     options = (
         ("force",
          {'short': 'f', 'action' : 'store_true',
@@ -57,13 +58,13 @@
          ),
         )
     actionverb = None
-    
+
     def ordered_instances(self):
         """return instances in the order in which they should be started,
         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,12 +75,13 @@
                         _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
         return allinstances
-    
+
     def run(self, args):
         """run the <command>_method on each argument (a list of application
         identifiers)
@@ -94,7 +96,7 @@
         else:
             askconfirm = False
         self.run_args(args, askconfirm)
-        
+
     def run_args(self, args, askconfirm):
         for appid in args:
             if askconfirm:
@@ -102,7 +104,7 @@
                 if not confirm('%s application %r ?' % (self.name, appid)):
                     continue
             self.run_arg(appid)
-            
+
     def run_arg(self, appid):
         cmdmeth = getattr(self, '%s_application' % self.name)
         try:
@@ -141,7 +143,7 @@
                     sys.exit(status)
             else:
                 self.run_arg(appid)
-    
+
 # base commands ###############################################################
 
 class ListCommand(Command):
@@ -153,16 +155,16 @@
     name = 'list'
     options = (
         ('verbose',
-         {'short': 'v', 'action' : 'store_true', 
-          'help': "display more information."}),        
+         {'short': 'v', 'action' : 'store_true',
+          'help': "display more information."}),
         )
-    
+
     def run(self, args):
         """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:
@@ -172,21 +174,21 @@
                 if not line:
                     continue
                 print '   ', line
-        print 
-        cubesdirs = ', '.join(CubicWebConfiguration.cubes_search_path())
+        print
         try:
-            namesize = max(len(x) for x in CubicWebConfiguration.available_cubes())
+            cubesdir = pathsep.join(cwcfg.cubes_search_path())
+            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,14 +212,14 @@
         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])
-                except Exception, exc: 
+                    config = cwcfg.config_for(appid, modes[0])
+                except Exception, exc:
                     print '    (BROKEN application, %s)' % exc
                     continue
         else:
@@ -259,7 +261,7 @@
           }
          ),
         )
-    
+
     def run(self, args):
         """run the command with its specific arguments"""
         from logilab.common.textutils import get_csv
@@ -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'))]
@@ -322,21 +323,21 @@
         print
         helper.postcreate()
 
-    
+
 class DeleteApplicationCommand(Command):
     """Delete an application. Will remove application's files and
     unregister it.
     """
     name = 'delete'
     arguments = '<application>'
-    
+
     options = ()
 
     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:
@@ -360,7 +361,7 @@
 
 class StartApplicationCommand(ApplicationCommand):
     """Start the given applications. If no application is given, start them all.
-    
+
     <application>...
       identifiers of the applications to start. If no application is
       given, start them all.
@@ -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')
@@ -413,22 +414,22 @@
 
 class StopApplicationCommand(ApplicationCommand):
     """Stop the given applications.
-    
+
     <application>...
       identifiers of the applications to stop. If no application is
       given, stop them all.
     """
     name = 'stop'
     actionverb = 'stopped'
-    
+
     def ordered_instances(self):
         instances = super(StopApplicationCommand, self).ordered_instances()
         instances.reverse()
         return instances
-    
+
     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']
@@ -459,12 +460,12 @@
             # already removed by twistd
             pass
         print 'application %s stopped' % appid
-    
+
 
 class RestartApplicationCommand(StartApplicationCommand,
                                 StopApplicationCommand):
     """Restart the given applications.
-    
+
     <application>...
       identifiers of the applications to restart. If no application is
       given, restart them all.
@@ -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)
@@ -496,30 +497,30 @@
             status = system('%s %s' % (forkcmd, appid))
             if status:
                 sys.exit(status)
-    
+
     def restart_application(self, appid):
         self.stop_application(appid)
         if self.start_application(appid):
             print 'application %s %s' % (appid, self.actionverb)
 
-        
+
 class ReloadConfigurationCommand(RestartApplicationCommand):
     """Reload the given applications. This command is equivalent to a
     restart for now.
-    
+
     <application>...
       identifiers of the applications to reload. If no application is
       given, reload them all.
     """
     name = 'reload'
-    
+
     def reload_application(self, appid):
         self.restart_application(appid)
-    
+
 
 class StatusCommand(ApplicationCommand):
     """Display status information about the given applications.
-    
+
     <application>...
       identifiers of the applications to status. If no application is
       given, get status information about all registered applications.
@@ -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']
@@ -574,7 +576,7 @@
          {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
           'default': None,
           'help': 'force migration from the indicated cubicweb version.'}),
-        
+
         ('fs-only',
          {'short': 's', 'action' : 'store_true',
           'default': False,
@@ -584,13 +586,13 @@
          {'short': 'n', 'action' : 'store_true',
           'default': False,
           'help': 'don\'t try to stop application before migration and to restart it after.'}),
-        
+
         ('verbosity',
          {'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
           'default': 1,
           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
 for everything."}),
-        
+
         ('backup-db',
          {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
           'default': None,
@@ -611,15 +613,17 @@
     def ordered_instances(self):
         # need this since mro return StopApplicationCommand implementation
         return ApplicationCommand.ordered_instances(self)
-    
+
     def upgrade_application(self, appid):
         from logilab.common.changelog import Version
-        if not (CubicWebConfiguration.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
-        config.set_sources_mode(self.config.ext_sources or ('migration',))
+        try:
+            config.set_sources_mode(self.config.ext_sources or ('migration',))
+        except AttributeError:
+            # not a server config
+            pass
         # get application and installed versions for the server and the componants
         print 'getting versions configuration from the repository...'
         mih = config.migration_handler()
@@ -642,7 +646,7 @@
                 continue
             if installedversion > applversion:
                 toupgrade.append( (cube, applversion, installedversion) )
-        cubicwebversion = config.cubicweb_version()           
+        cubicwebversion = config.cubicweb_version()
         if self.config.force_cubicweb_version:
             applcubicwebversion = Version(self.config.force_cubicweb_version)
             vcconf['cubicweb'] = applcubicwebversion
@@ -655,6 +659,9 @@
             return
         for cube, fromversion, toversion in toupgrade:
             print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
+        # only stop once we're sure we have something to do
+        if not (cwcfg.mode == 'dev' or self.config.nostartstop):
+            self.stop_application(appid)
         # run cubicweb/componants migration scripts
         mih.migrate(vcconf, reversed(toupgrade), self.config)
         # rewrite main configuration file
@@ -662,10 +669,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:
@@ -678,7 +684,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
 
@@ -701,7 +707,7 @@
           'help': 'only connect to the system source when the instance is '
           'using multiple sources. You can\'t use this option and the '
           '--ext-sources option at the same time.'}),
-        
+
         ('ext-sources',
          {'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
           'default': None,
@@ -710,11 +716,11 @@
 will connect to all defined sources. If 'migration' is given, appropriate \
 sources for migration will be automatically selected.",
           }),
-        
+
         )
     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
@@ -728,21 +734,22 @@
             mih.scripts_session(args)
         else:
             mih.interactive_shell()
-        mih.shutdown() 
+        mih.shutdown()
 
 
 class RecompileApplicationCatalogsCommand(ApplicationCommand):
     """Recompile i18n catalogs for applications.
-    
+
     <application>...
       identifiers of the applications to consider. If no application is
       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:
@@ -766,10 +773,10 @@
     """list available instances, useful for bash completion."""
     name = 'listinstances'
     hidden = True
-    
+
     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
 
@@ -778,10 +785,10 @@
     """list available componants, useful for bash completion."""
     name = 'listcubes'
     hidden = True
-    
+
     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,
@@ -798,10 +805,10 @@
                    ListInstancesCommand, ListCubesCommand,
                    ))
 
-                
+
 def run(args):
     """command line tool"""
-    CubicWebConfiguration.load_cwctl_plugins()
+    cwcfg.load_cwctl_plugins()
     main_run(args, __doc__)
 
 if __name__ == '__main__':
--- a/cwvreg.py	Thu May 14 12:50:14 2009 +0200
+++ b/cwvreg.py	Thu May 14 12:50:34 2009 +0200
@@ -1,32 +1,45 @@
 """extend the generic VRegistry with some cubicweb specific stuff
 
 :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 warnings import warn
+_ = unicode
 
 from logilab.common.decorators import cached, clear_cache
 
 from rql import RQLHelper
 
-from cubicweb import Binary, UnknownProperty
+from cubicweb import Binary, UnknownProperty, UnknownEid
 from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject
+from cubicweb.rtags import RTAGS
 
-_ = 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):
     """extend the generic VRegistry with some cubicweb specific stuff"""
-    
+
     def __init__(self, config, debug=None, initlog=True):
         if initlog:
             # first init log service
@@ -35,32 +48,34 @@
         self.schema = None
         self.reset()
         self.initialized = False
-        
+
     def items(self):
         return [item for item in self._registries.items()
                 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():
             self.register_property(key, **propdef)
-            
+
     def set_schema(self, schema):
         """set application'schema and load application objects"""
         self.schema = schema
         clear_cache(self, 'rqlhelper')
         # now we can load application's web objects
         self.register_objects(self.config.vregistry_path())
-        
+
     def update_schema(self, schema):
         """update .schema attribute on registered objects, necessary for some
         tests
@@ -72,56 +87,75 @@
             for objects in regcontent.values():
                 for obj in objects:
                     obj.schema = schema
-        
-    def register_objects(self, path, force_reload=None):
-        """overriden to handle type class cache issue"""
-        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__)
-            if not self.config.cleanup_interface_sobjects:
+
+    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
-            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]
+            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 remove objects requiring a missing interface"""
+        if super(CubicWebRegistry, self).register_objects(path, force_reload):
+            self.initialization_completed()
+            # call vreg_initialization_completed on appobjects and print
+            # registry content
+            for registry, objects in self.items():
+                self.debug('available in registry %s: %s', registry,
+                           sorted(objects))
+                for appobjects in objects.itervalues():
+                    for appobject in appobjects:
+                        appobject.vreg_initialization_completed()
+            # don't check rtags if we don't want to cleanup_interface_sobjects
+            for rtag in RTAGS:
+                rtag.init(self.schema,
+                          check=self.config.cleanup_interface_sobjects)
 
-    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)
-    
+    def initialization_completed(self):
+        # clear etype cache if you don't want to run into deep weirdness
+        clear_cache(self, 'etype_class')
+        # we may want to keep interface dependent objects (e.g.for i18n
+        # catalog generation)
+        if self.config.cleanup_interface_sobjects:
+            # remove vobjects that don't support any available interface
+            implemented_interfaces = set()
+            if 'Any' in self.get('etypes', ()):
+                for etype in self.schema.entities():
+                    cls = self.etype_class(etype)
+                    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):
         """return an entity class for the given entity type.
@@ -129,6 +163,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,18 +172,22 @@
         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)
+        return cls
 
     def render(self, registry, oid, req, **context):
         """select an object in a given registry and render it
 
         - registry: the registry's name
         - oid : the view to call
-        - req : the HTTP request         
+        - req : the HTTP request
         """
         objclss = self.registry_objects(registry, oid)
         try:
@@ -155,14 +195,14 @@
         except KeyError:
             rset = None
         selected = self.select(objclss, req, rset, **context)
-        return selected.dispatch(**context)
-        
-    def main_template(self, req, oid='main', **context):
+        return selected.render(**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)
@@ -176,17 +216,17 @@
         return [x for x in sorted(self.possible_objects(registry, *args, **kwargs),
                                   key=lambda x: x.propval('order'))
                 if x.propval('visible')]
-        
+
     def possible_actions(self, req, rset, **kwargs):
         if rset is None:
-            actions = self.possible_vobjects('actions', req, rset)
+            actions = self.possible_vobjects('actions', req, rset, **kwargs)
         else:
-            actions = rset.possible_actions() # cached implementation
+            actions = rset.possible_actions(**kwargs) # cached implementation
         result = {}
         for action in actions:
             result.setdefault(action.category, []).append(action)
         return result
-        
+
     def possible_views(self, req, rset, **kwargs):
         """return an iterator on possible views for this result set
 
@@ -204,7 +244,7 @@
             except Exception:
                 self.exception('error while trying to list possible %s views for %s',
                                vid, rset)
-                
+
     def select_box(self, oid, *args, **kwargs):
         """return the most specific view according to the result set"""
         try:
@@ -218,7 +258,7 @@
             return self.select_object('actions', oid, *args, **kwargs)
         except NoSelectableObject:
             return
-    
+
     def select_component(self, cid, *args, **kwargs):
         """return the most specific component according to the result set"""
         try:
@@ -231,7 +271,7 @@
         views = self.registry_objects('views', __vid)
         return self.select(views, req, rset, **kwargs)
 
-    
+
     # properties handling #####################################################
 
     def user_property_keys(self, withsitewide=False):
@@ -245,7 +285,7 @@
         """register a given property"""
         properties = self._registries['propertydefs']
         assert type in YAMS_TO_PY
-        properties[key] = {'type': type, 'vocabulary': vocabulary, 
+        properties[key] = {'type': type, 'vocabulary': vocabulary,
                            'default': default, 'help': help,
                            'sitewide': sitewide}
 
@@ -263,7 +303,7 @@
                         'default': None, 'vocabulary': None,
                         'help': _('%s software version of the database') % soft}
             raise UnknownProperty('unregistered property %r' % key)
-            
+
     def property_value(self, key):
         try:
             return self._registries['propertyvalues'][key]
@@ -286,7 +326,7 @@
             if not value in vocab:
                 raise ValueError(_('unauthorized value'))
         return value
-    
+
     def init_properties(self, propvalues):
         """init the property values registry using the given set of couple (key, value)
         """
@@ -302,42 +342,15 @@
                 self.warning('%s (you should probably delete that property '
                              'from the database)', ex)
 
-
-    def property_value_widget(self, propkey, req=None, **attrs):
-        """return widget according to key's type / vocab"""
-        from cubicweb.web.widgets import StaticComboBoxWidget, widget_factory
-        if req is None:
-            tr = unicode
-        else:
-            tr = req._
-        try:
-            pdef = self.property_info(propkey)
-        except UnknownProperty, ex:
-            self.warning('%s (you should probably delete that property '
-                         'from the database)', ex)
-            return widget_factory(self, 'EProperty', self.schema['value'], 'String',
-                                  description=u'', **attrs)
-        req.form['value'] = pdef['default'] # XXX hack to pass the default value
-        vocab = pdef['vocabulary']
-        if vocab is not None:
-            if callable(vocab):
-                # list() just in case its a generator function
-                vocabfunc = lambda e: list(vocab(propkey, req))
-            else:
-                vocabfunc = lambda e: vocab
-            w = StaticComboBoxWidget(self, 'EProperty', self.schema['value'], 'String',
-                                     vocabfunc=vocabfunc, description=tr(pdef['help']),
-                                     **attrs)
-        else:
-            w = widget_factory(self, 'EProperty', self.schema['value'], pdef['type'],
-                               description=tr(pdef['help']), **attrs)
-        return w
-
     def parse(self, session, rql, args=None):
         rqlst = self.rqlhelper.parse(rql)
         def type_from_eid(eid, session=session):
             return session.describe(eid)[0]
-        self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
+        try:
+            self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
+        except UnknownEid:
+            for select in rqlst.children:
+                select.solutions = []
         return rqlst
 
     @property
@@ -373,8 +386,8 @@
             vobject.schema = self.schema
             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,
@@ -383,9 +396,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	Thu May 14 12:50:14 2009 +0200
+++ b/dbapi.py	Thu May 14 12:50:34 2009 +0200
@@ -5,18 +5,18 @@
 (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
 from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
 from cubicweb.cwconfig import CubicWebNoAppConfiguration
-        
+
 _MARKER = object()
 
 class ConnectionProperties(object):
@@ -29,7 +29,7 @@
 
 def get_repository(method, database=None, config=None, vreg=None):
     """get a proxy object to the CubicWeb repository, using a specific RPC method.
-     
+
     Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
     argument should be given
     """
@@ -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,16 +54,16 @@
         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 '
                                   'application' % nsid)
         return core.getProxyForURI(uri)
-        
+
 def repo_connect(repo, user, password, cnxprops=None):
     """Constructor to create a new connection to the CubicWeb repository.
-    
+
     Returns a Connection instance.
     """
     cnxprops = cnxprops or ConnectionProperties('inmemory')
@@ -72,7 +72,7 @@
     if cnxprops.cnxtype == 'inmemory':
         cnx.vreg = repo.vreg
     return cnx
-    
+
 def connect(database=None, user=None, password=None, host=None,
             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
             initlog=True):
@@ -110,7 +110,7 @@
 
 def in_memory_cnx(config, user, password):
     """usefull method for testing and scripting to get a dbapi.Connection
-    object connected to an in-memory repository instance 
+    object connected to an in-memory repository instance
     """
     if isinstance(config, CubicWebRegistry):
         vreg = config
@@ -126,7 +126,7 @@
 
 
 class DBAPIRequest(RequestSessionMixIn):
-    
+
     def __init__(self, vreg, cnx=None):
         super(DBAPIRequest, self).__init__(vreg)
         try:
@@ -146,10 +146,10 @@
 
     def base_url(self):
         return self.vreg.config['base-url']
-    
+
     def from_controller(self):
         return 'view'
-    
+
     def set_connection(self, cnx, user=None):
         """method called by the session handler when the user is authenticated
         or an anonymous connection is open
@@ -157,7 +157,7 @@
         self.cnx = cnx
         self.cursor = cnx.cursor(self)
         self.set_user(user)
-    
+
     def set_default_language(self, vreg):
         try:
             self.lang = vreg.property_value('ui.language')
@@ -175,26 +175,26 @@
         rset.vreg = self.vreg
         rset.req = self
         return rset
-    
+
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         return self.cnx.describe(eid)
-    
+
     def source_defs(self):
         """return the definition of sources used by the repository."""
         return self.cnx.source_defs()
-            
+
     # entities cache management ###############################################
-    
+
     def entity_cache(self, eid):
         return self._eid_cache[eid]
-    
+
     def set_entity_cache(self, entity):
         self._eid_cache[entity.eid] = entity
 
     def cached_entities(self):
         return self._eid_cache.values()
-    
+
     def drop_entity_cache(self, eid=None):
         if eid is None:
             self._eid_cache = {}
@@ -210,11 +210,11 @@
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         return self.cnx.get_session_data(key, default, pop)
-        
+
     def set_session_data(self, key, value):
         """set value associated to `key` in session data"""
         return self.cnx.set_session_data(key, value)
-        
+
     def del_session_data(self, key):
         """remove value associated to `key` in session data"""
         return self.cnx.del_session_data(key)
@@ -222,7 +222,7 @@
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
         return self.cnx.get_shared_data(key, default, pop)
-        
+
     def set_shared_data(self, key, value, querydata=False):
         """set value associated to `key` in shared data
 
@@ -245,14 +245,14 @@
         self._user = user
         if user:
             self.set_entity_cache(user)
-        
+
     def execute(self, *args, **kwargs):
         """Session interface compatibility"""
         return self.cursor.execute(*args, **kwargs)
 
 set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
-        
-        
+
+
 # exceptions ##################################################################
 
 class ProgrammingError(Exception): #DatabaseError):
@@ -288,15 +288,15 @@
 """String constant stating the type of parameter marker formatting expected by
 the interface. Possible values are :
 
-                'qmark'         Question mark style, 
+                'qmark'         Question mark style,
                                 e.g. '...WHERE name=?'
-                'numeric'       Numeric, positional style, 
+                'numeric'       Numeric, positional style,
                                 e.g. '...WHERE name=:1'
-                'named'         Named style, 
+                'named'         Named style,
                                 e.g. '...WHERE name=:name'
-                'format'        ANSI C printf format codes, 
+                'format'        ANSI C printf format codes,
                                 e.g. '...WHERE name=%s'
-                'pyformat'      Python extended format codes, 
+                'pyformat'      Python extended format codes,
                                 e.g. '...WHERE name=%(name)s'
 """
 paramstyle = 'pyformat'
@@ -333,41 +333,37 @@
 
     def request(self):
         return DBAPIRequest(self.vreg, self)
-    
+
     def session_data(self):
         """return a dictionnary containing session data"""
         return self.data
-        
+
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         if pop:
             return self.data.pop(key, default)
         else:
             return self.data.get(key, default)
-        
+
     def set_session_data(self, key, value):
         """set value associated to `key` in session data"""
         self.data[key] = value
-        
+
     def del_session_data(self, key):
         """remove value associated to `key` in session data"""
         try:
             del self.data[key]
         except KeyError:
-            pass    
+            pass
 
     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"""
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
-        
+
     def set_shared_data(self, key, value, querydata=False):
         """set value associated to `key` in shared data
 
@@ -377,10 +373,10 @@
         repository side.
         """
         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
-        
+
     def get_schema(self):
         """Return the schema currently used by the repository.
-        
+
         This is NOT part of the DB-API.
         """
         if self._closed is not None:
@@ -418,10 +414,10 @@
             # application specific hooks
             if self._repo.config.application_hooks:
                 hm.register_hooks(config.load_hooks(self.vreg))
-            
+
     def source_defs(self):
         """Return the definition of sources used by the repository.
-        
+
         This is NOT part of the DB-API.
         """
         if self._closed is not None:
@@ -434,9 +430,9 @@
         eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
         if req is None:
             req = self.request()
-        rset = req.eid_rset(eid, 'EUser')
-        user = self.vreg.etype_class('EUser')(req, rset, row=0, groups=groups,
-                                              properties=properties)
+        rset = req.eid_rset(eid, 'CWUser')
+        user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups,
+                                               properties=properties)
         user['login'] = login # cache login
         return user
 
@@ -447,13 +443,13 @@
                 self.close()
             except:
                 pass
-    
+
     def describe(self, eid):
         return self._repo.describe(self.sessionid, eid)
-            
+
     def close(self):
         """Close the connection now (rather than whenever __del__ is called).
-        
+
         The connection will be unusable from this point forward; an Error (or
         subclass) exception will be raised if any operation is attempted with
         the connection. The same applies to all cursor objects trying to use the
@@ -469,7 +465,7 @@
         """Commit any pending transaction to the database. Note that if the
         database supports an auto-commit feature, this must be initially off. An
         interface method may be provided to turn it back on.
-            
+
         Database modules that do not support transactions should implement this
         method with void functionality.
         """
@@ -480,7 +476,7 @@
     def rollback(self):
         """This method is optional since not all databases provide transaction
         support.
-            
+
         In case a database does provide transactions this method causes the the
         database to roll back to the start of any pending transaction.  Closing
         a connection without committing the changes first will cause an implicit
@@ -514,7 +510,7 @@
     support is implemented (see also the connection's rollback() and commit()
     methods.)
     """
-    
+
     def __init__(self, connection, repo, req=None):
         """This read-only attribute return a reference to the Connection
         object on which the cursor was created.
@@ -526,7 +522,7 @@
         """This read/write attribute specifies the number of rows to fetch at a
         time with fetchmany(). It defaults to 1 meaning to fetch a single row
         at a time.
-        
+
         Implementations must observe this value with respect to the fetchmany()
         method, but are free to interact with the database a single row at a
         time. It may also be used in the implementation of executemany().
@@ -539,7 +535,7 @@
         self._closed = None
         self._index = 0
 
-        
+
     def close(self):
         """Close the cursor now (rather than whenever __del__ is called).  The
         cursor will be unusable from this point forward; an Error (or subclass)
@@ -547,30 +543,30 @@
         """
         self._closed = True
 
-            
+
     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
         """Prepare and execute a database operation (query or command).
         Parameters may be provided as sequence or mapping and will be bound to
         variables in the operation.  Variables are specified in a
         database-specific notation (see the module's paramstyle attribute for
         details).
-        
+
         A reference to the operation will be retained by the cursor.  If the
         same operation object is passed in again, then the cursor can optimize
         its behavior.  This is most effective for algorithms where the same
         operation is used, but different parameters are bound to it (many
         times).
-        
+
         For maximum efficiency when reusing an operation, it is best to use the
         setinputsizes() method to specify the parameter types and sizes ahead
         of time.  It is legal for a parameter to not match the predefined
         information; the implementation should compensate, possibly with a loss
         of efficiency.
-        
+
         The parameters may also be specified as list of tuples to e.g. insert
         multiple rows in a single operation, but this kind of usage is
         depreciated: executemany() should be used instead.
-        
+
         Return values are not defined by the DB-API, but this here it returns a
         ResultSet object.
         """
@@ -579,25 +575,25 @@
         self.req.decorate_rset(res)
         self._index = 0
         return res
-        
+
 
     def executemany(self, operation, seq_of_parameters):
         """Prepare a database operation (query or command) and then execute it
         against all parameter sequences or mappings found in the sequence
         seq_of_parameters.
-        
+
         Modules are free to implement this method using multiple calls to the
         execute() method or by using array operations to have the database
         process the sequence as a whole in one call.
-        
+
         Use of this method for an operation which produces one or more result
         sets constitutes undefined behavior, and the implementation is
         permitted (but not required) to raise an exception when it detects that
         a result set has been created by an invocation of the operation.
-        
+
         The same comments as for execute() also apply accordingly to this
         method.
-        
+
         Return values are not defined.
         """
         for parameters in seq_of_parameters:
@@ -610,7 +606,7 @@
     def fetchone(self):
         """Fetch the next row of a query result set, returning a single
         sequence, or None when no more data is available.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
         """
@@ -620,21 +616,21 @@
         self._index += 1
         return row
 
-        
+
     def fetchmany(self, size=None):
         """Fetch the next set of rows of a query result, returning a sequence
         of sequences (e.g. a list of tuples). An empty sequence is returned
         when no more rows are available.
-        
+
         The number of rows to fetch per call is specified by the parameter.  If
         it is not given, the cursor's arraysize determines the number of rows
         to be fetched. The method should try to fetch as many rows as indicated
         by the size parameter. If this is not possible due to the specified
         number of rows not being available, fewer rows may be returned.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
-        
+
         Note there are performance considerations involved with the size
         parameter.  For optimal performance, it is usually best to use the
         arraysize attribute.  If the size parameter is used, then it is best
@@ -648,12 +644,12 @@
         self._index += size
         return rows
 
-        
+
     def fetchall(self):
         """Fetch all (remaining) rows of a query result, returning them as a
         sequence of sequences (e.g. a list of tuples).  Note that the cursor's
         arraysize attribute can affect the performance of this operation.
-        
+
         An Error (or subclass) exception is raised if the previous call to
         execute*() did not produce any result set or no call was issued yet.
         """
@@ -669,39 +665,39 @@
     def setinputsizes(self, sizes):
         """This can be used before a call to execute*() to predefine memory
         areas for the operation's parameters.
-        
+
         sizes is specified as a sequence -- one item for each input parameter.
         The item should be a Type Object that corresponds to the input that
         will be used, or it should be an integer specifying the maximum length
         of a string parameter.  If the item is None, then no predefined memory
         area will be reserved for that column (this is useful to avoid
         predefined areas for large inputs).
-        
+
         This method would be used before the execute*() method is invoked.
-        
+
         Implementations are free to have this method do nothing and users are
         free to not use it.
         """
         pass
 
-        
+
     def setoutputsize(self, size, column=None):
         """Set a column buffer size for fetches of large columns (e.g. LONGs,
         BLOBs, etc.).  The column is specified as an index into the result
         sequence.  Not specifying the column will set the default size for all
         large columns in the cursor.
-        
+
         This method would be used before the execute*() method is invoked.
-        
+
         Implementations are free to have this method do nothing and users are
         free to not use it.
-        """    
+        """
         pass
 
-    
+
 class LogCursor(Cursor):
     """override the standard cursor to log executed queries"""
-    
+
     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
         """override the standard cursor to log executed queries"""
         tstart, cstart = time(), clock()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/compat	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/control	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,131 @@
+Source: cubicweb
+Section: web
+Priority: optional
+Maintainer: Logilab S.A. <contact@logilab.fr>
+Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>,
+           Julien Jehannet <julien.jehannet@logilab.fr>,
+           Aurélien Campéas <aurelien.campeas@logilab.fr>
+Build-Depends: debhelper (>= 5), python-dev (>=2.4), python-central (>= 0.5)
+Standards-Version: 3.8.0
+Homepage: http://www.cubicweb.org
+XS-Python-Version: >= 2.4, << 2.6
+
+
+Package: cubicweb
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version}), cubicweb-client (= ${source:Version})
+XB-Recommends: (postgresql, postgresql-plpython, postgresql-contrib) | mysql | sqlite3
+Recommends: postgresql | mysql | sqlite3
+Description: the complete CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package will install all the components you need to run cubicweb on
+ a single machine. You can also deploy cubicweb by running the different
+ process on different computers, in which case you need to install the
+ corresponding packages on the different hosts.
+
+
+Package: cubicweb-server
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Conflicts: cubicweb-multisources
+Replaces: cubicweb-multisources
+Provides: cubicweb-multisources
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-indexer (>= 0.6.1), python-psycopg2 | python-mysqldb | python-pysqlite2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: server part of the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the repository server part of the system.
+ .
+ This package provides the repository server part of the library and
+ necessary shared data files such as the schema library.
+
+
+Package: cubicweb-twisted
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Provides: cubicweb-web-frontend
+Depends: ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: twisted-based web interface for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a twisted based HTTP server to serve
+ the adaptative web interface (see cubicweb-web package).
+ .
+ This package provides only the twisted server part of the library.
+
+
+Package: cubicweb-web
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-docutils, python-vobject, python-elementtree
+Recommends: fckeditor
+Description: web interface library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides an adaptative web interface to the CubicWeb server.
+ Install the cubicweb-twisted package to serve this interface via HTTP.
+ .
+ This package provides the web interface part of the library and
+ necessary shared data files such as defaut views, images...
+
+
+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)
+Recommends: python-psyco
+Conflicts: cubicweb-core
+Replaces: cubicweb-core
+Description: common library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the common parts of the library used by both server
+ code and web application code.
+
+
+Package: cubicweb-ctl
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version})
+Description: tool to manage the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a control script to manage (create, upgrade, start,
+ stop, etc) CubicWeb applications. It also include the init.d script
+ to automatically start and stop CubicWeb applications on boot or shutdown.
+
+
+Package: cubicweb-client
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-ctl (= ${source:Version}), pyro
+Description: RQL command line client for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a RQL (Relation Query Language) command line client using
+ pyro to connect to a repository server.
+
+
+Package: cubicweb-dev
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-web (= ${source:Version}), python-pysqlite2
+Suggests: w3c-dtd-xhtml
+Description: tests suite and development tools for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the CubicWeb tests suite and some development tools
+ helping in the creation of application.
+
+
+Package: cubicweb-documentation
+Architecture: all
+Recommends: doc-base
+Description: documentation for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the system's documentation.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/rules	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+PY_VERSION:=$(shell pyversions -d)
+
+build: build-stamp
+build-stamp: 
+	dh_testdir
+	# XXX doesn't work if logilab-doctools, logilab-xml are not in build depends
+	# and I can't get pbuilder find them in its chroot :(
+	# cd doc && make
+	# FIXME cleanup and use sphinx-build as build-depends ?
+	python setup.py build
+	touch build-stamp
+
+clean: 
+	dh_testdir
+	dh_testroot
+	rm -f build-stamp configure-stamp
+	rm -rf build
+	#rm -rf debian/cubicweb-*/
+	find . -name "*.pyc" -delete
+	rm -f $(basename $(wildcard debian/*.in))
+	dh_clean
+
+install: build $(basename $(wildcard debian/*.in))
+	dh_testdir
+	dh_testroot
+	dh_clean
+	dh_installdirs
+
+	#python setup.py install_lib --no-compile --install-dir=debian/cubicweb-common/usr/lib/python2.4/site-packages/
+	python setup.py -q install --no-compile --prefix=debian/tmp/usr
+
+	# Put all the python library and data in cubicweb-common
+	# and scripts in cubicweb-server
+	dh_install -vi
+	#dh_lintian XXX not before debhelper 7
+
+	# Remove unittests directory (should be available in cubicweb-dev only)
+	rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
+	rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
+	rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
+	rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
+
+	# cubes directory must be managed as a valid python module
+	touch debian/cubicweb-common/usr/share/cubicweb/cubes/__init__.py
+
+%: %.in
+	sed "s/PY_VERSION/${PY_VERSION}/g" < $< > $@
+
+# Build architecture-independent files here.
+binary-indep: build install
+	dh_testdir
+	dh_testroot -i
+	dh_pycentral -i
+	dh_installinit -i -n --name cubicweb -u"defaults 99"
+	dh_installlogrotate -i
+	dh_installdocs -i -A README
+	dh_installman -i
+	dh_installchangelogs -i
+	dh_link -i
+	dh_compress -i -X.py -X.ini -X.xml
+	dh_fixperms -i
+	dh_installdeb -i
+	dh_gencontrol  -i
+	dh_md5sums -i
+	dh_builddeb -i
+
+binary-arch:
+
+binary: binary-indep 
+.PHONY: build clean binary binary-indep binary-arch
+
--- a/debian/changelog	Thu May 14 12:50:14 2009 +0200
+++ b/debian/changelog	Thu May 14 12:50:34 2009 +0200
@@ -1,3 +1,21 @@
+cubicweb (3.2.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Thu, 14 May 2009 12:31:06 +0200
+
+cubicweb (3.1.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Aurélien Campéas <aurelien.campeas@logilab.fr>  Mon, 06 Apr 2009 14:30:00 +0200
+
+cubicweb (3.1.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 06 Apr 2009 08:52:27 +0200
+
 cubicweb (3.1.2-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Thu May 14 12:50:14 2009 +0200
+++ b/debian/control	Thu May 14 12:50:34 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}
@@ -61,8 +60,8 @@
 Package: cubicweb-web
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-docutils, python-vobject, python-elementtree
-Recommends: fckeditor
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree
+Recommends: python-docutils, python-vobject, fckeditor
 Description: web interface library for the CubicWeb framework
  CubicWeb is a semantic web application framework.
  .
@@ -76,8 +75,8 @@
 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)
-Recommends: python-psyco
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.40.0), python-yams (>= 0.22.0), python-rql (>= 0.22.0)
+Recommends: python-simpletal (>= 4.0), python-lxml
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
 Description: common library for the CubicWeb framework
--- a/debian/cubicweb-ctl.postinst	Thu May 14 12:50:14 2009 +0200
+++ b/debian/cubicweb-ctl.postinst	Thu May 14 12:50:34 2009 +0200
@@ -13,25 +13,29 @@
 if [ "$1" = configure ]; then
     # XXX bw compat: erudi -> cubicweb migration
     if [ -e "/etc/erudi.d/" ]; then
-      mv /etc/erudi.d/* /etc/cubicweb.d/
-      echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/'
-      sed -i s/ginco/cubicweb/g /etc/*/*.py
-      sed -i s/erudi/cubicweb/ */*.conf
+      mv /etc/erudi.d/* /etc/cubicweb.d/ && (
+	  echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/'
+	  sed -i s/ginco/cubicweb/g /etc/*/*.py
+	  sed -i s/erudi/cubicweb/ */*.conf
+	  ) || true # empty dir
     fi
     if [ -e "/var/log/erudi/" ]; then
-      mv /var/log/erudi/* /var/log/cubicweb/
-      echo 'moved /var/log/erudi/* to /var/log/cubicweb/'
+      mv /var/log/erudi/* /var/log/cubicweb/ && (
+	  echo 'moved /var/log/erudi/* to /var/log/cubicweb/'
+	  ) || true # empty dir
     fi
     if [ -e "/var/lib/erudi/backup" ]; then
-      mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/
-      echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/'
+      mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/ && (
+	  echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/'
+	  ) || true # empty dir
     fi
     if [ -e "/var/lib/erudi/instances" ]; then
-      mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/
-      echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/'
+      mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/ && (
+	  echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/'
+	  ) || true # empty dir
     fi
 fi
-  
+
 #DEBHELPER#
- 
+
 exit 0
--- a/debian/cubicweb-web.postinst	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/__init__.py	Thu May 14 12:50:34 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
@@ -59,10 +59,10 @@
           'group': 'main', 'inputlevel': 1,
           }),
         ))
-                            
+
     if not os.environ.get('APYCOT_ROOT'):
         REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes'))
-    
+
     def __init__(self, appid, log_threshold=logging.CRITICAL+10):
         ServerConfiguration.__init__(self, appid)
         self.global_set_option('log-file', None)
@@ -71,7 +71,7 @@
         self.load_cwctl_plugins()
 
     anonymous_user = TwistedConfiguration.anonymous_user.im_func
-        
+
     @property
     def apphome(self):
         if exists(self.appid):
@@ -79,7 +79,7 @@
         # application cube test
         return abspath('..')
     appdatahome = apphome
-    
+
     def main_config_file(self):
         """return application's control configuration file"""
         return join(self.apphome, '%s.conf' % self.name)
@@ -116,7 +116,7 @@
         if not sources:
             sources = DEFAULT_SOURCES
         return sources
-    
+
     def load_defaults(self):
         super(TestServerConfiguration, self).load_defaults()
         # note: don't call global set option here, OptionManager may not yet be initialized
@@ -146,25 +146,25 @@
 
     def available_languages(self, *args):
         return ('en', 'fr', 'de')
-    
+
     def ext_resources_file(self):
         """return application's external resources file"""
         return join(self.apphome, 'data', 'external_resources')
-    
+
     def pyro_enabled(self):
         # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads
         return True
 
 
 class ApptestConfiguration(BaseApptestConfiguration):
-    
+
     def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
         BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
         self.init_repository = sourcefile is None
         self.sourcefile = sourcefile
         import re
         self.global_set_option('embed-allowed', re.compile('.*'))
-        
+
 
 class RealDatabaseConfiguration(ApptestConfiguration):
     init_repository = False
@@ -180,7 +180,7 @@
                               'password': u'gingkow',
                               },
                    }
-    
+
     def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
         ApptestConfiguration.__init__(self, appid)
         self.init_repository = False
@@ -191,7 +191,7 @@
         By default, we run tests with the sqlite DB backend.
         One may use its own configuration by just creating a
         'sources' file in the test directory from wich tests are
-        launched. 
+        launched.
         """
         self._sources = self.sourcesdef
         return self._sources
@@ -220,11 +220,11 @@
     """
     return type('MyRealDBConfig', (RealDatabaseConfiguration,),
                 {'sourcesdef': read_config(filename)})
-    
+
 
 class LivetestConfiguration(BaseApptestConfiguration):
     init_repository = False
-    
+
     def __init__(self, cube=None, sourcefile=None, pyro_name=None,
                  log_threshold=logging.CRITICAL):
         TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold)
@@ -254,7 +254,7 @@
             return False
 
 CubicWebConfiguration.cls_adjust_sys_path()
-                                                    
+
 def install_sqlite_path(querier):
     """This patch hotfixes the following sqlite bug :
        - http://www.sqlite.org/cvstrac/tktview?tn=1327,33
@@ -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
@@ -330,7 +331,7 @@
             os.remove('%s-cube' % dbfile)
         except OSError:
             pass
-    
+
 def init_test_database_sqlite(config, source, vreg=None):
     """initialize a fresh sqlite databse used for testing purpose"""
     import shutil
--- a/devtools/_apptest.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/_apptest.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Hidden internals for the devtools.apptest module
 
 :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"
@@ -20,11 +20,11 @@
 
 from cubicweb.devtools import ApptestConfiguration, init_test_database
 from cubicweb.devtools.fake import FakeRequest
-    
-SYSTEM_ENTITIES = ('EGroup', 'EUser',
-                   'EFRDef', 'ENFRDef',
-                   'EConstraint', 'EConstraintType', 'EProperty',
-                   'EEType', 'ERType',
+
+SYSTEM_ENTITIES = ('CWGroup', 'CWUser',
+                   'CWAttribute', 'CWRelation',
+                   'CWConstraint', 'CWConstraintType', 'CWProperty',
+                   'CWEType', 'CWRType',
                    'State', 'Transition', 'TrInfo',
                    'RQLExpression',
                    )
@@ -35,7 +35,7 @@
     'is', 'is_instance_of', 'owned_by', 'created_by', 'specializes',
     # workflow related
     'state_of', 'transition_of', 'initial_state', 'allowed_transition',
-    'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state', 
+    'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
     'condition',
     # permission
     'in_group', 'require_group', 'require_permission',
@@ -46,11 +46,11 @@
     'relation_type', 'from_entity', 'to_entity',
     'constrained_by', 'cstrtype', 'widget',
     # deducted from other relations
-    'primary_email', 
+    'primary_email',
                     )
 
 def unprotected_entities(app_schema, strict=False):
-    """returned a Set of each non final entity type, excluding EGroup, and EUser...
+    """returned a Set of each non final entity type, excluding CWGroup, and CWUser...
     """
     if strict:
         protected_entities = yams.schema.BASE_TYPES
@@ -58,16 +58,17 @@
         protected_entities = yams.schema.BASE_TYPES.union(set(SYSTEM_ENTITIES))
     entities = set(app_schema.entities())
     return entities - protected_entities
-    
+
 
 def ignore_relations(*relations):
+    global SYSTEM_RELATIONS
     SYSTEM_RELATIONS += relations
 
 class TestEnvironment(object):
     """TestEnvironment defines a context (e.g. a config + a given connection) in
     which the tests are executed
     """
-    
+
     def __init__(self, appid, reporter=None, verbose=False,
                  configcls=ApptestConfiguration, requestcls=FakeRequest):
         config = configcls(appid)
@@ -83,14 +84,13 @@
         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
         schema = self.vreg.schema
         # else we may run into problems since email address are ususally share in app tests
         # XXX should not be necessary anymore
-        schema.rschema('primary_email').set_rproperty('EUser', 'EmailAddress', 'composite', False)
+        schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False)
         self.deletable_entities = unprotected_entities(schema)
 
     def restore_database(self):
@@ -114,12 +114,12 @@
         self.cnx.vreg = self.vreg
         self.cnx.login = source['db-user']
         self.cnx.password = source['db-password']
-        
+
 
     def create_user(self, login, groups=('users',), req=None):
         req = req or self.create_request()
         cursor = self._orig_cnx.cursor(req)
-        rset = cursor.execute('INSERT EUser X: X login %(login)s, X upassword %(passwd)s,'
+        rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s,'
                               'X in_state S WHERE S name "activated"',
                               {'login': unicode(login), 'passwd': login.encode('utf8')})
         user = rset.get_entity(0, 0)
@@ -140,7 +140,7 @@
         if login == self.vreg.config.anonymous_user()[0]:
             self.cnx.anonymous_connection = True
         return self.cnx
-    
+
     def restore_connection(self):
         if not self.cnx is self._orig_cnx:
             try:
@@ -157,7 +157,7 @@
         """
         req = req or self.create_request(rql=rql)
         return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
-    
+
     def create_request(self, rql=None, **kwargs):
         """executes <rql>, builds a resultset, and returns a
         couple (rset, req) where req is a FakeRequest
@@ -167,14 +167,14 @@
         req = self.requestcls(self.vreg, form=kwargs)
         req.set_connection(self.cnx)
         return req
-        
+
     def get_rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
         """executes <rql>, builds a resultset, and returns a
         couple (rset, req) where req is a FakeRequest
         """
         return (self.execute(rql, args, eidkey),
                 self.create_request(rql=rql, **optional_args or {}))
-    
+
     def check_view(self, rql, vid, optional_args, template='main'):
         """checks if vreg.view() raises an exception in this environment
 
@@ -183,7 +183,7 @@
         """
         return self.call_view(vid, rql,
                               template=template, optional_args=optional_args)
-    
+
     def call_view(self, vid, rql, template='main', optional_args=None):
         """shortcut for self.vreg.view()"""
         assert template
@@ -227,23 +227,22 @@
             yield action
 
 class ExistingTestEnvironment(TestEnvironment):
-    
+
     def __init__(self, appid, sourcefile, verbose=False):
         config = ApptestConfiguration(appid, sourcefile=sourcefile)
         if verbose:
             print "init test database ..."
         source = config.sources()['system']
         self.vreg = 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" 
+            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"""
         cursor = self.cnx.cursor()
@@ -255,4 +254,3 @@
         cursor.execute('DELETE Any X WHERE X eid > %(x)s', {'x' : self.last_eid}, eid_key='x')
         print "cleaning done"
         self.cnx.commit()
-
--- a/devtools/apptest.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/apptest.py	Thu May 14 12:50:34 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"
@@ -14,6 +14,8 @@
 from logilab.common.pytest import nocoverage
 from logilab.common.umessage import message_from_string
 
+from logilab.common.deprecation import deprecated_function
+
 from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
 from cubicweb.devtools._apptest import TestEnvironment
 from cubicweb.devtools.fake import FakeRequest
@@ -30,11 +32,11 @@
     @property
     def message(self):
         return message_from_string(self.msg)
-    
+
     def __repr__(self):
         return '<Email to %s with subject %s>' % (','.join(self.recipients),
                                                   self.message.get('Subject'))
-    
+
 class MockSMTP:
     def __init__(self, server, port):
         pass
@@ -98,7 +100,7 @@
     env = None
     configcls = ApptestConfiguration
     requestcls = FakeRequest
-    
+
     # user / session management ###############################################
 
     def user(self, req=None):
@@ -116,13 +118,13 @@
 
     def restore_connection(self):
         self.env.restore_connection()
-        
+
     # db api ##################################################################
 
     @nocoverage
     def cursor(self, req=None):
         return self.env.cnx.cursor(req or self.request())
-    
+
     @nocoverage
     def execute(self, *args, **kwargs):
         return self.env.execute(*args, **kwargs)
@@ -130,19 +132,19 @@
     @nocoverage
     def commit(self):
         self.env.cnx.commit()
-    
+
     @nocoverage
     def rollback(self):
         try:
             self.env.cnx.rollback()
         except ProgrammingError:
             pass
-        
+
     # other utilities #########################################################
     def set_debug(self, debugmode):
         from cubicweb.server import set_debug
         set_debug(debugmode)
-    
+
     @property
     def config(self):
         return self.vreg.config
@@ -150,7 +152,7 @@
     def session(self):
         """return current server side session (using default manager account)"""
         return self.env.repo._sessions[self.env.cnx.sessionid]
-    
+
     def request(self, *args, **kwargs):
         """return a web interface request"""
         return self.env.create_request(*args, **kwargs)
@@ -158,16 +160,16 @@
     @nocoverage
     def rset_and_req(self, *args, **kwargs):
         return self.env.get_rset_and_req(*args, **kwargs)
-    
+
     def entity(self, rql, args=None, eidkey=None, req=None):
         return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
-    
+
     def etype_instance(self, etype, req=None):
         req = req or self.request()
         e = self.env.vreg.etype_class(etype)(req, None, None)
         e.eid = None
         return e
-    
+
     def add_entity(self, etype, **kwargs):
         rql = ['INSERT %s X' % etype]
 
@@ -183,15 +185,15 @@
             sub_rql = []
             for key, value in kwargs.iteritems():
                 # entities
-                if hasattr(value, 'eid'): 
+                if hasattr(value, 'eid'):
                     new_value = "%s__" % key.upper()
-                    
+
                     entities[new_value] = value.eid
                     rql_args[new_value] = value.eid
-                    
+
                     sub_rql.append("X %s %s" % (key, new_value))
                 # final attributes
-                else: 
+                else:
                     sub_rql.append('X %s %%(%s)s' % (key, key))
                     rql_args[key] = value
             rql.append(', '.join(sub_rql))
@@ -213,11 +215,18 @@
         self.vreg.config.global_set_option(optname, value)
 
     def pviews(self, req, rset):
-        return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset)) 
-        
+        return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset))
+
     def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
         return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
                 if a.category not in skipcategories]
+
+    def pactions_by_cats(self, req, rset, categories=('addrelated',)):
+        return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
+                if a.category in categories]
+
+    paddrelactions = deprecated_function(pactions_by_cats)
+
     def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
         res = {}
         for a in self.vreg.possible_vobjects('actions', req, rset):
@@ -225,20 +234,17 @@
                 res.setdefault(a.category, []).append(a.__class__)
         return res
 
-    def paddrelactions(self, req, rset):
-        return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
-                if a.category == 'addrelated']
-               
+
     def remote_call(self, fname, *args):
         """remote call simulation"""
         dump = simplejson.dumps
         args = [dump(arg) for arg in args]
-        req = self.request(mode='remote', fname=fname, pageid='123', arg=args)
+        req = self.request(fname=fname, pageid='123', arg=args)
         ctrl = self.env.app.select_controller('json', req)
         return ctrl.publish(), req
 
     # default test setup and teardown #########################################
-        
+
     def setup_database(self):
         pass
 
@@ -258,7 +264,7 @@
         self.setup_database()
         self.commit()
         MAILBOX[:] = [] # reset mailbox
-        
+
     @nocoverage
     def tearDown(self):
         self.rollback()
@@ -349,13 +355,13 @@
     """
     __metaclass__ = autorepo
     repo_config = None # set a particular config instance if necessary
-    
+
     # user / session management ###############################################
 
     def create_user(self, user, groups=('users',), password=None, commit=True):
         if password is None:
             password = user
-        eid = self.execute('INSERT EUser X: X login %(x)s, X upassword %(p)s,'
+        eid = self.execute('INSERT CWUser X: X login %(x)s, X upassword %(p)s,'
                             'X in_state S WHERE S name "activated"',
                             {'x': unicode(user), 'p': password})[0][0]
         groups = ','.join(repr(group) for group in groups)
@@ -363,9 +369,9 @@
                       {'x': eid})
         if commit:
             self.commit()
-        self.session.reset_pool()        
+        self.session.reset_pool()
         return eid
-    
+
     def login(self, login, password=None):
         cnx = repo_connect(self.repo, unicode(login), password or login,
                            ConnectionProperties('inmemory'))
@@ -374,7 +380,7 @@
 
     def current_session(self):
         return self.repo._sessions[self.cnxs[-1].sessionid]
-    
+
     def restore_connection(self):
         assert len(self.cnxs) == 1, self.cnxs
         cnx = self.cnxs.pop()
@@ -382,7 +388,7 @@
             cnx.close()
         except Exception, ex:
             print "exception occured while closing connection", ex
-        
+
     # db api ##################################################################
 
     def execute(self, rql, args=None, eid_key=None):
@@ -394,27 +400,27 @@
         # application entities for convenience
         self.session.set_pool()
         return rset
-    
+
     def commit(self):
         self.__commit(self.cnxid)
-        self.session.set_pool()        
-    
+        self.session.set_pool()
+
     def rollback(self):
         self.__rollback(self.cnxid)
-        self.session.set_pool()        
-    
+        self.session.set_pool()
+
     def close(self):
         self.__close(self.cnxid)
 
     # other utilities #########################################################
-    
+
     def set_debug(self, debugmode):
         from cubicweb.server import set_debug
         set_debug(debugmode)
-        
+
     def set_option(self, optname, value):
         self.vreg.config.global_set_option(optname, value)
-            
+
     def add_entity(self, etype, **kwargs):
         restrictions = ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
         rql = 'INSERT %s X' % etype
@@ -428,7 +434,7 @@
         user = unicode(config.sources()['system']['db-user'])
         passwd = config.sources()['system']['db-password']
         return user, passwd
-    
+
     def close_connections(self):
         for cnx in self.cnxs:
             try:
@@ -440,10 +446,10 @@
 
     pactions = EnvBasedTC.pactions.im_func
     pactionsdict = EnvBasedTC.pactionsdict.im_func
-    
+
     # default test setup and teardown #########################################
     copy_schema = False
-    
+
     def _prepare(self):
         MAILBOX[:] = [] # reset mailbox
         if hasattr(self, 'cnxid'):
@@ -488,18 +494,15 @@
     @property
     def schema(self):
         return self.repo.schema
-    
+
     def setUp(self):
         self._prepare()
         self.session.set_pool()
         self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-        #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/cwtwill.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/cwtwill.py	Thu May 14 12:50:34 2009 +0200
@@ -1,4 +1,9 @@
-"""cubicweb extensions for twill"""
+"""cubicweb extensions for twill
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
 
 import re
 from urllib import quote
@@ -24,9 +29,9 @@
                 # if url is specified linkurl must match
                 if url and linkurl != url:
                     continue
-                return        
+                return
     raise AssertionError('link %s (%s) not found' % (text, url))
-        
+
 
 def view(rql, vid=''):
     """
@@ -56,7 +61,7 @@
     twc.go('view?rql=%s&vid=edition' % quote(rql))
 
 
-        
+
 
 def setvalue(formname, fieldname, value):
     """
@@ -104,5 +109,5 @@
     browser._browser.form = form
     browser.submit(submit_button)
 
-    
+
 # missing actions: delete, copy, changeview
--- a/devtools/devctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/devctl.py	Thu May 14 12:50:34 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
 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
 
@@ -35,11 +36,11 @@
         if cube is None:
             self._cubes = ()
         else:
-            self._cubes = self.expand_cubes(self.my_cubes(cube))
+            self._cubes = self.reorder_cubes(self.expand_cubes(self.my_cubes(cube)))
 
     def my_cubes(self, cube):
         return (cube,) + self.cube_dependencies(cube) + self.cube_recommends(cube)
-    
+
     @property
     def apphome(self):
         return None
@@ -76,7 +77,12 @@
             if mod.__file__.startswith(path):
                 del sys.modules[name]
                 break
-    
+        # fresh rtags
+        from cubicweb import rtags
+        from cubicweb.web import uicfg
+        rtags.RTAGS[:] = []
+        reload(uicfg)
+
 def generate_schema_pot(w, cubedir=None):
     """generate a pot file with schema specific i18n messages
 
@@ -85,30 +91,31 @@
     """
     from cubicweb.cwvreg import CubicWebRegistry
     cube = cubedir and split(cubedir)[-1]
-    config = DevDepConfiguration(cube)
-    cleanup_sys_modules(config)
+    libconfig = DevDepConfiguration(cube)
+    libconfig.cleanup_interface_sobjects = False
+    cleanup_sys_modules(libconfig)
     if cubedir:
-        libschema = config.load_schema()
         config = DevCubeConfiguration(cube)
-        schema = config.load_schema()
+        config.cleanup_interface_sobjects = False
     else:
-        schema = config.load_schema()
-        libschema = None
-        config.cleanup_interface_sobjects = False
+        config = libconfig
+        libconfig = None
+    schema = config.load_schema(remove_unused_rtypes=False)
     vreg = CubicWebRegistry(config)
     # set_schema triggers objects registrations
     vreg.set_schema(schema)
     w(DEFAULT_POT_HEAD)
-    _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
+    _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube)
+
+
+def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
     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')
-    if libschema is not None:
+    if libconfig is not None:
+        libschema = libconfig.load_schema(remove_unused_rtypes=False)
         entities = [e for e in schema.entities() if not e in libschema]
     else:
         entities = schema.entities()
@@ -128,7 +135,7 @@
     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:
+    if libconfig is not None:
         relations = [r for r in schema.relations() if not r in libschema]
     else:
         relations = schema.relations()
@@ -143,46 +150,70 @@
             add_msg(w, rschema.description)
     w('# add related box generated message\n')
     w('\n')
+    actionbox = vreg['boxes']['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()),
+        for role, 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)
+                if libconfig is not None:
+                    librschema = libschema.get(rschema)
+                for teschema in rschema.targets(eschema, role):
+                    if libconfig is not None and librschema is not None:
+                        if role == 'subject':
+                            subjtype, objtype = eschema, teschema
                         else:
-                            label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
-                            label2 = "creating %s (%s %s %s %%(linkto)s)" % (teschema, teschema, rschema, eschema)
+                            subjtype, objtype = teschema, eschema
+                        if librschema.has_rdef(subjtype, objtype):
+                            continue
+                    if actionbox.appearsin_addmenu.etype_get(eschema, rschema,
+                                                             role, teschema):
+                        if role == 'subject':
+                            label = 'add %s %s %s %s' % (eschema, rschema,
+                                                         teschema, role)
+                            label2 = "creating %s (%s %%(linkto)s %s %s)" % (
+                                teschema, eschema, rschema, teschema)
+                        else:
+                            label = 'add %s %s %s %s' % (teschema, rschema,
+                                                         eschema, role)
+                            label2 = "creating %s (%s %s %s %%(linkto)s)" % (
+                                teschema, teschema, rschema, eschema)
                         add_msg(w, label)
                         add_msg(w, label2)
-    cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
+    #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
     done = set()
+    if libconfig is not None:
+        from cubicweb.cwvreg import CubicWebRegistry
+        libvreg = CubicWebRegistry(libconfig)
+        libvreg.set_schema(libschema) # trigger objects registration
+        # prefill done set
+        list(_iter_vreg_objids(libvreg, done))
+    for objid in _iter_vreg_objids(vreg, done):
+        add_msg(w, '%s_description' % objid)
+        add_msg(w, objid)
+
+def _iter_vreg_objids(vreg, done, prefix=None):
     for reg, objdict in vreg.items():
         for objects in objdict.values():
             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)
+                    break
+                if obj.property_defs:
+                    yield objid
                     done.add(objid)
+                    break
 
-                    
-def defined_in_library(libschema, etype, rtype, tetype, x):
-    """return true if the given relation definition exists in cubicweb's library"""
+
+def defined_in_library(etype, rtype, tetype, role):
+    """return true if the given relation definition exists in cubicweb's library
+    """
     if libschema is None:
         return False
-    if x == 'subject':
+    if role == 'subject':
         subjtype, objtype = etype, tetype
     else:
         subjtype, objtype = tetype, etype
@@ -211,7 +242,7 @@
 
 class UpdateCubicWebCatalogCommand(Command):
     """Update i18n catalogs for cubicweb library.
-    
+
     It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those
     files to add translations of newly added messages.
     """
@@ -251,8 +282,12 @@
             cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
             if lang is not None:
                 cmd += ' -L %s' % lang
-            potfiles.append(join(tempdir, '%s.pot' % id))
-            execute(cmd % (potfiles[-1], ' '.join(files)))
+            potfile = join(tempdir, '%s.pot' % id)
+            execute(cmd % (potfile, ' '.join(files)))
+            if exists(potfile):
+                potfiles.append(potfile)
+            else:
+                print 'WARNING: %s file not generated' % potfile
         print '******** merging .pot files'
         cubicwebpot = join(tempdir, 'cubicweb.pot')
         execute('msgcat %s > %s' % (' '.join(potfiles), cubicwebpot))
@@ -281,7 +316,7 @@
     """
     name = 'i18nupdate'
     arguments = '[<cube>...]'
-    
+
     def run(self, args):
         """run the command with its specific arguments"""
         if args:
@@ -291,74 +326,86 @@
             cubes = [cubepath for cubepath in cubes if exists(join(cubepath, 'i18n'))]
         update_cubes_catalogs(cubes)
 
+
 def update_cubes_catalogs(cubes):
+    toedit = []
+    for cubedir in cubes:
+        if not isdir(cubedir):
+            print 'not a directory', cubedir
+            continue
+        try:
+            toedit += update_cube_catalogs(cubedir)
+        except Exception:
+            import traceback
+            traceback.print_exc()
+            print 'error while updating catalogs for', cubedir
+    # instructions pour la suite
+    print '*' * 72
+    print 'you can now edit the following files:'
+    print '* ' + '\n* '.join(toedit)
+
+
+def update_cube_catalogs(cubedir):
     import shutil
     from tempfile import mktemp
     from logilab.common.fileutils import ensure_fs_mode
     from logilab.common.shellutils import find, rm
     from cubicweb.common.i18n import extract_from_tal, execute
     toedit = []
-    for cubedir in cubes:
-        cube = basename(normpath(cubedir))
-        if not isdir(cubedir):
-            print 'unknown cube', cube
-            continue
-        tempdir = mktemp()
-        mkdir(tempdir)
-        print '*' * 72
-        print 'updating %s cube...' % cube
-        chdir(cubedir)
-        potfiles = [join('i18n', scfile) for scfile in ('entities.pot',)
-                    if exists(join('i18n', scfile))]
-        print '******** extract schema messages'
-        schemapot = join(tempdir, 'schema.pot')
-        potfiles.append(schemapot)
-        # explicit close necessary else the file may not be yet flushed when
-        # we'll using it below
-        schemapotstream = file(schemapot, 'w')
-        generate_schema_pot(schemapotstream.write, cubedir)
-        schemapotstream.close()
-        print '******** extract TAL messages'
-        tali18nfile = join(tempdir, 'tali18n.py')
-        extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
-        print '******** extract Javascript messages'
-        jsfiles =  [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
-        if jsfiles:
-            tmppotfile = 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 exists(tmppotfile): 
-                potfiles.append(tmppotfile)
-        print '******** create cube specific catalog'
-        tmppotfile = join(tempdir, 'generated.pot')
-        cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
-        cubefiles.append(tali18nfile)
-        execute('xgettext --no-location --omit-header -k_ -o %s %s'
-                % (tmppotfile, ' '.join(cubefiles)))
-        if exists(tmppotfile): # doesn't exists of no translation string found
+    cube = basename(normpath(cubedir))
+    tempdir = mktemp()
+    mkdir(tempdir)
+    print '*' * 72
+    print 'updating %s cube...' % cube
+    chdir(cubedir)
+    potfiles = [join('i18n', scfile) for scfile in ('entities.pot',)
+                if exists(join('i18n', scfile))]
+    print '******** extract schema messages'
+    schemapot = join(tempdir, 'schema.pot')
+    potfiles.append(schemapot)
+    # explicit close necessary else the file may not be yet flushed when
+    # we'll using it below
+    schemapotstream = file(schemapot, 'w')
+    generate_schema_pot(schemapotstream.write, cubedir)
+    schemapotstream.close()
+    print '******** extract TAL messages'
+    tali18nfile = join(tempdir, 'tali18n.py')
+    extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
+    print '******** extract Javascript messages'
+    jsfiles =  [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
+    if jsfiles:
+        tmppotfile = 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 exists(tmppotfile):
             potfiles.append(tmppotfile)
-        potfile = join(tempdir, 'cube.pot')
-        print '******** merging .pot files'
-        execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
-        print '******** merging main pot file with existing translations'
-        chdir('i18n')
-        for lang in LANGS:
-            print '****', lang
-            cubepo = '%s.po' % lang
-            if not exists(cubepo):
-                shutil.copy(potfile, cubepo)
-            else:
-                execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo))
-                ensure_fs_mode(cubepo)
-                shutil.move('%snew' % cubepo, cubepo)
-            toedit.append(abspath(cubepo))
-        # cleanup
-        rm(tempdir)
-    # instructions pour la suite
-    print '*' * 72
-    print 'you can now edit the following files:'
-    print '* ' + '\n* '.join(toedit)
+    print '******** create cube specific catalog'
+    tmppotfile = join(tempdir, 'generated.pot')
+    cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
+    cubefiles.append(tali18nfile)
+    execute('xgettext --no-location --omit-header -k_ -o %s %s'
+            % (tmppotfile, ' '.join(cubefiles)))
+    if exists(tmppotfile): # doesn't exists of no translation string found
+        potfiles.append(tmppotfile)
+    potfile = join(tempdir, 'cube.pot')
+    print '******** merging .pot files'
+    execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
+    print '******** merging main pot file with existing translations'
+    chdir('i18n')
+    for lang in LANGS:
+        print '****', lang
+        cubepo = '%s.po' % lang
+        if not exists(cubepo):
+            shutil.copy(potfile, cubepo)
+        else:
+            execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo))
+            ensure_fs_mode(cubepo)
+            shutil.move('%snew' % cubepo, cubepo)
+        toedit.append(abspath(cubepo))
+    # cleanup
+    rm(tempdir)
+    return toedit
 
 
 class LiveServerCommand(Command):
@@ -367,7 +414,7 @@
     name = 'live-server'
     arguments = ''
     options = ()
-    
+
     def run(self, args):
         """run the command with its specific arguments"""
         from cubicweb.devtools.livetest import runserver
@@ -415,7 +462,7 @@
          ),
         )
 
-    
+
     def run(self, args):
         if len(args) != 1:
             raise BadCommandUsage("exactly one argument (cube name) is expected")
@@ -449,7 +496,7 @@
                     distname = 'cubicweb-' + distname
         else:
             distname = 'cubicweb-%s' % cubename.lower()
-        
+
         longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
         if verbose:
             longdesc = raw_input('Enter a long description (or nothing if you want to reuse the short one): ')
@@ -461,14 +508,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'],
@@ -488,7 +534,7 @@
             elif ans == 's':
                 break
         return includes
-    
+
 
 class ExamineLogCommand(Command):
     """Examine a rql log file.
@@ -507,23 +553,28 @@
     name = 'exlog'
     options = (
         )
-    
+
     def run(self, args):
         if args:
             raise BadCommandUsage("no argument expected")
         import re
         requests = {}
-        for line in sys.stdin:
+        for lineno, line in enumerate(sys.stdin):
             if not ' WHERE ' in line:
                 continue
             #sys.stderr.write( line )
-            rql, time = line.split('--')
-            rql = re.sub("(\'\w+': \d*)", '', rql)
-            req = requests.setdefault(rql, [])
-            time.strip()
-            chunks = time.split()
-            cputime = float(chunks[-3])
-            req.append( cputime )
+            try:
+                rql, time = line.split('--')
+                rql = re.sub("(\'\w+': \d*)", '', rql)
+                if '{' in rql:
+                    rql = rql[:rql.index('{')]
+                req = requests.setdefault(rql, [])
+                time.strip()
+                chunks = time.split()
+                cputime = float(chunks[-3])
+                req.append( cputime )
+            except Exception, exc:
+                sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
 
         stat = []
         for rql, times in requests.items():
@@ -531,9 +582,12 @@
 
         stat.sort()
         stat.reverse()
+
+        total_time = sum(time for time, occ, rql in stat)*0.01
+        print 'Percentage;Cumulative Time;Occurences;Query'
         for time, occ, rql in stat:
-            print time, occ, rql
-        
+            print '%.2f;%.2f;%s;%s' % (time/total_time, time, occ, rql)
+
 register_commands((UpdateCubicWebCatalogCommand,
                    UpdateTemplateCatalogCommand,
                    LiveServerCommand,
--- a/devtools/fake.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/fake.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Fake objects to ease testing of cubicweb without a fully working environment
 
 :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"
@@ -24,13 +24,13 @@
         self.apphome = apphome
         self._cubes = cubes
         self['auth-mode'] = 'cookie'
-        self['uid'] = None 
+        self['uid'] = None
         self['base-url'] = BASE_URL
         self['rql-cache-size'] = 100
-       
+
     def cubes(self, expand=False):
         return self._cubes
-    
+
     def sources(self):
         return {}
 
@@ -41,7 +41,7 @@
         self.properties = {'ui.encoding': 'UTF8',
                            'ui.language': 'en',
                            }
-        
+
     def property_value(self, key):
         return self.properties[key]
 
@@ -51,10 +51,10 @@
         'views' : [Mock(id='primary'), Mock(id='secondary'),
                          Mock(id='oneline'), Mock(id='list')],
         }
-    
+
     def registry_objects(self, name, oid=None):
         return self._registries[name]
-    
+
     def etype_class(self, etype):
         class Entity(dict):
             e_schema = self.schema[etype]
@@ -112,15 +112,15 @@
     def set_header(self, header, value):
         """set an output HTTP header"""
         pass
-    
+
     def add_header(self, header, value):
         """set an output HTTP header"""
         pass
-    
+
     def remove_header(self, header):
         """remove an output HTTP header"""
         pass
-    
+
     def get_header(self, header, default=None):
         """return the value associated with the given input header,
         raise KeyError if the header is not set
@@ -169,7 +169,7 @@
         self.is_internal_session = False
         self.is_super_session = self.user.eid == -1
         self._query_data = {}
-        
+
     def execute(self, *args):
         pass
     def commit(self, *args):
@@ -186,7 +186,7 @@
 
     def set_entity_cache(self, entity):
         pass
-    
+
 class FakeRepo(object):
     querier = None
     def __init__(self, schema, vreg=None, config=None):
@@ -199,8 +199,9 @@
 
     def internal_session(self):
         return FakeSession(self)
-    
-    def extid2eid(self, source, extid, etype, session, insert=True):
+
+    def extid2eid(self, source, extid, etype, session, insert=True,
+                  recreate=False):
         try:
             return self.extids[extid]
         except KeyError:
@@ -213,7 +214,7 @@
             self.eids[eid] = extid
             source.after_entity_insertion(session, extid, entity)
             return eid
-        
+
     def eid2extid(self, source, eid, session=None):
         return self.eids[eid]
 
@@ -228,7 +229,7 @@
     def __init__(self, uri):
         self.uri = uri
 
-        
+
 class FakePool(object):
     def source(self, uri):
         return FakeSource(uri)
--- a/devtools/fill.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/fill.py	Thu May 14 12:50:34 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
@@ -33,7 +33,7 @@
         if isinstance(cst, StaticVocabularyConstraint):
             return cst.vocabulary()
     return None
-    
+
 
 def get_max_length(eschema, attrname):
     """returns the maximum length allowed for 'attrname'"""
@@ -75,7 +75,7 @@
             value = self.__generate_value(attrname, index, **kwargs)
         _GENERATED_VALUES.setdefault((self.e_schema.type, attrname), set()).add(value)
         return value
-        
+
     def __generate_value(self, attrname, index, **kwargs):
         """generates a consistent value for 'attrname'"""
         attrtype = str(self.e_schema.destination(attrname)).lower()
@@ -100,7 +100,7 @@
         if choices is None:
             return None
         return unicode(choice(choices)) # FIXME
-        
+
     def generate_string(self, attrname, index, format=None):
         """generates a consistent value for 'attrname' if it's a string"""
         # First try to get choices
@@ -133,7 +133,7 @@
     def generate_password(self, attrname, index):
         """generates a consistent value for 'attrname' if it's a password"""
         return u'toto'
-        
+
     def generate_integer(self, attrname, index):
         """generates a consistent value for 'attrname' if it's an integer"""
         choosed = self.generate_choice(attrname, index)
@@ -145,29 +145,29 @@
         else:
             maxvalue = maxvalue or index
         return randint(minvalue or 0, maxvalue)
-    
+
     generate_int = generate_integer
-    
+
     def generate_float(self, attrname, index):
         """generates a consistent value for 'attrname' if it's a float"""
         return float(randint(-index, index))
-    
+
     def generate_decimal(self, attrname, index):
         """generates a consistent value for 'attrname' if it's a float"""
         return Decimal(str(self.generate_float(attrname, index)))
-    
+
     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):
         # modpython way
@@ -175,7 +175,7 @@
         fakefile.filename = "file_%s" % attrname
         fakefile.value = fakefile.getvalue()
         return fakefile
-    
+
     def generate_boolean(self, attrname, index):
         """generates a consistent value for 'attrname' if it's a boolean"""
         return index % 2 == 0
@@ -185,7 +185,7 @@
         # need this method else stupid values will be set which make mtconverter
         # raise exception
         return u'application/octet-stream'
-    
+
     def generate_Any_content_format(self, index, **kwargs):
         # content_format attribute of EmailPart has no vocabulary constraint, we
         # need this method else stupid values will be set which make mtconverter
@@ -236,7 +236,7 @@
                         returns acceptable values for this attribute
     """
     # XXX HACK, remove or fix asap
-    if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'EGroup', 'EUser')):
+    if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
         return []
     queries = []
     for index in xrange(entity_num):
@@ -250,7 +250,7 @@
                             args))
             assert not 'eid' in args, args
         else:
-            queries.append(('INSERT %s X' % etype, {}))        
+            queries.append(('INSERT %s X' % etype, {}))
     return queries
 
 
@@ -365,7 +365,7 @@
                     continue
                 subjcard, objcard = rschema.rproperty(subj, obj, 'cardinality')
                 # process mandatory relations first
-                if subjcard in '1+' or objcard in '1+': 
+                if subjcard in '1+' or objcard in '1+':
                     queries += self.make_relation_queries(sedict, oedict,
                                                           rschema, subj, obj)
                 else:
@@ -374,7 +374,7 @@
                 queries += self.make_relation_queries(sedict, oedict, rschema,
                                                       subj, obj)
         return queries
-        
+
     def qargs(self, subjeids, objeids, subjcard, objcard, subjeid, objeid):
         if subjcard in '?1':
             subjeids.remove(subjeid)
@@ -411,7 +411,7 @@
                         subjeids.remove(subjeid)
         if not subjeids:
             check_card_satisfied(objcard, objeids, subj, rschema, obj)
-            return 
+            return
         if not objeids:
             check_card_satisfied(subjcard, subjeids, subj, rschema, obj)
             return
@@ -452,7 +452,7 @@
                     used.add( (subjeid, objeid) )
                     yield q, self.qargs(subjeids, objeids, subjcard, objcard,
                                         subjeid, objeid)
-                    
+
 def check_card_satisfied(card, remaining, subj, rschema, obj):
     if card in '1+' and remaining:
         raise Exception("can't satisfy cardinality %s for relation %s %s %s"
@@ -466,8 +466,8 @@
     while objeid == avoid: # avoid infinite recursion like in X comment X
         objeid = choice(values)
     return objeid
-                    
-                
+
+
 
 # UTILITIES FUNCS ##############################################################
 def make_tel(num_tel):
--- a/devtools/htmlparser.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/htmlparser.py	Thu May 14 12:50:34 2009 +0200
@@ -1,20 +1,17 @@
 """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
-
-STRICT_DOCTYPE = str(STRICT_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
-TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
+from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE
+STRICT_DOCTYPE = str(STRICT_DOCTYPE)
+TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE)
 
 ERR_COUNT = 0
 
 class Validator(object):
-    
+
     def parse_string(self, data, sysid=None):
         try:
             data = self.preprocess_data(data)
@@ -55,24 +52,11 @@
         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)
+        return '<?xml version="1.0" encoding="UTF-8"?>%s\n%s' % (
+            STRICT_DOCTYPE, data)
 
-   
+
 class SaxOnlyValidator(Validator):
 
     def __init__(self):
@@ -85,7 +69,7 @@
         Validator.__init__(self)
         self.parser = etree.HTMLParser()
 
-    
+
 
 class PageInfo(object):
     """holds various informations on the view's output"""
@@ -103,7 +87,7 @@
         self.h4_tags = self.find_tag('h4')
         self.input_tags = self.find_tag('input')
         self.title_tags = [self.h1_tags, self.h2_tags, self.h3_tags, self.h4_tags]
-        
+
     def find_tag(self, tag):
         """return a list which contains text of all "tag" elements """
         if self.default_ns is None:
@@ -113,14 +97,14 @@
         if tag in ('a', 'input'):
             return [(elt.text, elt.attrib) for elt in self.etree.iterfind(iterstr)]
         return [u''.join(elt.xpath('.//text()')) for elt in self.etree.iterfind(iterstr)]
-         
+
     def appears(self, text):
         """returns True if <text> appears in the page"""
         return text in self.raw_text
 
     def __contains__(self, text):
         return text in self.source
-    
+
     def has_title(self, text, level=None):
         """returns True if <h?>text</h?>
 
@@ -150,7 +134,7 @@
                 if sre.match(title):
                     return True
             return False
-    
+
     def has_link(self, text, url=None):
         """returns True if <a href=url>text</a> was found in the page"""
         for link_text, attrs in self.a_tags:
@@ -164,7 +148,7 @@
                 except KeyError:
                     continue
         return False
-    
+
     def has_link_regexp(self, pattern, url=None):
         """returns True if <a href=url>pattern</a> was found in the page"""
         sre = re.compile(pattern)
--- a/devtools/livetest.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/livetest.py	Thu May 14 12:50:34 2009 +0200
@@ -1,4 +1,9 @@
-"""provide utilies for web (live) unit testing"""
+"""provide utilies for web (live) unit testing
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
 
 import socket
 import logging
@@ -36,17 +41,14 @@
         """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, ()
-    
-    
-    
+
+
+
 def make_site(cube, options=None):
     from cubicweb.etwist import twconfig # trigger configuration registration
     sourcefile = options.sourcefile
@@ -78,7 +80,7 @@
 def saveconf(templhome, port, user, passwd):
     import pickle
     conffile = file(join(templhome, 'test', 'livetest.conf'), 'w')
-    
+
     pickle.dump((port, user, passwd, get_starturl(port, user, passwd)),
                 conffile)
     conffile.close()
@@ -102,8 +104,8 @@
     from twill import browser as twb
     twc.OUT = new_output
     twb.OUT = new_output
-    
-    
+
+
 class LiveTestCase(TestCase):
 
     sourcefile = None
@@ -121,7 +123,7 @@
 
     def tearDown(self):
         self.teardown_db(self.cnx)
-    
+
 
     def setup_db(self, cnx):
         """override setup_db() to setup your environment"""
@@ -144,5 +146,3 @@
 
 if __name__ == '__main__':
     runserver()
-
-
--- a/devtools/repotest.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/repotest.py	Thu May 14 12:50:34 2009 +0200
@@ -43,7 +43,7 @@
                               'expected %s queries, got %s' % (len(equeries), len(queries)))
             for i, (rql, sol) in enumerate(queries):
                 self.assertEquals(rql, equeries[i][0])
-                self.assertEquals(sol, equeries[i][1])
+                self.assertEquals(sorted(sol), sorted(equeries[i][1]))
             idx = 2
         else:
             idx = 1
@@ -72,7 +72,7 @@
     def __contains__(self, key):
         return key in self.iterkeys()
     def __getitem__(self, key):
-        for key_, value in self.iteritems():
+        for key_, value in list.__iter__(self):
             if key == key_:
                 return value
         raise KeyError(key)
@@ -80,6 +80,17 @@
         return (x for x, y in list.__iter__(self))
     def iteritems(self):
         return (x for x in list.__iter__(self))
+    def items(self):
+        return [x for x in list.__iter__(self)]
+
+class DumbOrderedDict2(object):
+    def __init__(self, origdict, sortkey):
+        self.origdict = origdict
+        self.sortkey = sortkey
+    def __getattr__(self, attr):
+        return getattr(self.origdict, attr)
+    def __iter__(self):
+        return iter(sorted(self.origdict, key=self.sortkey))
 
 
 from logilab.common.testlib import TestCase
@@ -92,7 +103,7 @@
 
 class RQLGeneratorTC(TestCase):
     schema = None # set this in concret test
-    
+
     def setUp(self):
         self.rqlhelper = RQLHelper(self.schema, special_relations={'eid': 'uid',
                                                                    'has_text': 'fti'})
@@ -103,7 +114,7 @@
     def tearDown(self):
         ExecutionPlan._check_permissions = _orig_check_permissions
         rqlannotation._select_principal = _orig_select_principal
-        
+
     def _prepare(self, rql):
         #print '******************** prepare', rql
         union = self.rqlhelper.parse(rql)
@@ -122,7 +133,7 @@
 
 class BaseQuerierTC(TestCase):
     repo = None # set this in concret test
-    
+
     def setUp(self):
         self.o = self.repo.querier
         self.session = self.repo._sessions.values()[0]
@@ -137,7 +148,7 @@
         return self.session.unsafe_execute('Any MAX(X)')[0][0]
     def cleanup(self):
         self.session.unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
-        
+
     def tearDown(self):
         undo_monkey_patch()
         self.session.rollback()
@@ -148,7 +159,7 @@
 
     def set_debug(self, debug):
         set_debug(debug)
-        
+
     def _rqlhelper(self):
         rqlhelper = self.o._rqlhelper
         # reset uid_func so it don't try to get type from eids
@@ -164,8 +175,8 @@
         for select in rqlst.children:
             select.solutions.sort()
         return self.o.plan_factory(rqlst, kwargs, self.session)
-        
-    def _prepare(self, rql, kwargs=None):    
+
+    def _prepare(self, rql, kwargs=None):
         plan = self._prepare_plan(rql, kwargs)
         plan.preprocess(plan.rqlst)
         rqlst = plan.rqlst.children[0]
@@ -184,10 +195,10 @@
 
     def execute(self, rql, args=None, eid_key=None, build_descr=True):
         return self.o.execute(self.session, rql, args, eid_key, build_descr)
-    
+
     def commit(self):
         self.session.commit()
-        self.session.set_pool()        
+        self.session.set_pool()
 
 
 class BasePlannerTC(BaseQuerierTC):
@@ -217,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)]
 
@@ -230,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):
@@ -256,17 +267,17 @@
     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_var(self, sourcevars):
-            pass    
+        def _choose_term(self, sourceterms):
+            pass
 _orig_merge_input_maps = PartPlanInformation.merge_input_maps
-_orig_choose_var = PartPlanInformation._choose_var
+_orig_choose_term = PartPlanInformation._choose_term
 
 def _merge_input_maps(*args):
     return sorted(_orig_merge_input_maps(*args))
 
-def _choose_var(self, sourcevars):
+def _choose_term(self, sourceterms):
     # predictable order for test purpose
     def get_key(x):
         try:
@@ -279,17 +290,7 @@
             except AttributeError:
                 # const
                 return x.value
-    varsinorder = sorted(sourcevars, key=get_key)
-    if len(self._sourcesvars) > 1:
-        for var in varsinorder:
-            if not var.scope is self.rqlst:
-                return var, sourcevars.pop(var)
-    else:
-        for var in varsinorder:
-            if var.scope is self.rqlst:
-                return var, sourcevars.pop(var)
-    var = varsinorder[0]
-    return var, sourcevars.pop(var)
+    return _orig_choose_term(self, DumbOrderedDict2(sourceterms, get_key))
 
 
 def do_monkey_patch():
@@ -299,7 +300,7 @@
     ExecutionPlan.tablesinorder = None
     ExecutionPlan.init_temp_table = _init_temp_table
     PartPlanInformation.merge_input_maps = _merge_input_maps
-    PartPlanInformation._choose_var = _choose_var
+    PartPlanInformation._choose_term = _choose_term
 
 def undo_monkey_patch():
     RQLRewriter.insert_snippets = _orig_insert_snippets
@@ -307,5 +308,4 @@
     ExecutionPlan._check_permissions = _orig_check_permissions
     ExecutionPlan.init_temp_table = _orig_init_temp_table
     PartPlanInformation.merge_input_maps = _orig_merge_input_maps
-    PartPlanInformation._choose_var = _orig_choose_var
-
+    PartPlanInformation._choose_term = _orig_choose_term
--- a/devtools/stresstester.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/stresstester.py	Thu May 14 12:50:34 2009 +0200
@@ -5,13 +5,13 @@
 OPTIONS:
   -h / --help
      Display this help message and exit.
-     
+
   -u / --user <user>
      Connect as <user> instead of being prompted to give it.
   -p / --password <password>
      Automatically give <password> for authentication instead of being prompted
      to give it.
-     
+
   -n / --nb-times <num>
      Repeat queries <num> times.
   -t / --nb-threads <num>
@@ -21,7 +21,7 @@
   -o / --report-output <filename>
      Write profiler report into <filename> rather than on stdout
 
-Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
@@ -49,7 +49,7 @@
         self._times = times
         self._queries = queries
         self._reporter = reporter
-        
+
     def run(self):
         cursor = self._cursor
         times = self._times
@@ -80,7 +80,7 @@
     threads and can write a report that summarizes all profile informations
     """
     profiler_lock = threading.Lock()
-    
+
     def __init__(self, queries):
         self._queries = tuple(queries)
         self._profile_results = [(0., 0)] * len(self._queries)
@@ -111,8 +111,8 @@
         table_layout = Table(3, rheaders = True, children = table_elems)
         TextWriter().format(table_layout, output)
         # output.write('\n'.join(tmp_output))
-        
-        
+
+
 def run(args):
     """run the command line tool"""
     try:
@@ -150,7 +150,7 @@
         user = raw_input('login: ')
     if password is None:
         password = getpass('password: ')
-    from cubicweb.cwconfig import application_configuration 
+    from cubicweb.cwconfig import application_configuration
     config = application_configuration(args[0])
     # get local access to the repository
     print "Creating repo", prof_file
@@ -176,7 +176,7 @@
     else:
         QueryExecutor(repo_cursor, repeat, queries, reporter = reporter).run()
     reporter.dump_report(report_output)
-    
-    
+
+
 if __name__ == '__main__':
     run(sys.argv[1:])
--- a/devtools/test/data/schema/relations.rel	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/test/data/schema/relations.rel	Thu May 14 12:50:34 2009 +0200
@@ -23,11 +23,11 @@
 Project uses Project
 
 Version version_of Project inline
-Version todo_by EUser
+Version todo_by CWUser
 
 Comment about Bug inline
 Comment about Story inline
 Comment about Comment inline
 
-EUser interested_in Project
+CWUser interested_in Project
 
--- a/devtools/test/data/views/bug.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/test/data/views/bug.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,7 @@
 """only for unit tests !"""
 
-from cubicweb.common.view import EntityView
+from cubicweb.view import EntityView
+from cubicweb.selectors import implements
 
 HTML_PAGE = u"""<html>
   <body>
@@ -11,7 +12,7 @@
 
 class SimpleView(EntityView):
     id = 'simple'
-    accepts = ('Bug',)
+    __select__ = implements('Bug',)
 
     def call(self, **kwargs):
         self.cell_call(0, 0)
@@ -21,7 +22,7 @@
 
 class RaisingView(EntityView):
     id = 'raising'
-    accepts = ('Bug',)
+    __select__ = implements('Bug',)
 
     def cell_call(self, row, col):
         raise ValueError()
--- a/devtools/test/runtests.py	Thu May 14 12:50:14 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/test/unittest_dbfill.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/test/unittest_dbfill.py	Thu May 14 12:50:34 2009 +0200
@@ -31,7 +31,7 @@
             return getattr(self, '_available_%s_%s' % (etype, attrname))(etype, attrname)
         except AttributeError:
             return None
-    
+
     def _available_Person_firstname(self, etype, attrname):
         return [f.strip() for f in file(osp.join(DATADIR, 'firstnames.txt'))]
 
@@ -51,11 +51,11 @@
         year = date.year
         month = date.month
         day = date.day
-        self.failUnless(day in range(1, 29), '%s not in [0;28]' % day) 
+        self.failUnless(day in range(1, 29), '%s not in [0;28]' % day)
         self.failUnless(month in range(1, 13), '%s not in [1;12]' % month)
         self.failUnless(year in range(2000, 2005),
                         '%s not in [2000;2004]' % year)
-        
+
 
     def test_string(self):
         """test string generation"""
@@ -89,7 +89,7 @@
         for index in range(5):
             date_value = self.person_valgen._generate_value('birthday', index)
             self._check_date(date_value)
-        
+
     def test_phone(self):
         """tests make_tel utility"""
         self.assertEquals(make_tel(22030405), '22 03 04 05')
@@ -102,14 +102,14 @@
                           u'yo')
         self.assertEquals(self.person_valgen._generate_value('description', 12),
                           u'yo')
-                          
-        
+
+
 
 class ConstraintInsertionTC(TestCase):
 
     def test_writeme(self):
         self.skip('Test automatic insertion / Schema Constraints')
-    
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/devtools/test/unittest_fill.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/test/unittest_fill.py	Thu May 14 12:50:34 2009 +0200
@@ -20,7 +20,7 @@
         for attrname in attrvalues - set(self.attrvalues):
             delattr(_ValueGenerator, attrname)
 
-        
+
     def test_autoextend(self):
         self.failIf('generate_server' in dir(ValueGenerator))
         class MyValueGenerator(ValueGenerator):
--- a/devtools/test/unittest_testlib.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/test/unittest_testlib.py	Thu May 14 12:50:34 2009 +0200
@@ -22,14 +22,14 @@
             def test_error_view(self):
                 self.add_entity('Bug', title=u"bt")
                 self.view('raising', self.execute('Bug B'), template=None)
-            
+
             def test_correct_view(self):
-                self.view('primary', self.execute('EUser U'), template=None)
-            
+                self.view('primary', self.execute('CWUser U'), template=None)
+
         tests = [MyWebTest('test_error_view'), MyWebTest('test_correct_view')]
         result = self.runner.run(TestSuite(tests))
         self.assertEquals(result.testsRun, 2)
-        self.assertEquals(len(result.errors), 0)        
+        self.assertEquals(len(result.errors), 0)
         self.assertEquals(len(result.failures), 1)
 
 
@@ -97,13 +97,13 @@
     def test_source1(self):
         """make sure source is stored correctly"""
         self.assertEquals(self.page_info.source, HTML_PAGE2)
-        
+
     def test_source2(self):
         """make sure source is stored correctly - raise exception"""
         parser = htmlparser.DTDValidator()
         self.assertRaises(AssertionError, parser.parse_string, HTML_PAGE_ERROR)
 
-        
+
     def test_has_title_no_level(self):
         """tests h? tags information"""
         self.assertEquals(self.page_info.has_title('Test'), True)
@@ -128,7 +128,7 @@
         self.assertEquals(self.page_info.has_title_regexp('h[23] title', 2), True)
         self.assertEquals(self.page_info.has_title_regexp('h[23] title', 3), True)
         self.assertEquals(self.page_info.has_title_regexp('h[23] title', 4), False)
-    
+
     def test_appears(self):
         """tests PageInfo.appears()"""
         self.assertEquals(self.page_info.appears('CW'), True)
@@ -151,4 +151,3 @@
 
 if __name__ == '__main__':
     unittest_main()
-
--- a/devtools/testlib.py	Thu May 14 12:50:14 2009 +0200
+++ b/devtools/testlib.py	Thu May 14 12:50:34 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 ###############
@@ -114,16 +110,16 @@
     # maps vid : validator name (override content_type_validators)
     vid_validators = dict((vid, VALMAP[valkey])
                           for vid, valkey in VIEW_VALIDATORS.iteritems())
-    
+
     no_auto_populate = ()
-    ignored_relations = ()    
-        
+    ignored_relations = ()
+
     def custom_populate(self, how_many, cursor):
         pass
-        
+
     def post_populate(self, cursor):
         pass
-    
+
     @nocoverage
     def auto_populate(self, how_many):
         """this method populates the database with `how_many` entities
@@ -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,12 +180,12 @@
         :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)
         # else:
-        #     print 
+        #     print
         req.form['vid'] = vid
         view = self.vreg.select_view(vid, req, rset, **kwargs)
         # set explicit test description
@@ -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
+            viewfunc = view.render
+        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,21 +237,23 @@
                     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
 
 
     def to_test_etypes(self):
         return unprotected_entities(self.schema, strict=True)
-    
+
     def iter_automatic_rsets(self, limit=10):
         """generates basic resultsets for each entity type"""
         etypes = self.to_test_etypes()
         for etype in etypes:
             yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype))
-
         etype1 = etypes.pop()
-        etype2 = etypes.pop()
+        try:
+            etype2 = etypes.pop()
+        except KeyError:
+            etype2 = etype1
         # test a mixed query (DISTINCT/GROUP to avoid getting duplicate
         # X which make muledit view failing for instance (html validation fails
         # because of some duplicate "id" attributes)
@@ -272,7 +262,7 @@
         for rql in self.application_rql:
             yield self.execute(rql)
 
-                
+
     def list_views_for(self, rset):
         """returns the list of views that can be applied on `rset`"""
         req = rset.req
@@ -309,7 +299,7 @@
         req = rset.req
         for box in self.vreg.possible_objects('boxes', req, rset):
             yield box
-            
+
     def list_startup_views(self):
         """returns the list of startup views"""
         req = self.request()
@@ -318,7 +308,7 @@
                 yield view.id
             else:
                 not_selected(self.vreg, view)
-                
+
     def _test_everything_for(self, rset):
         """this method tries to find everything that can be tested
         for `rset` and yields a callable test (as needed in generative tests)
@@ -332,23 +322,19 @@
             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)
+            yield InnerTest(self._testname(rset, box.id, 'box'), box.render)
 
     @staticmethod
     def _testname(rset, objid, objtype):
         return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype)
-            
+
 
 class AutomaticWebTest(WebTest):
     """import this if you wan automatic tests to be ran"""
@@ -365,7 +351,7 @@
         for rset in self.iter_automatic_rsets(limit=10):
             for testargs in self._test_everything_for(rset):
                 yield testargs
-                
+
     ## startup views
     def test_startup_views(self):
         for vid in self.list_startup_views():
@@ -390,7 +376,7 @@
         vreg._selected[vobject.__class__] -= 1
     except (KeyError, AttributeError):
         pass
-        
+
 def vreg_instrumentize(testclass):
     from cubicweb.devtools.apptest import TestEnvironment
     env = testclass._env = TestEnvironment('data', configcls=testclass.configcls,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/README	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,43 @@
+====
+Book
+====
+
+----
+Part
+----
+
+Chapter
+=======
+
+Level 1 section
+---------------
+
+Level 2 section
+~~~~~~~~~~~~~~~
+
+Level 3 section
+```````````````
+
+
+
+*CubicWeb*
+
+
+inline directives:
+  :file:
+  :envvar:
+  :command:
+
+  :ref:, :mod:
+
+
+XXX
+* lien vers cw.cwconfig.CW_CUBES_PATH par ex.
+
+
+.. sourcecode:: python
+
+   class SomePythonCode:
+     ...
+
+.. XXX a comment
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/D050-architecture.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,14 @@
+.. -*- coding: utf-8 -*-
+
+
+Server Architecture
+-------------------
+
+.. image:: images/server-class-diagram.png
+
+`Diagramme ArgoUML`_
+
+[FIXME]
+Make a downloadable source of zargo file.
+
+.. _`Diagramme ArgoUML`: cubicweb.zargo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/rss-xml.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,45 @@
+.. -*- coding: utf-8 -*-
+
+RSS Channel
+-----------
+
+Assuming you have several blog entries, click on the title of the
+search box in the left column. A larger search box should appear. Enter::
+
+   Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
+
+and you get a list of blog entries.
+
+Click on your login at the top right corner. Chose "user preferences",
+then "boxes", then "possible views box" and check "visible = yes"
+before validating your changes.
+
+Enter the same query in the search box and you will see the same list,
+plus a box titled "possible views" in the left column. Click on
+"entityview", then "RSS". 
+
+You just applied the "RSS" view to the RQL selection you requested.
+
+That's it, you have a RSS channel for your blog.
+
+Try again with::
+
+    Any X ORDERBY D WHERE X is BlogEntry, X creation_date D, 
+    X entry_of B, B title "MyLife"
+
+Another RSS channel, but a bit more focused.
+
+A last one for the road::
+
+    Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
+    
+displayed with the RSS view, that's a channel for the last fifteen
+comments posted.
+
+[WRITE ME]
+
+* show that the RSS view can be used to display an ordered selection
+  of blog entries, thus providing a RSS channel
+
+* show that a different selection (by category) means a different channel
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/template.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,20 @@
+
+
+Templates
+---------
+
+*Templates* are specific views that do not depend on a result set. The basic
+class `Template` (`cubicweb.common.view`) is derived from the class `View`.
+
+To build a HTML page, a *main template* is used. In general, the template of
+identifier `main` is the one to use (it is not used in case an error is raised or for
+the login form for example). This template uses other templates in addition
+to the views which depends on the content to generate the HTML page to return.
+
+A *template* is responsible for:
+
+1. executing RQL query of data to render if necessary
+2. identifying the view to use to render data if it is not specified
+3. composing the HTML page to return
+
+You will find out more about templates in :ref:`templates`. 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/treemixin.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,100 @@
+
+Class `TreeMixIn`
+-----------------
+
+This class provides a tree interface. This mixin has to be inherited 
+explicitly and configured using the tree_attribute, parent_target and 
+children_target class attribute to benefit from this default implementation.
+
+This class provides the following methods:
+
+  * `different_type_children(entities=True)`, returns children entities
+    of different type as this entity. According to the `entities` parameter, 
+    returns entity objects (if entity=True) or the equivalent result set.
+
+  * `same_type_children(entities=True)`, returns children entities of 
+    the same type as this entity. According to the `entities` parameter, 
+    return entity objects (if entity=True) or the equivalent result set.
+  
+  * `iterchildren( _done=None)`, iters on the children of the entity.
+  
+  * `prefixiter( _done=None)`
+  
+  * `path()`, returns the list of eids from the root object to this object.
+  
+  * `iterparents()`, iters on the parents of the entity.
+  
+  * `notification_references(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.
+
+`TreeMixIn` implements also the ITree interface (``cubicweb.interfaces``):
+
+  * `parent()`, returns the parent entity if any, else None (e.g. if we are on the
+    root)
+
+  * `children(entities=True, sametype=False)`, returns children entities
+    according to the `entities` parameter, return entity objects or the
+    equivalent result set.
+
+  * `children_rql()`, returns the RQL query corresponding to the children
+    of the entity.
+
+  * `is_leaf()`, returns True if the entity does not have any children.
+
+  * `is_root()`, returns True if the entity does not have any parent.
+
+  * `root()`, returns the root object of the tree representation of
+    the entity and its related entities.
+
+Example of use
+``````````````
+
+Imagine you defined three types of entities in your schema, and they
+relates to each others as follows in ``schema.py``::
+
+  class Entity1(EntityType):
+      title = String()
+      is_related_to = SubjectRelation('Entity2', 'subject')
+
+  class Entity2(EntityType):
+      title = String()
+      belongs_to = SubjectRelation('Entity3', 'subject')
+
+  class Entity3(EntityType):
+      name = String()
+
+You would like to create a view that applies to both entity types
+`Entity1` and `Entity2` and which lists the entities they are related to.
+That means when you view `Entity1` you want to list all `Entity2`, and
+when you view `Entity2` you want to list all `Entity3`.
+
+In ``entities.py``::
+
+  class Entity1(TreeMixIn, AnyEntity):
+      id = 'Entity1'
+      __implements__ = AnyEntity.__implements__ + (ITree,)
+      __rtags__ = {('is_related_to', 'Entity2', 'object'): 'link'}
+      tree_attribute = 'is_related_to'
+
+      def children(self, entities=True):
+          return self.different_type_children(entities)
+
+  class Entity2(TreeMixIn, AnyEntity):
+      id = 'Entity2'
+      __implements__ = AnyEntity.__implements__ + (ITree,)
+      __rtags__ = {('belongs_to', 'Entity3', 'object'): 'link'}
+      tree_attribute = 'belongs_to'
+
+      def children(self, entities=True):
+          return self.different_type_children(entities)
+
+Once this is done, you can define your common view as follows::
+
+  class E1E2CommonView(baseviews.PrimaryView):
+      accepts = ('Entity11, 'Entity2')
+      
+      def render_entity_relations(self, entity, siderelations):
+          self.wview('list', entity.children(entities=False))
+
--- a/doc/book/en/A000-introduction.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-===================================
-Part I - Introduction to `CubicWeb`
-===================================
-
-This first part of the book will offer different reading path to
-present you with the `CubicWeb` framework, provide a tutorial to get a quick
-overview of its features and list its key concepts.
-
- 
-.. toctree::
-   :maxdepth: 2
-
-   A010-book-map.en.txt
-   A020-tutorial.en.txt
-   A030-foundation.en.txt
--- a/doc/book/en/A010-book-map.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Book map
-=========
-
-[WRITE ME]
-
-* explain how to use this book and what chapters to read in what order depending on the
-  objectives of the reader
-
--- a/doc/book/en/A020-tutorial.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Overview:
-
-Quick overview of `CubicWeb`
-============================
-
-`CubicWeb` is a semantic web application framework that favors reuse and
-object-oriented design.
-
-A `cube` is a component that includes a model defining the data types and a set of
-views to display the data. 
-
-An application is a `cube`, but usually an application is built by assembling
-a few smaller cubes.
-
-An `instance` is a specific installation of an application and includes
-configuration files.
-
-This tutorial will show how to create a `cube` and how to use it as an
-application to run an `instance`.
-
-.. include:: A02a-create-cube.en.txt
-.. include:: A02b-components.en.txt
-.. include:: A02c-maintemplate.en.txt
-.. include:: A02d-conclusion.en.txt
--- a/doc/book/en/A02a-create-cube.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Create your cube
-----------------
-
-Once your `CubicWeb` development environment is set up, you can create a new
-cube::
-
-  cubicweb-ctl newcube blog
-
-This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
-installation, ``/usr/share/cubicweb/cubes`` for debian packages installation) 
-a directory named ``blog`` reflecting the structure described in :ref:`cubesConcepts`.
-
-.. _DefineDataModel:
-
-Define your data model
-----------------------
-
-The data model or schema is the core of your `CubicWeb` application.
-It defines the type of content your application will handle.
-
-The data model of your cube ``blog`` is defined in the file ``schema.py``:
-
-::
-
-  from cubicweb.schema import format_constraint
-  
-  class Blog(EntityType):
-    title = String(maxsize=50, required=True)
-    description = String()
-
-  class BlogEntry(EntityType):
-    title = String(required=True, fulltextindexed=True, maxsize=256)
-    publish_date = Date(default='TODAY')
-    content = String(required=True, fulltextindexed=True)
-    entry_of = SubjectRelation('Blog', cardinality='?*') 
-
-
-A Blog has a title and a description. The title is a string that is
-required by the class EntityType and must be less than 50 characters. 
-The description is a string that is not constrained.
-
-A BlogEntry has a title, a publish_date and a content. The title is a
-string that is required and must be less than 100 characters. The
-publish_date is a Date with a default value of TODAY, meaning that
-when a BlogEntry is created, its publish_date will be the current day
-unless it is modified. The content is a string that will be indexed in
-the full-text index and has no constraint.
-
-A BlogEntry also has a relationship ``entry_of`` that links it to a
-Blog. The cardinality ``?*`` means that a BlogEntry can be part of
-zero or one Blog (``?`` means `zero or one`) and that a Blog can
-have any number of BlogEntry (``*`` means `any number including
-zero`). For completeness, remember that ``+`` means `one or more`.
-
-
-Create your instance
---------------------
-
-To use this cube as an application and create a new instance named ``blogdemo``, do::
-  
-  cubicweb-ctl create blog blogdemo
-
-
-This command will create a directory ``~/etc/cubicweb.d/blogdemo``
-which will contain all the configuration files required to start
-you web application.
-
-Welcome to your web application
--------------------------------
-
-Start your application in debug mode with the following command: ::
-
-  cubicweb-ctl start -D blogdemo
-
-
-You can now access your web application to create blogs and post messages
-by visiting the URL http://localhost:8080/.
-
-A login form will appear. By default, the application will not allow anonymous
-users to enter the application. To login, you need then use the admin account
-you created at the time you initialized the database with ``cubicweb-ctl
-create``.
-
-.. image:: images/login-form.png
-
-
-Once authenticated, you can start playing with your application 
-and create entities.
-
-.. image:: images/blog-demo-first-page.png
-
-Please notice that so far, the `CubicWeb` franework managed all aspects of 
-the web application based on the schema provided at first.
-
-
-Add entities
-------------
-
-We will now add entities in our web application.
-
-Add a Blog
-~~~~~~~~~~
-
-Let us create a few of these entities. Click on the `[+]` at the left of the
-link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
-``everything about technology`` as the description, then validate the form by
-clicking on ``Validate``.
-
-.. image:: images/cbw-create-blog.en.png
-   :alt: from to create blog
-
-Click on the logo at top left to get back to the home page, then
-follow the Blog link that will list for you all the existing Blog.
-You should be seeing a list with a single item ``Tech-blog`` you
-just created.
-
-.. image:: images/cbw-list-one-blog.en.png
-   :alt: displaying a list of a single blog
-
-Clicking on this item will get you to its detailed description except
-that in this case, there is not much to display besides the name and
-the phrase ``everything about technology``.
-
-Now get back to the home page by clicking on the top-left logo, then
-create a new Blog called ``MyLife`` and get back to the home page
-again to follow the Blog link for the second time. The list now
-has two items.
-
-.. image:: images/cbw-list-two-blog.en.png
-   :alt: displaying a list of two blogs
-
-Add a BlogEntry
-~~~~~~~~~~~~~~~
-
-Get back to the home page and click on [+] at the left of the link
-BlogEntry. Call this new entry ``Hello World`` and type in some text
-before clicking on ``Validate``. You added a new blog entry without
-saying to what blog it belongs. There is a box on the left entitled
-``actions``, click on the menu item ``modify``. You are back to the form
-to edit the blog entry you just created, except that the form now has
-another section with a combobox titled ``add relation``. Chose
-``entry_of`` in this menu and a second combobox appears where you pick
-``MyLife``. 
-
-You could also have, at the time you started to fill the form for a
-new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the 
-combobox titled ``add relation`` would have showed up.
-
-
-.. image:: images/cbw-add-relation-entryof.en.png
-   :alt: editing a blog entry to add a relation to a blog
-
-Validate the changes by clicking ``Validate``. The entity BlogEntry
-that is displayed now includes a link to the entity Blog named
-``MyLife``.
-
-.. image:: images/cbw-detail-one-blogentry.en.png
-   :alt: displaying the detailed view of a blogentry
-
-Note that all of this was handled by the framework and that the only input
-that was provided so far is the schema. To get a graphical view of the schema,
-point your browser to the URL http://localhost:8080/schema
-
-.. image:: images/cbw-schema.en.png
-   :alt: graphical view of the schema (aka data-model)
-
-
-.. _DefineViews:
-
-Define your entities views
---------------------------
-
-Each entity defined in a model inherits defaults views allowing
-different rendering of the data. You can redefine each of them
-according to your needs and preferences. If you feel like it then
-you have to know how a view is defined.
-
-
-The views selection principle
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A view is defined by a Python class which includes: 
-  
-  - an identifier (all objects in `CubicWeb` are entered in a registry
-    and this identifier will be used as a key)
-  
-  - a filter to select the resulsets it can be applied to
-
-A view has a set of methods complying
-with the `View` class interface (`cubicweb.common.view`).
-
-`CubicWeb` provides a lot of standard views for the type
-`EntityView`, for a complete list, you
-will have to read the code in directory ``cubicweb/web/views/``
-
-A view is applied on a `result set` which contains a set of
-entities we are trying to display. `CubicWeb` uses a selector
-mechanism which computes a score used to identify which view
-is the best to apply for the `result set` we are trying to 
-display. The standard library of selectors is in 
-``cubicweb.common.selector`` and a library of methods used to
-compute scores is available in ``cubicweb.vregistry.vreq``.
-
-It is possible to define multiple views for the same identifier
-and to associate selectors and filters to allow the application
-to find the best way to render the data. We will see more details
-on this in :ref:`DefinitionVues`.
-
-For example, the view named ``primary`` is the one used to display
-a single entity. We will now show you hos to customize this view.
-
-
-View customization
-~~~~~~~~~~~~~~~~~~
-
-If you wish to modify the way a `BlogEntry` is rendered, you will have to 
-overwrite the `primary` view defined in the module ``views`` of the cube
-``cubes/blog/views.py``.
-
-We can for example add in front of the pulication date a prefix specifying
-the date we see is the publication date.
-
-To do so, please apply the following changes:
-
-:: 
-
-  from cubicweb.web.views import baseviews
-
-
-  class BlogEntryPrimaryView(baseviews.PrimaryView):
-
-    accepts = ('BlogEntry',)
-
-    def render_entity_title(self, entity):
-        self.w(u'<h1>%s</h1>' % html_escape(entity.dc_title()))
-
-    def content_format(self, entity):
-        return entity.view('reledit', rtype='content_format')
-
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-
-        # display entity attributes with prefixes
-        self.w(u'<h1>%s</h1>' % entity.title)
-        self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
-        self.w(u'<p>%s</p>' % entity.content)
-        
-        # display relations
-        siderelations = []
-        if self.main_related_section:
-            self.render_entity_relations(entity, siderelations)
-
-.. note::
-  When a view is modified, it is not required to restart the application
-  server. Save the Python file and reload the page in your web browser
-  to view the changes.
-
-You can now see that the publication date has a prefix.
-
-.. image:: images/cbw-update-primary-view.en.png
-   :alt: modified primary view
-
-
-The above source code defines a new primary view for
-``BlogEntry``. 
-
-Since views are applied to resultsets and resulsets can be tables of
-data, it is needed to recover the entity from its (row,col)
-coordinates. We will get to this in more detail later.
-
-The view has a ``self.w()`` method that is used to output data. In our
-example we use it to output HTML tags and values of the entity's attributes.
--- a/doc/book/en/A02b-components.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubes:
-
-Cubes
------
-
-Standard library
-~~~~~~~~~~~~~~~~
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities are:
-
-* addressbook: PhoneNumber and PostalAddress
-
-* basket: Basket (like a shopping cart)
-
-* blog: Blog (a *very* basic blog)
-
-* classfolder: Folder (to organize things but grouping them in folders)
-
-* classtags: Tag (to tag anything)
-
-* file: File (to allow users to upload and store binary or text files)
-
-* link: Link (to collect links to web resources)
-
-* mailinglist: MailingList (to reference a mailing-list and the URLs
-  for its archives and its admin interface)
-
-* person: Person (easily mixed with addressbook)
-
-* task: Task (something to be done between start and stop date)
-
-* zone: Zone (to define places within larger places, for example a
-  city in a state in a country)
-
-The available system entities are:
-
-* comment: Comment (to attach comment threads to entities)
-
-
-Adding comments to BlogDemo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To import a cube in your application just change the line in the
-``__pkginfo__.py`` file and verify that the cube you are planning
-to use is listed by the command ``cubicweb-ctl list``.
-For example::
-
-    __use__ = ('comment',)
-
-will make the ``Comment`` entity available in your ``BlogDemo``
-application.
-
-Change the schema to add a relationship between ``BlogEntry`` and
-``Comment`` and you are done. Since the comment cube defines the
-``comments`` relationship, adding the line::
-
-    comments = ObjectRelation('Comment', cardinality='1*', composite='object')
-
-to the definition of a ``BlogEntry`` will be enough.
-
-Synchronize the data model
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once you modified your data model, you need to synchronize the
-database with your model. For this purpose, `CubicWeb` provides
-a very usefull command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive migration Python shell. (see 
-:ref:`cubicweb-ctl-shell` for more details))
-As you modified a relation from the `BlogEntry` schema,
-run the following command:
-::
-
-  synchronize_rschema('BlogEntry')
-  
-You can now start your application and add comments to each 
-`BlogEntry`.
--- a/doc/book/en/A02c-maintemplate.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Templates
----------
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below:
-
-.. image:: images/lax-book.06-main-template-layout.en.png
-
-In this section we will demonstrate a change in one of the main
-interesting template from the three you will look for, 
-that is to say, the HTMLPageHeader, the HTMLPageFooter 
-and the TheMainTemplate.
-
-
-Customize a template
-~~~~~~~~~~~~~~~~~~~~
-
-Based on the diagram below, each template can be overriden
-by your customized template. To do so, we recommand you create
-a Python module ``blog.views.templates`` to keep it organized.
-In this module you will have to import the parent class you are
-interested as follows: ::
-  
-  from cubicweb.web.views.basetemplates import HTMLPageHeader, \
-                                    HTMLPageFooter, TheMainTemplate
-
-and then create your sub-class::
-
-  class MyBlogHTMLPageHeader(HTMLPageHeader):
-      ...  
-
-Customize header
-`````````````````
-
-Let's now move the search box in the header and remove the login form
-from the header. We'll show how to move it to the left column of the application.
-
-Let's say we do not want anymore the login menu to be in the header
-
-First, to remove the login menu, we just need to comment out the display of the
-login graphic component such as follows: ::
-
-  class MyBlogHTMLPageHeader(HTMLPageHeader):
-    
-      def main_header(self, view):
-          """build the top menu with authentification info and the rql box"""
-          self.w(u'<table id="header"><tr>\n')
-          self.w(u'<td id="firstcolumn">')
-          self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
-          self.w(u'</td>\n')
-          # appliname and breadcrumbs
-          self.w(u'<td id="headtext">')
-          comp = self.vreg.select_component('appliname', self.req, self.rset)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w)
-          comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w, view=view)
-          self.w(u'</td>')
-          # logged user and help
-          #self.w(u'<td>\n')
-          #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
-          #comp.dispatch(w=self.w)
-          #self.w(u'</td><td>')
-
-          self.w(u'<td>')
-          helpcomp = self.vreg.select_component('help', self.req, self.rset)
-          if helpcomp: # may not be available if Card is not defined in the schema
-              helpcomp.dispatch(w=self.w)
-          self.w(u'</td>')
-          # lastcolumn
-          self.w(u'<td id="lastcolumn">')
-          self.w(u'</td>\n')
-          self.w(u'</tr></table>\n')
-          self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
-                        title=False, message=False)
-
-
-
-.. image:: images/lax-book.06-header-no-login.en.png
-
-Customize footer
-````````````````
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in: ::
-
-  from cubicweb.web.views.basetemplates import HTMLPageFooter
-
-  class MyHTMLPageFooter(HTMLPageFooter):
-
-      def call(self, **kwargs):
-          self.w(u'<div class="footer">')
-          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
-          self.w(u'</div>')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
-```````````````
-
-.. _TheMainTemplate:
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
-
-TheMainTemplate is responsible for the general layout of the entire application. 
-It defines the template of ``id = main`` that is used by the application. Is 
-also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
-be used based on TheMainTemplate called SimpleMainTemplate which does not have 
-a top section.
-
-.. image:: images/lax-book.06-simple-main-template.en.png
-
-XXX
-[WRITE ME]
-
-* customize MainTemplate and show that everything in the user
-  interface can be changed
-
--- a/doc/book/en/A02d-conclusion.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-What's next?
-------------
-
-We demonstrated how from a straight out of the box `CubicWeb`
-installation, you can build your web-application based on a
-schema. It's all already there: views, templates, permissions,
-etc. The step forward is now for you to customize according
-to your needs.
-
-More than a web application, many features are available to
-extend your application, for example: RSS channel integration 
-(:ref:`rss`), hooks (:ref:`hooks`), support of sources such as 
-Google App Engine (:ref:`gaecontents`) and lots of others to 
-discover through our book.
-
--- a/doc/book/en/A02d-rss-xml.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-RSS Channel
------------
-
-Assuming you have several blog entries, click on the title of the
-search box in the left column. A larger search box should appear. Enter::
-
-   Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
-
-and you get a list of blog entries.
-
-Click on your login at the top right corner. Chose "user preferences",
-then "boxes", then "possible views box" and check "visible = yes"
-before validating your changes.
-
-Enter the same query in the search box and you will see the same list,
-plus a box titled "possible views" in the left column. Click on
-"entityview", then "RSS". 
-
-You just applied the "RSS" view to the RQL selection you requested.
-
-That's it, you have a RSS channel for your blog.
-
-Try again with::
-
-    Any X ORDERBY D WHERE X is BlogEntry, X creation_date D, 
-    X entry_of B, B title "MyLife"
-
-Another RSS channel, but a bit more focused.
-
-A last one for the road::
-
-    Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
-    
-displayed with the RSS view, that's a channel for the last fifteen
-comments posted.
-
-[WRITE ME]
-
-* show that the RSS view can be used to display an ordered selection
-  of blog entries, thus providing a RSS channel
-
-* show that a different selection (by category) means a different channel
-
--- a/doc/book/en/A030-foundation.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-`CubicWeb` Foundations
-======================
-
-A little history...
--------------------
-
-`CubicWeb` is a web application framework developped by Logilab_ since 2001.
-
-Entirely written in Python, `CubicWeb` publishes data from all sorts
-of sources such as SQL database, LDAP directory and versioning system such
-as subversion.
-
-`CubicWeb` user interface was designed to let the final user a huge flexibility
-on how to select and how to display content. It allows to browse the knowledge
-database and to display the results with the best rendering according to
-the context.
-This interface flexibility gives back the user the control of the 
-rendering parameters that are usually reserved for developpers.
-
-
-We can list a couple of web applications developped with `CubicWeb`, an online
-public phone directory (see http://www.118000.fr/), a system for managing 
-digital studies and simulations for a research lab, a tool for shared children
-babysitting (see http://garde-partagee.atoukontact.fr/), a tool to manage
-software developpment (see http://www.logilab.org), an application for
-managing museums collections (see 
-http://collections.musees-haute-normandie.fr/collections/), etc.
-
-In 2008, `CubicWeb` was ported for a new type of source : the datastore 
-from `GoogleAppEngine`_.
-
-.. include:: A03a-concepts.en.txt
--- a/doc/book/en/A03a-concepts.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,536 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Concepts
---------
-
-This section aims to provide you the keys of success with `CubicWeb`
-by clarifying the terms specific to our framework.
-
-Global architecture
-~~~~~~~~~~~~~~~~~~~
-.. image:: images/archi_globale.en.png
-
-
-`CubicWeb` framework is a server/client application framework. Those two
-parties communicates through RQL (`CubicWeb` query language implementation)
-and ResultSet (which will be explained in :ref:`TermsVocabulary`).
-
-The server manages all interactions with sources.
-
-
-.. note::
-  For real, the client and server sides are integrated in the same
-  process and interact directly, without the needs for distants
-  calls using Pyro. It is important to note down that those two
-  sides, client/server, are disjointed and it is possible to execute
-  a couple of calls in distincts processes to balance the load of
-  your web site on one or more machines.
-
-.. _TermsVocabulary:
-
-Terms and vocabulary
-~~~~~~~~~~~~~~~~~~~~~
-
-`CubicWeb` defines its own terminology. To make sure there is no confusion
-while reading this book, we strongly recommand you take time to go through
-the following definitions that are the basics to understand while
-developing with `CubicWeb`.
-
-*schema*
-  The schema defines the data model of an application based on entities
-  and relations, modeled with a comprehensive language made of Python
-  classes based on `yams`_ library. This is the core piece
-  of an application. It is initially defined in the file system and is
-  stored in the database at the time an instance is created. `CubicWeb`
-  provides a certain number of system entities included automatically as
-  it is necessary for the core of `CubicWeb` and a library of
-  cubes (which defined application entities) that can be explicitely
-  included if necessary.
-
-*entity type*
-  An entity type is a set of attributes; the essential attribute of
-  an entity is its key, named eid.
-
-*relation type*
-  Entities are linked to each others by relations. In `CubicWeb`
-  relations are binary: by convention we name the first item of
-  a relation the `subject` and the second the `object`.
-
-*final entity type*
-  Final types corresponds to the basic types such as string of characters,
-  integers... Those types have a main property which is that they can
-  only be used as `object` of a relation. The attributes of an entity
-  (non final) are entities (finals).
-
-*final relation type*
-  A relation is said final if its `object` is a final type. This is equivalent
-  to an entity attribute.
-
-*relation definition*
-  A relation definition is a 3-uple (subject entity type, relation type, object
-  entity type), with an associated set of property such as cardinality, constraints...
-
-*repository*
-  This is the RQL server side of `CubicWeb`. Be carefull not to get
-  confused with a Mercurial repository or a debian repository.
-
-*source*
-  A data source is a container of data (SGBD, LDAP directory, `Google
-  App Engine`'s datastore ...) integrated in the
-  `CubicWeb` repository. This repository has at least one source, `system` which
-  contains the schema of the application, plain-text index and others
-  vital informations for the system.
-
-*configuration*
-  It is possible to create differents configurations for an instance:
-
-  - ``repository`` : repository only, accessible for clients using Pyro
-  - ``twisted`` : web interface only, access the repository using Pyro
-  - ``all-in-one`` : web interface and repository in a single process.
-     The repository could be or not accessible using Pyro.
-
-*cube*
-  A cube is a model grouping one or multiple data types and/or views
-  to provide a specific functionnality or a complete `CubicWeb` application
-  potentially using other cubes. The available cubes are located in the file
-  system at `/path/to/forest/cubicweb/cubes` for a Mercurial forest installation,
-  for a debian packages installation they will be located in
-  `/usr/share/cubicweb/cubes`.
-  Larger applications can be built faster by importing cubes,
-  adding entities and relationships and overriding the
-  views that need to display or edit informations not provided by
-  cubes.
-
-*instance*
-  An instance is a specific installation of one or multiple cubes. All the required
-  configuration files necessary for the well being of your web application
-  are grouped in an instance. This will refer to the cube(s) your application
-  is based on.
-  For example logilab.org and our intranet are two instances of a single
-  cube jpl, developped internally.
-  The instances are defined in the directory `/etc/cubicweb.d`.
-
-*application*
-  The term application is sometime used to talk about an instance
-  and sometimes to talk of a cube depending on the context.
-  So we would like to avoid using this term and try to use *cube* and
-  *instance* instead.
-
-*result set*
-  This object contains the results of an RQL query sent to the source
-  and informations on the query.
-
-*Pyro*
-  `Python Remote Object`_, distributed objects system similar to Java's RMI
-  (Remote Method Invocation), which can be used for the dialog between the web
-  side of the framework and the RQL repository.
-
-*query language*
-  A full-blown query language named RQL is used to formulate requests
-  to the database or any sources such as LDAP or `Google App Engine`'s
-  datastore.
-
-*views*
-  A view is applied to a `result set` to present it as HTML, XML,
-  JSON, CSV, etc. Views are implemented as Python classes. There is no
-  templating language.
-
-*generated user interface*
-  A user interface is generated on-the-fly from the schema definition:
-  entities can be created, displayed, updated and deleted. As display
-  views are not very fancy, it is usually necessary to develop your
-  own. Any generated view can be overridden by defining a new one with
-  the same identifier.
-
-*rql*
- Relation Query Language in order to empasize the way of browsing relations.
- This query language is inspired by SQL but is highest level, its implementation
- generates SQL.
-
-
-.. _`Python Remote Object`: http://pyro.sourceforge.net/
-.. _`yams`: http://www.logilab.org/project/yams/
-
-
-`CubicWeb` engine
-~~~~~~~~~~~~~~~~~
-
-The engine in `CubicWeb` is a set of classes managing a set of objects loaded
-dynamically at the startup of `CubicWeb` (*appobjects*). Those dynamics objects,
-based on the schema or the library, are building the final application.
-The differents dymanic components are for example:
-
-* client and server side
-
-  - entities definition, containing the logic which enables application data manipulation
-
-* client side
-
-  - *views*, or more specifically
-
-    - boxes
-    - header and footer
-    - forms
-    - page templates
-
-  - *actions*
-  - *controllers*
-
-* server side
-
-  - notification hooks
-  - notification views
-
-The components of the engine are:
-
-* a frontal web (only twisted is available so far), transparent for dynamic objects
-* an object that encapsulates the configuration
-* a `registry` (`cubicweb.cwvreg`) containing the dynamic objects loaded automatically
-
-Every *appobject* may access to the instance configuration using its *config* attribute
-and to the registry using its *vreg* attribute.
-
-API Python/RQL
-~~~~~~~~~~~~~~
-
-The Python API developped to interface with RQL is inspired from the standard db-api,
-with a Connection object having the methods cursor, rollback and commit essentially.
-The most important method is the `execute` method of a cursor :
-
-`execute(rqlstring, args=None, eid_key=None, build_descr=True)`
-
-:rqlstring: the RQL query to execute (unicode)
-:args: if the query contains substitutions, a dictionnary containing the values to use
-:eid_key:
-   an implementation detail of the RQL queries cache implies that if a substitution
-   is used to introduce an eid *susceptible to raise the ambiguities in the query
-   type resolution*, then we have to specify the correponding key in the dictionnary
-   through this argument
-
-
-The `Connection` object owns the methods `commit` and `rollback`. You *should
-never need to use them* during the development of the web interface based on
-the `CubicWeb` framework as it determines the end of the transaction depending
-on the query execution success.
-
-.. note::
-  While executing updates queries (SET, INSERT, DELETE), if a query generates
-  an error related to security, a rollback is automatically done on the current
-  transaction.
-
-
-The `Request` class (`cubicweb.web`)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A request instance is created when an HTTP request is sent to the web server.
-It contains informations such as forms parameters, user authenticated, etc.
-
-**Globally, a request represents a user query, either through HTTP or not
-(we also talk about RQL queries on the server side for example).**
-
-An instance of `Request` has the following attributes:
-
-* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
-  user
-* `form`, dictionnary containing the values of a web form
-* `encoding`, characters encoding to use in the response
-
-But also:
-
-:Session data handling:
-  * `session_data()`, returns a dictionnary containing all the session data
-  * `get_session_data(key, default=None)`, returns a value associated to the given
-    key or the value `default` if the key is not defined
-  * `set_session_data(key, value)`, assign a value to a key
-  * `del_session_data(key)`,  suppress the value associated to a key
-
-
-:Cookies handling:
-  * `get_cookie()`, returns a dictionnary containing the value of the header
-    HTTP 'Cookie'
-  * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`,
-    with a minimal 5 minutes length of duration by default (`maxage` = None
-    returns a *session* cookie which will expire when the user closes the browser
-    window)
-  * `remove_cookie(cookie, key)`, forces a value to expire
-
-:URL handling:
-  * `url()`, returns the full URL of the HTTP request
-  * `base_url()`, returns the root URL of the web application
-  * `relative_path()`, returns the relative path of the request
-
-:And more...:
-  * `set_content_type(content_type, filename=None)`, adds the header HTTP
-    'Content-Type'
-  * `get_header(header)`, returns the value associated to an arbitrary header
-    of the HTTP request
-  * `set_header(header, value)`, adds an arbitrary header in the response
-  * `cursor()` returns a RQL cursor on the session
-  * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()``
-  * `property_value(key)`, properties management (`EProperty`)
-  * dictionnary `data` to store data to share informations between components
-    *while a request is executed*
-
-Please note that this class is abstract and that a concrete implementation
-will be provided by the *frontend* web used (in particular *twisted* as of
-today). For the views or others that are executed on the server side,
-most of the interface of `Request` is defined in the session associated
-to the client.
-
-The `AppObject` class
-~~~~~~~~~~~~~~~~~~~~~
-
-In general:
-
-* we do not inherit directly from this class but from a more specific
-  class such as `AnyEntity`, `EntityView`, `AnyRsetView`,
-  `Action`...
-
-* to be recordable, a subclass has to define its own register (attribute
-  `__registry__`) and its identifier (attribute `id`). Usually we do not have
-  to take care of the register, only the identifier `id`.
-
-We can find a certain number of attributes and methods defined in this class
-and common to all the application objects.
-
-At the recording, the following attributes are dynamically added to
-the *subclasses*:
-
-* `vreg`, the `vregistry` of the application
-* `schema`, the application schema
-* `config`, the application configuration
-
-We also find on instances, the following attributes:
-
-* `req`, `Request` instance
-* `rset`, the *result set* associated to the object if necessary
-* `cursor`, rql cursor on the session
-
-
-:URL handling:
-  * `build_url(method=None, **kwargs)`, returns an absolute URL based on
-    the given arguments. The *controller* supposed to handle the response,
-    can be specified through the special parameter `method` (the connection
-    is theoretically done automatically :).
-
-  * `datadir_url()`, returns the directory of the application data
-    (contains static files such as images, css, js...)
-
-  * `base_url()`, shortcut to `req.base_url()`
-
-  * `url_quote(value)`, version *unicode safe* of the function `urllib.quote`
-
-:Data manipulation:
-
-  * `etype_rset(etype, size=1)`, shortcut to `vreg.etype_rset()`
-
-  * `eid_rset(eid, rql=None, descr=True)`, returns a *result set* object for
-    the given eid
-  * `entity(row, col=0)`, returns the entity corresponding to the data position
-    in the *result set* associated to the object
-
-  * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but
-    also call the method `complete()` on the entity before returning it
-
-:Data formatting:
-  * `format_date(date, date_format=None, time=False)` returns a string for a
-    mx date time according to application's configuration
-  * `format_time(time)` returns a string for a mx date time according to
-    application's configuration
-
-:And more...:
-
-  * `external_resource(rid, default=_MARKER)`, access to a value defined in the
-    configuration file `external_resource`
-
-  * `tal_render(template, variables)`, renders a precompiled page template with
-    variables in the given dictionary as context
-
-.. note::
-  When we inherit from `AppObject` (even not directly), you *always* have to use
-  **super()** to get the methods and attributes of the superclasses, and not
-  use the class identifier.
-  For example, instead of writting: ::
-
-      class Truc(PrimaryView):
-          def f(self, arg1):
-              PrimaryView.f(self, arg1)
-
-  You'd better write: ::
-
-      class Truc(PrimaryView):
-          def f(self, arg1):
-              super(Truc, self).f(arg1)
-
-.. _cubesConcepts:
-
-Cubes
-~~~~~
-
-What is a cube ?
-````````````````
-
-A cube is a model grouping one or more entity types and/or views associated
-in order to provide a specific feature or even a complete application using
-others cubes.
-
-You can decide to write your own set of cubes if you wish to re-use the
-entity types you develop. Lots of cubes are available from the `CubicWeb
-Forge`_ under a free software license.
-
-.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
-
-.. _foundationsCube:
-
-Standard structure for a cube
-`````````````````````````````
-
-A cube is structured as follows:
-
-::
-
-  mycube/
-  |
-  |-- data/
-  |   |-- cubes.mycube.css
-  |   |-- cubes.mycube.js
-  |   `-- external_resources
-  |
-  |-- debian/
-  |   |-- changelog
-  |   |-- compat
-  |   |-- control
-  |   |-- copyright
-  |   |-- cubicweb-mycube.prerm
-  |   `-- rules
-  |
-  |-- entities.py
-  |
-  |-- i18n/
-  |   |-- en.po
-  |   `-- fr.po
-  |
-  |-- __init__.py
-  |
-  |-- MANIFEST.in
-  |
-  |-- migration/
-  |   |-- postcreate.py
-  |   `-- precreate.py
-  |
-  |-- __pkginfo__.py
-  |
-  |-- schema.py
-  |
-  |-- setup.py
-  |
-  |-- site_cubicweb.py
-  |
-  |-- hooks.py
-  |
-  |-- test/
-  |   |-- data/
-  |   |   `-- bootstrap_cubes
-  |   |-- pytestconf.py
-  |   |-- realdb_test_mycube.py
-  |   `-- test_mycube.py
-  |
-  `-- views.py
-
-
-We can use subpackages instead of python modules for ``views.py``, ``entities.py``,
-``schema.py`` or ``hooks.py``. For example, we could have:
-
-::
-
-  mycube/
-  |
-  |-- entities.py
-  |-- hooks.py
-  `-- views/
-      |-- forms.py
-      |-- primary.py
-      `-- widgets.py
-
-
-where :
-
-* ``schema`` contains the schema definition (server side only)
-* ``entities`` contains the entities definition (server side and web interface)
-* ``sobjects`` contains hooks and/or views notifications (server side only)
-* ``views`` contains the web interface components (web interface only)
-* ``test`` contains tests related to the application (not installed)
-* ``i18n`` contains messages catalogs for supported languages (server side and
-  web interface)
-* ``data`` contains data files for static content (images, css, javascripts)
-  ...(web interface only)
-* ``migration`` contains initialization file for new instances (``postcreate.py``)
-  and a file containing dependencies of the component depending on the version
-  (``depends.map``)
-* ``debian`` contains all the files managing debian packaging (you will find
-  the usual files ``control``, ``rules``, ``changelog``... not installed)
-* file ``__pkginfo__.py`` provides component meta-data, especially the distribution
-  and the current version (server side and web interface) or sub-cubes used by
-  the cube.
-
-
-At least you should have:
-
-* the file ``__pkginfo__.py``
-* the schema definition
-  XXX false, we may want to have cubes which are only adding a service,
-  no persistent data (eg embeding for instance)
-
-
-Standard library
-````````````````
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities are:
-
-* addressbook_: PhoneNumber and PostalAddress
-
-* basket_: Basket (like a shopping cart)
-
-* blog_: Blog (a *very* basic blog)
-
-* comment_: Comment (to attach comment threads to entities)
-
-* event_: Event (define events, display them in calendars)
-
-* file_: File (to allow users to upload and store binary or text files)
-
-* folder_: Folder (to organize things but grouping them in folders)
-
-* keyword_: Keyword (to define classification schemes)
-
-* link_: Link (to collect links to web resources)
-
-* mailinglist_: MailingList (to reference a mailing-list and the URLs
-  for its archives and its admin interface)
-
-* person_: Person (easily mixed with addressbook)
-
-* tag_: Tag (to tag anything)
-
-* task_: Task (something to be done between start and stop date)
-
-* zone_: Zone (to define places within larger places, for example a
-  city in a state in a country)
-
-.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
-.. _basket: http://www.cubicweb.org/project/cubicweb-basket
-.. _blog: http://www.cubicweb.org/project/cubicweb-blog
-.. _comment: http://www.cubicweb.org/project/cubicweb-comment
-.. _event: http://www.cubicweb.org/project/cubicweb-event
-.. _file: http://www.cubicweb.org/project/cubicweb-file
-.. _folder: http://www.cubicweb.org/project/cubicweb-folder
-.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
-.. _link: http://www.cubicweb.org/project/cubicweb-link
-.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
-.. _person: http://www.cubicweb.org/project/cubicweb-person
-.. _tag: http://www.cubicweb.org/project/cubicweb-tag
-.. _task: http://www.cubicweb.org/project/cubicweb-task
-.. _zone: http://www.cubicweb.org/project/cubicweb-zone
--- a/doc/book/en/B0-data-model.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-The data model
-++++++++++++++
-
-.. toctree::
-   :maxdepth: 1
-
-   B0010-define-schema.en.txt
-   B0020-define-workflows.en.txt
-   B0030-data-as-objects.en.txt
-   B0040-migration.en.txt
-
--- a/doc/book/en/B000-development.en.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/en/B000-development.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -1,5 +1,6 @@
 .. -*- coding: utf-8 -*-
 
+.. _Part2:
 
 =====================
 Part II - Development
--- a/doc/book/en/B0010-define-schema.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Data model definition (*schema*)
-================================
-
-The schema is the core piece of a `CubicWeb` application as it defines
-the data model handled. It is based on entities types already defined
-in the `CubicWeb` standard library and others, more specific, we would 
-expect to find in one or more Python files under the `schema` directory.
-
-At this point, it is important to make clear the difference between
-relation type and relation definition: a relation type is only a relation
-name with potentially other additionnal properties (see XXXX), whereas a 
-relation definition is a complete triplet 
-"<subject entity type> <relation type> <object entity type>". 
-A relation type could have been implied if none is related to a 
-relation definition of the schema.
-
-
-.. include:: B0011-schema-stdlib.en.txt
-.. include:: B0012-schema-definition.en.txt
-
--- a/doc/book/en/B0011-schema-stdlib.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Pre-defined schemas in the library
-----------------------------------
-
-The library defines a set of entities schemas that are required by the system
-or commonly used in `CubicWeb` applications.
-Of course, you can extend those schemas if necessary.
-
-
-System schemas
-``````````````
-The system entities available are :
-
-* `EUser`, system users
-* `EGroup`, users groups
-* `EEType`, entity type
-* `ERType`, relation type
-
-* `State`, workflow state
-* `Transition`, workflow transition
-* `TrInfo`, record of a transition trafic for an entity 
-
-* `EmailAddress`, email address, used by the system to send notifications
-  to the users and also used by others optionnals schemas
-
-* `EProperty`, used to configure the application
-* `EPermission`, used to configure the security of the application
-
-* `Card`, generic documenting card
-* `Bookmark`, an entity type used to allow a user to customize his links within
-  the application
-
-Cubes available
-```````````````
-
-An application is based on several basic cubes. In the set of available
-basic cubes we can find for example :
-
-* addressbook_: PhoneNumber and PostalAddress
-
-* basket_: Basket (like a shopping cart)
-
-* blog_: Blog (a *very* basic blog)
-
-* comment_: Comment (to attach comment threads to entities)
-
-* email_: archiving management for emails (`Email`, `Emailpart`,
-  `Emailthread`)
-
-* event_: Event (define events, display them in calendars)
-
-* file_: File (to allow users to upload and store binary or text files)
-
-* folder_: Folder (to organize things but grouping them in folders)
-
-* keyword_: Keyword (to define classification schemes)
-
-* link_: Link (to collect links to web resources)
-
-* mailinglist_: MailingList (to reference a mailing-list and the URLs
-  for its archives and its admin interface)
-
-* person_: Person (easily mixed with addressbook)
-
-* tag_: Tag (to tag anything)
-
-* task_: Task (something to be done between start and stop date)
-
-* zone_: Zone (to define places within larger places, for example a
-  city in a state in a country)
-
-.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
-.. _basket: http://www.cubicweb.org/project/cubicweb-basket
-.. _blog: http://www.cubicweb.org/project/cubicweb-blog
-.. _comment: http://www.cubicweb.org/project/cubicweb-comment
-.. _email: http://www.cubicweb.org/project/cubicweb-email
-.. _event: http://www.cubicweb.org/project/cubicweb-event
-.. _file: http://www.cubicweb.org/project/cubicweb-file
-.. _folder: http://www.cubicweb.org/project/cubicweb-folder
-.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
-.. _link: http://www.cubicweb.org/project/cubicweb-link
-.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
-.. _person: http://www.cubicweb.org/project/cubicweb-person
-.. _tag: http://www.cubicweb.org/project/cubicweb-tag
-.. _task: http://www.cubicweb.org/project/cubicweb-task
-.. _zone: http://www.cubicweb.org/project/cubicweb-zone
-
-To declare the use of a component, once installed, add the name of the component
-to the variable `__use__` in the file `__pkginfo__.py` of your own component.
-
-.. note::
-  The listed cubes above are available as debian-packages on `CubicWeb's forge`_.
-
-.. _`CubicWeb's forge`: http://www.cubicweb.org/project?vtitle=All%20cubicweb%20projects
--- a/doc/book/en/B0012-schema-definition.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Entity type definition
-----------------------
-
-An entity type is defined by a Python class which inherits `EntityType`. The
-class name correponds to the type name. Then the content of the class contains
-the description of attributes and relations for the defined entity type,
-for example ::
-
-  class Personne(EntityType):
-    """A person with the properties and the relations necessary for my
-    application"""
-
-    last_name = String(required=True, fulltextindexed=True)
-    first_name = String(required=True, fulltextindexed=True)
-    title = String(vocabulary=('M', 'Mme', 'Mlle'))
-    date_of_birth = Date()
-    works_for = SubjectRelation('Company', cardinality='?*')
-
-* the name of the Python attribute corresponds to the name of the attribute
-  or the relation in `CubicWeb` application.
-
-* all built-in types are available : `String`, `Int`, `Float`,
-  `Boolean`, `Date`, `Datetime`, `Time`, `Byte`.
-
-* each entity type has at least the following meta-relations :
-
-  - `eid` (`Int`)
-  
-  - `creation_date` (`Datetime`)
-  
-  - `modification_date` (`Datetime`)
-  
-  - `created_by` (`EUser`) (which user created the entity)
-  
-  - `owned_by` (`EUser`) (who does the entity belongs to, by default the 
-     creator but not necessary and it could have multiple owners)
-     
-  - `is` (`EEType`)
-
-  
-* it is also possible to define relations of type object by using `ObjectRelation`
-  instead of `SubjectRelation`
-
-* the first argument of `SubjectRelation` and `ObjectRelation` gives respectively
-  the object/subject entity type of the relation. This could be :  
-
-  * a string corresponding to an entity type
-
-  * a tuple of string correponding to multiple entities types
-
-  * special string such as follows :
-
-    - "**" : all types of entities
-    - "*" : all types of non-meta entities 
-    - "@" : all types of meta entities but not system entities (e.g. used for
-      the basic schema description)
-
-* it is possible to use the attribute `meta` to flag an entity type as a `meta`
-  (e.g. used to describe/categorize other entities)
-
-* optional properties for attributes and relations : 
-
-  - `description` : string describing an attribute or a relation. By default
-    this string will be used in the editing form of the entity, which means
-    that it is supposed to help the end-user and should be flagged by the
-    function `_` to be properly internationalized.
-
-  - `constraints` : list of conditions/constraints that the relation needs to
-    satisfy (c.f. `Contraints`_)
-
-  - `cardinality` : two characters string which specify the cardinality of the
-    relation. The first character defines the cardinality of the relation on
-    the subject, the second on the object of the relation. When a relation
-    has multiple possible subjects or objects, the cardinality applies to all
-    and not on a one to one basis (so it must be consistent...). The possible
-    values are inspired from regular expressions syntax :
-
-    * `1`: 1..1
-    * `?`: 0..1
-    * `+`: 1..n
-    * `*`: 0..n
-
-  - `meta` : boolean indicating that the relation is a meta-relation (false by
-    default)
-
-* optionnal properties for attributes : 
-
-  - `required` : boolean indicating if the attribute is required (false by default)
-
-  - `unique` : boolean indicating if the value of the attribute has to be unique
-    or not within all entities of the same type (false by default)
-
-  - `indexed` : boolean indicating if an index needs to be created for this 
-    attribute in the database (false by default). This is usefull only if
-    you know that you will have to run numerous searches on the value of this
-    attribute.
-
-  - `default` : default value of the attribute. In case of date types, the values
-    which could be used correpond to the RQL keywords `TODAY` and `NOW`.
-  
-  - `vocabulary` : specify static possible values of an attribute
-
-* optionnal properties of type `String` : 
-
-  - `fulltextindexed` : boolean indicating if the attribute is part of
-    the full text index (false by default) (*applicable on the type `Byte`
-    as well*)
-
-  - `internationalizable` : boolean indicating if the value of the attribute
-    is internationalizable (false by default)
-
-  - `maxsize` : integer providing the maximum size of the string (no limit by default)
-
-* optionnal properties for relations : 
-
-  - `composite` : string indicating that the subject (composite == 'subject')
-    is composed of the objects of the relations. For the opposite case (when
-    the object is composed of the subjects of the relation), we just need
-    to set 'object' as the value. The composition implies that when the relation
-    is deleted (so when the composite is deleted), the composed are also deleted.
-
-Contraints
-``````````
-By default, the available constraints types are :
-
-* `SizeConstraint` : allows to specify a minimum and/or maximum size on
-  string (generic case of `maxsize`)
-
-* `BoundConstraint` : allows to specify a minimum and/or maximum value on 
-  numeric types
-
-* `UniqueConstraint` : identical to "unique=True"
-
-* `StaticVocabularyConstraint` : identical to "vocabulary=(...)"
-
-* `RQLConstraint` : allows to specify a RQL query that needs to be satisfied
-  by the subject and/or the object of the relation. In this query the variables
-  `S` and `O` are reserved for the entities subject and object of the 
-  relation.
-
-* `RQLVocabularyConstraint` : similar to the previous type of constraint except
-  that it does not express a "strong" constraint, which means it is only used to
-  restrict the values listed in the drop-down menu of editing form, but it does
-  not prevent another entity to be selected
-
-
-Relation definition
--------------------
-
-XXX add note about defining relation type / definition
-
-A relation is defined by a Python class heriting `RelationType`. The name
-of the class corresponds to the name of the type. The class then contains
-a description of the properties of this type of relation, and could as well 
-contains a string for the subject and a string for the object. This allows to create
-new definition of associated relations, (so that the class can have the 
-definition properties from the relation) for example ::
-
-  class locked_by(RelationType):
-    """relation on all entities indicating that they are locked"""
-    inlined = True
-    cardinality = '?*'
-    subject = '*'
-    object = 'EUser'
-
-In addition to the permissions, the properties of the relation types
-(shared also by all definition of relation of this type) are :
-
-
-* `inlined` : boolean handling the physical optimization for archiving
-  the relation in the subject entity table, instead of creating a specific
-  table for the relation. This applies to the relation when the cardinality
-  of subject->relation->object is 0..1 (`?`) or 1..1 (`1`)
-
-* `symetric` : boolean indication that the relation is symetrical, which
-  means `X relation Y` implies `Y relation X`
-
-In the case of simultaneous relations definitions, `subject` and `object`
-can both be equal to the value of the first argument of `SubjectRelation`
-and `ObjectRelation`.
-
-When a relation is not inlined and not symetrical, and it does not require
-specific permissions, its definition (by using `SubjectRelation` and
-`ObjectRelation`) is all we need.
-
-
-The security model
-------------------
-
-The security model of `cubicWeb` is based on `Access Control List`. 
-The main principles are:
-
-* users and groups of users
-* a user belongs to at least one group of user
-* permissions (read, update, create, delete)
-* permissions are assigned to groups (and not to users)
-
-For `CubicWeb` in particular:
-
-* we associate rights at the enttities/relations schema level
-* for each entity, we distinguish four kind of permissions: read,
-  add, update and delete
-* for each relation, we distinguish three king of permissions: read,
-  add and delete (we can not modify a relation)
-* the basic groups are: Administrators, Users and Guests
-* by default, users belongs to the group Users
-* there is a virtual group called `Owners users` to which we
-  can associate only deletion and update permissions
-* we can not add users to the `Owners users` group, they are
-  implicetely added to it according to the context of the objects
-  they own
-* the permissions of this group are only be checked on update/deletion
-  actions if all the other groups the user belongs does not provide
-  those permissions
-
-  
-Permissions definition
-``````````````````````
-
-Define permissions is set through to the attribute `permissions` of entities and
-relations types. It defines a dictionnary where the keys are the access types
-(action), and the values are the authorized groups or expressions.
-
-For an entity type, the possible actions are `read`, `add`, `update` and
-`delete`.
-
-For a relation type, the possible actions are `read`, `add`, and `delete`.
-
-For each access type, a tuple indicates the name of the authorized groups and/or
-one or multiple RQL expressions to satisfy to grant access. The access is
-provided once the user is in the listed groups or one of the RQL condition is
-satisfied.
-
-The standard groups are :
-
-* `guests`
-
-* `users`
-
-* `managers`
-
-* `owners` : virtual group corresponding to the entity's owner.
-  This can only be used for the actions `update` and `delete` of an entity
-  type.
-
-It is also possible to use specific groups if they are defined in the precreate 
-of the cube (``migration/precreate.py``).
-
-
-Use of RQL expression for writing rights
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-It is possible to define RQL expression to provide update permission 
-(`add`, `delete` and `update`) on relation and entity types.
-
-RQL expression for entity type permission :
-
-* you have to use the class `ERQLExpression`
-
-* the used expression corresponds to the WHERE statement of an RQL query
-
-* in this expression, the variables X and U are pre-defined references
-  respectively on the current entity (on which the action is verified) and
-  on the user who send the request
-
-* it is possible to use, in this expression, a special relation 
-  "has_<ACTION>_permission" where the subject is the user and the 
-  object is a any variable, meaning that the user needs to have
-  permission to execute the action <ACTION> on the entities related
-  to this variable 
-
-For RQL expressions on a relation type, the principles are the same except 
-for the following :
-
-* you have to use the class `RQLExpression` in the case of a non-final relation
-
-* in the expression, the variables S, O and U are pre-defined references
-  to respectively the subject and the object of the current relation (on
-  which the action is being verified) and the user who executed the query
-
-* we can also defined rights on attributes of an entity (non-final relation),
-  knowing that : 
-
-  - to defines RQL expression, we have to use the class `ERQLExpression`
-    in which X represents the entity the attribute belongs to
-
-  - the permissions `add` and `delete` are equivalent. Only `add`/`read`
-    are actually taken in consideration.
-
-In addition to that the entity type `EPermission` from the standard library
-allow to build very complex and dynamic security architecture. The schema of
-this entity type is as follow : ::
-
-    class EPermission(MetaEntityType):
-	"""entity type that may be used to construct some advanced security configuration
-	"""
-	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
-	require_group = SubjectRelation('EGroup', cardinality='+*',
-					description=_('groups to which the permission is granted'))
-	require_state = SubjectRelation('State',
-				    description=_("entity'state in which the permission is applyable"))
-	# can be used on any entity
-	require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
-					    description=_("link a permission to the entity. This "
-							  "permission should be used in the security "
-							  "definition of the entity's type to be useful."))
-
-
-Example of configuration ::
-
-
-    ...
-
-    class Version(EntityType):
-	"""a version is defining the content of a particular project's release"""
-
-	permissions = {'read':   ('managers', 'users', 'guests',),
-		       'update': ('managers', 'logilab', 'owners',),
-		       'delete': ('managers', ),
-		       'add':    ('managers', 'logilab',
-				  ERQLExpression('X version_of PROJ, U in_group G,'
-						 'PROJ require_permission P, P name "add_version",'
-						 'P require_group G'),)}
-
-    ...
-
-    class version_of(RelationType):
-	"""link a version to its project. A version is necessarily linked to one and only one project.
-	"""
-	permissions = {'read':   ('managers', 'users', 'guests',),
-		       'delete': ('managers', ),
-		       'add':    ('managers', 'logilab',
-				  RRQLExpression('O require_permission P, P name "add_version",'
-						 'U in_group G, P require_group G'),)
-		       }
-	inlined = True
-
-This configuration indicates that an entity `EPermission` named
-"add_version" can be associated to a project and provides rights to create
-new versions on this project to specific groups. It is important to notice that :
-
-* in such case, we have to protect both the entity type "Version" and the relation
-  associating a version to a project ("version_of")
-
-* because of the genricity of the entity type `EPermission`, we have to execute
-  a unification with the groups and/or the states if necessary in the expression
-  ("U in_group G, P require_group G" in the above example)
-
-Use of RQL expression for reading rights
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The principles are the same but with the following restrictions :
-
-* we can not use `RRQLExpression` on relation types for reading
-
-* special relations "has_<ACTION>_permission" can not be used
-
-
-Note on the use of RQL expression for `add` permission
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Potentially, the use of an RQL expression to add an entity or a relation
-can cause problems for the user interface, because if the expression uses
-the entity or the relation to create, then we are not able to verify the 
-permissions before we actually add the entity (please note that this is
-not a problem for the RQL server at all, because the permissions checks are
-done after the creation). In such case, the permission check methods 
-(check_perm, has_perm) can indicate that the user is not allowed to create 
-this entity but can obtain the permission. 
-To compensate this problem, it is usually necessary, for such case,
-to use an action that reflects the schema permissions but which enables
-to check properly the permissions so that it would show up if necessary.
-
-
-Updating your application with your new schema
-``````````````````````````````````````````````
-
-You have to get a shell on your application ::
-
-   cubicweb-ctl shell moninstance
-
-and type ::
-
-   add_entity_type('Personne')
-
-And restart your application!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/B0015-define-permissions.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,188 @@
+.. -*- coding: utf-8 -*-
+
+The security model
+------------------
+
+The security model of `cubicWeb` is based on `Access Control List`. 
+The main principles are:
+
+* users and groups of users
+* a user belongs to at least one group of user
+* permissions (read, update, create, delete)
+* permissions are assigned to groups (and not to users)
+
+For `CubicWeb` in particular:
+
+* we associate rights at the enttities/relations schema level
+* for each entity, we distinguish four kind of permissions: read,
+  add, update and delete
+* for each relation, we distinguish three king of permissions: read,
+  add and delete (we can not modify a relation)
+* the basic groups are: Administrators, Users and Guests
+* by default, users belongs to the group Users
+* there is a virtual group called `Owners users` to which we
+  can associate only deletion and update permissions
+* we can not add users to the `Owners users` group, they are
+  implicetely added to it according to the context of the objects
+  they own
+* the permissions of this group are only be checked on update/deletion
+  actions if all the other groups the user belongs does not provide
+  those permissions
+
+  
+Permissions definition
+``````````````````````
+
+Setting permissions is done with the attribute `permissions` of entities and
+relation types. It defines a dictionary where the keys are the access types
+(action), and the values are the authorized groups or expressions.
+
+For an entity type, the possible actions are `read`, `add`, `update` and
+`delete`.
+
+For a relation type, the possible actions are `read`, `add`, and `delete`.
+
+For each access type, a tuple indicates the name of the authorized groups and/or
+one or multiple RQL expressions to satisfy to grant access. The access is
+provided once the user is in the listed groups or one of the RQL condition is
+satisfied.
+
+The standard groups are :
+
+* `guests`
+
+* `users`
+
+* `managers`
+
+* `owners` : virtual group corresponding to the entity's owner.
+  This can only be used for the actions `update` and `delete` of an entity
+  type.
+
+It is also possible to use specific groups if they are defined in the precreate 
+of the cube (``migration/precreate.py``).
+
+
+Use of RQL expression for writing rights
+````````````````````````````````````````
+
+It is possible to define RQL expression to provide update permission 
+(`add`, `delete` and `update`) on relation and entity types.
+
+RQL expression for entity type permission :
+
+* you have to use the class `ERQLExpression`
+
+* the used expression corresponds to the WHERE statement of an RQL query
+
+* in this expression, the variables X and U are pre-defined references
+  respectively on the current entity (on which the action is verified) and
+  on the user who send the request
+
+* it is possible to use, in this expression, a special relation 
+  "has_<ACTION>_permission" where the subject is the user and the 
+  object is a any variable, meaning that the user needs to have
+  permission to execute the action <ACTION> on the entities related
+  to this variable 
+
+For RQL expressions on a relation type, the principles are the same except 
+for the following :
+
+* you have to use the class `RQLExpression` in the case of a non-final relation
+
+* in the expression, the variables S, O and U are pre-defined references
+  to respectively the subject and the object of the current relation (on
+  which the action is being verified) and the user who executed the query
+
+* we can also defined rights on attributes of an entity (non-final relation),
+  knowing that : 
+
+  - to defines RQL expression, we have to use the class `ERQLExpression`
+    in which X represents the entity the attribute belongs to
+
+  - the permissions `add` and `delete` are equivalent. Only `add`/`read`
+    are actually taken in consideration.
+
+In addition to that the entity type `EPermission` from the standard library
+allow to build very complex and dynamic security architecture. The schema of
+this entity type is as follow : ::
+
+    class EPermission(MetaEntityType):
+	"""entity type that may be used to construct some advanced security configuration
+	"""
+	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
+	require_group = SubjectRelation('EGroup', cardinality='+*',
+					description=_('groups to which the permission is granted'))
+	require_state = SubjectRelation('State',
+				    description=_("entity'state in which the permission is applyable"))
+	# can be used on any entity
+	require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
+					    description=_("link a permission to the entity. This "
+							  "permission should be used in the security "
+							  "definition of the entity's type to be useful."))
+
+
+Example of configuration ::
+
+
+    ...
+
+    class Version(EntityType):
+	"""a version is defining the content of a particular project's release"""
+
+	permissions = {'read':   ('managers', 'users', 'guests',),
+		       'update': ('managers', 'logilab', 'owners',),
+		       'delete': ('managers', ),
+		       'add':    ('managers', 'logilab',
+				  ERQLExpression('X version_of PROJ, U in_group G,'
+						 'PROJ require_permission P, P name "add_version",'
+						 'P require_group G'),)}
+
+    ...
+
+    class version_of(RelationType):
+	"""link a version to its project. A version is necessarily linked to one and only one project.
+	"""
+	permissions = {'read':   ('managers', 'users', 'guests',),
+		       'delete': ('managers', ),
+		       'add':    ('managers', 'logilab',
+				  RRQLExpression('O require_permission P, P name "add_version",'
+						 'U in_group G, P require_group G'),)
+		       }
+	inlined = True
+
+This configuration indicates that an entity `EPermission` named
+"add_version" can be associated to a project and provides rights to create
+new versions on this project to specific groups. It is important to notice that :
+
+* in such case, we have to protect both the entity type "Version" and the relation
+  associating a version to a project ("version_of")
+
+* because of the genricity of the entity type `EPermission`, we have to execute
+  a unification with the groups and/or the states if necessary in the expression
+  ("U in_group G, P require_group G" in the above example)
+
+Use of RQL expression for reading rights
+````````````````````````````````````````
+
+The principles are the same but with the following restrictions :
+
+* we can not use `RRQLExpression` on relation types for reading
+
+* special relations "has_<ACTION>_permission" can not be used
+
+
+Note on the use of RQL expression for `add` permission
+``````````````````````````````````````````````````````
+Potentially, the use of an RQL expression to add an entity or a relation
+can cause problems for the user interface, because if the expression uses
+the entity or the relation to create, then we are not able to verify the 
+permissions before we actually add the entity (please note that this is
+not a problem for the RQL server at all, because the permissions checks are
+done after the creation). In such case, the permission check methods 
+(check_perm, has_perm) can indicate that the user is not allowed to create 
+this entity but can obtain the permission. 
+To compensate this problem, it is usually necessary, for such case,
+to use an action that reflects the schema permissions but which enables
+to check properly the permissions so that it would show up if necessary.
+
--- a/doc/book/en/B0020-define-workflows.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,157 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Workflow:
-
-Workflow definition
-===================
-
-General
--------
-
-A workflow can be defined in a `CubicWeb` application thanks to the system 
-entities ``State`` and ``Transition``. Those are defined within all 
-`CubicWeb` application and can be set-up through the main administrator interface.
-
-Once your schema is defined, you can start creating the set of states and
-the required transitions for your applications entities.
-
-You first need to define the states and then the transitions between those
-to complete your workflow.
-
-A ``State`` defines the status of an entity. While creating a new state, 
-you will be first given the option to select the entity type the state
-can be applied to. By choosing ``Apply``, a new section will be displayed
-in the editing screen to enable you to add relation to the state you are
-creating.
-
-A ``Transition`` is also based on an entity type it can be applied to.
-By choosing ``Apply``, a new section will be displayed in the editing 
-screen to enable you to add relation to the transition you are
-creating.
-
-At the transition level you will also define the group of user which can
-aplly this transition to an object.
-
-
-Example of a simple workflow
-----------------------------
-
-Please see the tutorial to view and example of a simple workflow.
-
-
-[Create a simple workflow for BlogDemo, to have a moderator approve new blog 
-entry to be published. This implies, specify a dedicated group of blog
-moderator as well as hide the view of a blog entry to the user until
-it reaches the state published]
-
-Set-up a workflow
------------------
-
-We want to create a workflow to control the quality of the BlogEntry 
-submitted on your application. When a BlogEntry is created by a user
-its state should be `submitted`. To be visible to all, it needs to
-be in the state `published`. To move from `submitted` to `published`
-we need a transition that we can name `approve_blogentry`.
-
-We do not want every user to be allowed to change the state of a 
-BlogEntry. We need to define a group of user, `moderators`, and 
-this group will have appropriate permissions to approve BlogEntry
-to be published and visible to all.
-
-There are two ways to create a workflow, form the user interface,
-and also by defining it in ``migration/postcreate.py``. 
-This script is executed each time a new ``cubicweb-ctl db-init`` is done. 
-If you create the states and transitions through the user interface
-this means that next time you will need to initialize the database
-you will have to re-create all the entities. 
-We strongly recommand you create the workflow in ``migration\postcreate.py``
-and we will now show you how.
-The user interface would only be a reference for you to view the states 
-and transitions but is not the appropriate interface to define your
-application workflow.
-
-Update the schema
-~~~~~~~~~~~~~~~~~
-To enable a BlogEntry to have a State, we have to define a relation
-``in_state`` in the schema of BlogEntry. Please do as follows, add
-the line ``in_state (...)``::
-
-  class BlogEntry(EntityType):
-      title = String(maxsize=100, required=True)
-      publish_date = Date(default='TODAY')
-      text_format = String(meta=True, internationalizable=True, maxsize=50,
-                           default='text/rest', constraints=[format_constraint])
-      text = String(fulltextindexed=True)
-      category = String(vocabulary=('important','business'))
-      entry_of = SubjectRelation('Blog', cardinality='?*')
-      in_state = SubjectRelation('State', cardinality='1*')
-
-As you updated the schema, you will have re-execute ``cubicweb-ctl db-init``
-to initialize the database and migrate your existing entities.
-[WRITE ABOUT MIGRATION]
-
-Create states, transitions and group permissions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At the time the ``postcreate.py`` script is executed, several methods
-can be used. They are all defined in the ``class ServerMigrationHelper``.
-We will only discuss the method we use to create a wrokflow here.
-
-To define our workflow for BlogDemo, please add the following lines
-to ``migration/postcreate.py``::
-  
-  _ = unicode
-
-  moderators      = add_entity('EGroup', name=u"moderators")
-
-  submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
-  published = add_state(_('published'), 'BlogEntry')
-
-  add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
-
-  checkpoint()
-
-.. note::
-  Do not forget to add the `_()` in front of all states and transitions names while creating
-  a workflow so that they will be identified by the i18n catalog scripts.
-
-``add_entity`` is used here to define the new group of users that we
-need to define the transitions, `moderators`.
-If this group required by the transition is not defined before the
-transition is created, it will not create the relation `transition 
-require the group moderator`.
-
-``add_state`` expects as the first argument the name of the state you are
-willing to create, then the entity type on which the state can be applied, 
-and an optionnal argument to set if the state is the initial state
-of the entity type or not.
-
-``add_transition`` expects as the first argument the name of the 
-transition, then the entity type on which we can apply the transition,
-then the list of possible initial states from which the transition
-can be applied, the target state of the transition, and the permissions
-(e.g. list of the groups of users who can apply the transition, the user
-needs to belong to at least one of the listed group).
-
-
-We could have also added a RQL condition in addition to a group to 
-which the user should belong to. 
-
-If we use both RQL condition and group, the two must be satisfied 
-for the user to be allowed to apply the transition.
-
-If we use a RQL condition on a transition, we can use the following 
-variables:
-
-* `%(eid)s`, object's eid
-* `%(ueid)s`, user executing the query eid
-* `%(seid)s`, the object's current state eid
-
-
-.. image:: images/lax-book.03-transitions-view.en.png
-
-You can now notice that in the actions box of a BlogEntry, the state
-is now listed as well as the possible transitions from this state
-defined by the workflow. This transition, as defined in the workflow,
-will only being displayed for the users belonging to the group
-moderators of managers.
--- a/doc/book/en/B0030-data-as-objects.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Data as objects
-===============
-
-We will in this chapter introduce the objects that are used to handle
-the data stored in the database.
-
-Classes `Entity` and `AnyEntity`
---------------------------------
-
-To provide a specific behavior for each entity, we just need to define
-a class inheriting from `cubicweb.entities.AnyEntity`. In general, we have
-to defined those classes in a module of `entities` package of an application
-so that it will be available on both server and client side.
-
-The class `AnyEntity` is loaded dynamically from the class `Entity` 
-(`cubciweb.common.entity`). We define a sub-class to add methods or to
-specialize the handling of a given entity type
-
-Descriptors are added when classes are registered in order to initialize the class
-according to its schema:
-
-* we can access the defined attributes in the schema thanks the attributes of
-  the same name on instances (typed value)
-
-* we can access the defined relations in the schema thanks to the relations of
-  the same name on instances (entities instances list)
-
-The methods defined for `AnyEntity` or `Entity` are the following ones:
-
-* `has_eid()`, returns true is the entity has an definitive eid (e.g. not in the
-  creation process)
-        
-* `check_perm(action)`, checks if the user has the permission to execcute the
-  requested action on the entity
-
-:Formatting and output generation:
-
-  * `view(vid, **kwargs)`, apply the given view to the entity
-
-  * `absolute_url(**kwargs)`, returns an absolute URL to access the primary view
-    of an entity
-    
-  * `rest_path()`, returns a relative REST URL to get the entity
-
-  * `format(attr)`, returns the format (MIME type) of the field given un parameter
-
-  * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`, 
-    returns a string enabling the display of an attribute value in a given format
-    (the value is automatically recovered if necessary)
-
-  * `display_name(form='')`, returns a string to display the entity type by 
-    specifying the preferred form (`plural` for a plural form)
-
-:Data handling:
-
-  * `as_rset()`, converts the entity into an equivalent result set simulating the 
-     request `Any X WHERE X eid _eid_`
-
-  * `complete(skip_bytes=True)`, executes a request that recovers in one time
-    all the missing attributes of an entity
-
-  * `get_value(name)`, returns the value associated to the attribute name given
-    in parameter
-
-  * `related(rtype, x='subject', limit=None, entities=False)`, returns a list
-    of entities related to the current entity by the relation given in parameter
-
-  * `unrelated(rtype, targettype, x='subject', limit=None)`, returns a result set
-    corresponding to the entities not related to the current entity by the
-    relation given in parameter and satisfying its constraints
-
-  * `set_attributes(**kwargs)`, updates the attributes list with the corresponding
-    values given named parameters
-
-  * `copy_relations(ceid)`, copies the relations of the entities having the eid
-    given in the parameters on the current entity
-
-  * `last_modified(view)`, returns the date the object has been modified
-    (used by HTTP cache handling)
-
-  * `delete()` allows to delete the entity
-  
-:Standard meta-data (Dublin Core):
-
-  * `dc_title()`, returns a unicode string corresponding to the meta-data
-    `Title` (used by default the first attribute non-meta of the entity
-    schema)
-
-  * `dc_long_title()`, same as dc_title but can return a more
-    detailled title
-
-  * `dc_description(format='text/plain')`, returns a unicode string 
-    corresponding to the meta-data `Description` (look for a description
-    attribute by default)
-
-  * `dc_authors()`, returns a unicode string corresponding to the meta-data 
-    `Authors` (owners by default)
-
-  * `dc_date(date_format=None)`, returns a unicode string corresponding to 
-    the meta-data `Date` (update date by default)
-            
-:Vocabulary control on relations:
-
-  * `vocabulary(rtype, x='subject', limit=None)`, called by the
-    editing views, it returns a list of couples (label, eid) of entities
-    that could be related to the entity by the relation `rtype`
-  * `subject_relation_vocabulary(rtype, limit=None)`, called internally 
-    by  `vocabulary` in the case of a subject relation
-  * `object_relation_vocabulary(rtype, limit=None)`, called internally 
-    by  `vocabulary` in the case of an object relation
-  * `relation_vocabulary(rtype, targettype, x, limit=None)`, called
-    internally by `subject_relation_vocabulary` and `object_relation_vocabulary`
-
-
-*rtags*
--------
-
-*rtags* allows to specify certain behaviors of relations relative to a given
-entity type (see later). They are defined on the entity class by the attribute
-`rtags` which is a dictionnary with as its keys the triplet ::
-
-  <relation type>, <target entity type>, <context position ("subject" ou "object")>
-
-and as the values a `set` or a tuple of markers defining the properties that
-apply to this relation.
-
-It is possible to simplify this dictionnary:
-
-* if we want to specifiy a single marker, it is not necessary to
-  use a tuple as the value, the marker by itself (characters string)
-  is enough
-* if we only care about a single type of relation and not about the target
-  and the context position (or when this one is not ambigous), we can simply
-  use the name of the relation type as the key
-* if we want a marker to apply independently from the target entity type,
-  we have to use the string `*` as the target entity type
-
-
-Please note that this dictionnary is *treated at the time the class is created*.
-It is automatically merged with the parent class(es) (no need to copy the
-dictionnary from the parent class to modify it). Also, modify it after the 
-class is created will not have any effect...
-
-.. include:: B0031-define-entities.en.txt
--- a/doc/book/en/B0031-define-entities.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Parametrization and specific extensions
----------------------------------------
-
-Dynamic default values
-``````````````````````
-It is possible to define in the schema *static* default values.
-It is also possible to define in the schema *dynamic* default values
-by defining in the entity class a method `default_<attribut name>` for
-a given attribute.
-
-
-Loaded attributes and default sorting management
-````````````````````````````````````````````````
-
-* The class attribute `fetch_attrs` allows to defined in an entity class
-  a list of names of attributes or relations that should be automatically
-  loaded when we recover the entities of this type. In the case of relations,
-  we are limited to *subject of cardinality `?` or `1`* relations.
-
-* The class method `fetch_order(attr, var)` expects an attribute (or relation)
-  name as a parameter and a variable name, and it should return a string
-  to use in the requirement `ORDER BY` of an RQL query to automatically
-  sort the list of entities of such type according to this attribute, or
-  `None` if we do not want to sort on the attribute given in the parameter.
-  By default, the entities are sorted according to their creation date.
-
-* The class method `fetch_unrelated_order(attr, var)` is similar to the 
-  method `fetch_order` except that it is essentially used to control
-  the sorting of drop-down lists enabling relations creation in 
-  the editing view of an entity.
-
-The function `fetch_config(fetchattrs, mainattr=None)` simplifies the
-definition of the attributes to load and the sorting by returning a 
-list of attributes to pre-load (considering automatically the attributes
-of `AnyEntity`) and a sorting function based on the main attribute
-(the second parameter if specified otherwisethe first attribute from
-the list `fetchattrs`).
-This function is defined in `cubicweb.entities`.
-
-For example: ::
-
-  class Transition(AnyEntity):
-    """..."""
-    id = 'Transition'
-    fetch_attrs, fetch_order = fetch_config(['name'])
-
-Indicates that for the entity type "Transition", you have to pre-load
-the attribute `name` and sort by default on this attribute.
-
-
-Editing forms management
-````````````````````````
-It is possible to manage attributes/relations in the simple or multiple
-editing form thanks to the following *rtags*: 
-
-* `primary`, indicates that an attribute or a relation has to be
-  inserted **in the simple or multiple editing forms**. In the case of
-  a relation, the related entity editing form will be included in the
-  editing form and represented as a combobox. Each item of the
-  combobox is a link to an existing entity.
-
-* `secondary`, indicates that an attribute or a relation has to be
-  inserted **in the simple editing form only**. In the case of a
-  relation, the related entity editing form will be included in the
-  editing form and represented as a combobox. Each item of the combobox
-  is a link to an existing entity.
-
-* `inlineview`, includes the target entity's form in the editing form
-  of the current entity. It allows to create the target entity in the
-  same time as the current entity.
-
-* `generic`, indicates that a relation has to be inserted in the simple
-  editing form, in the generic box of relation creation.
-
-* `generated`, indicates that an attribute is dynamically computed
-  or other,  and that it should not be displayed in the editing form.
-
-If necessary, it is possible to overwrite the method  
-`relation_category(rtype, x='subject')` to dynamically compute
-a relation editing category.
-
-``add_related`` box management
-``````````````````````````````
-
-The box ``add_related`` is an automatic box that allows to create
-an entity automatically related to the initial entity (context in
-which the box is displayed). By default, the links generated in this
-box are computed from the schema properties of the displayed entity,
-but it is possible to explicitely specify them thanks to the
-following *rtags*:
-
-* `link`, indicates that a relation is in general created pointing
-  to an existing entity and that we should not to display a link
-  for this relation
-
-* `create`, indicates that a relation is in general created pointing
-  to new entities and that we should display a link to create a new
-  entity and link to it automatically
-
-
-If necessary, it is possible to overwrite the method  
-`relation_mode(rtype, targettype, x='subject')` to dynamically
-compute a relation creation category.
-
-Please note that if at least one action belongs to the `addrelated` category,
-the automatic behavior is desactivated in favor of an explicit behavior
-(e.g. display of `addrelated` category actions only).
-
-
-Filtering table forms management
-````````````````````````````````
-
-By default, the view ``table`` manages automatically a filtering
-form of its content. The algorithm is as follows:
-
-1. we consider that the first column contains the entities to constraint
-2. we collect the first entity of the table (row 0) to represent all the 
-   others
-3. for all the others variables defined in the original request:
-   
-   1. if the varaible is related to the main variable by at least one relation
-   2. we call the method ``filterform_vocabulary(rtype, x)`` on the entity,
-      if nothing is returned (meaning a tuple `Non`, see below), we go to the
-      next variable, otherwise a form filtering element is created based on
-      the vocabulary values returned
-
-4. there is no others limitations to the `RQL`, it can include sorting, grouping
-   conditions... Javascripts functions are used to regenerate a request based on the
-   initial request and on the selected values from the filtering form.
-
-The method ``filterform_vocabulary(rtype, x, var, rqlst, args, cachekey)`` takes 
-the name of a relation and the target as parameters, which indicates of the
-entity on which we apply the method is subject or object of the relation. It
-has to return:
-
-* a 2-uple of None if it does not know how to handle the relation
-
-* a type and a list containing the vocabulary
-  
-  * the list has to contain couples (value, label)
-  * the type indicates if the value designate an integer (`type == 'int'`),
-    a string (`type =='string'` or a non-final relation (`type == 'eid'`)
-
-For example in our application managing tickets, we want to be able to filter
-them by :
-
-* type
-* priority
-* state (in_state)
-* tag (tags)
-* version (done_in)
-
-For that we define the following method: ::
-
-
-    class Ticket(AnyEntity):
-
-	...
-
-	def filterform_vocabulary(self, rtype, x, var, rqlst, args, cachekey):
-	    _ = self.req._
-	    if rtype == 'type':
-		return 'string', [(x, _(x)) for x in ('bug', 'story')]
-	    if rtype == 'priority':
-		return 'string', [(x, _(x)) for x in ('minor', 'normal', 'important')]
-	    if rtype == 'done_in':
-		rql = insert_attr_select_relation(rqlst, var, rtype, 'num')
-		return 'eid', self.req.execute(rql, args, cachekey)
-	    return super(Ticket, self).filterform_vocabulary(rtype, x, var, rqlst,
-							     args, cachekey)
-
-.. note::
-  Filtering on state and tags is automatically installed, no need to handle it.
-
--- a/doc/book/en/B0040-migration.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _migration:
-
-Migration
-=========
-
-One of the main concept in `CubicWeb` is to create incremental applications
-and for this purpose multiple actions are provided to facilitate the improvement
-of an application and in particular changes applied to the data model
-without loosing existing data.
-
-The current version of an application model is provided in the file
-`__pkginfo__.py` as a tuple of 3 integers.
-
-Migration scripts management
-----------------------------
-
-Migration scripts needs to be located in the directory `migration` of your
-application and nammed accordingly:
-
-::
-
-  <version n° X.Y.Z>[_<description>]_<mode>.py
-
-in which : 
-
-* X.Y.Z is the model version number to which the script enable to migrate
-
-* *mode* (between the last "_" and the extension ".py") indicates which part
-  of the application (RQL server, web server) the script applies to in case
-  of distributed installation. Its value could be :
-
-  * `common`, applies to the RQL server as well as the web server and updates
-    files on the hard drive (configuration files migration for example).
-
-  * `web`, applies only to the web server and updates files on the hard drive
-
-  * `repository`, applies only to the RQL server and updates files on the
-    hard drive
-
-  * `Any`, applies only to the RQL server and updates data in the database
-    (schema and data migration for example)
-
-Again in the directory `migration`, the file `depends.map` allows to indicate
-that to migrate to a particular model version, you always have to first migrate
-to a particular `CubicWeb` version. This file can contains comments (lines
-starting by `#`) and a dependancy is listed as follows: ::
-  
-  <model version n° X.Y.Z> : <cubicweb version n° X.Y.Z>
-
-For example: ::
-
-  0.12.0: 2.26.0
-  0.13.0: 2.27.0
-  # 0.14 works with 2.27 <= erudi <= 2.28 at least
-  0.15.0: 2.28.0
-
-Base context
-------------
-
-The following identifiers are pre-defined in the migration scripts:
-
-* `config`, instance configuration
-
-* `interactive_mode`, boolean indicating that the script is executed in
-  an intercative mode or not 
-
-* `appltemplversion`, application model version of the instance
-
-* `applerudiversion`, cubicweb instance version
-
-* `templversion`, installed application model version
-
-* `erudiversion`, installed cubicweb version
-
-* `confirm(question)`, function interrogating the user and returning true
-  if the user answers yes, false otherwise (always returns true when in a
-  non-interactive mode)
-
-* `_`, function fonction equivalent to `unicode` allowing to flag the strings
-  to internationalize in the migration scripts
-
-In the `repository` scripts, the following identifiers are also defined:
-
-* `checkpoint`, request confirming and executing a "commit" at checking point
-
-* `repo_schema`, instance persisting schema (e.g. instance schema of the
-  current migration)
-
-* `newschema`, installed schema on the file system (e.g. schema of 
-  the updated model and erudi)
-
-* `sqlcursor`, SQL cursor for very rare cases where it is really
-   necessary or beneficial to go through the sql
-
-* `repo`, repository object
-
-                        
-Schema migration
-----------------
-The following functions for schema migration are available in the `repository`
-scripts:
-
-* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new
-  attribute to an existing entity type. If the attribute type is not specified, 
-  then it is extracted from the updated schema.
-        
-* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an
-  existing entity type.
-
-* `rename_attribute(etype, oldname, newname, commit=True)`, rename an attribute
-            
-* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type.
-  If `auto` is True, all the relations using this entity type and having a known
-  entity type on the other hand will automatically be added.
-
-* `drop_entity_type(etype, commit=True)`, removes an entity type and all the 
-  relations using it.
-
-* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type
-            
-* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation
-  type. If `addrdef` is True, all the relations definitions of this type will
-  be added.
-
-* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
-  definitions of this type.
-
-* `rename_relation(oldname, newname, commit=True)`, renames a relation.
-
-* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
-  relation definition.
-
-* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes
-  a relation definition.
-
-* `synchronize_permissions(ertype, commit=True)`, synchronizes permissions on
-  an entity type or relation type.
-        
-* `synchronize_rschema(rtype, commit=True)`, synchronizes properties and permissions
-  on a relation type.
-                
-* `synchronize_eschema(etype, commit=True)`, synchronizes properties and persmissions
-  on an entity type.
-    
-* `synchronize_schema(commit=True)`, synchronizes the persisting schema with the
-  updated schema (but without adding or removing new entity types, relations types 
-  or even relations definitions).
-        
-* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes
-  properties of a relation definition by using the nammed parameters of the properties
-  to change.
-
-* `set_widget(etype, rtype, widget, commit=True)`, changes the widget used for the
-  relation <rtype> of entity type <etype>.
-
-* `set_size_constraint(etype, rtype, size, commit=True)`, changes the size constraints
-  for the relation <rtype> of entity type <etype>.
-
-Data migration
---------------
-The following functions for data migration are available in the `repository` scripts:
-
-* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
-  query, either to interrogate or update. A result set object is returned.  
-
-* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
-  type. The attributes and relations values are specified using the nammed and
-  positionned parameters.
-
-Workflow creation
------------------
-
-The following functions for workflow creation are available in the `repository`
-scripts:
-
-* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, adds a new state
-  in the workflow.
-    
-* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`, 
-  adds a new transition in the workflow.
-
-You can find more details about workflows in the chapter :ref:`Workflow` .
-
-Configuration migration
------------------------
-
-The following functions for configuration migration are available in all the
-scripts:
-
-* `option_renamed(oldname, newname)`, indicates that an option has been renammed
-
-* `option_group_change(option, oldgroup, newgroup)`, indicates that an option does not
-  belong anymore to the same group.
-
-* `option_added(oldname, newname)`, indicates that an option has been added.
-
-* `option_removed(oldname, newname)`, indicates that an option has been deleted.
-
-
-Others migration functions
---------------------------
-Those functions are only used for low level operations that could not be 
-accomplished otherwise or to repair damaged databases during interactive 
-session. They are available in the `repository` scripts:
-
-* `sqlexec(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query
-* `add_entity_type_table(etype, commit=True)`
-* `add_relation_type_table(rtype, commit=True)`
-* `uninline_relation(rtype, commit=True)`
--- a/doc/book/en/B081-i18n.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Internationalisation:
-
-
-Internationalisation
-====================
-
-Le système d'internationalisation de l'interface web de CubicWeb est basé sur le
-système `GNU gettext`_.
-
-.. _`GNU gettext`: http://www.gnu.org/software/gettext/
-
-Messages à internationaliser
-----------------------------
-
-Marquage des messages à internaliser
-````````````````````````````````````
-Les chaines de caractères à internationaliser sont marqués par l'appel à la
-fonction `_` *OU* par la méthode équivalent de la requête dans le code python ou
-dans les expressions python de template TAL. 
-
-Dans les templates cubicweb-tal, il est également possible d'insérer une chaine à
-traduire via les balises `i18n:content` et  `i18n:replace`.
-
-De plus des messages correspondant aux entités/relations utilisés par le schéma
-de l'application seront automatiquement ajoutés.
-
-Renvoi d'un message internationalisé lors de la construction d'une page
-```````````````````````````````````````````````````````````````````````
-La fonction *built-in* `_` ne doit servir qu'**à marquer les messages à
-traduire**, non pas à récupérer une traduction. Il faut pour cela utiliser la
-méthode `_` de l'objet requête, sans quoi vous récupérerez l'identifiant de
-message au lieu de sa traduction dans la langue propre à la requête.1
-
-
-Gestion des catalogues de traduction
-------------------------------------
-Une fois l'application rendu internationalisable coté code, reste à gérer les
-catalogues de traductions. cubicweb-ctl intègre pour cela les commandes suivantes : 
-
-* `i18nlibupdate`, met à jour les catalogues de messages *de la librairie
-  cubicweb*. Sauf si vous développez sur le framework (et non votre propre
-  application), vous ne devriez pas avoir à utiliser cette commande
-
-* `i18nupdate`, met à jour les catalogues de messages *du composant* (ou de tous
-  les composants). A la suite de cette commande, vous devez mettre à jour les
-  fichiers de traduction *.po* dans le sous-répertoire "i18n" de votre
-  template. Évidemment les traductions précédentes toujours utilisées ont été
-  conservées.
-
-* `i18ncompile`, recompile les catalogues de messages *d'une instance* (ou de
-  toutes les instances) après mise à jour des catalogues de son composant. Cela
-  est effectué automatiquement lors d'une création ou d'une mise à jour. Les
-  catalogues de messages compilés se trouvent dans le répertoire
-  "i18n/<lang>/LC_MESSAGES/cubicweb.mo" de l'application où `lang` est
-  l'identifiant de la langue sur 2 lettres ('en' ou 'fr' par exemple)
-
-
-Le cas classique
-````````````````
-Vous avez ajouté et/ou modifié des messages d'un composant utilisé par votre
-application (en ajoutant une nouvelle vue ou en ayant modifié le schéma par
-exemple) :
-
-1. `cubicweb-ctl i18nupdate <composant>`
-2. éditer les fichiers <composant>/xxx.po dans pour y rajouter les traductions
-   manquantes (`msgstr` vide) 
-3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18n compile <monapplication>`
-
--- a/doc/book/en/B1-web-interface.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Web interface
-+++++++++++++
-.. toctree::
-   :maxdepth: 1
-
-   B1010-request.en.txt
-   B1020-define-views.en.txt
-   B1030-form-management.en.txt
-   B1040-actions.en.txt
-   B1050-boxes.en.txt
-   B1060-templates.en.txt
-   B1070-ui-components.en.txt
-   B1080-ajax-json.en.txt
-   B1090-internationalization.en.txt
-   B1100-online-doc.en.txt
-   B1110-embedding-external-page.en.txt
-   B1120-urlrewrite.en.txt
-   B1130-css.en.txt
--- a/doc/book/en/B1010-request.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Request
-=======
-
-[WRITE ME]
-
-* the request object
-
--- a/doc/book/en/B1020-define-views.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _DefinitionVues:
-
-Views definition
-================
-
-This chapter aims to describe the concept of a `view` used all along
-the development of a web application and how it has been implemented
-in `CubicWeb`.
-
-We'll start with a description of the interface providing you with a basic
-understanding of the classes and methods available, then detail the view
-selection principle which makes `CubicWeb` web interface very flexible.
-
-Basic class for views
----------------------
-
-Class `View` (`cubicweb.common.view`)
-`````````````````````````````````````
-
-This class is an abstraction of a view class, used as a base class for every
-renderable object such as views, templates, graphic components, etc.
-
-A `View` is instantiated to render a result set or part of a result set. `View`
-subclasses may be parametrized 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 views
-      must not be embeded in the main template for HTML pages)
-    * 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.
-
-A view writes to its output stream thanks to its attribute `w` (`UStreamIO`).
-
-The basic interface for views is as follows (remember that the result set has a
-tabular structure with rows and columns, hence cells):
-
-* `dispatch(**context)`, render the view by calling `call` or
-  `cell_call` depending on the given parameters
-* `call(**kwargs)`, call the view for a complete result set or null (default 
-  implementation calls `cell_call()` on each cell of the result set)
-* `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set
-* `url()`, returns the URL enabling us to get the view with the current
-  result set 
-* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier 
-  `__vid` on the given result set. It is possible to give a view identifier
-  of fallback that will be used if the view requested is not applicable to the
-  result set
-  
-* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except
-  the flow is automatically passed in the parameters
-  
-* `html_headers()`, returns a list of HTML headers to set by the main template
-
-* `page_title()`, returns the title to use in the HTML header `title`
-
-* `creator(eid)`, returns the eid and the login of the entity creator of the entity
-  having the eid given in the parameter 
-
-Other basic views classes
-`````````````````````````
-Here are some of the subclasses of `View` defined in `cubicweb.common.view`
-that are more concrete as they relates to data rendering within the application:
-
-* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
-* `StartupView`, start view that does not require a result set to apply to
-* `AnyRsetView`, view applied to any result set 
-* `EmptyRsetView`, view applied to an empty result set
-
-The selection view principle
-----------------------------
-
-A view is essentially defined by:
-
-- an identifier (all objects in `CubicWeb` are entered in a registry
-  and this identifier will be used as a key). This is defined in the class
-  attribute ``id``.
-  
-- a filter to select the resulsets it can be applied to. This is defined in
-  the class attribute ``__selectors__``, which expects a tuple of selectors
-  as its value.
-
-
-For a given identifier, multiple views can be defined. `CubicWeb` uses
-a selector which computes scores so that it can identify and select the
-best view to apply in context. The selectors library is in 
-``cubicweb.common.selector`` and a library of the methods used to
-compute scores is in ``cubicweb.vregistry.vreq``.
-
-.. include:: B1021-views-selectors.en.txt
-
-Registerer
-``````````
-A view is also customizable through its attribute ``__registerer__``.
-This is used at the time the application is launched to manage how
-objects (views, graphic components, actions, etc.) 
-are registered in the `cubicWeb` registry.
-
-A `registerer` can, for example, identify when we register an 
-object that is equivalent to an already registered object, which
-could happen when we define two `primary` views for an entity type.
-
-The purpose of a `registerer` is to control objects registry
-at the application startup whereas `selectors` controls objects
-when they are selected for display.
-
-
-`CubicWeb` provides a lot of standard views for the default class
-`EntityType`. You can find them in ``cubicweb/web/views/``.
-
-.. include:: B1022-views-stdlib.en.txt
-
-
-Examples of views class 
------------------------
-
-- Using the attribute `templatable`
-
-  ::
-    
-
-    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
-    
-
-
-- Using the attribute `__selectors__`
-
-  ::
-    
-
-    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_selector)
-        accepts = ('Any',)
-        search_states = ('linksearch',)
-
-    
-
-
-
-Example of a view customization
--------------------------------
-
-We'll show you now an example of a ``primary`` view and how to customize it.
-
-If you want to change the way a ``BlogEntry`` is displayed, just override 
-the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` ::
-
-  01. from ginco.web.views import baseviews
-  02.
-  03. class BlogEntryPrimaryView(baseviews.PrimaryView):
-  04.
-  05.     accepts = ('BlogEntry',)
-  06.
-  07.     def cell_call(self, row, col):
-  08.         entity = self.entity(row, col)
-  09.         self.w(u'<h1>%s</h1>' % entity.title)
-  10.         self.w(u'<p>published on %s in category %s</p>' % \
-  11.                (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
-  12.         self.w(u'<p>%s</p>' % entity.text)
-
-The above source code defines a new primary view (`line 03`) for
-``BlogEntry`` (`line 05`). 
-
-Since views are applied to resultsets and resulsets can be tables of
-data, it is needed to recover the entity from its (row,col)
-coordinates (`line 08`). We will get to this in more detail later.
-
-The view has a ``self.w()`` method that is used to output data. Here `lines
-09-12` output HTML tags and values of the entity's attributes.
-
-When displaying same blog entry as before, you will notice that the
-page is now looking much nicer.
-
-.. image:: images/lax-book.09-new-view-blogentry.en.png
-   :alt: blog entries now look much nicer
-
-Let us now improve the primary view of a blog ::
-
-  01. class BlogPrimaryView(baseviews.PrimaryView):
-  02. 
-  03.     accepts = ('Blog',)
-  04.
-  05.     def cell_call(self, row, col):
-  06.         entity = self.entity(row, col)
-  07.         self.w(u'<h1>%s</h1>' % entity.title)
-  08.         self.w(u'<p>%s</p>' % entity.description)
-  09.         rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
-  10.         self.wview('primary', rset)
-
-In the above source code, `lines 01-08` are similar to the previous
-view we defined.
-
-At `line 09`, a simple request in made to build a resultset with all
-the entities linked to the current ``Blog`` entity by the relationship
-``entry_of``. The part of the framework handling the request knows
-about the schema and infer that such entities have to be of the
-``BlogEntry`` kind and retrieves them.
-
-The request returns a selection of data called a resultset. At 
-`line 10` the view 'primary' is applied to this resultset to output
-HTML. 
-
-**This is to be compared to interfaces and protocols in object-oriented
-languages. Applying a given view to all the entities of a resultset only
-requires the availability, for each entity of this resultset, of a
-view with that name that can accepts the entity.**
-
-Assuming we added entries to the blog titled `MyLife`, displaying it
-now allows to read its description and all its entries.
-
-.. image:: images/lax-book.10-blog-with-two-entries.en.png
-   :alt: a blog and all its entries
-
-**Before we move forward, remember that the selection/view principle is
-at the core of `CubicWeb`. Everywhere in the engine, data is requested
-using the RQL language, then HTML/XML/text/PNG is output by applying a
-view to the resultset returned by the query. That is where most of the
-flexibility comes from.**
-
-[WRITE ME]
-
-* implementing interfaces, calendar for blog entries
-* show that a calendar view can export data to ical
-
-We will implement the `cubicweb.interfaces.ICalendarable` interfaces on
-entities.BlogEntry and apply the OneMonthCalendar and iCalendar views
-to resultsets like "Any E WHERE E is BlogEntry"
-
-* create view "blogentry table" with title, publish_date, category
-
-We will show that by default the view that displays 
-"Any E,D,C WHERE E publish_date D, E category C" is the table view.
-Of course, the same can be obtained by calling
-self.wview('table',rset)
-
-* in view blog, select blogentries and apply view "blogentry table"
-* demo ajax by filtering blogentry table on category
-
-we did the same with 'primary', but with tables we can turn on filters
-and show that ajax comes for free.
-[FILLME]
-
-
-Templates
----------
-
-*Templates* are specific view that does not depend on a result set. The basic
-class `Template` (`cubicweb.common.view`) is derived from the class `View`.
-
-To build a HTML page, a *main template* is used. In general, the template of
-identifier `main` is the one to use (it is not used in case an error is raised or for
-the login form for example). This template uses other templates in addition
-to the views which depends on the content to generate the HTML page to return.
-
-A *template* is responsible for:
-
-1. executing RQL query of data to render if necessary
-2. identifying the view to use to render data if it is not specified
-3. composing the HTML page to return
-
-You will find out more about templates in :ref:`templates`. 
-
-XML views, binaries...
-----------------------
-For the views generating other formats that HTML (an image generated dynamically
-for example), and which can not usually be included in the HTML page generated
-by the main template (see above), you have to:
-
-* set the atribute `templatable` of the class to `False`
-* set, through the attribute `content_type` of the class, the MIME type generated
-  by the view to `application/octet-stream`
-
-For the views dedicated to binary content creation (an image dynamically generated
-for example), we have to set the attribute `binary` of the class to `True` (which
-implies that `templatable == False`, so that the attribute `w` of the view could be
-replaced by a binary flow instead of unicode).
-
-(X)HTML tricks to apply
------------------------
-
-Some web browser (Firefox for example) are not happy with empty `<div>`
-(by empty we mean that there is no content in the tag, but there
-could be attributes), so we should always use `<div></div>` even if
-it is empty and not use `<div/>`.
-
--- a/doc/book/en/B1021-views-selectors.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Selectors
-`````````
-
-Selectors are scoring functions that are called by the view
-dispatcher to tell whenever a view can be applied to a given result
-set and request. Selector sets are the glue that tie views to the data
-model. Using them appropriately is an essential part of the
-construction of well behaved cubes.
-
-When no score is higher than the others, an exception is raised
-``NoSelectableObject`` to let you know that the engine was not able to
-identify the view to apply. In such case you would need to review your
-design and make sure your views are properly defined.
-
-`CubicWeb` provides its own set of selectors that you can use and here
-is a description of some of the most common used:
-
-*yes*
-    This selector accepts everything which basically means to any result
-    set.
-
-*none_rset*
-    This selector accepts no result set, so it can be applied to any
-    object.
-
-*rset*
-    This selector accepts any result set, whatever the number of objects
-    in the result set.
-
-*nonempty_rset*
-    This selector accepts any non empty result set.
-
-*empty_rset*
-    This selector accepts empty (only) result set.
-
-*one_line_rset*
-    This selector accepts result set with a single line of result.
-
-*two_lines_rset*
-    This selector accepts result set with *at least* two lines of result.
-
-*two_cols_rset*
-    This selector accepts result set with *at least* one line and two columns of result.
-
-*anonymous_user*
-    This selector accepts if user is anonymous.
-
-*authenticated_user*
-    This selector accepts if user is authenticated.
-
-
-Of course you will write your own set of selectors as you get familiar with the
-framework.
-
--- a/doc/book/en/B1022-views-stdlib.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Predefined views in the library
-```````````````````````````````
-A certain number of views are used to build the web interface, which apply
-to one or more entities. Their identifier is what distinguish them from
-each others and the main ones are:
-
-*primary*
-    Primary view of an entity, this is the view called by default when a single
-    non final entity is in the result set and needs to be displayed. 
-    This view is supposed to render a maximum of informations about the entity.
-
-*text*
-    This is the simplest text view for an entity. It displays the 
-    title of an entity. It should not contain HTML.
-
-*oneline*
-    This is a hyper linked *text* view. Similar to the `secondary` view, 
-    but called when we want the view to stand on a single line, or just 
-    get a brief view. By default this view uses the
-    parameter `MAX_LINE_CHAR` to control the result size.
-
-*secondary*
-    This is a combinaison of an icon and a *oneline* view.
-    By default it renders the two first attributes of the entity as a 
-    clickable link redirecting to the primary view.
-
-*incontext, outofcontext*
-    Similar to the `secondary` view, but called when an entity is considered
-    as in or out of context. By default it respectively returns the result of 
-    `textincontext` and `textoutofcontext` wrapped in a link leading to 
-    the primary view of the entity.
-
-*textincontext, textoutofcontext*
-    Similar to the `text` view, but called when an entity is considered out or
-    in context. By default it returns respectively the result of the 
-    methods `.dc_title` and `.dc_long_title` of the entity.
-
-*list*
-    This view displays a list of entities by creating a HTML list (`<ul>`) 
-    and call the view `listitem` for each entity of the result set.
-
-*listitem*
-    This view redirects by default to the `outofcontext` view.
-
-*rss*
-    Creates a RSS/XML view and call the view `rssitem` for each entity of
-    the result set.
-
-*rssitem*
-    Create a RSS/XML view for each entity based on the results of the dublin core
-    methods of the entity (`dc_*`)
-
-*sidebox*
-  This view displays usually a side box of some related entities 
-  in a primary view.
-
-Start view:
-
-*index*
-    This view defines the home page of your application. It does not require
-    a result set to apply to.
-
-*schema*
-    A view dedicated to the display of the schema of the application
-
-Special views:
-
-*noresult*
-    This view is the default view used when no result has been found
-    (e.g. empty result set).
-
-*final*
-    Display the value of a cell without trasnformation (in case of a non final
-    entity, we see the eid). Applicable on any result set.
-
-*table*
-    Creates a HTML table (`<table>`) and call the view `cell` for each cell of
-    the result set. Applicable on any result set.
-
-*cell*
-    By default redirects to the `final` view if this is a final entity or
-    `outofcontext` view otherwise
-
-*null*
-    This view is the default view used when nothing needs to be rendered.
-    It is always applicable and it does not return anything
--- a/doc/book/en/B1030-form-management.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Forms handling
-==============
-
-Automatically generated forms management for handled entities
--------------------------------------------------------------
-
-XXX FILLME
-
-* forms ``edition`` and ``creation``
-
-The form generated by default does not fit your needs? You are not
-required to re-do all by hands! :)
-
-* rtags primary, secondary, generated, generic,
-  `Entity.relation_category(rtype, x='subject')`
-* inline_view (now a rtag?)
-* widget specification
-
-Editing controller behavior by default (id: `edit`)
----------------------------------------------------
-
-Editing control
-```````````````
-
-Re-requisites: the parameters related to entities to edit are
-specified as follows ::
-
-  <field name>:<entity eid>
-
-where entity eid could be a letter in case of an entity to create. We
-name those parameters as *qualified*.
-
-1. Retrieval of entities to edit by looking for the forms parameters
-   starting by `eid:` and also having a parameter `__type` associated
-   (also *qualified* by eid)
-
-2. For all the attributes and the relations of an entity to edit:
-
-   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
-      qualified in the case of a relation where the entity is object
-   2. if found, the value returned is considered as the initial value
-      for this relaiton and we then look for the new value(s)  in the parameter
-      <relation name> (qualified)
-   3. if the value returned is different from the initial value, an database update
-      request is done
-
-3. For each entity to edit:
-
-   1. if a qualified parameter `__linkto` is specified, its value has to be
-      a string (or a list of string) such as: ::
-
-        <relation type>:<eids>:<target>
-
-      where <target> is either `subject` or `object` and each eid could be
-      separated from the others by a `_`. Target specifies if the *edited entity*
-      is subject or object of the relation and each relation specified will
-      be inserted.
-
-    2. if a qualified parameter `__clone_eid` is specified for an entity, the
-       relations of the specified entity passed as value of this parameter are
-       copied on the edited entity.
-
-    3. if a qualified parameter `__delete` is specified, its value must be
-       a string or a list of string such as follows: ::
-
-          <ssubjects eids>:<relation type>:<objects eids>
-
-       where each eid subject or object can be seperated from the other
-       by `_`. Each relation specified will be deleted.
-
-    4. if a qualified parameter `__insert` is specified, its value should
-       follow the same pattern as `__delete`, but each relation specified is
-       inserted.
-
-4. If the parameters `__insert` and/or `__delete` are found not qualified,
-   they are interpreted as explained above (independantly from the number
-   of entities edited).
-
-5. If no entity is edited but the form contains the parameters `__linkto`
-   and `eid`, this one is interpreted by using the value specified for `eid`
-   to designate the entity on which to add the relations.
-
-
-.. note::
-
-   * If the parameter `__action_delete` is found, all the entities specified
-     as to be edited will be deleted.
-
-   * If the parameter`__action_cancel` is found, no action is completed.
-
-   * If the parameter `__action_apply` is found, the editing is applied
-     normally but the redirection is done on the form
-     (see :ref:`RedirectionControl`).
-
-   * The parameter `__method` is also supported as for the main template
-     (XXX not very consistent, maybe __method should be dealed in the view
-     controller).
-
-   * If no entity is found to be edited and if there is no parameter
-     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
-     `__insert`, an error is raised.
-
-   * Using the parameter `__message` in the form will allow to use its value
-     as a message to provide the user once the editing is completed.
-
-
-.. _RedirectionControl:
-
-Redirection control
-```````````````````
-Once editing is completed, there is still an issue left: where should we go
-now? If nothing is specified, the controller will do his job but it does not
-mean we will be happy with the result. We can control that by using the
-following parameters:
-
-* `__redirectpath`: path of the URL (relative to the root URL of the site,
-  no form parameters
-
-* `__redirectparams`: forms parameters to add to the path
-
-* `__redirectrql`: redirection RQL request
-
-* `__redirectvid`: redirection view identifier
-
-* `__errorurl`: initial form URL, used for redirecting in case a validation
-  error is raised during editing. If this one is not specified, an error page
-  is displayed instead of going back to the form (which is, if necessary,
-  responsible for displaying the errors)
-
-* `__form_id`: initial view form identifier, used if `__action_apply` is
-  found
-
-In general we use either `__redirectpath` and `__redirectparams` or
-`__redirectrql` and `__redirectvid`.
-
--- a/doc/book/en/B1040-actions.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Actions
-=========
-
-[WRITE ME]
-
-* talk about actions that appear in the action box
-
--- a/doc/book/en/B1050-boxes.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Boxes
-=========
-
-[WRITE ME]
-
-* boxes in the web interface
-
--- a/doc/book/en/B1060-templates.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _templates:
-
-Templates
-=========
-
-[WRITE ME]
-
-* talk about main templates, etc.
-
-
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below :
-
-.. image:: images/lax-book.06-main-template-layout.en.png
-
-In this section we will go through a couple of the primary templates
-you must be interested in, that is to say, the HTMLPageHeader,
-the HTMLPageFooter and the TheMainTemplate.
-
-
-HTMLPageHeader
---------------
-
-Customize header
-~~~~~~~~~~~~~~~~
-
-Let's now move the search box in the header and remove the login form
-from the header. We'll show how to move it to the left column of the application.
-
-Let's say we do not want anymore the login menu to be in the header, but we 
-prefer it to be in the left column just below the logo. As the left column is
-rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_. 
-
-First, to remove the login menu, we just need to comment out the display of the
-login component such as follows : ::
-
-  class MyHTMLPageHeader(HTMLPageHeader):
-    
-      def main_header(self, view):
-          """build the top menu with authentification info and the rql box"""
-          self.w(u'<table id="header"><tr>\n')
-          self.w(u'<td id="firstcolumn">')
-          self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
-          self.w(u'</td>\n')
-          # appliname and breadcrumbs
-          self.w(u'<td id="headtext">')
-          comp = self.vreg.select_component('appliname', self.req, self.rset)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w)
-          comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w, view=view)
-          self.w(u'</td>')
-          # logged user and help
-          #self.w(u'<td>\n')
-          #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
-          #comp.dispatch(w=self.w)
-          #self.w(u'</td><td>')
-
-          self.w(u'<td>')
-          helpcomp = self.vreg.select_component('help', self.req, self.rset)
-          if helpcomp: # may not be available if Card is not defined in the schema
-              helpcomp.dispatch(w=self.w)
-          self.w(u'</td>')
-          # lastcolumn
-          self.w(u'<td id="lastcolumn">')
-          self.w(u'</td>\n')
-          self.w(u'</tr></table>\n')
-          self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
-                        title=False, message=False)
-
-
-
-.. image:: images/lax-book.06-header-no-login.en.png
-
-Let's now move the search box in the top-right header area. To do so, we will
-first create a method to get the search box display and insert it in the header
-table.
-
-::
-
- from ginco.web.views.basetemplates import HTMLPageHeader
- class MyHTMLPageHeader(HTMLPageHeader):
-    def main_header(self, view):
-        """build the top menu with authentification info and the rql box"""
-        self.w(u'<table id="header"><tr>\n')
-        self.w(u'<td id="firstcolumn">')
-        self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
-        self.w(u'</td>\n')
-        # appliname and breadcrumbs
-        self.w(u'<td id="headtext">')
-        comp = self.vreg.select_component('appliname', self.req, self.rset)
-        if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w)
-        comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
-        if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w, view=view)
-        self.w(u'</td>')
-        
-        # logged user and help
-        #self.w(u'<td>\n')
-        #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
-        #comp.dispatch(w=self.w)
-        #self.w(u'</td><td>')
-        
-        # search box
-        self.w(u'<td>')
-        self.get_searchbox(view, 'left')
-        self.w(u'</td>')
-
-        self.w(u'<td>')
-        helpcomp = self.vreg.select_component('help', self.req, self.rset)
-        if helpcomp: # may not be available if Card is not defined in the schema
-            helpcomp.dispatch(w=self.w)
-        self.w(u'</td>')
-        # lastcolumn
-        self.w(u'<td id="lastcolumn">')
-        self.w(u'</td>\n')
-        self.w(u'</tr></table>\n')
-        self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
-                      title=False, message=False)
-
-    def get_searchbox(self, view, context):
-        boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
-                                                 view=view, context=context))
-        if boxes:
-            for box in boxes:
-                if box.id == 'search_box':
-                    box.dispatch(w=self.w, view=view)
-
- 
-
-
-HTMLPageFooter
---------------
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in : 
-::
-
-  form ginco.web.views.basetemplates import HTMLPageFooter
-  class MyHTMLPageFooter(HTMLPageFooter):
-      def call(self, **kwargs):
-          self.w(u'<div class="footer">')
-          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
-          self.w(u'</div>')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
----------------
-.. _TheMainTemplate:
-
-TheMainTemplate is responsible for the general layout of the entire application. 
-It defines the template of ``id = main`` that is used by the application.
-
-The default main template (`cubicweb.web.views.basetemplates.TheMainTemplate`)
-builds the page based on the following pattern:
-
-.. image:: images/main_template_layout.png
-
-The rectangle containing `view.dispatch()` represents the area where the content
-view has to be displayed. The others represents sub-templates called to complete
-the page. A default implementation of those is provided in 
-`cubicweb.views.basetemplates`. You can, of course, overload those sub-templates
-to implement your own customization of the HTML page.
-
-We can also control certain aspects of the main template thanks to the following
-forms parameters:
-
-* `__notemplate`, if present (whatever the value assigned), only the content view
-  is returned
-* `__force_display`, if present and its value is not null, no navigation 
-  whatever the number of entities to display
-* `__method`, if the result set to render contains only one entity and this 
-  parameter is set, it refers to a method to call on the entity by passing it
-  the dictionnary of the forms parameters, before going the classic way (through
-  step 1 and 2 described juste above)
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
-
-
-CSS changes
------------
-
-We cannot modify the order in which the application is reading the CSS. In
-the case we want to create new CSS style, the best is to define it a in a new
-CSS located under ``myapp/data/``.
-
-
-.. [TRANSLATE ME FROM FRENCH]
-.. 03-XX-external_resources.fr.txt
-
-[TODO]
-Add login menu in left column
-
-
-[WRITE ME]
-
-* customize MainTemplate and show that everything in the user
-  interface can be changed
-
-[TODO]
-Rajouter une section pour definir la terminologie utilisee.
-Dans ginco-doc rajouter une section pour cubciweb-ctl shell ou
-on liste les commandes dispos.
--- a/doc/book/en/B1070-ui-components.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Others web interface components
-===============================
-
-Actions
--------
-XXXFILLME
-
-Component, VComponent
----------------------
-XXXFILLME
-
-EProperty
----------
-XXXFILLME
--- a/doc/book/en/B1080-ajax-json.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-AJAX
-====
-JSON bla  bla
-XXX FILLME
-
-
-Le contrôleur 'json'
---------------------
-XXX FILLME
-
-
-API Javascript
---------------
-XXX FILLME
--- a/doc/book/en/B1090-internationalization.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _internationalization:
-
-
-Internationalization
-====================
-
-Cubicweb fully supports the internalization of it's content and interface.
-
-Cubicweb's interface internationalization is based on the translation project `GNU gettext`_.
-
-.. _`GNU gettext`: http://www.gnu.org/software/gettext/
-
-Cubicweb' internalization involves two steps:
-
-* in your Python code and cubicweb-tal templates : mark translatable strings
-
-* in your application : handle the translation catalog
- 
-String internationalization
----------------------------
-
-In the Python code and cubicweb-tal templates translatable strings can be
-marked in one of the following ways :
-
- * by using the *built-in* function `_` ::
-
-     class PrimaryView(EntityView):
-         """the full view of an non final entity"""
-         id = 'primary'
-         title = _('primary')
-
-  OR
-
- * by using the equivalent request's method ::
-   
-     class NoResultView(EmptyRsetView):
-         """default view when no result has been found"""
-         id = 'noresult'
-    
-         def call(self, **kwargs):
-             self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
-                 % self.req._('No result matching query'))
-
-The goal of the *built-in* function `_` is only **to mark the
-translatable strings**, it will only return the string to translate
-it-self, but not its translation.
-
-In the other hand the request's method `self.req._` is meant to retrieve the
-proper translation of translation strings in the requested language.
-
-Translations in cubicweb-tal template can also be done with TAL tags
-`i18n:content` and `i18n:replace`.
-
-.. note::
-
-   We dont need to mark the translation strings of entities/relations
-   used by a particular application's schema as they are generated
-   automatically.
-
-
-Handle the translation catalog 
-------------------------------
-
-Once the internationalization is done in your application's code, you need
-to populate and update the translation catalog. Cubicweb provides the
-following commands for this purpose:
-
-
-* `i18nlibupdate` updates Cubicweb framework's translation
-  catalogs. Unless you work on the framework development, you don't
-  need to use this command.
-
-* `i18nupdate` updates the translation catalogs of *one particular
-  component* (or of all components). After this command is
-  executed you must update the translation files *.po* in the "i18n"
-  directory of your template. This command will of course not remove
-  existing translations still in use.
-
-* `i18ncompile` recompile the translation catalogs of *one particular
-  instance* (or of all instances) after the translation catalogs of
-  its components have been updated. This command is automatically
-  called every time you create or update your instance. The compiled
-  catalogs (*.mo*) are stored in the i18n/<lang>/LC_MESSAGES of
-  application where `lang` is the language identifier ('en' or 'fr'
-  for exemple).
-
-
-Example 
-```````
-You have added and/or modified some translation strings in your application
-(after creating a new view or modifying the application's schema for exemple). 
-To update the translation catalogs you need to do:
- 
-1. `cubicweb-ctl i18nupdate <component>`
-2. Edit the <component>/xxx.po  files and add missing translations (empty `msgstr`) 
-3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18ncompile <myapplication>`
-
--- a/doc/book/en/B1100-online-doc.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Online documentation system
-===========================
-
-[WRITE ME]
-
-* describe the on-line documentation system
-
--- a/doc/book/en/B1110-embedding-external-page.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Embedding external pages
-========================
-
-[WRITE ME]
-
-* including external content
-
--- a/doc/book/en/B1120-urlrewrite.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-URL Rewriting
-=============
-
-
-[WRITE ME]
-
-* show how urls are mapped to selections and views and explain URLRewriting 
-
--- a/doc/book/en/B1130-css.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-CSS changes
-===========
-
-XXX FIXME explain CSS used by cubciweb
-
-We cannot modify the order in which the application is reading the CSS. In
-the case we want to create new CSS style, the best is to define it a in a new
-CSS located under ``myapp/data/`` and use those new styles while writing
-customized views and templates.
-
-If you want to modify an existing CSS styling property, you will have to use
-``!important`` declaration to override the existing property. The application
-apply a higher priority on the default CSS and you can not change that. 
-Customized CSS will not be read first.
--- a/doc/book/en/B2-repository-customization.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Repository customization
-++++++++++++++++++++++++
-.. toctree::
-   :maxdepth: 1
-
-   B2010-sessions.en.txt
-   B2020-hooks.en.txt
-   B2030-notifications.en.txt
-   B2040-repository-operations.en.txt
-   B2050-google-appengine.en.txt
-   B2060-repository-tasks.en.txt
-
-
--- a/doc/book/en/B2010-sessions.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Sessions
-========
-
-[WRITE ME]
-
-* authentication and management of sessions
-
--- a/doc/book/en/B2020-hooks.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _hooks:
-
-Hooks
-=====
-
-XXX FILLME
-
-*Hooks* are executed before or after updating an entity or a relation in the
-repository.
-
-Their prototypes are as follows: 
-    
-    * after_add_entity     (session, entity)
-    * after_update_entity  (session, entity)
-    * after_delete_entity  (session, eid)
-    * before_add_entity    (session, entity)
-    * before_update_entity (session, entity)
-    * before_delete_entity (session, eid)
-
-    * after_add_relation     (session, fromeid, rtype, toeid)
-    * after_delete_relation  (session, fromeid, rtype, toeid)
-    * before_add_relation    (session, fromeid, rtype, toeid)
-    * before_delete_relation (session, fromeid, rtype, toeid)
-    
-    * server_startup
-    * server_shutdown
-    
-    * session_open
-    * session_close
-
--- a/doc/book/en/B2030-notifications.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Notifications management
-========================
-
-XXX FILLME
--- a/doc/book/en/B2040-repository-operations.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Repository operations
-======================
-
-[WRITE ME]
-
-* repository operations
-
--- a/doc/book/en/B2050-google-appengine.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _gaecontents:
-
-==========================
-Google AppEngine Datastore
-==========================
-
-
-.. include:: B2051-intro.en.txt
-.. include:: B2052-install.en.txt
--- a/doc/book/en/B2051-intro.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Introduction
-============
-
-What is  `Google AppEngine` ?
-------------------------------
-
-`Google AppEngine`_ is provided with a partial port of the `Django`
-framework, but Google stated at Google IO 2008 that it would not
-support a specific Python web framework and that all
-community-supported frameworks would be more than welcome [1]_. 
-
-Therefore `Logilab`_ ported `CubicWeb` to run on top of `Google AppEngine`'s
-datastore.
-
-.. _`Google AppEngine`: http://code.google.com/appengine/docs/whatisgoogleappengine.html
-.. _Logilab: http://www.logilab.fr/
-.. [1] for more on this matter, read our blog at http://www.logilab.org/blogentry/5216
-
-
-Essentials
-----------
-
-To build a web application for `Google App Engine`'s datastore, you
-need to have a good understanding of the main concepts of our 
-`CubicWeb` framework.
-
-The main concepts are:
-
-  - *schema*
-
-  - *query language*
-
-  - *result set*
-
-  - *views*
-
-  - *generated user interface*
-
-  - *cube*
-
-You can find detailled explanation of those concepts in :ref:`TermsVocabulary`.
-
--- a/doc/book/en/B2052-install.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,219 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _installation:
-
-Installation
-============
-
-Download the source
--------------------
-
-- The `Google AppEngine SDK` can be downloaded from:
-  http://code.google.com/appengine/downloads.html
-
-
-Please follow instructions on how to install `CubicWeb` framework
-(:ref:`CubicWebInstallation`). 
-
-Once ``cubicweb-ctl`` is installed, then you can create a Google
-App Engine extension of our framework by running the command ::
-
-   cubicweb-ctl newgapp <myapp>
-
-This will create a directory containing ::
- 
-   `-- myapp/
-       |-- app.conf
-       |-- app.yaml
-       |-- bin/
-       |    `-- laxctl
-       |-- boostrap_cubes
-       |-- cubes/
-       |    |-- addressbook/
-       |    ..
-       |    |-- comment
-       |    ..
-       |    `-- zone/
-       |-- cubicweb/
-       |-- custom.py
-       |-- cw-cubes/
-       |-- dateutil/
-       |-- docutils/
-       |-- fckeditor/
-       |-- i18n/
-       |-- index.yaml
-       |-- loader.py
-       |-- logilab/
-       |-- main.py
-       |-- migration.py
-       |-- mx/
-       |-- roman.py
-       |-- rql/
-       |-- schema.py
-       |-- simplejson/
-       |-- tools/
-       |-- views.py
-       |-- vobject/
-       |-- yams/
-       `-- yapps/
-
-  
-This skeleton directory is a working `AppEngine` application. You will
-recognize the files ``app.yaml`` and ``main.py``. All the rest is the
-`CubicWeb` framework and its third-party libraries. You will notice that 
-the directory ``cubes`` is a library of reusable cubes.
-
-The main directories that you should know about are:
-
-  - ``cubes`` : this is a library of reusable yams cubes. To use 
-    those cubes you will list them in the variable 
-    `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`. 
-  - [WHICH OTHER ONES SHOULD BE LISTED HERE?]
-
-Dependencies
-------------
-
-Before starting anything, please make sure the following packages are installed:
-  - yaml : by default google appengine is providing yaml; make sure you can
-    import it. We recommend you create a symbolic link yaml instead of installing 
-    and using python-yaml:
-    yaml -> full/path/to/google_appengine/lib/yaml/lib/yaml/
-  - gettext
-
-Setup
------
-
-Once you executed ``cubicweb-ctl newgapp <myapp>``, you can use that ``myapp/`` 
-as an application directory and do as follows.
-
-This installation directory provides a configuration for an instance of `CubicWeb`
-ported for Google App Engine. It is installed with its own command ``laxctl`` 
-which is a port of the command tool ``cubicweb-ctl`` originally developped for 
-`CubicWeb`.
-
-You can have the details of available commands by running ::
-
-   $ python myapp/bin/laxctl --help
-
-
-Generating translation files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-`CubicWeb` is fully internationalized. Translation catalogs are found in
-``myapp/i18n``. To compile the translation files, use the `gettext` tools
-or the ``laxctl`` command ::
-
-  $ python myapp/bin/laxctl i18nupdate 
-  $ python myapp/bin/laxctl i18ncompile 
-
-Ignore the errors that print "No translation file found for domain
-'erudi'". They disappear after the first run of i18ncompile.
-
-.. note:: The command  myapp/bin/laxctl i18nupdate needs to be executed
-   only if your application is using cubes from ginco-apps.
-   Otherwise, please skip it.
-
-You will never need to add new entries in the translation catalog. Instead we would
-recommand you to use ``self.req._("msgId")`` in your application code
-to flag new message id to add to the catalog, where ``_`` refers to
-xgettext that is used to collect new strings to translate. 
-While running ``laxctl i18nupdate``, new string will be added to the catalogs.
-
-Generating the data directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In order to generate the ``myapp/data`` directory that holds the static
-files like stylesheets and icons, you need to run the command::
-
-  $ python myapp/bin/laxctl populatedata
-
-Generating the schema diagram
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-There is a view named ``schema`` that displays a diagram of the
-entity-relationship graph defined by the schema. This diagram has to
-be generated from the command line::
-
-  $ python myapp/bin/laxctl genschema
-
-Application configuration
--------------------------
-
-Authentication
-~~~~~~~~~~~~~~
-
-You have the option of using or not google authentication for your application.
-This has to be define in ``app.conf`` and ``app.yaml``.
-
-In ``app.conf`` modify the following variable::
- 
-  # does this application rely on google authentication service or not.
-  use-google-auth=no
- 
-In ``app.yaml`` comment the `login: required` set by default in the handler::
-
-  - url: .*
-  script: main.py
-  # comment the line below to allow anonymous access or if you don't want to use
-  # google authentication service
-  #login: required
-
-
-
-
-Quickstart : launch the application
------------------------------------
-
-On Mac OS X platforms, drag that directory on the
-`GoogleAppEngineLauncher`.
-
-On Unix and Windows platforms, run it with the dev_appserver::
-
-  $ python /path/to/google_appengine/dev_appserver.py /path/to/myapp/
-
-Once the local server is started, visit `http://MYAPP_URL/_load <http://localhost:8080/_load>`_ and sign in as administrator. 
-This will initialize the repository and enable you to log in into 
-the application and continue the installation.
-
-You should be redirected to a page displaying a message `content initialized`.
-
-Initialize the datastore
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-You, then, want to visit  `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
-If you selected not  to use google authentication, you will be prompted to a 
-login form where you should initialize the administrator login (recommended
-to use admin/admin at first). You will then be redirected to a page providing
-you the value to provide to ``./bin/laxctl --cookie``.
-
-If you choosed to use google authentication, then you will not need to set up
-and administrator login but you will get the cookie value as well.
-
-This cookie values needs to be provided to ``laxctl`` commands
-in order to handle datastore administration requests.
-
-.. image:: images/lax-book.02-cookie-values.en.png
-   :alt: displaying the detailed view of the cookie values returned
-
-
-.. note:: In case you are not redirected to a page providing the 
-   option --cookie value, please visit one more time  
-   `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
-
-Once, you have this value, then return to the shell and execute ::
- 
-  $ python myapp/bin/laxctl db-init --cookie='dev_appserver_login=test@example.com:True; __session=7bbe973a6705bc5773a640f8cf4326cc' localhost:8080
-
-.. note:: In the case you are not using google authentication, the value returned
-   by `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ 
-   will look like :
-   --cookie='__session=2b45d1a9c36c03d2a30cedb04bc37b6d'
-
-Log out by clicking in the menu at the top right corner
-and restart browsing from `http://MYAPP_URL/ <http://localhost:8080>`_ 
-as a normal user.
-
-Unless you did something to change it, http://MYAPP_URL should be
-http://localhost:8080/
-
-
--- a/doc/book/en/B2060-repository-tasks.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tasks
-=========
-
-[WRITE ME]
-
-* repository tasks
-
--- a/doc/book/en/B3-test.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tests
-+++++
-.. toctree::
-   :maxdepth: 1
-
-   B3010-tests.en.txt
-
--- a/doc/book/en/B3010-tests.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tests
-=====
-
-Unit tests
-----------
-
-`CubicWeb` framework provides essentially two Python test classes in the
-module `cubicweb.devtools.apptest`:
-
-* `EnvBasedTC`, to simulate a complete environment (web + repository)
-* `RepositoryBasedTC`, to simulate a repository environment only
-
-Thos two classes almost have the same interface and offers numerous methods to
-write tests rapidely and efficiently.
-
-XXX FILLME describe API
-
-In most of the cases, you will inherit `EnvBasedTC` to write Unittest or
-functional tests for your entities, views, hooks, etc...
-
-Email notifications tests
--------------------------
-When running tests potentially generated e-mails are not really
-sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`. 
-This list is reset at each test *setUp* (by the setUp of classes `EnvBasedTC`
-and `RepositoryBasedTC`).
-
-	
-You can test your notifications by analyzing the contents of this list, which
-contains objects with two attributes:
-* `recipients`, the list of recipients
-* `msg`, object email.Message
-
-Automatic testing
------------------
-XXXFILLME
--- a/doc/book/en/B4-advanced.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Advanced
-++++++++
-.. toctree::
-   :maxdepth: 1
-
-   B4010-configuration.en.txt
-   B4020-dbapi.en.txt
-   B4030-registry.en.txt
-   B4040-rss-xml.en.txt 
--- a/doc/book/en/B4010-configuration.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Configuration
--------------
-
-[WRITE ME]
-
-* the config object. adding configuration option
-
--- a/doc/book/en/B4020-dbapi.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-DB-API
-=========
-
-[WRITE ME]
-
-* direct connection to the repository
-
--- a/doc/book/en/B4030-registry.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-The Registry
-------------
-
-[WRITE ME]
-
-* talk about the vreg singleton, appobjects, registration and selection
-
-
-Details of the recording process
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At startup, the `registry` or registers base, inspects a number of directories
-looking for compatible classes definition. After a recording process, the objects
-are assigned to registers so that they can be selected dynamically while the
-application is running.
-
-The base class of those objects is `AppRsetObject` (module `cubicweb.common.appobject`).
-
-XXX registers example
-XXX actual details of the recording process!
-
-Runtime objects selection
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-XXX tell why it's a cw foundation!
-
-Application objects are stored in the registry using a two level hierarchy :
-
-  object's `__registry__` : object's `id` : [list of app objects]
-
-The following rules are applied to select an object given a register and an id and an input context:
-* each object has a selector which may be built from a set of basic (or not :)
-  
-  selectors using `chainall` or `chainfirst` combinators
-
-* a selector return a score >= 0
-* a score of 0 means the objects can't be applied to the input context
-* the object with the greatest score is selected. If multiple objects have an
-  identical score, one of them is selected randomly (this is usually a bug)
-
-The object's selector is the `__select__` class method on the object's class.
-
-The score is used to choose the most pertinent objects where there are more than
-one selectable object. For instance, if you're selecting the primary
-(eg `id = 'primary'`) view (eg `__registry__ = 'view'`) for a result set containing
-a `Card` entity, 2 objects will probably be selectable:
-
-* the default primary view (`accepts = 'Any'`)
-* the specific `Card` primary view (`accepts = 'Card'`)
-
-This is because primary views are using the `accept_selector` which is considering the
-`accepts` class attribute of the object's class. Other primary views specific to other
-entity types won't be selectable in this case. And among selectable objects, the
-accept selector will return a higher score the the second view since it's more
-specific, so it will be selected as expected.
-
-Usually, you won't define it directly but by defining the `__selectors__` tuple
-on the class, with ::
-
-  __selectors__ = (sel1, sel2)
-
-which is equivalent to ::
-
-  __select__ = classmethod(chainall(sel1, sel2))
-
-The former is prefered since it's shorter and it's ease overriding in
-subclasses (you have access to sub-selectors instead of the wrapping function).
-
-:chainall(selectors...): if one selector return 0, return 0, else return the sum of scores
-
-:chainfirst(selectors...): return the score of the first selector which has a non zero score
-
-XXX describe standard selector (link to generated api doc!)
-
-Example
-````````
-
-Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe
-vers les entrées de ce blog, non vers l'entité blog elle-même.
-
-L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité
-qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation
-par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce
-qu'on veut.
-
-La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient
-plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler
-la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel
-à limited_rql)
-
-Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité,
-l'autre pour un rset qui contient plusieurs entité.
-
-Donc... On a déja dans web/views/boxes.py la classe RSSIconBox qui fonctionne. Son
-sélecteur ::
-
-  class RSSIconBox(ExtResourcesBoxTemplate):
-    """just display the RSS icon on uniform result set"""
-    __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (nfentity_selector,)
-
-
-indique qu'il prend en compte :
-
-* les conditions d'apparition de la boite (faut remonter dans les classes parentes
-  pour voir le détail)
-* nfentity_selector, qui filtre sur des rset contenant une liste d'entité non finale
-
-ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique
-pour le 1er cas ::
-
-  class EntityRSSIconBox(RSSIconBox):
-    """just display the RSS icon on uniform result set for a single entity"""
-    __selectors__ = RSSIconBox.__selectors__ + (onelinerset_selector,)
-
-
-Ici, on ajoute onelinerset_selector, qui filtre sur des rset de taille 1. Il faut
-savoir que quand on chaine des selecteurs, le score final est la somme des scores
-renvoyés par chaque sélecteur (sauf si l'un renvoie zéro, auquel cas l'objet est
-non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector
-rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la
-classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un
-score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée.
-
-Voili voilou, il reste donc pour finir tout ça :
-
-* à définir le contenu de la méthode call de EntityRSSIconBox
-* fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur
-  AnyEntity
-* surcharger cette methode dans blog.Blog
-
-
-When to use selectors?
-```````````````````````
-
-Il faut utiliser les sélecteurs pour faire des choses différentes en
-fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la
-nature de `self.rset` dans un objet, il faut très sérieusement se
-poser la question s'il ne vaut pas mieux avoir deux objets différent
-avec des sélecteurs approprié.
-
-If this is so fundamental, why don't I see them more often?
-```````````````````````````````````````````````````````````
-
-Because you're usually using base classes which are hiding the plumbing
-of __registry__ (almost always), id (often when using "standard" object),
-register and selector.
--- a/doc/book/en/C000-administration.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-==========================
-Part III - Administration
-==========================
-
-This part is for installation and administration of the `CubicWeb` framework and
-applications based on that framework.
- 
-.. toctree::
-   :maxdepth: 1
-
-   C010-setup.en.txt
-   C020-site-config.en.txt
-   C030-instance-config.en.txt
-   C040-rql.en.txt
-   C041-rql-tutorial.en.txt
--- a/doc/book/en/C010-setup.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _MiseEnPlaceEnv:
-
-===================================================
-Installation and set-up of a `CubicWeb` environment
-===================================================
-
-.. include:: C011-installation.en.txt
-.. include:: C012-create-instance.en.txt
-.. include:: C013-cubicweb-ctl.en.txt
-
--- a/doc/book/en/C011-installation.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _CubicWebInstallation:
-
-Installation
-============
-
-Installation of `Cubicweb` and its dependencies
------------------------------------------------
-
-`CubicWeb` is packaged for Debian and Ubuntu, but can be installed from source
-using a tarball or the Mercurial version control system.
-
-Debian and Ubuntu packages
-```````````````````````````
-Depending on the distribution you are using, add the appropriate line to your list
-of sources (for example by editing ``/etc/apt/sources.list``).
-
-For Debian Lenny::
-
-  deb http://ftp.logilab.org/dists/ lenny/
-
-For Debian Sid::
-
-  deb http://ftp.logilab.org/dists/ sid/
-
-For Ubuntu Hardy::
-
-  deb http://ftp.logilab.org/dists/ hardy/
-
-
-You can now install the required packages with the following command: ::
-
-  apt-get update 
-  apt-get install cubicweb
-  apt-get install cubicweb-dev
-
-
-This is it!
-
-Install from source
-```````````````````
-
-You can download the archive containing the sources from our `ftp site`_ at: ::
-
-  http://ftp.logilab.org/pub/cubicweb/
-
-.. _`ftp site`: http://ftp.logilab.org/pub/cubicweb/
-
-or keep up to date with on-going development by using Mercurial and its forest
-extension::
-
-  hg fclone http://www.logilab.org/hg/forests/cubicweb
-
-See :ref:`MercurialPresentation` for more details about Mercurial.
-
-Postgres installation
-`````````````````````
-
-Please refer to the `Postgresql project online documentation`_.
-
-.. _`Postgresql project online documentation`: http://www.postgresql.org/
-
-You need to install the three following packages: `postgres-8.3`,
-`postgres-contrib-8.3` and `postgresql-plpython-8.3`.
-
-
-Then you can install:
-
-* `pyro` if you wish the repository to be accessible through Pyro
-  or if the client and the server are not running on the same machine
-  (in which case the packages will have to be installed on both
-  machines)
-
-* `python-ldap` if you plan to use a LDAP source on the server
-
-.. _ConfigurationEnv:
-
-Environment configuration
--------------------------
-
-If you installed `CubicWeb` by cloning the Mercurial forest, then you
-will need to update the environment variable PYTHONPATH by adding  
-the path to the forest ``cubicweb``:
-
-Add the following lines to either `.bashrc` or `.bash_profile` to configure
-your development environment ::
-  
-  export PYTHONPATH=/full/path/to/cubicweb-forest
-
-If you installed the debian packages, no configuration is required.
-Your new cubes will be placed in `/usr/share/cubicweb/cubes` and
-your applications will be placed in `/etc/cubicweb.d`.
-
-.. _ConfigurationPostgres:
-
-Postgres configuration
-----------------------
-
-* First you have to initialize the database Postgres with the command ``initdb``.
-  ::
-
-    $ initdb -D /path/to/pgsql
-
-  Once initialized, you can launch the database server Postgres 
-  with the command: ::
-  
-    $ postgres -D /path/to/psql
-
-  If you cannot execute this command due to permission issues, please
-  make sure that your username has write access on the database.
-  ::
- 
-    $ chown username /path/to/pgsql
-
-* Create a superuser for `CubicWeb` instance (**root**) ::
-
-    createuser -s username
-
-  Initialize the password of the superuser you just created with 
-  ``su - postgres`` and ``psql``.
-
-  This password will be asked to you later on where you will create an
-  instance with `cubicweb-ctl create`
-
-.. [XXX] Est-ce que ces etapes sont vraiment necessaires?  sand : lors de l'installation de ma bdd cela n'a pas ete fait et il semble que tout aille bien. Doit etre verifie avec les experts.
-
-* installation of plain-text index extension ::
-
-    cat /usr/share/postgresql/8.3/contrib/tsearch2.sql | psql -U username template1
-
-* installation of plpythonu language by default ::
-
-    createlang -U pgadmin plpythonu template1
-
-
-Pyro configuration
-------------------
-
-If you use Pyro, it is required to have a name server Pyro running on your
-network (by default it is identified by a broadcast request).
-
-To do so, you need to :
-
-* launch the server manually before starting cubicweb with `pyro-ns`
-
-* launch the server manually before starting cubicweb as a server with
-  `pyro-nsd start`
-
-* edit the file ``/etc/default/pyro-nsd`` so that the name server pyro
-  will be launched automatically when the machine fire up
-
-
--- a/doc/book/en/C012-create-instance.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Creation of your first instance
-===============================
-
-What is an instance?
---------------------
-
-A `CubicWeb` instance is a directory in ``~/etc/cubicweb.d``
-which enables us to run a web application. An instance is based on
-one or more cubes.
-
-An instance is a container that refers to cubes and configuration 
-parameters for your web application.
-
-We recommand not to define schema, entities or views in the instance
-file system itself but in the cube, in order to maintain re-usability of
-entities and their views. We strongly recommand to develop cubes which
-could be used in other instances (modular approach).
-
-
-What is a cube?
----------------
-
-A cube defines entities, their views, their schemas and workflows
-in an independant directory located in ``/path/to/forest/cubicweb/cubes/``
-for a Mercurial installation or in ``/usr/share/cubicweb/cubes`` for
-a debian package installation.
-
-When an instance is created, you list one or more cubes that your instance
-will use. Using a cube means having the entities defined in your cube's schema
-available in your instance as well as their views and workflows.
-
-.. note::
-   The commands used below are more detailled in the section dedicated to 
-   :ref:`cubicweb-ctl`.
-
-
-Create a cube
--------------
-
-Let's start by creating the cube environment in which we will develop ::
-
-  cd ~/hg
-
-  cubicweb-ctl newcube mycube
-
-  # answer questions 
-  hg init moncube
-  cd mycube
-  hg add .
-  hg ci
-
-If all went well, you should see the cube you just create in the list
-returned by `cubicweb-ctl list` in the section *Available components*,
-and if it is not the case please refer to :ref:`ConfigurationEnv`.
-
-To use a cube, you have to list it in the variable ``__use__``
-of the file ``__pkginfo__.py`` of the instance.
-This variable is used for the instance packaging (dependencies
-handled by system utility tools such as APT) and the usable cubes
-at the time the base is created (import_erschema('MyCube') will
-not properly work otherwise).
-
-Instance creation
------------------
-
-Now that we created our cube, we can create an instance to view our
-application in a web browser. To do so we will use a `all-in-on` 
-configuration to simplify things ::
-
-  cubicweb-ctl create -c all-in-one mycube myinstance
-
-.. note::
-  Please note that we created a new cube for a demo purpose but
-  you could have use an existing cube available in our standard library
-  such as blog or person for example.
-
-A serie of questions will be prompted to you, the default answer is usually
-sufficient. You can anyway modify the configuration later on by editing
-configuration files. When a user/psswd is requested to access the database
-please use the login you create at the time you configured the database
-(:ref:`ConfigurationPostgres`).
-
-It is important to distinguish here the user used to access the database and
-the user used to login to the cubicweb application. When a `CubicWeb` application
-starts, it uses the login/psswd for the database to get the schema and handle
-low level transaction. But, when ``cubicweb-ctl create`` asks for
-a manager login/psswd of `CubicWeb`, it refers to an application user you will
-use during the development to administrate your web application. It will be 
-possible, later on, to create others users for your final web application.
-
-When this command is completed, the definition of your instance is
-located in *~/etc/cubicweb.d/moninstance/*. To launch it, you just type ::
-
-  cubicweb-ctl start -D myinstance
-
-The option `-D` specify the *debug mode* : the instance is not running in
-server mode and does not disconnect from the termnial, which simplifies debugging
-in case the instance is not properly launched. You can see how it looks by
-visiting the URL `http://localhost:8080` (the port number depends of your 
-configuration). To login, please use the cubicweb administrator login/psswd you 
-defined when you created the instance.
-
-To shutdown the instance, Crtl-C in the terminal window is enough.
-If you did not use the option `-D`, then type ::
-
-  cubicweb-ctl stop myinstance
-
-This is it! All is settled down to start developping your data model...
-
-
-Usage of `cubicweb-liveserver`
-``````````````````````````````
-
-To quickly test a new cube, you can also use the script `cubicweb-liveserver`
-which allows to create an application in memory (use of SQLite database by 
-default) and make it accessible through a web server ::
-
-  cubicweb-ctl live-server mycube
-
-or by using an existing database (SQLite or Postgres)::
-
-  cubicweb-ctl live-server -s myfile_sources mycube
-
--- a/doc/book/en/C013-cubicweb-ctl.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubicweb-ctl:
-
-``cubicweb-ctl`` tool
-=====================
-
-`cubicweb-ctl` is the swiss knife to manage `CubicWeb` instances.
-The general syntax is ::
-
-  cubicweb-ctl <command> [options command] <arguments commands>
-
-To view available commands ::
-
-  cubicweb-ctl
-  cubicweb-ctl --help
-
-Please note that the commands available depends on the `CubicWeb` packages
-and cubes that have been installed.
-
-To view the help menu on specific command ::
-
-  cubicweb-ctl <command> --help
-
-Command to create a cube
-------------------------
-
-* ``newcube``, create a new cube on the file system based on the name
-  given in the parameters. This command create a cube from an application
-  skeleton that includes default files required for debian packaging.
-  
-
-Command to create an instance
------------------------------
-* ``create``, creates the files for the instance configuration
-* ``db-create``, creates the system database of an instance (tables and
-  extensions only)
-* ``db-init``, initializes the system database of an instance
-  (schema, groups, users, workflows...)
-
-By default, those three commandes are encapsulated in ``create`` so
-that they can be executed consecutively.
-
-Command to create an instance for Google AppEngine datastore source
--------------------------------------------------------------------
-* ``newgapp``, creates the configuration files for an instance
-
-This command needs to be followed by the commands responsible for
-the database initialization. As those are specific to the `datastore`,
-specific Google AppEgine database, they are not available for now
-in cubicweb-ctl, but they are available in the instance created.
-
-For more details, please see :ref:`gaecontents` .
-
-Commands to launch instance
----------------------------
-* ``start``, starts one or more or all instances
-* ``stop``, stops one or more or all instances
-* ``restart``, restarts one or more or all instances
-* ``status``, returns the status of the instance
-
-Commands to maintain instances
-------------------------------
-* ``upgrade``, launches the existing instances migration when a new version
-  of `CubicWeb` or the cubes installed is available
-* ``shell``, opens a migration shell for manual maintenance of the instance
-  (see :ref:`cubicweb-ctl-shell` for more details)
-* ``db-dump``, creates a dump of the system database
-* ``db-restore``, restores a dump of the system database
-* ``db-check``, checks data integrity of an instance. If the automatic correction
-  is activated, it is recommanded to create a dump before this operation.
-* ``schema-sync``, synchronizes the persistent schema of an instance with
-  the application schema. It is recommanded to create a dump before this operation.
-
-Commands to maintain i18n catalogs
-----------------------------------
-* ``i18nlibupdate``, regenerates messages catalogs of the `CubicWeb` library
-* ``i18nupdate``, regenerates the messages catalogs of a cube
-* ``i18ncompile``, recompiles the messages catalogs of an instance. 
-  This is automatically done while upgrading.
-
-Cf :ref:`Internationalisation`.
-
-Other commands
---------------
-* ``list``, provides a list of the available configuration, cubes
-  and instances.
-* ``delete``, deletes an instance (configuration files and database)
-
-
-.. _cubicweb-ctl-shell:
-
-``cubicweb-ctl shell`` addon
-----------------------------
-
-This migration shell provides an interactive interface to all
-the migrations functions described in the chapter :ref:`migration`.
-
-Usage
-`````
-::
-  
-  ``cubicweb-ctl shell myapp``
-
-Examples
---------
-
-Create an instance from an existing cube
-````````````````````````````````````````
-
-To create an instance from an existing cube, execute the following
-command ::
-
-   cubicweb-ctl create <cube_name> <instance_name>
-
-This command will create the configuration files of an instance in
-``~/etc/cubicweb.d/<instance_name>``.
-The tool ``cubicweb-ctl`` allows you to execute the command ``db-create``
-and ``db-init`` when you run ``create`` so that you can complete an
-instance creation in a single command.
-
-If you decide not to execut those commands while ``cubicweb-ctl create``,
-then you will have to execute them seperately(``cubicweb-ctl db-create``,
-``cubicweb-ctl db-init`` ) otherwise your installation will not be complete
-and you will not be able to launch your instance.
-
-
-Creation of an instance from a new cube
-```````````````````````````````````````
-
-Create first your new cube cube ::
-
-   cubicweb-ctl newcube <mycube>
-
-This will create a new cube in ``/path/to/forest/cubicweb/cubes/<mycube>``
-for a Mercurial forest installation, or in ``/usr/share/cubicweb/cubes``
-for a debian packages installation, and then create an instance as 
-explained just above.
-
-
--- a/doc/book/en/C020-site-config.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-User interface for web site configuration
-=========================================
-
-.. image:: images/lax-book.03-site-config-panel.en.png
-
-This panel allows you to configure the appearance of your application site.
-Six menus are available and we will go through each of them to explain how
-to use them.
-
-Navigation
-~~~~~~~~~~
-This menu provides you a way to adjust some navigation options depending on
-your needs, such as the number of entities to display by page of results.
-Follows the detailled list of available options :
-  
-* navigation.combobox-limit : maximum number of entities to display in related
-  combo box (sample format: 23)
-* navigation.page-size : maximum number of objects displayed by page of results 
-  (sample format: 23)
-* navigation.related-limit : maximum number of related entities to display in 
-  the primary view (sample format: 23)
-* navigation.short-line-size : maximum number of characters in short description
-  (sample format: 23)
-
-UI
-~~
-This menu provides you a way to customize the user interface settings such as
-date format or encoding in the produced html.
-Follows the detailled list of available options :
-
-* ui.date-format : how to format date in the ui ("man strftime" for format description)
-* ui.datetime-format : how to format date and time in the ui ("man strftime" for format
-  description)
-* ui.default-text-format : default text format for rich text fields.
-* ui.encoding : user interface encoding
-* ui.fckeditor :should html fields being edited using fckeditor (a HTML WYSIWYG editor).
-  You should also select text/html as default text format to actually get fckeditor.
-* ui.float-format : how to format float numbers in the ui
-* ui.language : language of the user interface
-* ui.main-template : id of main template used to render pages
-* ui.site-title	: site title, which is displayed right next to the logo in the header
-* ui.time-format : how to format time in the ui ("man strftime" for format description)
-
-
-Actions
-~~~~~~~
-This menu provides a way to configure the context in which you expect the actions
-to be displayed to the user and if you want the action to be visible or not. 
-You must have notice that when you view a list of entities, an action box is 
-available on the left column which display some actions as well as a drop-down 
-menu for more actions. 
-
-The context available are :
-
-* mainactions : actions listed in the left box
-* moreactions : actions listed in the `more` menu of the left box
-* addrelated : add actions listed in the left box
-* useractions : actions listed in the first section of drop-down menu 
-  accessible from the right corner user login link
-* siteactions : actions listed in the second section of drop-down menu
-  accessible from the right corner user login link
-* hidden : select this to hide the specific action
-
-Boxes
-~~~~~
-The application has already a pre-defined set of boxes you can use right away. 
-This configuration section allows you to place those boxes where you want in the
-application interface to customize it. 
-
-The available boxes are :
-
-* actions box : box listing the applicable actions on the displayed data
-
-* boxes_blog_archives_box : box listing the blog archives 
-
-* possible views box : box listing the possible views for the displayed data
-
-* rss box : RSS icon to get displayed data as a RSS thread
-
-* search box : search box
-
-* startup views box : box listing the configuration options available for 
-  the application site, such as `Preferences` and `Site Configuration`
-
-Components
-~~~~~~~~~~
-[WRITE ME]
-
-Contextual components
-~~~~~~~~~~~~~~~~~~~~~
-[WRITE ME]
-
--- a/doc/book/en/C030-instance-config.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Configure an instance
-=====================
-
-While creating an instance, a configuration file is generated in::
-
-    $ (CW_REGISTRY) / <instance> / <configuration name>.conf
-
-For example::
-
-    /etc/cubicweb.d/JPL/all-in-one.conf
-
-It is a simple text file format INI. In the following description,
-each option name is prefixed with its own section and followed by its
-default value if necessary, e.g. "`<section>.<option>` [value]."
-
-
-Configuring the Web server
---------------------------
-:`web.auth-model` [cookie]:
-    authentication mode, cookie or http
-:`web.realm`:
-    realm of the application in http authentication mode
-:`web.http-session-time` [0]:
-    period of inactivity of an HTTP session before it closes automatically.
-    Duration in seconds, 0 meaning no expiration (or more exactly at the
-    closing of the browser client)
-
-:`main.anonymous-user`, `main.anonymous-password`:
-    login and password to use to connect to the RQL server with
-    HTTP anonymous connection. EUser account should exist.
-
-:`main.base-url`:
-    url base site to be used to generate the urls of web pages
-
-Https configuration
-```````````````````
-It is possible to make a site accessible for anonymous http connections
-and https for authenticated users. This requires to
-use apache (for example) for redirection and the variable `main.https-url`
-of configuration file.
-
-:Example:
-
-   For an apache redirection of a site accessible via `http://localhost/demo`
-   and `https://localhost/demo` and actually running on port 8080, it
-   takes to the http:::
-
-     RewriteCond %(REQUEST_URI) ^/demo
-     RewriteRule ^/demo$/demo/
-     RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
-  
-   and for the https:::
-
-     RewriteCond %(REQUEST_URI) ^/ demo
-     RewriteRule ^/demo$/demo/
-     RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]
-
-
-   and we will file in the all-in-one.conf of the instance:::
-
-     base-url = http://localhost/demo
-     https-url = `https://localhost/demo`
-
-Setting up the web
---------------------------------
-:`web.embed-allowed`:
-    regular expression matching sites which could be "embedded" in
-    the site (controllers 'embed')
-:`web.submit-url`:
-    url where the bugs encountered in the application can be mailed to
-
-
-RQL server configuration
-------------------------
-:`main.host`:
-    host name if it can not be detected correctly
-:`main.pid-file`:
-    file where will be written the server pid
-:`main.uid`:
-    user account to use for launching the server when it is
-    root launched by init
-:`main.session-time [30*60]`:
-    timeout of a RQL session
-:`main.query-log-file`:
-    file where all requests RQL executed by the server are written
-
-
-Pyro configuration for the instance
------------------------------------
-Web server side:
-
-:`pyro-client.pyro-application-id`:
-    pyro identifier of RQL server (e.g. the instance name)
-
-RQL server side:
-
-:`pyro-server.pyro-port`:
-    pyro port number. If none is specified, a port is assigned
-    automatically.
-
-RQL and web servers side:
-
-:`pyro-name-server.pyro-ns-host`:
-    hostname hosting pyro server name. If no value is
-    specified, it is located by a request from broadcast
-:`pyro-name-server.pyro-ns-group` [cubicweb]:
-    pyro group in which to save the application
-
-
-Configuring e-mail
-------------------
-RQL and web server side:
-
-:`email.mangle-mails [no]`:
-    indicates whether the email addresses must be displayed as is or
-    transformed
-
-RQL server side:
-
-:`email.smtp-host [mail]`:
-    hostname hosting the SMTP server to use for outgoing mail
-:`email.smtp-port [25]`:
-    SMTP server port to use for outgoing mail
-:`email.sender-name`:
-    name to use for outgoing mail of the application
-:`email.sender-addr`:
-    address for outgoing mail of the application
-:`email.default dest-addrs`:
-    destination addresses by default, if used by the configuration of the
-    dissemination of the model (separated by commas)
-:`email.supervising-addrs`:
-    destination addresses of e-mails of supervision (separated by
-    commas)
-
-
-Configuring logging
--------------------
-:`main.log-threshold`:
-    level of filtering messages (DEBUG, INFO, WARNING, ERROR)
-:`main.log-file`:
-    file to write messages
-
-
-Configuring Eproperties
------------------------
-Other configuration settings are in the form of entities `EProperty`
-in the database. It must be edited via the web interface or by
-RQL queries.
-
-:`ui.encoding`:
-    Character encoding to use for the web
-:`navigation.short-line-size`: # XXX should be in ui
-    number of characters for "short" display
-:`navigation.page-size`:
-    maximum number of entities to show per results page
-:`navigation.related-limit`:
-    number of related entities to show up on primary entity view
-:`navigation.combobox-limit`:
-    number of entities unrelated to show up on the drop-down lists of
-    the sight on an editing entity view
--- a/doc/book/en/C040-rql.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,646 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _RQL:
-
-======================================
-RQL language (Relation Query Language)
-======================================
-
-Introduction
-============
-
-Goals RQL
----------
-
-The goal is to have a language emphasizing the way of browsing
-relations. As such, attributes will be regarded as cases of
-special relations (in terms of implementation, the user
-language not to see virtually no difference between an attribute and a
-relation).
-
-RQL is inspired by SQL but is the highest level. A knowledge of the 
-`CubicWeb` schema defining the application is necessary.
-
-Comparison with existing languages
-----------------------------------
-
-SQL
-```
-RQL builds on the features of SQL but is at a higher level
-(the current implementation of RQL generates SQL). For that it is limited
-to the way of browsing relations and introduces variables. 
-The user does not need to know the model underlying SQL, but the `CubicWeb` 
-schema defining the application.
-
-Versa
-`````
-Should I look in more detail, but here is already some ideas for
-the moment ... Versa_ is the language most similar to what we wanted
-to do, but the model underlying data being RDF, there is some
-number of things such as namespaces or handling of the RDF types which 
-does not interest us. On the functionality level, Versa_ is very comprehensive
-including through many functions of conversion and basic types manipulation,
-which may need to be guided at one time or another. 
-Finally, the syntax is a little esoteric.
-
-Sparql
-``````
-
-The query language most similar to RQL is SPARQL_, defined by the W3C to serve
-for the semantic web. 
-
-
-The different types of queries
-------------------------------
-
-Search (`Any`)
-   This type of query can extract entities and attributes of entities.
-
-Inserting entities (`INSERT`)
-   This type of query is used to insert new entities in the database. It
-   will also create direct relationships entities newly created.
-
-Update entities, relations creation (`SET`)
-   This type of query updates existing entities in the database,
-   or create relations between existing entities.
-
-Deletion of entities or relationship (`DELETE`)
-   This type of query allows for the removal of entities and relations existing
-   in the database.
-
-Search Query
-------------
-
-   [ `DISTINCT`] <entity type> V1 (V2) \ *
-   [ `GROUPBY` V1 (V2) \*] [ `ORDERBY` <orderterms>]
-   [ `WHERE` <restriction>]
-   [ `LIMIT` <value>] [ `OFFSET` <value>]
-
-:entity type:
-   Type of selected variables.
-   The special type `Any` is equivalent to not specify a type.
-:restriction:
-   list of relations to go through whic follow the pattern
-     `V1 relation V2 | <static value>`
-:orderterms:
-   Definition of the selection order: variable or column number followed by
-   sorting method ( `ASC`, `DESC`), ASC is the default.
-:note for grouped queries:
-   For grouped queries (e.g., a clause `GROUPBY`), all
-   selected variables must be aggregated or grouped.
-
-
-
-- *Search for the object of identifier 53*
-   ::
-
-        Any WHERE X
-        X eid 53
-
-- *Search material such as comics, owned by syt and available*
-   ::
-
-        WHERE X Document
-        X occurence_of F, F class C, C name 'Comics'
-        X owned_by U, U login 'syt'
-        X available true
-
-- *Looking for people working for eurocopter interested in training*
-   ::
-
-        Any P WHERE
-        P is Person, P work_for P, S name 'Eurocopter'
-        P interested_by T, T name 'training'
-
-- *Search note less than 10 days old written by jphc or ocy*
-   ::
-
-        Any N WHERE
-        N is Note, N written_on D, D day> (today -10),
-        N written_by P, P name 'jphc' or P name 'ocy'
-
-- *Looking for people interested in training or living in Paris*
-   ::
-
-        Any P WHERE
-        P is Person, (P interested_by T, T name 'training') OR
-        (P city 'Paris')
-
-- *The name and surname of all people*
-   ::
-
-        Any N, P WHERE
-        X is Person, X name N, X first_name P
-
-   Note that the selection of several entities generally force
-   the use of "Any" because the type specification applies otherwise
-   to all the selected variables. We could write here
-   ::
-
-        String N, P WHERE
-        X is Person, X name N, X first_name P
-
-
-Insertion query
----------------
-
-    `INSERT` <entity type> V1 (, <entity type> V2) \ * `:` <assignments>
-    [ `WHERE` <restriction>]
-
-: assignments:
-   list of relations to assign in the form `V1 relationship V2 | <static value>`
-
-The restriction can define variables used in assignments.
-
-Caution, if a restriction is specified, the insertion is done for 
-*each line results returned by the restriction*.
-
-- *Insert a new person named 'foo'*
-   ::
-
-        INSERT Person X: X name 'foo'
-
-- *Insert a new person named 'foo', another called 'nice' and a 'friend' relation
-  between them*
-  ::
-
-        INSERT Person X, Person Y: X name 'foo', Y name 'nice', X friend Y
-
-- *Insert a new person named 'foo' and a 'friend' relation with an existing 
-  person called 'nice'*
-  ::
-
-        INSERT Person X: X name 'foo', X friend  Y WHERE name 'nice'
-
-Update and relation creation queries
-------------------------------------
-    `SET` <assignements>
-    [ `WHERE` <restriction>]
-
-Caution, if a restriction is specified, the update is done *for
-each line results returned by the restriction*.
-
-- *Renaming of the person named 'foo' to 'bar' with the first name changed*
-  ::
-
-        SET X name 'bar', X first_name 'original' where X is Person X name 'foo'
-
-- *Insert a relation of type 'know' between objects linked by 
-  the relation of type 'friend'*
-  ::
-
-        SET X know Y  WHERE X friend Y
-
-
-Deletion query
---------------
-    `DELETE` (<entity type> V) | (V1 relation v2 ),...
-    [ `WHERE` <restriction>]
-
-Caution, if a restriction is specified, the deletion is made *for
-each line results returned by the restriction*.
-
-- *Deletion of the person named 'foo'*
-  ::
-
-        DELETE Person X WHERE X name 'foo'
-
-- *Removal of all relations of type 'friend' from the person named 'foo'*
-  ::
-
-        DELETE X friend Y WHERE X is Person, X name 'foo'
-
-
-Undocumented (yet) type of queries
-----------------------------------
-
-**Limit / offset**
-::
-    
-    Any P ORDERBY N LIMIT 5 OFFSET 10 WHERE P is Person, P firstname N
-
-**Function calls**
-::
-    
-    Any UPPER(N) WHERE P firstname N
-
-**Exists**
-::
-    
-    Any X ORDERBY PN,N
-    WHERE X num N, X version_of P, P name PN, 
-          EXISTS(X in_state S, S name IN ("dev", "ready"))
-          OR EXISTS(T tags X, T name "priority")
-
-**Left outer join**
-::
-
-    Any T,P,V WHERE T is Ticket, T concerns P, T done_in V?
-    
-    
-**Having**
-::
-    
-    Any X GROUPBY X WHERE X knows Y HAVING COUNT(Y) > 10
-
-**Simple union**
-::
-
-    (Any X WHERE X is Person) UNION (Any X WHERE X is Company)
-    
-**Complex union**
-::
-
-     DISTINCT Any W, REF
-        WITH W, REF BEING 
-            (
-	      (Any W, REF WHERE W is Workcase, W ref REF, 
-                                 W concerned_by D, D name "Logilab")
-               UNION 
-              (Any W, REF WHERE W is Workcase, W ref REF, '
-                                W split_into WP, WP name "WP1")
-            )
-
-
-Language definition
-===================
-
-Reserved keywords
------------------
-The keywords are not case sensitive.
-
-::
-
-     DISTINCT, INSERT, SET, DELETE,
-     WHERE, AND, OR, NOT, EXISTS,
-     IN, LIKE, UNION, WITH, BEING,
-     TRUE, FALSE, NULL, TODAY, NOW,
-     LIMIT, OFFSET,
-     HAVING, GROUPBY, ORDERBY, ASC, DESC
-
-
-Variables and Typing
---------------------
-
-With RQL, we do not distinguish between entities and attributes. The
-value of an attribute is considered an entity of a particular type (see
-below), linked to one (real) entity by a relation called the name of
-the attribute.
-
-Entities and values to browse and/or select are represented in
-the query by *variables* that must be written in capital letters.
-
-There is a special type **Any**, referring to a non specific type.
-
-We can restrict the possible types for a variable using the
-special relation **is**.
-The possible type(s) for each variable is derived from the schema
-according to the constraints expressed above and thanks to the relations between
-each variable.
-
-Built-in types
-``````````````
-
-The base types supported are string (between double or single quotes),
-integers or floats (the separator is the'.'), dates and
-boolean. We expect to receive a schema in which types String,
-Int, Float, Date and Boolean are defined.
-
-* `String` (literal: between double or single quotes).
-* `Int`, `Float` (separator being'.').
-* `Date`, `Datetime`, `Time` (literal: string YYYY/MM/DD [hh:mm] or keywords
-   `TODAY` and `NOW`).
-* `Boolean` (keywords `TRUE` and `FALSE`).
-* `Keyword` NULL.
-
-
-Operators
----------
-
-Logical Operators
-```````````````````
-::
-
-     AND, OR, ','
-
-',' is equivalent to 'AND' but with the smallest among the priority
-of logical operators (see :ref:`PriorityOperators`).
-
-Mathematical Operators
-``````````````````````
-::
-
-     +, -, *, /
-
-Comparison operators
-````````````````````
-::
-
-     =, <, <=, >=, > = ~, IN, LIKE
-
-* The operator `=` is the default operator.
-
-* The operator `LIKE` equivalent to `~=` can be used with the
-  special character `%` in a string to indicate that the chain 
-  must start or finish by a prefix/suffix:
-  ::
-
-     Any X WHERE X name =~ 'Th%'
-     Any X WHERE X name LIKE '%lt'
-
-* The operator `IN` provides a list of possible values:
-  ::
-  
-    Any X WHERE X name IN ( 'chauvat', 'fayolle', 'di mascio', 'thenault')
-
-
-XXX nico: A trick <> 'bar' would not it be more convenient than NOT A
-trick 'bar'?
-
-.. _PriorityOperators:
-
-Operators priority
-``````````````````
-
-1. '*', '/'
-
-2. '+', '-'
-
-3. 'and'
-
-4. 'or'
-
-5. ','
-
-
-Advanced Features
------------------
-
-Functions aggregates
-````````````````````
-::
-
-     COUNT, MIN, MAX, AVG, SUM
-
-Functions on string
-```````````````````
-::
-
-     UPPER, LOWER
-
-Optional relations
-``````````````````
-
-* They allow you to select entities related or not to another.
-
-* You must use the `?` behind the variable to specify that the relation
-  toward it is optional:
-
-   - Anomalies of a project attached or not to a version ::
-
-       Any X, V WHERE X concerns P, P eid 42, X corrected_in V?
-
-   - All cards and the project they document if necessary ::
-
-       Any C, P WHERE C is Card, P? documented_by C
-
-
-
-BNF grammar
------------
-
-The terminal elements are in capital letters, non-terminal in lowercase.
-The value of the terminal elements (between quotes) is a Python regular
-expression.
-::
-
-     statement:: = (select | delete | insert | update) ';'
-
-
-     # select specific rules
-     select      ::= 'DISTINCT'? E_TYPE selected_terms restriction? group? sort?
-
-     selected_terms ::= expression ( ',' expression)*
-
-     group       ::= 'GROUPBY' VARIABLE ( ',' VARIABLE)*
-
-     sort        ::= 'ORDERBY' sort_term ( ',' sort_term)*
-
-     sort_term   ::=  VARIABLE sort_method =?
-
-     sort_method ::= 'ASC' | 'DESC'
-
-
-     # delete specific rules
-     delete ::= 'DELETE' (variables_declaration | relations_declaration) restriction?
-
-
-     # insert specific rules
-     insert ::= 'INSERT' variables_declaration ( ':' relations_declaration)? restriction?
-
-
-     # update specific rules
-     update ::= 'SET' relations_declaration restriction
-
-
-     # common rules
-     variables_declaration ::= E_TYPE VARIABLE (',' E_TYPE VARIABLE)*
-
-     relations_declaration ::= simple_relation (',' simple_relation)*
-
-     simple_relation ::= VARIABLE R_TYPE expression
-
-     restriction ::= 'WHERE' relations
-
-     relations   ::= relation (LOGIC_OP relation)*
-                   | '(' relations')'
-
-     relation    ::= 'NOT'? VARIABLE R_TYPE COMP_OP? expression
-                   | 'NOT'? R_TYPE VARIABLE 'IN' '(' expression (',' expression)* ')'
-                   
-     expression  ::= var_or_func_or_const (MATH_OP var_or_func_or_const) *
-                   | '(' expression ')'
-
-     var_or_func_or_const ::= VARIABLE | function | constant
-
-     function    ::= FUNCTION '(' expression ( ',' expression) * ')'
-
-     constant    ::= KEYWORD | STRING | FLOAT | INT
-
-     # tokens
-     LOGIC_OP ::= ',' | 'GOLD' | 'AND'
-     MATH_OP  ::= '+' | '-' | '/' | '*'
-     COMP_OP  ::= '>' | '>=' | '=' | '<=' | '<' | '~=' | 'LIKE'
-
-     FUNCTION ::= 'MIN' | 'MAX' | 'SUM' | 'AVG' | 'COUNT' | 'upper' | 'LOWER'
-
-     VARIABLE ::= '[A-Z][A-Z0-9]*'
-     E_TYPE   ::= '[A-Z]\w*'
-     R_TYPE   ::= '[a-z_]+'
-
-     KEYWORD  ::= 'TRUE' | 'FALSE' | 'NULL' | 'TODAY' | 'NOW'
-     STRING   ::= "'([^'\]|\\.)*'" |'"([^\"]|\\.)*\"'
-     FLOAT    ::= '\d+\.\d*'
-     INT      ::= '\d+'
-
-
-Remarks
--------
-
-Sorting and groups
-``````````````````
-
-- For grouped queries (e.g. with a GROUPBY clause), all
-  selected variables should be grouped.
-
-- To group and/or sort by attributes, we can do: "X,L user U, U
-  login L GROUPBY L, X ORDERBY L"
-
-- If the sorting method (SORT_METHOD) is not specified, then the sorting is
-  ascendant.
-
-Negation
-````````
-
-* A query such as `Document X WHERE NOT X owned_by U` means "the
-  documents have no relation `owned_by`".
-* But the query `Document X WHERE NOT X owned_by U, U login "syt"`
-  means "the documents have no relation `owned_by` with the user
-  syt". They may have a relation "owned_by" with another user.
-
-Identity
-````````
-
-You can use the special relation `identity` in a query to 
-add an identity constraint between two variables. This is equivalent
-to ``is`` in python::
-
-   Any A WHERE A comments B, A identity B
-
-return all objects that comment themselves. The relation
-`identity` is especially useful when defining the rules for securities
-with `RQLExpressions`.
-
-Implementation
-==============
-
-Internal representation (syntactic tree)
-----------------------------------------
-
-The tree research does not contain the selected variables 
-(e.g. there is only what follows "WHERE").
-
-The insertion tree does not contain the variables inserted or relations
-defined on these variables (e.g. there is only what follows "WHERE").
-
-The removal tree does not contain the deleted variables and relations
-(e.g. there is only what follows the "WHERE").
-
-The update tree does not contain the variables and relations updated
-(e.g. there is only what follows the "WHERE").
-
-::
-
-     Select         ((Relationship | And | Gold)?, Group?, Sort?)
-     Insert         (Relations | And | Gold)?
-     Delete         (Relationship | And | Gold)?
-     Update         (Relations | And | Gold)?
-
-     And            ((Relationship | And | Gold), (Relationship | And | Gold))
-     Or             ((Relationship | And | Gold), (Relationship | And | Gold))
-
-     Relationship   ((VariableRef, Comparison))
-
-     Comparison     ((Function | MathExpression | Keyword | Constant | VariableRef) +)
-
-     Function       (())
-     MathExpression ((MathExpression | Keyword | Constant | VariableRef), (MathExpression | Keyword | Constant | VariableRef))
-
-     Group          (VariableRef +)
-     Sort           (SortTerm +)
-     SortTerm       (VariableRef +)
-
-     VariableRef    ()
-     Variable       ()
-     Keyword        ()
-     Constant       ()
-
-
-Remarks
--------
-
-- The current implementation does not support linking two relations of type
-  'is' with a OR. I do not think that the negation is  supported on this type 
-  of relation (XXX FIXME to be confirmed).
-
-- Relations defining the variables must be left to those using them. 
-  For example::
-
-     Point P where P abs X, P ord Y, P value X+Y
-
-   is valid, but::
-
-     Point P where P abs X, P value X+Y, P ord Y
-
-   is not.
-
-RQL logs
---------
-
-You can configure the `CubicWeb` application so that you keep a log
-of the queries executed against your database. To do so, you want to
-edit the configuration file of your application 
-``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the
-variable ``query-log-file``: ::
-
-  # web application query log file
-  query-log-file=/tmp/rql-myapp.log
-
-
-Conclusion
-==========
-
-Limitations
------------
-
-It lacks at the moment:
-
-- COALESCE
-
-- restrictions on groups (HAVING)
-
-and certainly other things ...
-
-A disadvantage is that to use this language we must know the
-format used (with real relation names and entities, not those viewing
-in the user interface). On the other hand, we can not really bypass
-that, and it is the job of a user interface to hide the RQL.
-
-
-Topics
-------
-
-It would be convenient to express the schema matching
-relations (non-recursive rules)::
-
-     Document class Type <-> Document occurence_of Fiche class Type
-     Sheet class Type    <-> Form collection Collection class Type
-    
-Therefore 1. becomes::
-
-     Document X where
-     X class C, C name 'Cartoon'
-     X owned_by U, U login 'syt'
-     X available true
-
-I'm not sure that we should handle this at RQL level ...
-
-There should also be a special relation 'anonymous'.
-
-
-
-.. _Versa: http://uche.ogbuji.net/tech/rdf/versa/
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-
-
-[FIXME] see also RQL documentation in source rql/doc.
--- a/doc/book/en/D000-annex.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-=================
-Part IV - Annexes
-=================
-
-The following chapters are reference material.
- 
-.. toctree::
-   :maxdepth: 1
-
-   D010-faq.en.txt
-   D020-api-reference.en.txt
-   D030-architecture.en.txt
-   D040-modules-stdlib.en.txt
-   D050-modules-cbw-api.en.txt
-   D060-mercurial.en.txt
-   D070-cookbook.en.txt
--- a/doc/book/en/D010-faq.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Frequently Asked Questions
-==========================
-
-[XXX 'copy answer from forum' means reusing text from
-http://groups.google.com/group/google-appengine/browse_frm/thread/c9476925f5f66ec6
-and
-http://groups.google.com/group/google-appengine/browse_frm/thread/d791ce17e2716147/eb078f8cfe8426e0
-and
-http://groups.google.com/group/google-appengine/browse_frm/thread/f48cf6099973aef5/c28cd6934dd72457
-]
-
-* Why does not CubicWeb have a template language ?
-
-  There are enough template languages out there. You can use your
-  preferred template language if you want. [explain how to use a
-  template language]
-
-  `CubicWeb` does not define its own templating language as this was
-  not our goal. Based on our experience, we realized that
-  we could gain productivity by letting designers use design tools
-  and developpers develop without the use of the templating language
-  as an intermediary that could not be anyway efficient for both parties.
-  Python is the templating language that we use in `CubicWeb`, but again,
-  it does not prevent you from using a templating language.
-
-  The reason template languages are not used in this book is that
-  experience has proved us that using pure python was less cumbersome.
-
-* Why do you think using pure python is better than using a template language ?
-
-  Python is an Object Oriented Programming language and as such it
-  already provides a consistent and strong architecture and syntax
-  a templating language would not reach.
-
-  When doing development, you need a real language and template
-  languages are not real languages.
-
-  Using Python enables developing applications for which code is
-  easier to maintain with real functions/classes/contexts
-  without the need of learning a new dialect. By using Python,
-  we use standard OOP techniques and this is a key factor in a
-  robust application.
-
-* Why do you use the GPL license to prevent me from doing X ?
-
-  GPL means that *if* you redistribute your application, you need to
-  redistribute it *and* the changes you made *and* the code _linked_
-  to it under the GPL licence.
-
-  Publishing a web site has nothing to do with redistributing
-  source code. A fair amount of companies use modified GPL code
-  for internal use. And someone could publish a `CubicWeb` component
-  under a BSD licence for others to plug into a GPL framework without
-  any problem. The only thing we are trying to prevent here is someone
-  taking the framework and packaging it as closed source to his own
-  clients.
-
-
-* CubicWeb looks pretty recent. Is it stable ?
-
-  It is constantly evolving, piece by piece.  The framework has
-  evolved over the past seven years and data has been migrated from
-  one schema to the other ever since. There is a well-defined way to
-  handle data and schema migration.
-
-* Why is the RQL query language looking similar to X ?
-
-  It may remind you of SQL but it is higher level than SQL, more like
-  SPARQL. Except that SPARQL did not exist when we started the project.
-  Having SPARQL has a query language has been in our backlog for years.
-
-  That RQL language is what is going to make a difference with django-
-  like frameworks for several reasons.
-
-  1. accessing data is *much* easier with it. One can write complex
-     queries with RQL that would be tedious to define and hard to maintain
-     using an object/filter suite of method calls.
-
-  2. it offers an abstraction layer allowing your applications to run
-     on multiple back-ends. That means not only various SQL backends
-     (postgresql, sqlite, mysql), but also multiple databases at the
-     same time, and also non-SQL data stores like LDAP directories and
-     subversion/mercurial repositories (see the `vcsfile`
-     component). Google App Engine is yet another supported target for
-     RQL.
-
-[copy answer from forum, explain why similar to sparql and why better
-  than django and SQL]
-
-* which ajax library
-
-  [we use jquery and things on top of that]
-
-* `Error while publishing rest text ...`
-
-  While modifying the description of an entity, you get an error message in
-  the application `Error while publishing ...` for Rest text and plain text.
-  The server returns a traceback like as follows ::
-
-      2008-10-06 15:05:08 - (erudi.rest) ERROR: error while publishing ReST text
-      Traceback (most recent call last):
-      File "/home/sandrine/src/blogdemo/ginco/common/rest.py", line 217, in rest_publish
-      File "/usr/lib/python2.5/codecs.py", line 817, in open
-      file = __builtin__.open(filename, mode, buffering)
-      TypeError: __init__() takes at most 3 arguments (4 given)
-
-
-  This can be fixed by applying the patch described in :
-  http://code.google.com/p/googleappengine/issues/detail?id=48
-
-* What are hooks used for?
-
-  Hooks are executed around (actually before or after) events.  The
-  most common events are data creation, update and deletion.  They
-  permit additional constraint checking (those not expressible at the
-  schema level), pre and post computations depending on data
-  movements.
-
-  As such, they are a vital part of the framework.
-
-  Other kinds of hooks, called Operations, are available
-  for execution just before commit.
-
-* When should you define an HTML template rather than define a graphical component?
-
-  An HTML template cannot contain code, hence it is only about static
-  content.  A component is made of code and operations that apply on a
-  well defined context (request, result set). It enables much more
-  dynamic views.
-
-* What is the difference between `AppRsetObject` and `AppObject` ?
-
-  `AppRsetObject` instances are selected on a request and a result
-  set. `AppObject` instances are directly selected by id.
-
-HOW TO
-======
-
-[TO COMPLETE]
-
-
-* How to update a database after a schema modification?
-
-  It depends on what has been modified in the schema.
-
-  * Update of a non final relation.
-
-  * Update of a final relation.
-
-
-* How to create an anonymous user?
-
-  This allows to bypass authentication for your site. In the
-  ``all-in-one.conf`` file of your instance, define the anonymous user
-  as follows ::
-
-    # login of the Erudi user account to use for anonymous user (if you want to
-    # allow anonymous)
-    anonymous-user=anon
-
-    # password of the Erudi user account matching login
-    anonymous-password=anon
-
-  You also must ensure that this `anon` user is a registered user of
-  the DB backend. This could be the admin account (for development
-  purposes, of course).
-
-
-* How to change the application logo?
-
-  There are two ways of changing the logo.
-
-  1. The easiest way to use a different logo is to replace the existing
-     ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
-     By default all application will look for a ``logo.png`` to be
-     rendered in the logo section.
-
-     .. image:: images/lax-book.06-main-template-logo.en.png
-
-  2. In your cube directory, you can specify which file to use for the logo.
-     This is configurable in ``mycube/data/external_resources``: ::
-
-       LOGO = DATADIR/path/to/mylogo.gif
-
-     where DATADIR is ``mycubes/data``.
-
-* How to configure LDAP source?
-
-  Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
-  Configuring an LDAP source is about declaring that source in your
-  instance configuration file such as: ::
-
-    [ldapuser]
-    adapter=ldapuser
-    # ldap host
-    host=myhost
-    # base DN to lookup for usres
-    user-base-dn=ou=People,dc=mydomain,dc=fr
-    # user search scope
-    user-scope=ONELEVEL
-    # classes of user
-    user-classes=top,posixAccount
-    # attribute used as login on authentication
-    user-login-attr=uid
-    # name of a group in which ldap users will be by default
-    user-default-group=users
-    # map from ldap user attributes to erudi attributes
-    user-attrs-map=gecos:email,uid:login
-
-  Any change applied to configuration file requires to restart your
-  application.
-
-* I get NoSelectableObject exceptions: how do I debug selectors ?
-
-  You just need to put the appropriate context manager around view/component
-  selection: ::
-
-    from cubicweb.common.selectors import traced_selection
-    with traced_selection():
-        comp = self.vreg.select_object('contentnavigation', 'wfhistory',
-                                       self.req, rset, context='navcontentbottom')
-
-  This will yield additional WARNINGs, like this: ::
-
-    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
--- a/doc/book/en/D020-api-reference.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-API Reference
-=============
-
-Schema API
-----------
-
-Base Types
-~~~~~~~~~~
-
-Base types are defined as a set in yams.BASE_TYPES that includes:
-`String`, `Int`, `Float`, `Boolean`, `Date`, `Time`, `Datetime`,
-`Interval`, `Password`, `Bytes`.
-
-See `yams' API <http://www.cubicweb.org/doc/en/modindex.html>`_
-
-Constraints
-~~~~~~~~~~~
-
-Constraints are defined in yams.constraints and include:
-`UniqueConstraint`, `SizeConstraint`, `RegexpConstraint`,
-`BoundConstraint`, `IntervalBoundConstraint`,
-`StaticVocabularyConstraint`, `MultipleStaticVocabularyConstraint`.
-
-See `yams' API <http://www.cubicweb.org/doc/en/modindex.html>`_
-
-Views API
----------
-
-See `yams' API <http://www.cubicweb.org/doc/en/modindex.html>`_
-[WRITE ME]
-
--- a/doc/book/en/D030-architecture.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Architecture du serveur
------------------------
-
-.. image:: images/server-class-diagram.png
-
-`Diagramme ArgoUML`_
-
-[FIXME]
-Make a downloadable source of zargo file.
-
-.. _`Diagramme ArgoUML`: cubicweb.zargo
--- a/doc/book/en/D040-modules-stdlib.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-================
-Standard library
-================
-
-:mod:`cubes.addressbook` 
-========================
-
-.. automodule:: cubes.addressbook
-   :members:
-
-:mod:`cubes.basket` 
-========================
-
-.. automodule:: cubes.basket
-   :members:
-
-:mod:`cubes.blog` 
-========================
-
-.. automodule:: cubes.blog
-   :members:
-
-:mod:`cubes.book` 
-========================
-
-.. automodule:: cubes.book
-   :members:
-
-:mod:`cubes.comment` 
-========================
-
-.. automodule:: cubes.comment
-   :members:
-
-:mod:`cubes.company` 
-========================
-
-.. automodule:: cubes.company
-   :members:
-
-
-:mod:`cubes.conference` 
-========================
-
-.. automodule:: cubes.conference
-   :members:
-
-:mod:`cubes.email` 
-========================
-
-.. automodule:: cubes.email
-   :members:
-
-:mod:`cubes.event` 
-========================
-
-.. automodule:: cubes.event
-   :members:
-
-:mod:`cubes.expense` 
-========================
-
-.. automodule:: cubes.expense
-   :members:
-
-
-:mod:`cubes.file` 
-========================
-
-.. automodule:: cubes.file
-   :members:
-
-:mod:`cubes.folder` 
-========================
-
-.. automodule:: cubes.folder
-   :members:
-
-:mod:`cubes.i18ncontent` 
-========================
-
-.. automodule:: cubes.i18ncontent
-   :members:
-
-:mod:`cubes.invoice` 
-========================
-
-.. automodule:: cubes.invoice
-   :members:
-
-:mod:`cubes.keyword` 
-========================
-
-.. automodule:: cubes.keyword
-   :members:
-
-:mod:`cubes.link` 
-========================
-
-.. automodule:: cubes.link
-   :members:
-
-:mod:`cubes.mailinglist` 
-========================
-
-.. automodule:: cubes.mailinglist
-   :members:
-
-:mod:`cubes.person` 
-========================
-
-.. automodule:: cubes.person
-   :members:
-
-:mod:`cubes.shopcart` 
-========================
-
-.. automodule:: cubes.shopcart
-   :members:
-
-:mod:`cubes.skillmat` 
-========================
-
-.. automodule:: cubes.skillmat
-   :members:
-
-:mod:`cubes.tag` 
-========================
-
-.. automodule:: cubes.tag
-   :members:
-
-:mod:`cubes.task` 
-========================
-
-.. automodule:: cubes.task
-   :members:
-
-:mod:`cubes.workcase` 
-========================
-
-.. automodule:: cubes.workcase
-   :members:
-
-:mod:`cubes.workorder` 
-========================
-
-.. automodule:: cubes.workorder
-   :members:
-
-:mod:`cubes.zone` 
-========================
-
-.. automodule:: cubes.zone
-   :members:
-
--- a/doc/book/en/D050-modules-cbw-api.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1961 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-============
-CubicWeb API
-============
-
-:mod:`cubicweb.hercule`
-=======================
-
-.. automodule:: cubicweb.hercule
-   :members:
-
-:mod:`cubicweb.cwctl`
-=====================
-
-.. automodule:: cubicweb.cwctl
-   :members:
-
-:mod:`cubicweb.schema`
-======================
-
-.. automodule:: cubicweb.schema
-   :members:
-
-:mod:`cubicweb.cwconfig`
-========================
-
-.. automodule:: cubicweb.cwconfig
-   :members:
-
-:mod:`cubicweb.schemaviewer`
-============================
-
-.. automodule:: cubicweb.schemaviewer
-   :members:
-
-:mod:`cubicweb._exceptions`
-===========================
-
-.. automodule:: cubicweb._exceptions
-   :members:
-
-:mod:`cubicweb.dbapi`
-=====================
-
-.. automodule:: cubicweb.dbapi
-   :members:
-
-:mod:`cubicweb.toolsutils`
-==========================
-
-.. automodule:: cubicweb.toolsutils
-   :members:
-
-:mod:`cubicweb.cwvreg`
-======================
-
-.. automodule:: cubicweb.cwvreg
-   :members:
-
-:mod:`cubicweb.md5crypt`
-========================
-
-.. automodule:: cubicweb.md5crypt
-   :members:
-
-:mod:`cubicweb.rset`
-====================
-
-.. automodule:: cubicweb.rset
-   :members:
-
-:mod:`cubicweb`
-===============
-
-.. automodule:: cubicweb
-   :members:
-
-:mod:`cubicweb.setup`
-=====================
-
-.. automodule:: cubicweb.setup
-   :members:
-
-:mod:`cubicweb.gettext`
-=======================
-
-.. automodule:: cubicweb.gettext
-   :members:
-
-:mod:`cubicweb.interfaces`
-==========================
-
-.. automodule:: cubicweb.interfaces
-   :members:
-
-:mod:`cubicweb.vregistry`
-=========================
-
-.. automodule:: cubicweb.vregistry
-   :members:
-
-:mod:`cubicweb.web.httpcache`
-=============================
-
-.. automodule:: cubicweb.web.httpcache
-   :members:
-
-:mod:`cubicweb.web.webconfig`
-=============================
-
-.. automodule:: cubicweb.web.webconfig
-   :members:
-
-:mod:`cubicweb.web.request`
-===========================
-
-.. automodule:: cubicweb.web.request
-   :members:
-
-:mod:`cubicweb.web._exceptions`
-===============================
-
-.. automodule:: cubicweb.web._exceptions
-   :members:
-
-:mod:`cubicweb.web.webctl`
-==========================
-
-.. automodule:: cubicweb.web.webctl
-   :members:
-
-:mod:`cubicweb.web.application`
-===============================
-
-.. automodule:: cubicweb.web.application
-   :members:
-
-:mod:`cubicweb.web.controller`
-==============================
-
-.. automodule:: cubicweb.web.controller
-   :members:
-
-:mod:`cubicweb.web.widgets`
-===========================
-
-.. automodule:: cubicweb.web.widgets
-   :members:
-
-:mod:`cubicweb.web.htmlwidgets`
-===============================
-
-.. automodule:: cubicweb.web.htmlwidgets
-   :members:
-
-:mod:`cubicweb.web`
-===================
-
-.. automodule:: cubicweb.web
-   :members:
-
-:mod:`cubicweb.web.form`
-========================
-
-.. automodule:: cubicweb.web.form
-   :members:
-
-:mod:`cubicweb.web.box`
-=======================
-
-.. automodule:: cubicweb.web.box
-   :members:
-
-:mod:`cubicweb.web.component`
-=============================
-
-.. automodule:: cubicweb.web.component
-   :members:
-
-:mod:`cubicweb.web.action`
-==========================
-
-.. automodule:: cubicweb.web.action
-   :members:
-
-:mod:`cubicweb.web.facet`
-=========================
-
-.. automodule:: cubicweb.web.facet
-   :members:
-
-:mod:`cubicweb.web.views.plots`
-===============================
-
-.. automodule:: cubicweb.web.views.plots
-   :members:
-
-:mod:`cubicweb.web.views.error`
-===============================
-
-.. automodule:: cubicweb.web.views.error
-   :members:
-
-:mod:`cubicweb.web.views.magicsearch`
-=====================================
-
-.. automodule:: cubicweb.web.views.magicsearch
-   :members:
-
-:mod:`cubicweb.web.views.basetemplates`
-=======================================
-
-.. automodule:: cubicweb.web.views.basetemplates
-   :members:
-
-:mod:`cubicweb.web.views.idownloadable`
-=======================================
-
-.. automodule:: cubicweb.web.views.idownloadable
-   :members:
-
-:mod:`cubicweb.web.views.ajaxedit`
-==================================
-
-.. automodule:: cubicweb.web.views.ajaxedit
-   :members:
-
-:mod:`cubicweb.web.views.wfentities`
-====================================
-
-.. automodule:: cubicweb.web.views.wfentities
-   :members:
-
-:mod:`cubicweb.web.views.navigation`
-====================================
-
-.. automodule:: cubicweb.web.views.navigation
-   :members:
-
-:mod:`cubicweb.web.views.schemaentities`
-========================================
-
-.. automodule:: cubicweb.web.views.schemaentities
-   :members:
-
-:mod:`cubicweb.web.views.treeview`
-==================================
-
-.. automodule:: cubicweb.web.views.treeview
-   :members:
-
-:mod:`cubicweb.web.views.startup`
-=================================
-
-.. automodule:: cubicweb.web.views.startup
-   :members:
-
-:mod:`cubicweb.web.views.iprogress`
-===================================
-
-.. automodule:: cubicweb.web.views.iprogress
-   :members:
-
-:mod:`cubicweb.web.views.euser`
-===============================
-
-.. automodule:: cubicweb.web.views.euser
-   :members:
-
-:mod:`cubicweb.web.views.facets`
-================================
-
-.. automodule:: cubicweb.web.views.facets
-   :members:
-
-:mod:`cubicweb.web.views.emailaddress`
-======================================
-
-.. automodule:: cubicweb.web.views.emailaddress
-   :members:
-
-:mod:`cubicweb.web.views.sessions`
-==================================
-
-.. automodule:: cubicweb.web.views.sessions
-   :members:
-
-:mod:`cubicweb.web.views.timetable`
-===================================
-
-.. automodule:: cubicweb.web.views.timetable
-   :members:
-
-:mod:`cubicweb.web.views.timeline`
-==================================
-
-.. automodule:: cubicweb.web.views.timeline
-   :members:
-
-:mod:`cubicweb.web.views.baseviews`
-===================================
-
-.. automodule:: cubicweb.web.views.baseviews
-   :members:
-
-:mod:`cubicweb.web.views.boxes`
-===============================
-
-.. automodule:: cubicweb.web.views.boxes
-   :members:
-
-:mod:`cubicweb.web.views.old_calendar`
-======================================
-
-.. automodule:: cubicweb.web.views.old_calendar
-   :members:
-
-:mod:`cubicweb.web.views.card`
-==============================
-
-.. automodule:: cubicweb.web.views.card
-   :members:
-
-:mod:`cubicweb.web.views.ibreadcrumbs`
-======================================
-
-.. automodule:: cubicweb.web.views.ibreadcrumbs
-   :members:
-
-:mod:`cubicweb.web.views.basecontrollers`
-=========================================
-
-.. automodule:: cubicweb.web.views.basecontrollers
-   :members:
-
-:mod:`cubicweb.web.views.embedding`
-===================================
-
-.. automodule:: cubicweb.web.views.embedding
-   :members:
-
-:mod:`cubicweb.web.views.actions`
-=================================
-
-.. automodule:: cubicweb.web.views.actions
-   :members:
-
-:mod:`cubicweb.web.views.editcontroller`
-========================================
-
-.. automodule:: cubicweb.web.views.editcontroller
-   :members:
-
-:mod:`cubicweb.web.views.debug`
-===============================
-
-.. automodule:: cubicweb.web.views.debug
-   :members:
-
-:mod:`cubicweb.web.views.urlpublishing`
-=======================================
-
-.. automodule:: cubicweb.web.views.urlpublishing
-   :members:
-
-:mod:`cubicweb.web.views.baseforms`
-===================================
-
-.. automodule:: cubicweb.web.views.baseforms
-   :members:
-
-:mod:`cubicweb.web.views.urlrewrite`
-====================================
-
-.. automodule:: cubicweb.web.views.urlrewrite
-   :members:
-
-:mod:`cubicweb.web.views.massmailing`
-=====================================
-
-.. automodule:: cubicweb.web.views.massmailing
-   :members:
-
-:mod:`cubicweb.web.views`
-=========================
-
-.. automodule:: cubicweb.web.views
-   :members:
-
-:mod:`cubicweb.web.views.eproperties`
-=====================================
-
-.. automodule:: cubicweb.web.views.eproperties
-   :members:
-
-:mod:`cubicweb.web.views.tabs`
-==============================
-
-.. automodule:: cubicweb.web.views.tabs
-   :members:
-
-:mod:`cubicweb.web.views.vcard`
-===============================
-
-.. automodule:: cubicweb.web.views.vcard
-   :members:
-
-:mod:`cubicweb.web.views.wdoc`
-==============================
-
-.. automodule:: cubicweb.web.views.wdoc
-   :members:
-
-:mod:`cubicweb.web.views.authentication`
-========================================
-
-.. automodule:: cubicweb.web.views.authentication
-   :members:
-
-:mod:`cubicweb.web.views.tableview`
-===================================
-
-.. automodule:: cubicweb.web.views.tableview
-   :members:
-
-:mod:`cubicweb.web.views.management`
-====================================
-
-.. automodule:: cubicweb.web.views.management
-   :members:
-
-:mod:`cubicweb.web.views.igeocodable`
-=====================================
-
-.. automodule:: cubicweb.web.views.igeocodable
-   :members:
-
-:mod:`cubicweb.web.views.xbel`
-==============================
-
-.. automodule:: cubicweb.web.views.xbel
-   :members:
-
-:mod:`cubicweb.web.views.bookmark`
-==================================
-
-.. automodule:: cubicweb.web.views.bookmark
-   :members:
-
-:mod:`cubicweb.web.views.apacherewrite`
-=======================================
-
-.. automodule:: cubicweb.web.views.apacherewrite
-   :members:
-
-:mod:`cubicweb.web.views.dynimages`
-===================================
-
-.. automodule:: cubicweb.web.views.dynimages
-   :members:
-
-:mod:`cubicweb.web.views.searchrestriction`
-===========================================
-
-.. automodule:: cubicweb.web.views.searchrestriction
-   :members:
-
-:mod:`cubicweb.web.views.basecomponents`
-========================================
-
-.. automodule:: cubicweb.web.views.basecomponents
-   :members:
-
-:mod:`cubicweb.web.views.calendar`
-==================================
-
-.. automodule:: cubicweb.web.views.calendar
-   :members:
-
-:mod:`cubicweb.sobjects.supervising`
-====================================
-
-.. automodule:: cubicweb.sobjects.supervising
-   :members:
-
-:mod:`cubicweb.sobjects.hooks`
-==============================
-
-.. automodule:: cubicweb.sobjects.hooks
-   :members:
-
-:mod:`cubicweb.sobjects.email`
-==============================
-
-.. automodule:: cubicweb.sobjects.email
-   :members:
-
-:mod:`cubicweb.sobjects`
-========================
-
-.. automodule:: cubicweb.sobjects
-   :members:
-
-:mod:`cubicweb.sobjects.notification`
-=====================================
-
-.. automodule:: cubicweb.sobjects.notification
-   :members:
-
-:mod:`cubicweb.wsgi.request`
-============================
-
-.. automodule:: cubicweb.wsgi.request
-   :members:
-
-:mod:`cubicweb.wsgi`
-====================
-
-.. automodule:: cubicweb.wsgi
-   :members:
-
-:mod:`cubicweb.wsgi.handler`
-============================
-
-.. automodule:: cubicweb.wsgi.handler
-   :members:
-
-:mod:`cubicweb.etwist.server`
-=============================
-
-.. automodule:: cubicweb.etwist.server
-   :members:
-
-:mod:`cubicweb.etwist.request`
-==============================
-
-.. automodule:: cubicweb.etwist.request
-   :members:
-
-:mod:`cubicweb.etwist.twconfig`
-===============================
-
-.. automodule:: cubicweb.etwist.twconfig
-   :members:
-
-:mod:`cubicweb.etwist`
-======================
-
-.. automodule:: cubicweb.etwist
-   :members:
-
-:mod:`cubicweb.etwist.twctl`
-============================
-
-.. automodule:: cubicweb.etwist.twctl
-   :members:
-
-:mod:`cubicweb.goa.goaconfig`
-=============================
-
-.. automodule:: cubicweb.goa.goaconfig
-   :members:
-
-:mod:`cubicweb.goa.rqlinterpreter`
-==================================
-
-.. automodule:: cubicweb.goa.rqlinterpreter
-   :members:
-
-:mod:`cubicweb.goa.dbmyams`
-===========================
-
-.. automodule:: cubicweb.goa.dbmyams
-   :members:
-
-:mod:`cubicweb.goa.db`
-======================
-
-.. automodule:: cubicweb.goa.db
-   :members:
-
-:mod:`cubicweb.goa.goactl`
-==========================
-
-.. automodule:: cubicweb.goa.goactl
-   :members:
-
-:mod:`cubicweb.goa.goavreg`
-===========================
-
-.. automodule:: cubicweb.goa.goavreg
-   :members:
-
-:mod:`cubicweb.goa`
-===================
-
-.. automodule:: cubicweb.goa
-   :members:
-
-:mod:`cubicweb.goa.gaesource`
-=============================
-
-.. automodule:: cubicweb.goa.gaesource
-   :members:
-
-:mod:`cubicweb.goa.dbinit`
-==========================
-
-.. automodule:: cubicweb.goa.dbinit
-   :members:
-
-:mod:`cubicweb.goa.testlib`
-===========================
-
-.. automodule:: cubicweb.goa.testlib
-   :members:
-
-:mod:`cubicweb.goa.appobjects.dbmgmt`
-=====================================
-
-.. automodule:: cubicweb.goa.appobjects.dbmgmt
-   :members:
-
-:mod:`cubicweb.goa.appobjects.gauthservice`
-===========================================
-
-.. automodule:: cubicweb.goa.appobjects.gauthservice
-   :members:
-
-:mod:`cubicweb.goa.appobjects.sessions`
-=======================================
-
-.. automodule:: cubicweb.goa.appobjects.sessions
-   :members:
-
-:mod:`cubicweb.goa.appobjects`
-==============================
-
-.. automodule:: cubicweb.goa.appobjects
-   :members:
-
-:mod:`cubicweb.goa.appobjects.components`
-=========================================
-
-.. automodule:: cubicweb.goa.appobjects.components
-   :members:
-
-:mod:`cubicweb.goa.tools.laxctl`
-================================
-
-.. automodule:: cubicweb.goa.tools.laxctl
-   :members:
-
-:mod:`cubicweb.goa.tools.generate_schema_img`
-=============================================
-
-.. automodule:: cubicweb.goa.tools.generate_schema_img
-   :members:
-
-:mod:`cubicweb.goa.tools`
-=========================
-
-.. automodule:: cubicweb.goa.tools
-   :members:
-
-:mod:`cubicweb.goa.tools.i18n`
-==============================
-
-.. automodule:: cubicweb.goa.tools.i18n
-   :members:
-
-:mod:`cubicweb.goa.overrides.mttransforms`
-==========================================
-
-.. automodule:: cubicweb.goa.overrides.mttransforms
-   :members:
-
-:mod:`cubicweb.goa.overrides.rqlannotation`
-===========================================
-
-.. automodule:: cubicweb.goa.overrides.rqlannotation
-   :members:
-
-:mod:`cubicweb.goa.overrides.toolsutils`
-========================================
-
-.. automodule:: cubicweb.goa.overrides.toolsutils
-   :members:
-
-:mod:`cubicweb.goa.overrides`
-=============================
-
-.. automodule:: cubicweb.goa.overrides
-   :members:
-
-:mod:`cubicweb.goa.overrides.server__init__`
-============================================
-
-.. automodule:: cubicweb.goa.overrides.server__init__
-   :members:
-
-:mod:`cubicweb.goa.overrides.server_utils`
-==========================================
-
-.. automodule:: cubicweb.goa.overrides.server_utils
-   :members:
-
-:mod:`cubicweb.common.mttransforms`
-===================================
-
-.. automodule:: cubicweb.common.mttransforms
-   :members:
-
-:mod:`cubicweb.common.utils`
-============================
-
-.. automodule:: cubicweb.common.utils
-   :members:
-
-:mod:`cubicweb.common.schema`
-=============================
-
-.. automodule:: cubicweb.common.schema
-   :members:
-
-:mod:`cubicweb.common.tal`
-==========================
-
-.. automodule:: cubicweb.common.tal
-   :members:
-
-:mod:`cubicweb.common.appobject`
-================================
-
-.. automodule:: cubicweb.common.appobject
-   :members:
-
-:mod:`cubicweb.common.migration`
-================================
-
-.. automodule:: cubicweb.common.migration
-   :members:
-
-:mod:`cubicweb.common.rest`
-===========================
-
-.. automodule:: cubicweb.common.rest
-   :members:
-
-:mod:`cubicweb.common.html4zope`
-================================
-
-.. automodule:: cubicweb.common.html4zope
-   :members:
-
-:mod:`cubicweb.common.view`
-===========================
-
-.. automodule:: cubicweb.common.view
-   :members:
-
-:mod:`cubicweb.common.selectors`
-================================
-
-.. automodule:: cubicweb.common.selectors
-   :members:
-
-:mod:`cubicweb.common.entity`
-=============================
-
-.. automodule:: cubicweb.common.entity
-   :members:
-
-:mod:`cubicweb.common.mail`
-===========================
-
-.. automodule:: cubicweb.common.mail
-   :members:
-
-:mod:`cubicweb.common.mixins`
-=============================
-
-.. automodule:: cubicweb.common.mixins
-   :members:
-
-:mod:`cubicweb.common`
-======================
-
-.. automodule:: cubicweb.common
-   :members:
-
-:mod:`cubicweb.common.uilib`
-============================
-
-.. automodule:: cubicweb.common.uilib
-   :members:
-
-:mod:`cubicweb.common.registerers`
-==================================
-
-.. automodule:: cubicweb.common.registerers
-   :members:
-
-:mod:`cubicweb.common.i18n`
-===========================
-
-.. automodule:: cubicweb.common.i18n
-   :members:
-
-:mod:`cubicweb.entities.schemaobjs`
-===================================
-
-.. automodule:: cubicweb.entities.schemaobjs
-   :members:
-
-:mod:`cubicweb.entities.wfobjs`
-===============================
-
-.. automodule:: cubicweb.entities.wfobjs
-   :members:
-
-:mod:`cubicweb.entities`
-========================
-
-.. automodule:: cubicweb.entities
-   :members:
-
-:mod:`cubicweb.entities.authobjs`
-=================================
-
-.. automodule:: cubicweb.entities.authobjs
-   :members:
-
-:mod:`cubicweb.entities.lib`
-============================
-
-.. automodule:: cubicweb.entities.lib
-   :members:
-
-:mod:`cubicweb.server.server`
-=============================
-
-.. automodule:: cubicweb.server.server
-   :members:
-
-:mod:`cubicweb.server.utils`
-============================
-
-.. automodule:: cubicweb.server.utils
-   :members:
-
-:mod:`cubicweb.server.checkintegrity`
-=====================================
-
-.. automodule:: cubicweb.server.checkintegrity
-   :members:
-
-:mod:`cubicweb.server.rqlrewrite`
-=================================
-
-.. automodule:: cubicweb.server.rqlrewrite
-   :members:
-
-:mod:`cubicweb.server.rqlannotation`
-====================================
-
-.. automodule:: cubicweb.server.rqlannotation
-   :members:
-
-:mod:`cubicweb.server.hooks`
-============================
-
-.. automodule:: cubicweb.server.hooks
-   :members:
-
-:mod:`cubicweb.server.hooksmanager`
-===================================
-
-.. automodule:: cubicweb.server.hooksmanager
-   :members:
-
-:mod:`cubicweb.server.securityhooks`
-====================================
-
-.. automodule:: cubicweb.server.securityhooks
-   :members:
-
-:mod:`cubicweb.server.schemahooks`
-==================================
-
-.. automodule:: cubicweb.server.schemahooks
-   :members:
-
-:mod:`cubicweb.server.session`
-==============================
-
-.. automodule:: cubicweb.server.session
-   :members:
-
-:mod:`cubicweb.server.serverctl`
-================================
-
-.. automodule:: cubicweb.server.serverctl
-   :members:
-
-:mod:`cubicweb.server.serverconfig`
-===================================
-
-.. automodule:: cubicweb.server.serverconfig
-   :members:
-
-:mod:`cubicweb.server.pool`
-===========================
-
-.. automodule:: cubicweb.server.pool
-   :members:
-
-:mod:`cubicweb.server.mssteps`
-==============================
-
-.. automodule:: cubicweb.server.mssteps
-   :members:
-
-:mod:`cubicweb.server.hookhelper`
-=================================
-
-.. automodule:: cubicweb.server.hookhelper
-   :members:
-
-:mod:`cubicweb.server`
-======================
-
-.. automodule:: cubicweb.server
-   :members:
-
-:mod:`cubicweb.server.sqlutils`
-===============================
-
-.. automodule:: cubicweb.server.sqlutils
-   :members:
-
-:mod:`cubicweb.server.schemaserial`
-===================================
-
-.. automodule:: cubicweb.server.schemaserial
-   :members:
-
-:mod:`cubicweb.server.repository`
-=================================
-
-.. automodule:: cubicweb.server.repository
-   :members:
-
-:mod:`cubicweb.server.ssplanner`
-================================
-
-.. automodule:: cubicweb.server.ssplanner
-   :members:
-
-:mod:`cubicweb.server.msplanner`
-================================
-
-.. automodule:: cubicweb.server.msplanner
-   :members:
-
-:mod:`cubicweb.server.querier`
-==============================
-
-.. automodule:: cubicweb.server.querier
-   :members:
-
-:mod:`cubicweb.server.migractions`
-==================================
-
-.. automodule:: cubicweb.server.migractions
-   :members:
-
-:mod:`cubicweb.server.sources.rql2sql`
-======================================
-
-.. automodule:: cubicweb.server.sources.rql2sql
-   :members:
-
-:mod:`cubicweb.server.sources.ldapuser`
-=======================================
-
-.. automodule:: cubicweb.server.sources.ldapuser
-   :members:
-
-:mod:`cubicweb.server.sources`
-==============================
-
-.. automodule:: cubicweb.server.sources
-   :members:
-
-:mod:`cubicweb.server.sources.pyrorql`
-======================================
-
-.. automodule:: cubicweb.server.sources.pyrorql
-   :members:
-
-:mod:`cubicweb.server.sources.native`
-=====================================
-
-.. automodule:: cubicweb.server.sources.native
-   :members:
-
-:mod:`cubicweb.server.sources.extlite`
-======================================
-
-.. automodule:: cubicweb.server.sources.extlite
-   :members:
-
-:mod:`cubicweb.devtools.devctl`
-===============================
-
-.. automodule:: cubicweb.devtools.devctl
-   :members:
-
-:mod:`cubicweb.devtools.pkginfo`
-================================
-
-.. automodule:: cubicweb.devtools.pkginfo
-   :members:
-
-:mod:`cubicweb.devtools.migrtest`
-=================================
-
-.. automodule:: cubicweb.devtools.migrtest
-   :members:
-
-:mod:`cubicweb.devtools.htmlparser`
-===================================
-
-.. automodule:: cubicweb.devtools.htmlparser
-   :members:
-
-:mod:`cubicweb.devtools`
-========================
-
-.. automodule:: cubicweb.devtools
-   :members:
-
-:mod:`cubicweb.devtools.fill`
-=============================
-
-.. automodule:: cubicweb.devtools.fill
-   :members:
-
-:mod:`cubicweb.devtools._apptest`
-=================================
-
-.. automodule:: cubicweb.devtools._apptest
-   :members:
-
-:mod:`cubicweb.devtools.stresstester`
-=====================================
-
-.. automodule:: cubicweb.devtools.stresstester
-   :members:
-
-:mod:`cubicweb.devtools.fake`
-=============================
-
-.. automodule:: cubicweb.devtools.fake
-   :members:
-
-:mod:`cubicweb.devtools.apptest`
-================================
-
-.. automodule:: cubicweb.devtools.apptest
-   :members:
-
-:mod:`cubicweb.devtools.livetest`
-=================================
-
-.. automodule:: cubicweb.devtools.livetest
-   :members:
-
-:mod:`cubicweb.devtools.testlib`
-================================
-
-.. automodule:: cubicweb.devtools.testlib
-   :members:
-
-:mod:`cubicweb.devtools.repotest`
-=================================
-
-.. automodule:: cubicweb.devtools.repotest
-   :members:
-
-:mod:`cubicweb.devtools.cwtwill`
-================================
-
-.. automodule:: cubicweb.devtools.cwtwill
-   :members:
-
-:mod:`cubicweb.misc.cwdesklets.rqlsensor`
-=========================================
-
-.. automodule:: cubicweb.misc.cwdesklets.rqlsensor
-   :members:
-
-:mod:`cubicweb.embedded.mx`
-===========================
-
-.. automodule:: cubicweb.embedded.mx
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.mxDateTime_python`
-======================================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.mxDateTime_python
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.ARPA`
-=========================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.ARPA
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.ISO`
-========================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.ISO
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.Parser`
-===========================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.Parser
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime`
-====================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.Timezone`
-=============================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.Timezone
-   :members:
-
-:mod:`cubicweb.embedded.mx.DateTime.DateTime`
-=============================================
-
-.. automodule:: cubicweb.embedded.mx.DateTime.DateTime
-   :members:
-
-:mod:`indexer`
-==============
-
-.. automodule:: indexer
-   :members:
-
-:mod:`indexer.indexable_objects`
-================================
-
-.. automodule:: indexer.indexable_objects
-   :members:
-
-:mod:`indexer.search`
-=====================
-
-.. automodule:: indexer.search
-   :members:
-
-:mod:`indexer.query_objects`
-============================
-
-.. automodule:: indexer.query_objects
-   :members:
-
-:mod:`indexer._exceptions`
-==========================
-
-.. automodule:: indexer._exceptions
-   :members:
-
-:mod:`indexer.setup`
-====================
-
-.. automodule:: indexer.setup
-   :members:
-
-:mod:`indexer.query`
-====================
-
-.. automodule:: indexer.query
-   :members:
-
-:mod:`logilab`
-==============
-
-.. automodule:: logilab
-   :members:
-
-:mod:`logilab.constraint.propagation`
-=====================================
-
-.. automodule:: logilab.constraint.propagation
-   :members:
-
-:mod:`logilab.constraint.psyco_wrapper`
-=======================================
-
-.. automodule:: logilab.constraint.psyco_wrapper
-   :members:
-
-:mod:`logilab.constraint.fd`
-============================
-
-.. automodule:: logilab.constraint.fd
-   :members:
-
-:mod:`logilab.constraint.fi`
-============================
-
-.. automodule:: logilab.constraint.fi
-   :members:
-
-:mod:`logilab.constraint`
-=========================
-
-.. automodule:: logilab.constraint
-   :members:
-
-:mod:`logilab.constraint.setup`
-===============================
-
-.. automodule:: logilab.constraint.setup
-   :members:
-
-:mod:`logilab.constraint.interfaces`
-====================================
-
-.. automodule:: logilab.constraint.interfaces
-   :members:
-
-:mod:`logilab.constraint.distributors`
-======================================
-
-.. automodule:: logilab.constraint.distributors
-   :members:
-
-:mod:`logilab.common.clcommands`
-================================
-
-.. automodule:: logilab.common.clcommands
-   :members:
-
-:mod:`logilab.common.table`
-===========================
-
-.. automodule:: logilab.common.table
-   :members:
-
-:mod:`logilab.common.interface`
-===============================
-
-.. automodule:: logilab.common.interface
-   :members:
-
-:mod:`logilab.common.logger`
-============================
-
-.. automodule:: logilab.common.logger
-   :members:
-
-:mod:`logilab.common.cli`
-=========================
-
-.. automodule:: logilab.common.cli
-   :members:
-
-:mod:`logilab.common.xmlrpcutils`
-=================================
-
-.. automodule:: logilab.common.xmlrpcutils
-   :members:
-
-:mod:`logilab.common.corbautils`
-================================
-
-.. automodule:: logilab.common.corbautils
-   :members:
-
-:mod:`logilab.common.cache`
-===========================
-
-.. automodule:: logilab.common.cache
-   :members:
-
-:mod:`logilab.common.astutils`
-==============================
-
-.. automodule:: logilab.common.astutils
-   :members:
-
-:mod:`logilab.common.daemon`
-============================
-
-.. automodule:: logilab.common.daemon
-   :members:
-
-:mod:`logilab.common.tree`
-==========================
-
-.. automodule:: logilab.common.tree
-   :members:
-
-:mod:`logilab.common.textutils`
-===============================
-
-.. automodule:: logilab.common.textutils
-   :members:
-
-:mod:`logilab.common.modutils`
-==============================
-
-.. automodule:: logilab.common.modutils
-   :members:
-
-:mod:`logilab.common.fileutils`
-===============================
-
-.. automodule:: logilab.common.fileutils
-   :members:
-
-:mod:`logilab.common.patricia`
-==============================
-
-.. automodule:: logilab.common.patricia
-   :members:
-
-:mod:`logilab.common.date`
-==========================
-
-.. automodule:: logilab.common.date
-   :members:
-
-:mod:`logilab.common.optparser`
-===============================
-
-.. automodule:: logilab.common.optparser
-   :members:
-
-:mod:`logilab.common.twisted_distutils`
-=======================================
-
-.. automodule:: logilab.common.twisted_distutils
-   :members:
-
-:mod:`logilab.common.decorators`
-================================
-
-.. automodule:: logilab.common.decorators
-   :members:
-
-:mod:`logilab.common.db`
-========================
-
-.. automodule:: logilab.common.db
-   :members:
-
-:mod:`logilab.common.deprecation`
-=================================
-
-.. automodule:: logilab.common.deprecation
-   :members:
-
-:mod:`logilab.common.tasksqueue`
-================================
-
-.. automodule:: logilab.common.tasksqueue
-   :members:
-
-:mod:`logilab.common.changelog`
-===============================
-
-.. automodule:: logilab.common.changelog
-   :members:
-
-:mod:`logilab.common.shellutils`
-================================
-
-.. automodule:: logilab.common.shellutils
-   :members:
-
-:mod:`logilab.common.sqlgen`
-============================
-
-.. automodule:: logilab.common.sqlgen
-   :members:
-
-:mod:`logilab.common.optik_ext`
-===============================
-
-.. automodule:: logilab.common.optik_ext
-   :members:
-
-:mod:`logilab.common.configuration`
-===================================
-
-.. automodule:: logilab.common.configuration
-   :members:
-
-:mod:`logilab.common.visitor`
-=============================
-
-.. automodule:: logilab.common.visitor
-   :members:
-
-:mod:`logilab.common.pytest`
-============================
-
-.. automodule:: logilab.common.pytest
-   :members:
-
-:mod:`logilab.common`
-=====================
-
-.. automodule:: logilab.common
-   :members:
-
-:mod:`logilab.common.setup`
-===========================
-
-.. automodule:: logilab.common.setup
-   :members:
-
-:mod:`logilab.common.logservice`
-================================
-
-.. automodule:: logilab.common.logservice
-   :members:
-
-:mod:`logilab.common.debugger`
-==============================
-
-.. automodule:: logilab.common.debugger
-   :members:
-
-:mod:`logilab.common.html`
-==========================
-
-.. automodule:: logilab.common.html
-   :members:
-
-:mod:`logilab.common.vcgutils`
-==============================
-
-.. automodule:: logilab.common.vcgutils
-   :members:
-
-:mod:`logilab.common.compat`
-============================
-
-.. automodule:: logilab.common.compat
-   :members:
-
-:mod:`logilab.common.logging_ext`
-=================================
-
-.. automodule:: logilab.common.logging_ext
-   :members:
-
-:mod:`logilab.common.umessage`
-==============================
-
-.. automodule:: logilab.common.umessage
-   :members:
-
-:mod:`logilab.common.proc`
-==========================
-
-.. automodule:: logilab.common.proc
-   :members:
-
-:mod:`logilab.common.monclient`
-===============================
-
-.. automodule:: logilab.common.monclient
-   :members:
-
-:mod:`logilab.common.bind`
-==========================
-
-.. automodule:: logilab.common.bind
-   :members:
-
-:mod:`logilab.common.graph`
-===========================
-
-.. automodule:: logilab.common.graph
-   :members:
-
-:mod:`logilab.common.testlib`
-=============================
-
-.. automodule:: logilab.common.testlib
-   :members:
-
-:mod:`logilab.common.contexts`
-==============================
-
-.. automodule:: logilab.common.contexts
-   :members:
-
-:mod:`logilab.common.adbh`
-==========================
-
-.. automodule:: logilab.common.adbh
-   :members:
-
-:mod:`logilab.common.pdf_ext`
-=============================
-
-.. automodule:: logilab.common.pdf_ext
-   :members:
-
-:mod:`logilab.common.monserver`
-===============================
-
-.. automodule:: logilab.common.monserver
-   :members:
-
-:mod:`logilab.common.ureports.nodes`
-====================================
-
-.. automodule:: logilab.common.ureports.nodes
-   :members:
-
-:mod:`logilab.common.ureports`
-==============================
-
-.. automodule:: logilab.common.ureports
-   :members:
-
-:mod:`logilab.common.ureports.html_writer`
-==========================================
-
-.. automodule:: logilab.common.ureports.html_writer
-   :members:
-
-:mod:`logilab.common.ureports.text_writer`
-==========================================
-
-.. automodule:: logilab.common.ureports.text_writer
-   :members:
-
-:mod:`logilab.common.ureports.docbook_writer`
-=============================================
-
-.. automodule:: logilab.common.ureports.docbook_writer
-   :members:
-
-:mod:`logilab.mtconverter.engine`
-=================================
-
-.. automodule:: logilab.mtconverter.engine
-   :members:
-
-:mod:`logilab.mtconverter.transform`
-====================================
-
-.. automodule:: logilab.mtconverter.transform
-   :members:
-
-:mod:`logilab.mtconverter`
-==========================
-
-.. automodule:: logilab.mtconverter
-   :members:
-
-:mod:`logilab.mtconverter.setup`
-================================
-
-.. automodule:: logilab.mtconverter.setup
-   :members:
-
-:mod:`logilab.mtconverter.transforms.html2text`
-===============================================
-
-.. automodule:: logilab.mtconverter.transforms.html2text
-   :members:
-
-:mod:`logilab.mtconverter.transforms.cmdtransforms`
-===================================================
-
-.. automodule:: logilab.mtconverter.transforms.cmdtransforms
-   :members:
-
-:mod:`logilab.mtconverter.transforms.python`
-============================================
-
-.. automodule:: logilab.mtconverter.transforms.python
-   :members:
-
-:mod:`logilab.mtconverter.transforms.pygmentstransforms`
-========================================================
-
-.. automodule:: logilab.mtconverter.transforms.pygmentstransforms
-   :members:
-
-:mod:`logilab.mtconverter.transforms`
-=====================================
-
-.. automodule:: logilab.mtconverter.transforms
-   :members:
-
-:mod:`logilab.mtconverter.transforms.piltransforms`
-===================================================
-
-.. automodule:: logilab.mtconverter.transforms.piltransforms
-   :members:
-
-:mod:`logilab.devtools.cvstatus`
-================================
-
-.. automodule:: logilab.devtools.cvstatus
-   :members:
-
-:mod:`logilab.devtools.changelog`
-=================================
-
-.. automodule:: logilab.devtools.changelog
-   :members:
-
-:mod:`logilab.devtools`
-=======================
-
-.. automodule:: logilab.devtools
-   :members:
-
-:mod:`logilab.devtools.setup`
-=============================
-
-.. automodule:: logilab.devtools.setup
-   :members:
-
-:mod:`logilab.devtools.cvslog`
-==============================
-
-.. automodule:: logilab.devtools.cvslog
-   :members:
-
-:mod:`logilab.devtools.lgp.utils`
-=================================
-
-.. automodule:: logilab.devtools.lgp.utils
-   :members:
-
-:mod:`logilab.devtools.lgp.tag`
-===============================
-
-.. automodule:: logilab.devtools.lgp.tag
-   :members:
-
-:mod:`logilab.devtools.lgp.setupinfo`
-=====================================
-
-.. automodule:: logilab.devtools.lgp.setupinfo
-   :members:
-
-:mod:`logilab.devtools.lgp.changelog`
-=====================================
-
-.. automodule:: logilab.devtools.lgp.changelog
-   :members:
-
-:mod:`logilab.devtools.lgp.preparedist`
-=======================================
-
-.. automodule:: logilab.devtools.lgp.preparedist
-   :members:
-
-:mod:`logilab.devtools.lgp.build`
-=================================
-
-.. automodule:: logilab.devtools.lgp.build
-   :members:
-
-:mod:`logilab.devtools.lgp.clean`
-=================================
-
-.. automodule:: logilab.devtools.lgp.clean
-   :members:
-
-:mod:`logilab.devtools.lgp`
-===========================
-
-.. automodule:: logilab.devtools.lgp
-   :members:
-
-:mod:`logilab.devtools.lgp.setup`
-=================================
-
-.. automodule:: logilab.devtools.lgp.setup
-   :members:
-
-:mod:`logilab.devtools.lgp.check`
-=================================
-
-.. automodule:: logilab.devtools.lgp.check
-   :members:
-
-:mod:`logilab.devtools.lgp.exceptions`
-======================================
-
-.. automodule:: logilab.devtools.lgp.exceptions
-   :members:
-
-:mod:`logilab.devtools.templates`
-=================================
-
-.. automodule:: logilab.devtools.templates
-   :members:
-
-:mod:`logilab.devtools.templates.setup`
-=======================================
-
-.. automodule:: logilab.devtools.templates.setup
-   :members:
-
-:mod:`logilab.devtools.lib.coverage`
-====================================
-
-.. automodule:: logilab.devtools.lib.coverage
-   :members:
-
-:mod:`logilab.devtools.lib.manifest`
-====================================
-
-.. automodule:: logilab.devtools.lib.manifest
-   :members:
-
-:mod:`logilab.devtools.lib.pkginfo`
-===================================
-
-.. automodule:: logilab.devtools.lib.pkginfo
-   :members:
-
-:mod:`logilab.devtools.lib`
-===========================
-
-.. automodule:: logilab.devtools.lib
-   :members:
-
-:mod:`logilab.devtools.vcslib.cvsparse`
-=======================================
-
-.. automodule:: logilab.devtools.vcslib.cvsparse
-   :members:
-
-:mod:`logilab.devtools.vcslib.svn`
-==================================
-
-.. automodule:: logilab.devtools.vcslib.svn
-   :members:
-
-:mod:`logilab.devtools.vcslib.node`
-===================================
-
-.. automodule:: logilab.devtools.vcslib.node
-   :members:
-
-:mod:`logilab.devtools.vcslib`
-==============================
-
-.. automodule:: logilab.devtools.vcslib
-   :members:
-
-:mod:`logilab.devtools.vcslib.interfaces`
-=========================================
-
-.. automodule:: logilab.devtools.vcslib.interfaces
-   :members:
-
-:mod:`logilab.devtools.vcslib.cvs`
-==================================
-
-.. automodule:: logilab.devtools.vcslib.cvs
-   :members:
-
-:mod:`logilab.devtools.vcslib.hg`
-=================================
-
-.. automodule:: logilab.devtools.vcslib.hg
-   :members:
-
-:mod:`rql.nodes`
-================
-
-.. automodule:: rql.nodes
-   :members:
-
-:mod:`rql.undo`
-===============
-
-.. automodule:: rql.undo
-   :members:
-
-:mod:`rql.utils`
-================
-
-.. automodule:: rql.utils
-   :members:
-
-:mod:`rql.base`
-===============
-
-.. automodule:: rql.base
-   :members:
-
-:mod:`rql.analyze`
-==================
-
-.. automodule:: rql.analyze
-   :members:
-
-:mod:`rql._exceptions`
-======================
-
-.. automodule:: rql._exceptions
-   :members:
-
-:mod:`rql.compare`
-==================
-
-.. automodule:: rql.compare
-   :members:
-
-:mod:`rql.stmts`
-================
-
-.. automodule:: rql.stmts
-   :members:
-
-:mod:`rql.parser_main`
-======================
-
-.. automodule:: rql.parser_main
-   :members:
-
-:mod:`rql.stcheck`
-==================
-
-.. automodule:: rql.stcheck
-   :members:
-
-:mod:`rql.parser`
-=================
-
-.. automodule:: rql.parser
-   :members:
-
-:mod:`rql`
-==========
-
-.. automodule:: rql
-   :members:
-
-:mod:`rql.setup`
-================
-
-.. automodule:: rql.setup
-   :members:
-
-:mod:`rql.interfaces`
-=====================
-
-.. automodule:: rql.interfaces
-   :members:
-
-:mod:`rql.editextensions`
-=========================
-
-.. automodule:: rql.editextensions
-   :members:
-
-:mod:`rql.fol`
-==============
-
-.. automodule:: rql.fol
-   :members:
-
-:mod:`rqlgen`
-=============
-
-.. automodule:: rqlgen
-   :members:
-
-:mod:`yams.schema`
-==================
-
-.. automodule:: yams.schema
-   :members:
-
-:mod:`yams.reader`
-==================
-
-.. automodule:: yams.reader
-   :members:
-
-:mod:`yams.schema2sql`
-======================
-
-.. automodule:: yams.schema2sql
-   :members:
-
-:mod:`yams._exceptions`
-=======================
-
-.. automodule:: yams._exceptions
-   :members:
-
-:mod:`yams.sqlreader`
-=====================
-
-.. automodule:: yams.sqlreader
-   :members:
-
-:mod:`yams.schema2dot`
-======================
-
-.. automodule:: yams.schema2dot
-   :members:
-
-:mod:`yams`
-===========
-
-.. automodule:: yams
-   :members:
-
-:mod:`yams.setup`
-=================
-
-.. automodule:: yams.setup
-   :members:
-
-:mod:`yams.interfaces`
-======================
-
-.. automodule:: yams.interfaces
-   :members:
-
-:mod:`yams.buildobjs`
-=====================
-
-.. automodule:: yams.buildobjs
-   :members:
-
-:mod:`yams.constraints`
-=======================
-
-.. automodule:: yams.constraints
-   :members:
--- a/doc/book/en/D060-mercurial.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _MercurialPresentation:
-
-Introducing Mercurial
-=====================
-
-Introduction
-````````````
-Mercurial_ manages a distributed repository containing revisions
-trees (each revision indicates the changes required to obtain the
-next, and so on). Locally, we have a repository containing revisions
-tree, and a working directory. It is possible
-to put in its working directory, one of the versions of its local repository,
-modify and then push it in its repository. 
-It is also possible to get revisions from another repository or to export
-its own revisions from the local repository to another repository.
-
-.. _Mercurial: http://www.selenic.com/mercurial/
-
-In contrast to CVS/Subversion, we usually create a repository by
-project to manage.
-
-In a collaborative development, we usually create a central repository
-accessible to all developers of the project. These central repository is used
-as a reference. According to its needs, then everyone can have a local repository,
-that you will have to synchronize with the central repository from time to time.
-
-
-Major commands
-``````````````
-* Create a local repository::
-
-     hg clone ssh://myhost//home/src/repo
-
-* See the contents of the local repository (graphical tool in Tk)::
-
-     hgview
-
-* Add a sub-directory or file in the current directory::
-
-     hg add subdir
-
-* Move to the working directory a specific revision (or last
-  revision) from the local repository::
-
-     hg update [identifier-revision]
-     hg up [identifier-revision]
-
-* Get in its local repository, the tree of revisions contained in a
-  remote repository (this does not change the local directory)::
-
-     hg pull ssh://myhost//home/src/repo
-     hg pull -u ssh://myhost//home/src/repo # equivalent to pull + update
-
-* See what are the heads of branches of the local repository if a `pull`
-  returned a new branch::
-
-     hg heads
-
-* Submit the working directory in the local repository (and create a new
-  revision)::
-
-     hg commit
-     hg ci
-
-* Merge with the mother revision of local directory, another revision from
-  the local respository (the new revision will be then two mothers
-  revisions)::
-
-     hg merge identifier-revision
-
-* Export to a remote repository, the tree of revisions in its content
-  local respository (this does not change the local directory)::
-
-     hg push ssh://myhost//home/src/repo
-
-* See what local revisions are not in another repository::
-
-     hg outgoing ssh://myhost//home/src/repo
-
-* See what are the revisions of a repository not found locally::
-
-     hg incoming ssh://myhost//home/src/repo
-
-* See what is the revision of the local repository which has been taken out 
-  from the working directory and amended::
-
-     hg parent
-
-* See the differences between the working directory and the mother revision
-  of the local repository, possibly to submit them in the local repository::
-
-     hg diff
-     hg commit-tool
-     hg ct
-
-
-Best Practices
-``````````````
-* Remember to `hg pull -u` regularly, and particularly before
-   a `hg commit`.
-
-* Remember to `hg push` when your repository contains a version
-  relatively stable of your changes.
-
-* If a `hg pull -u` created a new branch head:
-
-   1. find its identifier with `hg head`
-   2. merge with `hg merge`
-   3. `hg ci`
-   4. `hg push`
-
-Installation of the forest extension
-````````````````````````````````````
-
-Set up the forest extension by getting a copy of the sources 
-from http://hg.akoha.org/hgforest/ and adding the following 
-lines to your ``~/.hgrc``: ::
-
-   [extensions]
-   hgext.forest=
-   # or, if forest.py is not in the hgext dir:
-   # forest=/path/to/forest.py
-
-
-More information
-````````````````
-
-For more information about Mercurial, please refer to the Mercurial project online documentation_.
-
-.. _documentation: http://www.selenic.com/mercurial/wiki/
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/D060-modules-stdlib.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,158 @@
+.. -*- coding: utf-8 -*-
+
+================
+Standard library
+================
+
+:mod:`cubes.addressbook` 
+========================
+
+.. automodule:: cubes.addressbook
+   :members:
+
+:mod:`cubes.basket` 
+========================
+
+.. automodule:: cubes.basket
+   :members:
+
+:mod:`cubes.blog` 
+========================
+
+.. automodule:: cubes.blog
+   :members:
+
+:mod:`cubes.book` 
+========================
+
+.. automodule:: cubes.book
+   :members:
+
+:mod:`cubes.comment` 
+========================
+
+.. automodule:: cubes.comment
+   :members:
+
+:mod:`cubes.company` 
+========================
+
+.. automodule:: cubes.company
+   :members:
+
+
+:mod:`cubes.conference` 
+========================
+
+.. automodule:: cubes.conference
+   :members:
+
+:mod:`cubes.email` 
+========================
+
+.. automodule:: cubes.email
+   :members:
+
+:mod:`cubes.event` 
+========================
+
+.. automodule:: cubes.event
+   :members:
+
+:mod:`cubes.expense` 
+========================
+
+.. automodule:: cubes.expense
+   :members:
+
+
+:mod:`cubes.file` 
+========================
+
+.. automodule:: cubes.file
+   :members:
+
+:mod:`cubes.folder` 
+========================
+
+.. automodule:: cubes.folder
+   :members:
+
+:mod:`cubes.i18ncontent` 
+========================
+
+.. automodule:: cubes.i18ncontent
+   :members:
+
+:mod:`cubes.invoice` 
+========================
+
+.. automodule:: cubes.invoice
+   :members:
+
+:mod:`cubes.keyword` 
+========================
+
+.. automodule:: cubes.keyword
+   :members:
+
+:mod:`cubes.link` 
+========================
+
+.. automodule:: cubes.link
+   :members:
+
+:mod:`cubes.mailinglist` 
+========================
+
+.. automodule:: cubes.mailinglist
+   :members:
+
+:mod:`cubes.person` 
+========================
+
+.. automodule:: cubes.person
+   :members:
+
+:mod:`cubes.shopcart` 
+========================
+
+.. automodule:: cubes.shopcart
+   :members:
+
+:mod:`cubes.skillmat` 
+========================
+
+.. automodule:: cubes.skillmat
+   :members:
+
+:mod:`cubes.tag` 
+========================
+
+.. automodule:: cubes.tag
+   :members:
+
+:mod:`cubes.task` 
+========================
+
+.. automodule:: cubes.task
+   :members:
+
+:mod:`cubes.workcase` 
+========================
+
+.. automodule:: cubes.workcase
+   :members:
+
+:mod:`cubes.workorder` 
+========================
+
+.. automodule:: cubes.workorder
+   :members:
+
+:mod:`cubes.zone` 
+========================
+
+.. automodule:: cubes.zone
+   :members:
+
--- a/doc/book/en/D070-cookbook.en.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Cook book
-=========
-
-We gathered together some of our tricks and scripts that could make
-life easier.
-
-
-* How to import LDAP users in `CubicWeb`?
-
-  Here is a very usefull script which enables you to import LDAP users
-  into your `CubicWeb` application by runing the following: ::
-
-
-    import os
-    import pwd
-    import sys
-
-    from logilab.common.db import get_connection
-
-    def getlogin():
-        """avoid usinng os.getlogin() because of strange tty / stdin problems
-        (man 3 getlogin)
-        Another solution would be to use $LOGNAME, $USER or $USERNAME
-        """
-        return pwd.getpwuid(os.getuid())[0]
-
-
-    try:
-        database = sys.argv[1]
-    except IndexError:
-        print 'USAGE: python ldap2system.py <database>'
-        sys.exit(1)
-
-    if raw_input('update %s db ? [y/n]: ' % database).strip().lower().startswith('y'):
-        cnx = get_connection(user=getlogin(), database=database)
-        cursor = cnx.cursor()
-
-        insert = ('INSERT INTO euser (creation_date, eid, modification_date, login, firstname, surname, last_login_time, upassword) '
-                  "VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, %(firstname)s, %(surname)s, %(mtime)s, './fqEz5LeZnT6');")
-        update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
-        cursor.execute("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'")
-        for eid, type, source, extid, mtime in cursor.fetchall():
-            if type != 'EUser':
-                print "don't know what to do with entity type", type
-                continue
-            if source != 'ldapuser':
-                print "don't know what to do with source type", source
-                continue
-            ldapinfos = dict(x.strip().split('=') for x in extid.split(','))
-            login = ldapinfos['uid']
-            firstname = ldapinfos['uid'][0].upper()
-            surname = ldapinfos['uid'][1:].capitalize()
-            if login != 'jcuissinat':
-                args = dict(eid=eid, type=type, source=source, login=login,
-                            firstname=firstname, surname=surname, mtime=mtime)
-                print args
-                cursor.execute(insert, args)
-                cursor.execute(update, args)
-
-        cnx.commit()
-        cnx.close()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/D070-modules-cbw-api.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1961 @@
+.. -*- coding: utf-8 -*-
+
+============
+CubicWeb API
+============
+
+:mod:`cubicweb.hercule`
+=======================
+
+.. automodule:: cubicweb.hercule
+   :members:
+
+:mod:`cubicweb.cwctl`
+=====================
+
+.. automodule:: cubicweb.cwctl
+   :members:
+
+:mod:`cubicweb.schema`
+======================
+
+.. automodule:: cubicweb.schema
+   :members:
+
+:mod:`cubicweb.cwconfig`
+========================
+
+.. automodule:: cubicweb.cwconfig
+   :members:
+
+:mod:`cubicweb.schemaviewer`
+============================
+
+.. automodule:: cubicweb.schemaviewer
+   :members:
+
+:mod:`cubicweb._exceptions`
+===========================
+
+.. automodule:: cubicweb._exceptions
+   :members:
+
+:mod:`cubicweb.dbapi`
+=====================
+
+.. automodule:: cubicweb.dbapi
+   :members:
+
+:mod:`cubicweb.toolsutils`
+==========================
+
+.. automodule:: cubicweb.toolsutils
+   :members:
+
+:mod:`cubicweb.cwvreg`
+======================
+
+.. automodule:: cubicweb.cwvreg
+   :members:
+
+:mod:`cubicweb.md5crypt`
+========================
+
+.. automodule:: cubicweb.md5crypt
+   :members:
+
+:mod:`cubicweb.rset`
+====================
+
+.. automodule:: cubicweb.rset
+   :members:
+
+:mod:`cubicweb`
+===============
+
+.. automodule:: cubicweb
+   :members:
+
+:mod:`cubicweb.setup`
+=====================
+
+.. automodule:: cubicweb.setup
+   :members:
+
+:mod:`cubicweb.gettext`
+=======================
+
+.. automodule:: cubicweb.gettext
+   :members:
+
+:mod:`cubicweb.interfaces`
+==========================
+
+.. automodule:: cubicweb.interfaces
+   :members:
+
+:mod:`cubicweb.vregistry`
+=========================
+
+.. automodule:: cubicweb.vregistry
+   :members:
+
+:mod:`cubicweb.web.httpcache`
+=============================
+
+.. automodule:: cubicweb.web.httpcache
+   :members:
+
+:mod:`cubicweb.web.webconfig`
+=============================
+
+.. automodule:: cubicweb.web.webconfig
+   :members:
+
+:mod:`cubicweb.web.request`
+===========================
+
+.. automodule:: cubicweb.web.request
+   :members:
+
+:mod:`cubicweb.web._exceptions`
+===============================
+
+.. automodule:: cubicweb.web._exceptions
+   :members:
+
+:mod:`cubicweb.web.webctl`
+==========================
+
+.. automodule:: cubicweb.web.webctl
+   :members:
+
+:mod:`cubicweb.web.application`
+===============================
+
+.. automodule:: cubicweb.web.application
+   :members:
+
+:mod:`cubicweb.web.controller`
+==============================
+
+.. automodule:: cubicweb.web.controller
+   :members:
+
+:mod:`cubicweb.web.widgets`
+===========================
+
+.. automodule:: cubicweb.web.widgets
+   :members:
+
+:mod:`cubicweb.web.htmlwidgets`
+===============================
+
+.. automodule:: cubicweb.web.htmlwidgets
+   :members:
+
+:mod:`cubicweb.web`
+===================
+
+.. automodule:: cubicweb.web
+   :members:
+
+:mod:`cubicweb.web.form`
+========================
+
+.. automodule:: cubicweb.web.form
+   :members:
+
+:mod:`cubicweb.web.box`
+=======================
+
+.. automodule:: cubicweb.web.box
+   :members:
+
+:mod:`cubicweb.web.component`
+=============================
+
+.. automodule:: cubicweb.web.component
+   :members:
+
+:mod:`cubicweb.web.action`
+==========================
+
+.. automodule:: cubicweb.web.action
+   :members:
+
+:mod:`cubicweb.web.facet`
+=========================
+
+.. automodule:: cubicweb.web.facet
+   :members:
+
+:mod:`cubicweb.web.views.plots`
+===============================
+
+.. automodule:: cubicweb.web.views.plots
+   :members:
+
+:mod:`cubicweb.web.views.error`
+===============================
+
+.. automodule:: cubicweb.web.views.error
+   :members:
+
+:mod:`cubicweb.web.views.magicsearch`
+=====================================
+
+.. automodule:: cubicweb.web.views.magicsearch
+   :members:
+
+:mod:`cubicweb.web.views.basetemplates`
+=======================================
+
+.. automodule:: cubicweb.web.views.basetemplates
+   :members:
+
+:mod:`cubicweb.web.views.idownloadable`
+=======================================
+
+.. automodule:: cubicweb.web.views.idownloadable
+   :members:
+
+:mod:`cubicweb.web.views.ajaxedit`
+==================================
+
+.. automodule:: cubicweb.web.views.ajaxedit
+   :members:
+
+:mod:`cubicweb.web.views.wfentities`
+====================================
+
+.. automodule:: cubicweb.web.views.wfentities
+   :members:
+
+:mod:`cubicweb.web.views.navigation`
+====================================
+
+.. automodule:: cubicweb.web.views.navigation
+   :members:
+
+:mod:`cubicweb.web.views.schemaentities`
+========================================
+
+.. automodule:: cubicweb.web.views.schemaentities
+   :members:
+
+:mod:`cubicweb.web.views.treeview`
+==================================
+
+.. automodule:: cubicweb.web.views.treeview
+   :members:
+
+:mod:`cubicweb.web.views.startup`
+=================================
+
+.. automodule:: cubicweb.web.views.startup
+   :members:
+
+:mod:`cubicweb.web.views.iprogress`
+===================================
+
+.. automodule:: cubicweb.web.views.iprogress
+   :members:
+
+:mod:`cubicweb.web.views.euser`
+===============================
+
+.. automodule:: cubicweb.web.views.euser
+   :members:
+
+:mod:`cubicweb.web.views.facets`
+================================
+
+.. automodule:: cubicweb.web.views.facets
+   :members:
+
+:mod:`cubicweb.web.views.emailaddress`
+======================================
+
+.. automodule:: cubicweb.web.views.emailaddress
+   :members:
+
+:mod:`cubicweb.web.views.sessions`
+==================================
+
+.. automodule:: cubicweb.web.views.sessions
+   :members:
+
+:mod:`cubicweb.web.views.timetable`
+===================================
+
+.. automodule:: cubicweb.web.views.timetable
+   :members:
+
+:mod:`cubicweb.web.views.timeline`
+==================================
+
+.. automodule:: cubicweb.web.views.timeline
+   :members:
+
+:mod:`cubicweb.web.views.baseviews`
+===================================
+
+.. automodule:: cubicweb.web.views.baseviews
+   :members:
+
+:mod:`cubicweb.web.views.boxes`
+===============================
+
+.. automodule:: cubicweb.web.views.boxes
+   :members:
+
+:mod:`cubicweb.web.views.old_calendar`
+======================================
+
+.. automodule:: cubicweb.web.views.old_calendar
+   :members:
+
+:mod:`cubicweb.web.views.card`
+==============================
+
+.. automodule:: cubicweb.web.views.card
+   :members:
+
+:mod:`cubicweb.web.views.ibreadcrumbs`
+======================================
+
+.. automodule:: cubicweb.web.views.ibreadcrumbs
+   :members:
+
+:mod:`cubicweb.web.views.basecontrollers`
+=========================================
+
+.. automodule:: cubicweb.web.views.basecontrollers
+   :members:
+
+:mod:`cubicweb.web.views.embedding`
+===================================
+
+.. automodule:: cubicweb.web.views.embedding
+   :members:
+
+:mod:`cubicweb.web.views.actions`
+=================================
+
+.. automodule:: cubicweb.web.views.actions
+   :members:
+
+:mod:`cubicweb.web.views.editcontroller`
+========================================
+
+.. automodule:: cubicweb.web.views.editcontroller
+   :members:
+
+:mod:`cubicweb.web.views.debug`
+===============================
+
+.. automodule:: cubicweb.web.views.debug
+   :members:
+
+:mod:`cubicweb.web.views.urlpublishing`
+=======================================
+
+.. automodule:: cubicweb.web.views.urlpublishing
+   :members:
+
+:mod:`cubicweb.web.views.baseforms`
+===================================
+
+.. automodule:: cubicweb.web.views.baseforms
+   :members:
+
+:mod:`cubicweb.web.views.urlrewrite`
+====================================
+
+.. automodule:: cubicweb.web.views.urlrewrite
+   :members:
+
+:mod:`cubicweb.web.views.massmailing`
+=====================================
+
+.. automodule:: cubicweb.web.views.massmailing
+   :members:
+
+:mod:`cubicweb.web.views`
+=========================
+
+.. automodule:: cubicweb.web.views
+   :members:
+
+:mod:`cubicweb.web.views.eproperties`
+=====================================
+
+.. automodule:: cubicweb.web.views.eproperties
+   :members:
+
+:mod:`cubicweb.web.views.tabs`
+==============================
+
+.. automodule:: cubicweb.web.views.tabs
+   :members:
+
+:mod:`cubicweb.web.views.vcard`
+===============================
+
+.. automodule:: cubicweb.web.views.vcard
+   :members:
+
+:mod:`cubicweb.web.views.wdoc`
+==============================
+
+.. automodule:: cubicweb.web.views.wdoc
+   :members:
+
+:mod:`cubicweb.web.views.authentication`
+========================================
+
+.. automodule:: cubicweb.web.views.authentication
+   :members:
+
+:mod:`cubicweb.web.views.tableview`
+===================================
+
+.. automodule:: cubicweb.web.views.tableview
+   :members:
+
+:mod:`cubicweb.web.views.management`
+====================================
+
+.. automodule:: cubicweb.web.views.management
+   :members:
+
+:mod:`cubicweb.web.views.igeocodable`
+=====================================
+
+.. automodule:: cubicweb.web.views.igeocodable
+   :members:
+
+:mod:`cubicweb.web.views.xbel`
+==============================
+
+.. automodule:: cubicweb.web.views.xbel
+   :members:
+
+:mod:`cubicweb.web.views.bookmark`
+==================================
+
+.. automodule:: cubicweb.web.views.bookmark
+   :members:
+
+:mod:`cubicweb.web.views.apacherewrite`
+=======================================
+
+.. automodule:: cubicweb.web.views.apacherewrite
+   :members:
+
+:mod:`cubicweb.web.views.dynimages`
+===================================
+
+.. automodule:: cubicweb.web.views.dynimages
+   :members:
+
+:mod:`cubicweb.web.views.searchrestriction`
+===========================================
+
+.. automodule:: cubicweb.web.views.searchrestriction
+   :members:
+
+:mod:`cubicweb.web.views.basecomponents`
+========================================
+
+.. automodule:: cubicweb.web.views.basecomponents
+   :members:
+
+:mod:`cubicweb.web.views.calendar`
+==================================
+
+.. automodule:: cubicweb.web.views.calendar
+   :members:
+
+:mod:`cubicweb.sobjects.supervising`
+====================================
+
+.. automodule:: cubicweb.sobjects.supervising
+   :members:
+
+:mod:`cubicweb.sobjects.hooks`
+==============================
+
+.. automodule:: cubicweb.sobjects.hooks
+   :members:
+
+:mod:`cubicweb.sobjects.email`
+==============================
+
+.. automodule:: cubicweb.sobjects.email
+   :members:
+
+:mod:`cubicweb.sobjects`
+========================
+
+.. automodule:: cubicweb.sobjects
+   :members:
+
+:mod:`cubicweb.sobjects.notification`
+=====================================
+
+.. automodule:: cubicweb.sobjects.notification
+   :members:
+
+:mod:`cubicweb.wsgi.request`
+============================
+
+.. automodule:: cubicweb.wsgi.request
+   :members:
+
+:mod:`cubicweb.wsgi`
+====================
+
+.. automodule:: cubicweb.wsgi
+   :members:
+
+:mod:`cubicweb.wsgi.handler`
+============================
+
+.. automodule:: cubicweb.wsgi.handler
+   :members:
+
+:mod:`cubicweb.etwist.server`
+=============================
+
+.. automodule:: cubicweb.etwist.server
+   :members:
+
+:mod:`cubicweb.etwist.request`
+==============================
+
+.. automodule:: cubicweb.etwist.request
+   :members:
+
+:mod:`cubicweb.etwist.twconfig`
+===============================
+
+.. automodule:: cubicweb.etwist.twconfig
+   :members:
+
+:mod:`cubicweb.etwist`
+======================
+
+.. automodule:: cubicweb.etwist
+   :members:
+
+:mod:`cubicweb.etwist.twctl`
+============================
+
+.. automodule:: cubicweb.etwist.twctl
+   :members:
+
+:mod:`cubicweb.goa.goaconfig`
+=============================
+
+.. automodule:: cubicweb.goa.goaconfig
+   :members:
+
+:mod:`cubicweb.goa.rqlinterpreter`
+==================================
+
+.. automodule:: cubicweb.goa.rqlinterpreter
+   :members:
+
+:mod:`cubicweb.goa.dbmyams`
+===========================
+
+.. automodule:: cubicweb.goa.dbmyams
+   :members:
+
+:mod:`cubicweb.goa.db`
+======================
+
+.. automodule:: cubicweb.goa.db
+   :members:
+
+:mod:`cubicweb.goa.goactl`
+==========================
+
+.. automodule:: cubicweb.goa.goactl
+   :members:
+
+:mod:`cubicweb.goa.goavreg`
+===========================
+
+.. automodule:: cubicweb.goa.goavreg
+   :members:
+
+:mod:`cubicweb.goa`
+===================
+
+.. automodule:: cubicweb.goa
+   :members:
+
+:mod:`cubicweb.goa.gaesource`
+=============================
+
+.. automodule:: cubicweb.goa.gaesource
+   :members:
+
+:mod:`cubicweb.goa.dbinit`
+==========================
+
+.. automodule:: cubicweb.goa.dbinit
+   :members:
+
+:mod:`cubicweb.goa.testlib`
+===========================
+
+.. automodule:: cubicweb.goa.testlib
+   :members:
+
+:mod:`cubicweb.goa.appobjects.dbmgmt`
+=====================================
+
+.. automodule:: cubicweb.goa.appobjects.dbmgmt
+   :members:
+
+:mod:`cubicweb.goa.appobjects.gauthservice`
+===========================================
+
+.. automodule:: cubicweb.goa.appobjects.gauthservice
+   :members:
+
+:mod:`cubicweb.goa.appobjects.sessions`
+=======================================
+
+.. automodule:: cubicweb.goa.appobjects.sessions
+   :members:
+
+:mod:`cubicweb.goa.appobjects`
+==============================
+
+.. automodule:: cubicweb.goa.appobjects
+   :members:
+
+:mod:`cubicweb.goa.appobjects.components`
+=========================================
+
+.. automodule:: cubicweb.goa.appobjects.components
+   :members:
+
+:mod:`cubicweb.goa.tools.laxctl`
+================================
+
+.. automodule:: cubicweb.goa.tools.laxctl
+   :members:
+
+:mod:`cubicweb.goa.tools.generate_schema_img`
+=============================================
+
+.. automodule:: cubicweb.goa.tools.generate_schema_img
+   :members:
+
+:mod:`cubicweb.goa.tools`
+=========================
+
+.. automodule:: cubicweb.goa.tools
+   :members:
+
+:mod:`cubicweb.goa.tools.i18n`
+==============================
+
+.. automodule:: cubicweb.goa.tools.i18n
+   :members:
+
+:mod:`cubicweb.goa.overrides.mttransforms`
+==========================================
+
+.. automodule:: cubicweb.goa.overrides.mttransforms
+   :members:
+
+:mod:`cubicweb.goa.overrides.rqlannotation`
+===========================================
+
+.. automodule:: cubicweb.goa.overrides.rqlannotation
+   :members:
+
+:mod:`cubicweb.goa.overrides.toolsutils`
+========================================
+
+.. automodule:: cubicweb.goa.overrides.toolsutils
+   :members:
+
+:mod:`cubicweb.goa.overrides`
+=============================
+
+.. automodule:: cubicweb.goa.overrides
+   :members:
+
+:mod:`cubicweb.goa.overrides.server__init__`
+============================================
+
+.. automodule:: cubicweb.goa.overrides.server__init__
+   :members:
+
+:mod:`cubicweb.goa.overrides.server_utils`
+==========================================
+
+.. automodule:: cubicweb.goa.overrides.server_utils
+   :members:
+
+:mod:`cubicweb.common.mttransforms`
+===================================
+
+.. automodule:: cubicweb.common.mttransforms
+   :members:
+
+:mod:`cubicweb.common.utils`
+============================
+
+.. automodule:: cubicweb.common.utils
+   :members:
+
+:mod:`cubicweb.common.schema`
+=============================
+
+.. automodule:: cubicweb.common.schema
+   :members:
+
+:mod:`cubicweb.common.tal`
+==========================
+
+.. automodule:: cubicweb.common.tal
+   :members:
+
+:mod:`cubicweb.common.appobject`
+================================
+
+.. automodule:: cubicweb.common.appobject
+   :members:
+
+:mod:`cubicweb.common.migration`
+================================
+
+.. automodule:: cubicweb.common.migration
+   :members:
+
+:mod:`cubicweb.common.rest`
+===========================
+
+.. automodule:: cubicweb.common.rest
+   :members:
+
+:mod:`cubicweb.common.html4zope`
+================================
+
+.. automodule:: cubicweb.common.html4zope
+   :members:
+
+:mod:`cubicweb.common.view`
+===========================
+
+.. automodule:: cubicweb.common.view
+   :members:
+
+:mod:`cubicweb.common.selectors`
+================================
+
+.. automodule:: cubicweb.common.selectors
+   :members:
+
+:mod:`cubicweb.common.entity`
+=============================
+
+.. automodule:: cubicweb.common.entity
+   :members:
+
+:mod:`cubicweb.common.mail`
+===========================
+
+.. automodule:: cubicweb.common.mail
+   :members:
+
+:mod:`cubicweb.common.mixins`
+=============================
+
+.. automodule:: cubicweb.common.mixins
+   :members:
+
+:mod:`cubicweb.common`
+======================
+
+.. automodule:: cubicweb.common
+   :members:
+
+:mod:`cubicweb.common.uilib`
+============================
+
+.. automodule:: cubicweb.common.uilib
+   :members:
+
+:mod:`cubicweb.common.registerers`
+==================================
+
+.. automodule:: cubicweb.common.registerers
+   :members:
+
+:mod:`cubicweb.common.i18n`
+===========================
+
+.. automodule:: cubicweb.common.i18n
+   :members:
+
+:mod:`cubicweb.entities.schemaobjs`
+===================================
+
+.. automodule:: cubicweb.entities.schemaobjs
+   :members:
+
+:mod:`cubicweb.entities.wfobjs`
+===============================
+
+.. automodule:: cubicweb.entities.wfobjs
+   :members:
+
+:mod:`cubicweb.entities`
+========================
+
+.. automodule:: cubicweb.entities
+   :members:
+
+:mod:`cubicweb.entities.authobjs`
+=================================
+
+.. automodule:: cubicweb.entities.authobjs
+   :members:
+
+:mod:`cubicweb.entities.lib`
+============================
+
+.. automodule:: cubicweb.entities.lib
+   :members:
+
+:mod:`cubicweb.server.server`
+=============================
+
+.. automodule:: cubicweb.server.server
+   :members:
+
+:mod:`cubicweb.server.utils`
+============================
+
+.. automodule:: cubicweb.server.utils
+   :members:
+
+:mod:`cubicweb.server.checkintegrity`
+=====================================
+
+.. automodule:: cubicweb.server.checkintegrity
+   :members:
+
+:mod:`cubicweb.server.rqlrewrite`
+=================================
+
+.. automodule:: cubicweb.server.rqlrewrite
+   :members:
+
+:mod:`cubicweb.server.rqlannotation`
+====================================
+
+.. automodule:: cubicweb.server.rqlannotation
+   :members:
+
+:mod:`cubicweb.server.hooks`
+============================
+
+.. automodule:: cubicweb.server.hooks
+   :members:
+
+:mod:`cubicweb.server.hooksmanager`
+===================================
+
+.. automodule:: cubicweb.server.hooksmanager
+   :members:
+
+:mod:`cubicweb.server.securityhooks`
+====================================
+
+.. automodule:: cubicweb.server.securityhooks
+   :members:
+
+:mod:`cubicweb.server.schemahooks`
+==================================
+
+.. automodule:: cubicweb.server.schemahooks
+   :members:
+
+:mod:`cubicweb.server.session`
+==============================
+
+.. automodule:: cubicweb.server.session
+   :members:
+
+:mod:`cubicweb.server.serverctl`
+================================
+
+.. automodule:: cubicweb.server.serverctl
+   :members:
+
+:mod:`cubicweb.server.serverconfig`
+===================================
+
+.. automodule:: cubicweb.server.serverconfig
+   :members:
+
+:mod:`cubicweb.server.pool`
+===========================
+
+.. automodule:: cubicweb.server.pool
+   :members:
+
+:mod:`cubicweb.server.mssteps`
+==============================
+
+.. automodule:: cubicweb.server.mssteps
+   :members:
+
+:mod:`cubicweb.server.hookhelper`
+=================================
+
+.. automodule:: cubicweb.server.hookhelper
+   :members:
+
+:mod:`cubicweb.server`
+======================
+
+.. automodule:: cubicweb.server
+   :members:
+
+:mod:`cubicweb.server.sqlutils`
+===============================
+
+.. automodule:: cubicweb.server.sqlutils
+   :members:
+
+:mod:`cubicweb.server.schemaserial`
+===================================
+
+.. automodule:: cubicweb.server.schemaserial
+   :members:
+
+:mod:`cubicweb.server.repository`
+=================================
+
+.. automodule:: cubicweb.server.repository
+   :members:
+
+:mod:`cubicweb.server.ssplanner`
+================================
+
+.. automodule:: cubicweb.server.ssplanner
+   :members:
+
+:mod:`cubicweb.server.msplanner`
+================================
+
+.. automodule:: cubicweb.server.msplanner
+   :members:
+
+:mod:`cubicweb.server.querier`
+==============================
+
+.. automodule:: cubicweb.server.querier
+   :members:
+
+:mod:`cubicweb.server.migractions`
+==================================
+
+.. automodule:: cubicweb.server.migractions
+   :members:
+
+:mod:`cubicweb.server.sources.rql2sql`
+======================================
+
+.. automodule:: cubicweb.server.sources.rql2sql
+   :members:
+
+:mod:`cubicweb.server.sources.ldapuser`
+=======================================
+
+.. automodule:: cubicweb.server.sources.ldapuser
+   :members:
+
+:mod:`cubicweb.server.sources`
+==============================
+
+.. automodule:: cubicweb.server.sources
+   :members:
+
+:mod:`cubicweb.server.sources.pyrorql`
+======================================
+
+.. automodule:: cubicweb.server.sources.pyrorql
+   :members:
+
+:mod:`cubicweb.server.sources.native`
+=====================================
+
+.. automodule:: cubicweb.server.sources.native
+   :members:
+
+:mod:`cubicweb.server.sources.extlite`
+======================================
+
+.. automodule:: cubicweb.server.sources.extlite
+   :members:
+
+:mod:`cubicweb.devtools.devctl`
+===============================
+
+.. automodule:: cubicweb.devtools.devctl
+   :members:
+
+:mod:`cubicweb.devtools.pkginfo`
+================================
+
+.. automodule:: cubicweb.devtools.pkginfo
+   :members:
+
+:mod:`cubicweb.devtools.migrtest`
+=================================
+
+.. automodule:: cubicweb.devtools.migrtest
+   :members:
+
+:mod:`cubicweb.devtools.htmlparser`
+===================================
+
+.. automodule:: cubicweb.devtools.htmlparser
+   :members:
+
+:mod:`cubicweb.devtools`
+========================
+
+.. automodule:: cubicweb.devtools
+   :members:
+
+:mod:`cubicweb.devtools.fill`
+=============================
+
+.. automodule:: cubicweb.devtools.fill
+   :members:
+
+:mod:`cubicweb.devtools._apptest`
+=================================
+
+.. automodule:: cubicweb.devtools._apptest
+   :members:
+
+:mod:`cubicweb.devtools.stresstester`
+=====================================
+
+.. automodule:: cubicweb.devtools.stresstester
+   :members:
+
+:mod:`cubicweb.devtools.fake`
+=============================
+
+.. automodule:: cubicweb.devtools.fake
+   :members:
+
+:mod:`cubicweb.devtools.apptest`
+================================
+
+.. automodule:: cubicweb.devtools.apptest
+   :members:
+
+:mod:`cubicweb.devtools.livetest`
+=================================
+
+.. automodule:: cubicweb.devtools.livetest
+   :members:
+
+:mod:`cubicweb.devtools.testlib`
+================================
+
+.. automodule:: cubicweb.devtools.testlib
+   :members:
+
+:mod:`cubicweb.devtools.repotest`
+=================================
+
+.. automodule:: cubicweb.devtools.repotest
+   :members:
+
+:mod:`cubicweb.devtools.cwtwill`
+================================
+
+.. automodule:: cubicweb.devtools.cwtwill
+   :members:
+
+:mod:`cubicweb.misc.cwdesklets.rqlsensor`
+=========================================
+
+.. automodule:: cubicweb.misc.cwdesklets.rqlsensor
+   :members:
+
+:mod:`cubicweb.embedded.mx`
+===========================
+
+.. automodule:: cubicweb.embedded.mx
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.mxDateTime_python`
+======================================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.mxDateTime_python
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.ARPA`
+=========================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.ARPA
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.ISO`
+========================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.ISO
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.Parser`
+===========================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.Parser
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime`
+====================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.Timezone`
+=============================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.Timezone
+   :members:
+
+:mod:`cubicweb.embedded.mx.DateTime.DateTime`
+=============================================
+
+.. automodule:: cubicweb.embedded.mx.DateTime.DateTime
+   :members:
+
+:mod:`indexer`
+==============
+
+.. automodule:: indexer
+   :members:
+
+:mod:`indexer.indexable_objects`
+================================
+
+.. automodule:: indexer.indexable_objects
+   :members:
+
+:mod:`indexer.search`
+=====================
+
+.. automodule:: indexer.search
+   :members:
+
+:mod:`indexer.query_objects`
+============================
+
+.. automodule:: indexer.query_objects
+   :members:
+
+:mod:`indexer._exceptions`
+==========================
+
+.. automodule:: indexer._exceptions
+   :members:
+
+:mod:`indexer.setup`
+====================
+
+.. automodule:: indexer.setup
+   :members:
+
+:mod:`indexer.query`
+====================
+
+.. automodule:: indexer.query
+   :members:
+
+:mod:`logilab`
+==============
+
+.. automodule:: logilab
+   :members:
+
+:mod:`logilab.constraint.propagation`
+=====================================
+
+.. automodule:: logilab.constraint.propagation
+   :members:
+
+:mod:`logilab.constraint.psyco_wrapper`
+=======================================
+
+.. automodule:: logilab.constraint.psyco_wrapper
+   :members:
+
+:mod:`logilab.constraint.fd`
+============================
+
+.. automodule:: logilab.constraint.fd
+   :members:
+
+:mod:`logilab.constraint.fi`
+============================
+
+.. automodule:: logilab.constraint.fi
+   :members:
+
+:mod:`logilab.constraint`
+=========================
+
+.. automodule:: logilab.constraint
+   :members:
+
+:mod:`logilab.constraint.setup`
+===============================
+
+.. automodule:: logilab.constraint.setup
+   :members:
+
+:mod:`logilab.constraint.interfaces`
+====================================
+
+.. automodule:: logilab.constraint.interfaces
+   :members:
+
+:mod:`logilab.constraint.distributors`
+======================================
+
+.. automodule:: logilab.constraint.distributors
+   :members:
+
+:mod:`logilab.common.clcommands`
+================================
+
+.. automodule:: logilab.common.clcommands
+   :members:
+
+:mod:`logilab.common.table`
+===========================
+
+.. automodule:: logilab.common.table
+   :members:
+
+:mod:`logilab.common.interface`
+===============================
+
+.. automodule:: logilab.common.interface
+   :members:
+
+:mod:`logilab.common.logger`
+============================
+
+.. automodule:: logilab.common.logger
+   :members:
+
+:mod:`logilab.common.cli`
+=========================
+
+.. automodule:: logilab.common.cli
+   :members:
+
+:mod:`logilab.common.xmlrpcutils`
+=================================
+
+.. automodule:: logilab.common.xmlrpcutils
+   :members:
+
+:mod:`logilab.common.corbautils`
+================================
+
+.. automodule:: logilab.common.corbautils
+   :members:
+
+:mod:`logilab.common.cache`
+===========================
+
+.. automodule:: logilab.common.cache
+   :members:
+
+:mod:`logilab.common.astutils`
+==============================
+
+.. automodule:: logilab.common.astutils
+   :members:
+
+:mod:`logilab.common.daemon`
+============================
+
+.. automodule:: logilab.common.daemon
+   :members:
+
+:mod:`logilab.common.tree`
+==========================
+
+.. automodule:: logilab.common.tree
+   :members:
+
+:mod:`logilab.common.textutils`
+===============================
+
+.. automodule:: logilab.common.textutils
+   :members:
+
+:mod:`logilab.common.modutils`
+==============================
+
+.. automodule:: logilab.common.modutils
+   :members:
+
+:mod:`logilab.common.fileutils`
+===============================
+
+.. automodule:: logilab.common.fileutils
+   :members:
+
+:mod:`logilab.common.patricia`
+==============================
+
+.. automodule:: logilab.common.patricia
+   :members:
+
+:mod:`logilab.common.date`
+==========================
+
+.. automodule:: logilab.common.date
+   :members:
+
+:mod:`logilab.common.optparser`
+===============================
+
+.. automodule:: logilab.common.optparser
+   :members:
+
+:mod:`logilab.common.twisted_distutils`
+=======================================
+
+.. automodule:: logilab.common.twisted_distutils
+   :members:
+
+:mod:`logilab.common.decorators`
+================================
+
+.. automodule:: logilab.common.decorators
+   :members:
+
+:mod:`logilab.common.db`
+========================
+
+.. automodule:: logilab.common.db
+   :members:
+
+:mod:`logilab.common.deprecation`
+=================================
+
+.. automodule:: logilab.common.deprecation
+   :members:
+
+:mod:`logilab.common.tasksqueue`
+================================
+
+.. automodule:: logilab.common.tasksqueue
+   :members:
+
+:mod:`logilab.common.changelog`
+===============================
+
+.. automodule:: logilab.common.changelog
+   :members:
+
+:mod:`logilab.common.shellutils`
+================================
+
+.. automodule:: logilab.common.shellutils
+   :members:
+
+:mod:`logilab.common.sqlgen`
+============================
+
+.. automodule:: logilab.common.sqlgen
+   :members:
+
+:mod:`logilab.common.optik_ext`
+===============================
+
+.. automodule:: logilab.common.optik_ext
+   :members:
+
+:mod:`logilab.common.configuration`
+===================================
+
+.. automodule:: logilab.common.configuration
+   :members:
+
+:mod:`logilab.common.visitor`
+=============================
+
+.. automodule:: logilab.common.visitor
+   :members:
+
+:mod:`logilab.common.pytest`
+============================
+
+.. automodule:: logilab.common.pytest
+   :members:
+
+:mod:`logilab.common`
+=====================
+
+.. automodule:: logilab.common
+   :members:
+
+:mod:`logilab.common.setup`
+===========================
+
+.. automodule:: logilab.common.setup
+   :members:
+
+:mod:`logilab.common.logservice`
+================================
+
+.. automodule:: logilab.common.logservice
+   :members:
+
+:mod:`logilab.common.debugger`
+==============================
+
+.. automodule:: logilab.common.debugger
+   :members:
+
+:mod:`logilab.common.html`
+==========================
+
+.. automodule:: logilab.common.html
+   :members:
+
+:mod:`logilab.common.vcgutils`
+==============================
+
+.. automodule:: logilab.common.vcgutils
+   :members:
+
+:mod:`logilab.common.compat`
+============================
+
+.. automodule:: logilab.common.compat
+   :members:
+
+:mod:`logilab.common.logging_ext`
+=================================
+
+.. automodule:: logilab.common.logging_ext
+   :members:
+
+:mod:`logilab.common.umessage`
+==============================
+
+.. automodule:: logilab.common.umessage
+   :members:
+
+:mod:`logilab.common.proc`
+==========================
+
+.. automodule:: logilab.common.proc
+   :members:
+
+:mod:`logilab.common.monclient`
+===============================
+
+.. automodule:: logilab.common.monclient
+   :members:
+
+:mod:`logilab.common.bind`
+==========================
+
+.. automodule:: logilab.common.bind
+   :members:
+
+:mod:`logilab.common.graph`
+===========================
+
+.. automodule:: logilab.common.graph
+   :members:
+
+:mod:`logilab.common.testlib`
+=============================
+
+.. automodule:: logilab.common.testlib
+   :members:
+
+:mod:`logilab.common.contexts`
+==============================
+
+.. automodule:: logilab.common.contexts
+   :members:
+
+:mod:`logilab.common.adbh`
+==========================
+
+.. automodule:: logilab.common.adbh
+   :members:
+
+:mod:`logilab.common.pdf_ext`
+=============================
+
+.. automodule:: logilab.common.pdf_ext
+   :members:
+
+:mod:`logilab.common.monserver`
+===============================
+
+.. automodule:: logilab.common.monserver
+   :members:
+
+:mod:`logilab.common.ureports.nodes`
+====================================
+
+.. automodule:: logilab.common.ureports.nodes
+   :members:
+
+:mod:`logilab.common.ureports`
+==============================
+
+.. automodule:: logilab.common.ureports
+   :members:
+
+:mod:`logilab.common.ureports.html_writer`
+==========================================
+
+.. automodule:: logilab.common.ureports.html_writer
+   :members:
+
+:mod:`logilab.common.ureports.text_writer`
+==========================================
+
+.. automodule:: logilab.common.ureports.text_writer
+   :members:
+
+:mod:`logilab.common.ureports.docbook_writer`
+=============================================
+
+.. automodule:: logilab.common.ureports.docbook_writer
+   :members:
+
+:mod:`logilab.mtconverter.engine`
+=================================
+
+.. automodule:: logilab.mtconverter.engine
+   :members:
+
+:mod:`logilab.mtconverter.transform`
+====================================
+
+.. automodule:: logilab.mtconverter.transform
+   :members:
+
+:mod:`logilab.mtconverter`
+==========================
+
+.. automodule:: logilab.mtconverter
+   :members:
+
+:mod:`logilab.mtconverter.setup`
+================================
+
+.. automodule:: logilab.mtconverter.setup
+   :members:
+
+:mod:`logilab.mtconverter.transforms.html2text`
+===============================================
+
+.. automodule:: logilab.mtconverter.transforms.html2text
+   :members:
+
+:mod:`logilab.mtconverter.transforms.cmdtransforms`
+===================================================
+
+.. automodule:: logilab.mtconverter.transforms.cmdtransforms
+   :members:
+
+:mod:`logilab.mtconverter.transforms.python`
+============================================
+
+.. automodule:: logilab.mtconverter.transforms.python
+   :members:
+
+:mod:`logilab.mtconverter.transforms.pygmentstransforms`
+========================================================
+
+.. automodule:: logilab.mtconverter.transforms.pygmentstransforms
+   :members:
+
+:mod:`logilab.mtconverter.transforms`
+=====================================
+
+.. automodule:: logilab.mtconverter.transforms
+   :members:
+
+:mod:`logilab.mtconverter.transforms.piltransforms`
+===================================================
+
+.. automodule:: logilab.mtconverter.transforms.piltransforms
+   :members:
+
+:mod:`logilab.devtools.cvstatus`
+================================
+
+.. automodule:: logilab.devtools.cvstatus
+   :members:
+
+:mod:`logilab.devtools.changelog`
+=================================
+
+.. automodule:: logilab.devtools.changelog
+   :members:
+
+:mod:`logilab.devtools`
+=======================
+
+.. automodule:: logilab.devtools
+   :members:
+
+:mod:`logilab.devtools.setup`
+=============================
+
+.. automodule:: logilab.devtools.setup
+   :members:
+
+:mod:`logilab.devtools.cvslog`
+==============================
+
+.. automodule:: logilab.devtools.cvslog
+   :members:
+
+:mod:`logilab.devtools.lgp.utils`
+=================================
+
+.. automodule:: logilab.devtools.lgp.utils
+   :members:
+
+:mod:`logilab.devtools.lgp.tag`
+===============================
+
+.. automodule:: logilab.devtools.lgp.tag
+   :members:
+
+:mod:`logilab.devtools.lgp.setupinfo`
+=====================================
+
+.. automodule:: logilab.devtools.lgp.setupinfo
+   :members:
+
+:mod:`logilab.devtools.lgp.changelog`
+=====================================
+
+.. automodule:: logilab.devtools.lgp.changelog
+   :members:
+
+:mod:`logilab.devtools.lgp.preparedist`
+=======================================
+
+.. automodule:: logilab.devtools.lgp.preparedist
+   :members:
+
+:mod:`logilab.devtools.lgp.build`
+=================================
+
+.. automodule:: logilab.devtools.lgp.build
+   :members:
+
+:mod:`logilab.devtools.lgp.clean`
+=================================
+
+.. automodule:: logilab.devtools.lgp.clean
+   :members:
+
+:mod:`logilab.devtools.lgp`
+===========================
+
+.. automodule:: logilab.devtools.lgp
+   :members:
+
+:mod:`logilab.devtools.lgp.setup`
+=================================
+
+.. automodule:: logilab.devtools.lgp.setup
+   :members:
+
+:mod:`logilab.devtools.lgp.check`
+=================================
+
+.. automodule:: logilab.devtools.lgp.check
+   :members:
+
+:mod:`logilab.devtools.lgp.exceptions`
+======================================
+
+.. automodule:: logilab.devtools.lgp.exceptions
+   :members:
+
+:mod:`logilab.devtools.templates`
+=================================
+
+.. automodule:: logilab.devtools.templates
+   :members:
+
+:mod:`logilab.devtools.templates.setup`
+=======================================
+
+.. automodule:: logilab.devtools.templates.setup
+   :members:
+
+:mod:`logilab.devtools.lib.coverage`
+====================================
+
+.. automodule:: logilab.devtools.lib.coverage
+   :members:
+
+:mod:`logilab.devtools.lib.manifest`
+====================================
+
+.. automodule:: logilab.devtools.lib.manifest
+   :members:
+
+:mod:`logilab.devtools.lib.pkginfo`
+===================================
+
+.. automodule:: logilab.devtools.lib.pkginfo
+   :members:
+
+:mod:`logilab.devtools.lib`
+===========================
+
+.. automodule:: logilab.devtools.lib
+   :members:
+
+:mod:`logilab.devtools.vcslib.cvsparse`
+=======================================
+
+.. automodule:: logilab.devtools.vcslib.cvsparse
+   :members:
+
+:mod:`logilab.devtools.vcslib.svn`
+==================================
+
+.. automodule:: logilab.devtools.vcslib.svn
+   :members:
+
+:mod:`logilab.devtools.vcslib.node`
+===================================
+
+.. automodule:: logilab.devtools.vcslib.node
+   :members:
+
+:mod:`logilab.devtools.vcslib`
+==============================
+
+.. automodule:: logilab.devtools.vcslib
+   :members:
+
+:mod:`logilab.devtools.vcslib.interfaces`
+=========================================
+
+.. automodule:: logilab.devtools.vcslib.interfaces
+   :members:
+
+:mod:`logilab.devtools.vcslib.cvs`
+==================================
+
+.. automodule:: logilab.devtools.vcslib.cvs
+   :members:
+
+:mod:`logilab.devtools.vcslib.hg`
+=================================
+
+.. automodule:: logilab.devtools.vcslib.hg
+   :members:
+
+:mod:`rql.nodes`
+================
+
+.. automodule:: rql.nodes
+   :members:
+
+:mod:`rql.undo`
+===============
+
+.. automodule:: rql.undo
+   :members:
+
+:mod:`rql.utils`
+================
+
+.. automodule:: rql.utils
+   :members:
+
+:mod:`rql.base`
+===============
+
+.. automodule:: rql.base
+   :members:
+
+:mod:`rql.analyze`
+==================
+
+.. automodule:: rql.analyze
+   :members:
+
+:mod:`rql._exceptions`
+======================
+
+.. automodule:: rql._exceptions
+   :members:
+
+:mod:`rql.compare`
+==================
+
+.. automodule:: rql.compare
+   :members:
+
+:mod:`rql.stmts`
+================
+
+.. automodule:: rql.stmts
+   :members:
+
+:mod:`rql.parser_main`
+======================
+
+.. automodule:: rql.parser_main
+   :members:
+
+:mod:`rql.stcheck`
+==================
+
+.. automodule:: rql.stcheck
+   :members:
+
+:mod:`rql.parser`
+=================
+
+.. automodule:: rql.parser
+   :members:
+
+:mod:`rql`
+==========
+
+.. automodule:: rql
+   :members:
+
+:mod:`rql.setup`
+================
+
+.. automodule:: rql.setup
+   :members:
+
+:mod:`rql.interfaces`
+=====================
+
+.. automodule:: rql.interfaces
+   :members:
+
+:mod:`rql.editextensions`
+=========================
+
+.. automodule:: rql.editextensions
+   :members:
+
+:mod:`rql.fol`
+==============
+
+.. automodule:: rql.fol
+   :members:
+
+:mod:`rqlgen`
+=============
+
+.. automodule:: rqlgen
+   :members:
+
+:mod:`yams.schema`
+==================
+
+.. automodule:: yams.schema
+   :members:
+
+:mod:`yams.reader`
+==================
+
+.. automodule:: yams.reader
+   :members:
+
+:mod:`yams.schema2sql`
+======================
+
+.. automodule:: yams.schema2sql
+   :members:
+
+:mod:`yams._exceptions`
+=======================
+
+.. automodule:: yams._exceptions
+   :members:
+
+:mod:`yams.sqlreader`
+=====================
+
+.. automodule:: yams.sqlreader
+   :members:
+
+:mod:`yams.schema2dot`
+======================
+
+.. automodule:: yams.schema2dot
+   :members:
+
+:mod:`yams`
+===========
+
+.. automodule:: yams
+   :members:
+
+:mod:`yams.setup`
+=================
+
+.. automodule:: yams.setup
+   :members:
+
+:mod:`yams.interfaces`
+======================
+
+.. automodule:: yams.interfaces
+   :members:
+
+:mod:`yams.buildobjs`
+=====================
+
+.. automodule:: yams.buildobjs
+   :members:
+
+:mod:`yams.constraints`
+=======================
+
+.. automodule:: yams.constraints
+   :members:
--- a/doc/book/en/MERGE_ME-tut-create-app.en.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/en/MERGE_ME-tut-create-app.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -323,7 +323,7 @@
   
   _ = unicode
 
-  moderators      = add_entity('EGroup', name=u"moderators")
+  moderators      = add_entity('CWGroup', name=u"moderators")
 
   submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
   published = add_state(_('published'), 'BlogEntry')
--- a/doc/book/en/Z010-beginners.en.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/en/Z010-beginners.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -2,11 +2,10 @@
 
 .. _QuickInstall:
 
-===========================================
 Quick Installation of a `CubicWeb` instance
 ===========================================
 
-.. include:: C011-installation.en.txt
+.. include:: C010-setup.en.txt
 .. include:: Z012-create-instance.en.txt
 
 
--- a/doc/book/en/Z012-create-instance.en.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/en/Z012-create-instance.en.txt	Thu May 14 12:50:34 2009 +0200
@@ -1,5 +1,6 @@
 .. -*- coding: utf-8 -*-
 
+===============================
 Creation of your first instance
 ===============================
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/create-instance.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,63 @@
+.. -*- coding: utf-8 -*-
+
+Creation of your first instance
+===============================
+
+Instance creation
+-----------------
+
+Now that we created our cube, we can create an instance to view our
+application in a web browser. To do so we will use a `all-in-one` 
+configuration to simplify things ::
+
+  cubicweb-ctl create -c all-in-one mycube myinstance
+
+.. note::
+  Please note that we created a new cube for a demo purpose but
+  you could have use an existing cube available in our standard library
+  such as blog or person for example.
+
+A serie of questions will be prompted to you, the default answer is usually
+sufficient. You can anyway modify the configuration later on by editing
+configuration files. When a user/psswd is requested to access the database
+please use the login you create at the time you configured the database
+(:ref:`ConfigurationPostgres`).
+
+It is important to distinguish here the user used to access the database and the
+user used to login to the cubicweb application. When an instance starts, it uses
+the login/psswd for the database to get the schema and handle low level
+transaction. But, when :command:`cubicweb-ctl create` asks for a manager
+login/psswd of `CubicWeb`, it refers to the user you will use during the
+development to administrate your web application. It will be possible, later on,
+to use this user to create others users for your final web application.
+
+
+Instance administration
+-----------------------
+
+start / stop
+~~~~~~~~~~~~
+When this command is completed, the definition of your instance is
+located in :file:`~/etc/cubicweb.d/myinstance/*`. To launch it, you just type ::
+
+  cubicweb-ctl start -D myinstance
+
+The option `-D` specify the *debug mode* : the instance is not running in
+server mode and does not disconnect from the termnial, which simplifies debugging
+in case the instance is not properly launched. You can see how it looks by
+visiting the URL `http://localhost:8080` (the port number depends of your 
+configuration). To login, please use the cubicweb administrator login/psswd you 
+defined when you created the instance.
+
+To shutdown the instance, Crtl-C in the terminal window is enough.
+If you did not use the option `-D`, then type ::
+
+  cubicweb-ctl stop myinstance
+
+This is it! All is settled down to start developping your data model...
+
+
+upgrade
+~~~~~~~
+XXX feed me
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/gae.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,233 @@
+.. -*- coding: utf-8 -*-
+
+CubicWeb in Google AppEngine
+============================
+
+What is  `Google AppEngine` ?
+------------------------------
+
+`Google AppEngine`_ is provided with a partial port of the `Django`
+framework, but Google stated at Google IO 2008 that it would not
+support a specific Python web framework and that all
+community-supported frameworks would be more than welcome [1]_. 
+
+Therefore `Logilab`_ ported `CubicWeb` to run on top of `Google AppEngine`'s
+datastore.
+
+.. _`Google AppEngine`: http://code.google.com/appengine/docs/whatisgoogleappengine.html
+.. _Logilab: http://www.logilab.fr/
+.. [1] for more on this matter, read our blog at http://www.logilab.org/blogentry/5216
+
+Download the source
+-------------------
+
+- The `Google AppEngine SDK` can be downloaded from:
+  http://code.google.com/appengine/downloads.html
+
+
+Please follow instructions on how to install `CubicWeb` framework
+(:ref:`CubicWebInstallation`). 
+
+Installation
+------------
+
+Once ``cubicweb-ctl`` is installed, then you can create a Google
+App Engine extension of our framework by running the command ::
+
+   cubicweb-ctl newgapp <myapp>
+
+This will create a directory containing ::
+ 
+   `-- myapp/
+       |-- app.conf
+       |-- app.yaml
+       |-- bin/
+       |    `-- laxctl
+       |-- boostrap_cubes
+       |-- cubes/
+       |    |-- addressbook/
+       |    ..
+       |    |-- comment
+       |    ..
+       |    `-- zone/
+       |-- cubicweb/
+       |-- custom.py
+       |-- cw-cubes/
+       |-- dateutil/
+       |-- docutils/
+       |-- fckeditor/
+       |-- i18n/
+       |-- index.yaml
+       |-- loader.py
+       |-- logilab/
+       |-- main.py
+       |-- migration.py
+       |-- mx/
+       |-- roman.py
+       |-- rql/
+       |-- schema.py
+       |-- simplejson/
+       |-- tools/
+       |-- views.py
+       |-- vobject/
+       |-- yams/
+       `-- yapps/
+
+  
+This skeleton directory is a working `AppEngine` application. You will
+recognize the files ``app.yaml`` and ``main.py``. All the rest is the
+`CubicWeb` framework and its third-party libraries. You will notice that 
+the directory ``cubes`` is a library of reusable cubes.
+
+The main directories that you should know about are:
+
+  - ``cubes`` : this is a library of reusable yams cubes. To use 
+    those cubes you will list them in the variable 
+    `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`. 
+  - [WHICH OTHER ONES SHOULD BE LISTED HERE?]
+
+Dependencies
+~~~~~~~~~~~~
+
+Before starting anything, please make sure the following packages are installed:
+  - yaml : by default google appengine is providing yaml; make sure you can
+    import it. We recommend you create a symbolic link yaml instead of installing 
+    and using python-yaml:
+    yaml -> full/path/to/google_appengine/lib/yaml/lib/yaml/
+  - gettext
+
+Setup
+~~~~~
+
+Once you executed ``cubicweb-ctl newgapp <myapp>``, you can use that ``myapp/`` 
+as an application directory and do as follows.
+
+This installation directory provides a configuration for an instance of `CubicWeb`
+ported for Google App Engine. It is installed with its own command ``laxctl`` 
+which is a port of the command tool ``cubicweb-ctl`` originally developped for 
+`CubicWeb`.
+
+You can have the details of available commands by running ::
+
+   $ python myapp/bin/laxctl --help
+
+
+Generating translation files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`CubicWeb` is fully internationalized. Translation catalogs are found in
+``myapp/i18n``. To compile the translation files, use the `gettext` tools
+or the ``laxctl`` command ::
+
+  $ python myapp/bin/laxctl i18nupdate 
+  $ python myapp/bin/laxctl i18ncompile 
+
+Ignore the errors that print "No translation file found for domain
+'cubicweb'". They disappear after the first run of i18ncompile.
+
+.. note:: The command  myapp/bin/laxctl i18nupdate needs to be executed
+   only if your application is using cubes from cubicweb-apps.
+   Otherwise, please skip it.
+
+You will never need to add new entries in the translation catalog. Instead we would
+recommand you to use ``self.req._("msgId")`` in your application code
+to flag new message id to add to the catalog, where ``_`` refers to
+xgettext that is used to collect new strings to translate. 
+While running ``laxctl i18nupdate``, new string will be added to the catalogs.
+
+Generating the data directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to generate the ``myapp/data`` directory that holds the static
+files like stylesheets and icons, you need to run the command::
+
+  $ python myapp/bin/laxctl populatedata
+
+Generating the schema diagram
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There is a view named ``schema`` that displays a diagram of the
+entity-relationship graph defined by the schema. This diagram has to
+be generated from the command line::
+
+  $ python myapp/bin/laxctl genschema
+
+Application configuration
+-------------------------
+
+Authentication
+~~~~~~~~~~~~~~
+
+You have the option of using or not google authentication for your application.
+This has to be define in ``app.conf`` and ``app.yaml``.
+
+In ``app.conf`` modify the following variable::
+ 
+  # does this application rely on google authentication service or not.
+  use-google-auth=no
+ 
+In ``app.yaml`` comment the `login: required` set by default in the handler::
+
+  - url: .*
+  script: main.py
+  # comment the line below to allow anonymous access or if you don't want to use
+  # google authentication service
+  #login: required
+
+
+
+
+Quickstart : launch the application
+-----------------------------------
+
+On Mac OS X platforms, drag that directory on the
+`GoogleAppEngineLauncher`.
+
+On Unix and Windows platforms, run it with the dev_appserver::
+
+  $ python /path/to/google_appengine/dev_appserver.py /path/to/myapp/
+
+Once the local server is started, visit `http://MYAPP_URL/_load <http://localhost:8080/_load>`_ and sign in as administrator. 
+This will initialize the repository and enable you to log in into 
+the application and continue the installation.
+
+You should be redirected to a page displaying a message `content initialized`.
+
+Initialize the datastore
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You, then, want to visit  `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
+If you selected not  to use google authentication, you will be prompted to a 
+login form where you should initialize the administrator login (recommended
+to use admin/admin at first). You will then be redirected to a page providing
+you the value to provide to ``./bin/laxctl --cookie``.
+
+If you choosed to use google authentication, then you will not need to set up
+and administrator login but you will get the cookie value as well.
+
+This cookie values needs to be provided to ``laxctl`` commands
+in order to handle datastore administration requests.
+
+.. image:: ../images/lax-book.02-cookie-values.en.png
+   :alt: displaying the detailed view of the cookie values returned
+
+
+.. note:: In case you are not redirected to a page providing the 
+   option --cookie value, please visit one more time  
+   `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
+
+Once, you have this value, then return to the shell and execute ::
+ 
+  $ python myapp/bin/laxctl db-init --cookie='dev_appserver_login=test@example.com:True; __session=7bbe973a6705bc5773a640f8cf4326cc' localhost:8080
+
+.. note:: In the case you are not using google authentication, the value returned
+   by `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ 
+   will look like :
+   --cookie='__session=2b45d1a9c36c03d2a30cedb04bc37b6d'
+
+Log out by clicking in the menu at the top right corner
+and restart browsing from `http://MYAPP_URL/ <http://localhost:8080>`_ 
+as a normal user.
+
+Unless you did something to change it, http://MYAPP_URL should be
+http://localhost:8080/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,35 @@
+.. -*- coding: utf-8 -*-
+
+.. _Part3:
+
+-------------------------
+Part III - Administration
+-------------------------
+
+This part is for installation and administration of the `CubicWeb` framework and
+applications based on that framework.
+
+.. toctree::
+   :maxdepth: 1
+
+   setup
+   create-instance
+   instance-config
+   site-config
+   multisources
+   ldap
+   gae
+
+
+
+RQL logs
+--------
+
+You can configure the `CubicWeb` application to keep a log
+of the queries executed against your database. To do so,
+edit the configuration file of your application
+``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the
+variable ``query-log-file``::
+
+  # web application query log file
+  query-log-file=/tmp/rql-myapp.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/instance-config.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,163 @@
+.. -*- coding: utf-8 -*-
+
+
+Configure an instance
+=====================
+
+While creating an instance, a configuration file is generated in::
+
+    $ (CW_REGISTRY) / <instance> / <configuration name>.conf
+
+For example::
+
+    /etc/cubicweb.d/JPL/all-in-one.conf
+
+It is a simple text file format INI. In the following description,
+each option name is prefixed with its own section and followed by its
+default value if necessary, e.g. "`<section>.<option>` [value]."
+
+
+Configuring the Web server
+--------------------------
+:`web.auth-model` [cookie]:
+    authentication mode, cookie or http
+:`web.realm`:
+    realm of the application in http authentication mode
+:`web.http-session-time` [0]:
+    period of inactivity of an HTTP session before it closes automatically.
+    Duration in seconds, 0 meaning no expiration (or more exactly at the
+    closing of the browser client)
+
+:`main.anonymous-user`, `main.anonymous-password`:
+    login and password to use to connect to the RQL server with
+    HTTP anonymous connection. CWUser account should exist.
+
+:`main.base-url`:
+    url base site to be used to generate the urls of web pages
+
+Https configuration
+```````````````````
+It is possible to make a site accessible for anonymous http connections
+and https for authenticated users. This requires to
+use apache (for example) for redirection and the variable `main.https-url`
+of configuration file.
+
+:Example:
+
+   For an apache redirection of a site accessible via `http://localhost/demo`
+   and `https://localhost/demo` and actually running on port 8080, it
+   takes to the http:::
+
+     RewriteCond %(REQUEST_URI) ^/demo
+     RewriteRule ^/demo$ /demo/
+     RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
+  
+   and for the https:::
+
+     RewriteCond %(REQUEST_URI) ^/ demo
+     RewriteRule ^/demo$/demo/
+     RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]
+
+
+   and we will file in the all-in-one.conf of the instance:::
+
+     base-url = http://localhost/demo
+     https-url = `https://localhost/demo`
+
+Setting up the web
+--------------------------------
+:`web.embed-allowed`:
+    regular expression matching sites which could be "embedded" in
+    the site (controllers 'embed')
+:`web.submit-url`:
+    url where the bugs encountered in the application can be mailed to
+
+
+RQL server configuration
+------------------------
+:`main.host`:
+    host name if it can not be detected correctly
+:`main.pid-file`:
+    file where will be written the server pid
+:`main.uid`:
+    user account to use for launching the server when it is
+    root launched by init
+:`main.session-time [30*60]`:
+    timeout of a RQL session
+:`main.query-log-file`:
+    file where all requests RQL executed by the server are written
+
+
+Pyro configuration for the instance
+-----------------------------------
+Web server side:
+
+:`pyro-client.pyro-application-id`:
+    pyro identifier of RQL server (e.g. the instance name)
+
+RQL server side:
+
+:`pyro-server.pyro-port`:
+    pyro port number. If none is specified, a port is assigned
+    automatically.
+
+RQL and web servers side:
+
+:`pyro-name-server.pyro-ns-host`:
+    hostname hosting pyro server name. If no value is
+    specified, it is located by a request from broadcast
+:`pyro-name-server.pyro-ns-group` [cubicweb]:
+    pyro group in which to save the application
+
+
+Configuring e-mail
+------------------
+RQL and web server side:
+
+:`email.mangle-mails [no]`:
+    indicates whether the email addresses must be displayed as is or
+    transformed
+
+RQL server side:
+
+:`email.smtp-host [mail]`:
+    hostname hosting the SMTP server to use for outgoing mail
+:`email.smtp-port [25]`:
+    SMTP server port to use for outgoing mail
+:`email.sender-name`:
+    name to use for outgoing mail of the application
+:`email.sender-addr`:
+    address for outgoing mail of the application
+:`email.default dest-addrs`:
+    destination addresses by default, if used by the configuration of the
+    dissemination of the model (separated by commas)
+:`email.supervising-addrs`:
+    destination addresses of e-mails of supervision (separated by
+    commas)
+
+
+Configuring logging
+-------------------
+:`main.log-threshold`:
+    level of filtering messages (DEBUG, INFO, WARNING, ERROR)
+:`main.log-file`:
+    file to write messages
+
+
+Configuring persistent properties
+---------------------------------
+Other configuration settings are in the form of entities `CWProperty`
+in the database. It must be edited via the web interface or by
+RQL queries.
+
+:`ui.encoding`:
+    Character encoding to use for the web
+:`navigation.short-line-size`: # XXX should be in ui
+    number of characters for "short" display
+:`navigation.page-size`:
+    maximum number of entities to show per results page
+:`navigation.related-limit`:
+    number of related entities to show up on primary entity view
+:`navigation.combobox-limit`:
+    number of entities unrelated to show up on the drop-down lists of
+    the sight on an editing entity view
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/ldap.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,4 @@
+LDAP integration
+================
+
+XXX feed me
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/multisources.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,4 @@
+Integrating some data from another instance
+===========================================
+
+XXX feed me
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/setup.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,207 @@
+.. -*- coding: utf-8 -*-
+
+.. _SetUpEnv:
+
+===================================================
+Installation and set-up of a `CubicWeb` environment
+===================================================
+
+Installation of `Cubicweb` and its dependencies
+-----------------------------------------------
+
+`CubicWeb` is packaged for Debian and Ubuntu, but can be installed from source
+using a tarball or the Mercurial version control system.
+
+.. _DebianInstallation:
+
+Debian and Ubuntu packages
+```````````````````````````
+
+Depending on the distribution you are using, add the appropriate line to your list
+of sources (for example by editing ``/etc/apt/sources.list``).
+
+For Debian Lenny::
+
+  deb http://ftp.logilab.org/dists/ lenny/
+
+For Debian Sid::
+
+  deb http://ftp.logilab.org/dists/ sid/
+
+For Ubuntu Hardy::
+
+  deb http://ftp.logilab.org/dists/ hardy/
+
+
+You can now install the required packages with the following command::
+
+  apt-get update 
+  apt-get install cubicweb cubicweb-dev
+
+`cubicweb` installs the framework itself, allowing you to create
+new applications.
+
+`cubicweb-dev` installs the development environment allowing you to
+develop new cubes.
+
+There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball.
+
+
+Install from source
+```````````````````
+
+You can download the archive containing the sources from our `ftp site`_ at::
+
+  http://ftp.logilab.org/pub/cubicweb/
+
+.. _`ftp site`: http://ftp.logilab.org/pub/cubicweb/
+
+or keep up to date with on-going development by using Mercurial and its forest
+extension::
+
+  hg fclone http://www.logilab.org/hg/forests/cubicweb
+
+See :ref:`MercurialPresentation` for more details about Mercurial.
+
+Postgres installation
+`````````````````````
+
+Please refer to the `Postgresql project online documentation`_.
+
+.. _`Postgresql project online documentation`: http://www.postgresql.org/
+
+You need to install the three following packages: `postgres-8.3`,
+`postgres-contrib-8.3` and `postgresql-plpython-8.3`.
+
+
+Then you can install:
+
+* `pyro` if you wish the repository to be accessible through Pyro
+  or if the client and the server are not running on the same machine
+  (in which case the packages will have to be installed on both
+  machines)
+
+* `python-ldap` if you plan to use a LDAP source on the server
+
+.. _ConfigurationEnv:
+
+Environment configuration
+-------------------------
+
+If you installed `CubicWeb` by cloning the Mercurial forest, then you
+will need to update the environment variable PYTHONPATH by adding  
+the path to the forest ``cubicweb``:
+
+Add the following lines to either `.bashrc` or `.bash_profile` to configure
+your development environment ::
+  
+  export PYTHONPATH=/full/path/to/cubicweb-forest
+
+If you installed the debian packages, no configuration is required.
+Your new cubes will be placed in `/usr/share/cubicweb/cubes` and
+your applications will be placed in `/etc/cubicweb.d`.
+
+To use others directories then you will have to configure the
+following environment variables as follows::
+
+    export CW_CUBES_PATH=~/lib/cubes
+    export CW_REGISTRY=~/etc/cubicweb.d/
+    export CW_INSTANCE_DATA=$CW_REGISTRY
+    export CW_RUNTIME=/tmp
+
+.. note::
+    The values given above are our suggestions but of course
+    can be different.
+
+
+Databases configuration
+-----------------------
+
+
+
+.. _ConfigurationPostgres:
+
+Postgres configuration
+``````````````````````
+
+.. note::
+    If you already have an existing cluster and postgres server
+    running, you do not need to execute the initilization step
+    of your Postgres database.
+
+* First, initialize the database Postgres with the command ``initdb``.
+  ::
+
+    $ initdb -D /path/to/pgsql
+
+  Once initialized, start the database server Postgres 
+  with the command::
+  
+    $ postgres -D /path/to/psql
+
+  If you cannot execute this command due to permission issues, please
+  make sure that your username has write access on the database.
+  ::
+ 
+    $ chown username /path/to/pgsql
+
+* The database authentication can be either set to `ident sameuser`
+  or `md5`. 
+  If set to `md5`, make sure to use an existing user
+  of your database.
+  If set to `ident sameuser`, make sure that your
+  client's operating system user name has a matching user in
+  the database. If not, please do as follow to create a user::
+    
+    $ su
+    $ su - postgres
+    $ createuser -s -P username
+
+  The option `-P` (for password prompt), will encrypt the password with
+  the method set in the configuration file ``pg_hba.conf``. 
+  If you do not use this option `-P`, then the default value will be null
+  and you will need to set it with::
+    
+    $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
+
+  This login/password will be requested when you will create an
+  instance with `cubicweb-ctl create` to initialize the database of
+  your application.
+
+.. note::
+    The authentication method can be configured in ``pg_hba.conf``.
+
+
+.. FIXME Are these steps really necessary? It seemed to work without.
+
+* Installation of plain-text index extension ::
+
+    cat /usr/share/postgresql/8.3/contrib/tsearch2.sql | psql -U username template1
+
+* Installation of plpythonu language by default ::
+
+    createlang -U pgadmin plpythonu template1
+
+MySql configuration
+```````````````````
+Yout must add the following lines in /etc/mysql/my.cnf file::
+
+    transaction-isolation = READ-COMMITTED
+    default-storage-engine=INNODB
+    default-character-set=utf8
+    max_allowed_packet = 128M
+
+Pyro configuration
+------------------
+
+If you use Pyro, it is required to have a name server Pyro running on your
+network (by default it is detected by a broadcast request).
+
+To do so, you need to :
+
+* launch the server manually before starting cubicweb as a server with
+  `pyro-nsd start`
+
+* edit the file ``/etc/default/pyro-nsd`` so that the name server pyro
+  will be launched automatically when the machine fire up
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/site-config.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,94 @@
+.. -*- coding: utf-8 -*-
+
+User interface for web site configuration
+=========================================
+
+.. image:: ../images/lax-book.03-site-config-panel.en.png
+
+This panel allows you to configure the appearance of your application site.
+Six menus are available and we will go through each of them to explain how
+to use them.
+
+Navigation
+~~~~~~~~~~
+This menu provides you a way to adjust some navigation options depending on
+your needs, such as the number of entities to display by page of results.
+Follows the detailled list of available options :
+  
+* navigation.combobox-limit : maximum number of entities to display in related
+  combo box (sample format: 23)
+* navigation.page-size : maximum number of objects displayed by page of results 
+  (sample format: 23)
+* navigation.related-limit : maximum number of related entities to display in 
+  the primary view (sample format: 23)
+* navigation.short-line-size : maximum number of characters in short description
+  (sample format: 23)
+
+UI
+~~
+This menu provides you a way to customize the user interface settings such as
+date format or encoding in the produced html.
+Follows the detailled list of available options :
+
+* ui.date-format : how to format date in the ui ("man strftime" for format description)
+* ui.datetime-format : how to format date and time in the ui ("man strftime" for format
+  description)
+* ui.default-text-format : default text format for rich text fields.
+* ui.encoding : user interface encoding
+* ui.fckeditor :should html fields being edited using fckeditor (a HTML WYSIWYG editor).
+  You should also select text/html as default text format to actually get fckeditor.
+* ui.float-format : how to format float numbers in the ui
+* ui.language : language of the user interface
+* ui.main-template : id of main template used to render pages
+* ui.site-title	: site title, which is displayed right next to the logo in the header
+* ui.time-format : how to format time in the ui ("man strftime" for format description)
+
+
+Actions
+~~~~~~~
+This menu provides a way to configure the context in which you expect the actions
+to be displayed to the user and if you want the action to be visible or not. 
+You must have notice that when you view a list of entities, an action box is 
+available on the left column which display some actions as well as a drop-down 
+menu for more actions. 
+
+The context available are :
+
+* mainactions : actions listed in the left box
+* moreactions : actions listed in the `more` menu of the left box
+* addrelated : add actions listed in the left box
+* useractions : actions listed in the first section of drop-down menu 
+  accessible from the right corner user login link
+* siteactions : actions listed in the second section of drop-down menu
+  accessible from the right corner user login link
+* hidden : select this to hide the specific action
+
+Boxes
+~~~~~
+The application has already a pre-defined set of boxes you can use right away. 
+This configuration section allows you to place those boxes where you want in the
+application interface to customize it. 
+
+The available boxes are :
+
+* actions box : box listing the applicable actions on the displayed data
+
+* boxes_blog_archives_box : box listing the blog archives 
+
+* possible views box : box listing the possible views for the displayed data
+
+* rss box : RSS icon to get displayed data as a RSS thread
+
+* search box : search box
+
+* startup views box : box listing the configuration options available for 
+  the application site, such as `Preferences` and `Site Configuration`
+
+Components
+~~~~~~~~~~
+[WRITE ME]
+
+Contextual components
+~~~~~~~~~~~~~~~~~~~~~
+[WRITE ME]
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/cookbook.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,80 @@
+.. -*- coding: utf-8 -*-
+
+Cook book
+=========
+
+We gathered together some of our tricks and scripts that could make
+life easier.
+
+
+* How to import LDAP users in `CubicWeb`?
+
+  Here is a very useful script which enables you to import LDAP users
+  into your `CubicWeb` application by running the following: ::
+
+
+    import os
+    import pwd
+    import sys
+
+    from logilab.common.db import get_connection
+
+    def getlogin():
+        """avoid usinng os.getlogin() because of strange tty / stdin problems
+        (man 3 getlogin)
+        Another solution would be to use $LOGNAME, $USER or $USERNAME
+        """
+        return pwd.getpwuid(os.getuid())[0]
+
+
+    try:
+        database = sys.argv[1]
+    except IndexError:
+        print 'USAGE: python ldap2system.py <database>'
+        sys.exit(1)
+
+    if raw_input('update %s db ? [y/n]: ' % database).strip().lower().startswith('y'):
+        cnx = get_connection(user=getlogin(), database=database)
+        cursor = cnx.cursor()
+
+        insert = ('INSERT INTO euser (creation_date, eid, modification_date, login, firstname, surname, last_login_time, upassword) '
+                  "VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, %(firstname)s, %(surname)s, %(mtime)s, './fqEz5LeZnT6');")
+        update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
+        cursor.execute("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'")
+        for eid, type, source, extid, mtime in cursor.fetchall():
+            if type != 'CWUser':
+                print "don't know what to do with entity type", type
+                continue
+            if source != 'ldapuser':
+                print "don't know what to do with source type", source
+                continue
+            ldapinfos = dict(x.strip().split('=') for x in extid.split(','))
+            login = ldapinfos['uid']
+            firstname = ldapinfos['uid'][0].upper()
+            surname = ldapinfos['uid'][1:].capitalize()
+            if login != 'jcuissinat':
+                args = dict(eid=eid, type=type, source=source, login=login,
+                            firstname=firstname, surname=surname, mtime=mtime)
+                print args
+                cursor.execute(insert, args)
+                cursor.execute(update, args)
+
+        cnx.commit()
+        cnx.close()
+
+
+* How to load data from a script?
+
+  The following script aims at loading data within a script assuming pyro-nsd is
+  running and your application is configured with ``pyro-server=yes``, otherwise
+  you would not be able to use dbapi. ::
+
+    from cubicweb import dbapi
+        
+    cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
+    cur = cnx.cursor()
+    for name in ('Personal', 'Professional', 'Computers'):
+        cur.execute('INSERT Blog B: B name %s', name)
+    cnx.commit()
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/cubicweb-ctl.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,122 @@
+.. -*- coding: utf-8 -*-
+
+.. _cubicweb-ctl:
+
+``cubicweb-ctl`` tool
+=====================
+
+`cubicweb-ctl` is the swiss knife to manage `CubicWeb` instances.
+The general syntax is ::
+
+  cubicweb-ctl <command> [options command] <arguments commands>
+
+To view available commands ::
+
+  cubicweb-ctl
+  cubicweb-ctl --help
+
+Please note that the commands available depends on the `CubicWeb` packages
+and cubes that have been installed.
+
+To view the help menu on specific command ::
+
+  cubicweb-ctl <command> --help
+
+Command to create a cube
+------------------------
+
+* ``newcube``, create a new cube on the file system based on the name
+  given in the parameters. This command create a cube from an application
+  skeleton that includes default files required for debian packaging.
+  
+
+Command to create an instance
+-----------------------------
+* ``create``, creates the files for the instance configuration
+* ``db-create``, creates the system database of an instance (tables and
+  extensions only)
+* ``db-init``, initializes the system database of an instance
+  (schema, groups, users, workflows...)
+
+By default, those three commandes are encapsulated in ``create`` so
+that they can be executed consecutively.
+
+Command to create an instance for Google AppEngine datastore source
+-------------------------------------------------------------------
+* ``newgapp``, creates the configuration files for an instance
+
+This command needs to be followed by the commands responsible for
+the database initialization. As those are specific to the `datastore`,
+specific Google AppEgine database, they are not available for now
+in cubicweb-ctl, but they are available in the instance created.
+
+For more details, please see :ref:`gaecontents` .
+
+Commands to control instances
+-----------------------------
+* ``start``, starts one or more or all instances
+* ``stop``, stops one or more or all instances
+* ``restart``, restarts one or more or all instances
+* ``status``, returns the status of the instance
+
+Commands to maintain instances
+------------------------------
+* ``upgrade``, launches the existing instances migration when a new version
+  of `CubicWeb` or the cubes installed is available
+* ``shell``, opens a migration shell for manual maintenance of the instance
+* ``db-dump``, creates a dump of the system database
+* ``db-restore``, restores a dump of the system database
+* ``db-check``, checks data integrity of an instance. If the automatic correction
+  is activated, it is recommanded to create a dump before this operation.
+* ``schema-sync``, synchronizes the persistent schema of an instance with
+  the application schema. It is recommanded to create a dump before this operation.
+
+Commands to maintain i18n catalogs
+----------------------------------
+* ``i18nlibupdate``, regenerates messages catalogs of the `CubicWeb` library
+* ``i18nupdate``, regenerates the messages catalogs of a cube
+* ``i18ncompile``, recompiles the messages catalogs of an instance. 
+  This is automatically done while upgrading.
+
+See also chapter :ref:`internationalisation`.
+
+Other commands
+--------------
+* ``list``, provides a list of the available configuration, cubes
+  and instances.
+* ``delete``, deletes an instance (configuration files and database)
+
+
+Create an instance from an existing cube
+````````````````````````````````````````
+
+To create an instance from an existing cube, execute the following
+command ::
+
+   cubicweb-ctl create <cube_name> <instance_name>
+
+This command will create the configuration files of an instance in
+``~/etc/cubicweb.d/<instance_name>``.
+The tool ``cubicweb-ctl`` allows you to execute the command ``db-create``
+and ``db-init`` when you run ``create`` so that you can complete an
+instance creation in a single command.
+
+If you decide not to execut those commands while ``cubicweb-ctl create``,
+then you will have to execute them seperately(``cubicweb-ctl db-create``,
+``cubicweb-ctl db-init`` ) otherwise your installation will not be complete
+and you will not be able to launch your instance.
+
+
+Creation of an instance from a new cube
+```````````````````````````````````````
+
+Create first your new cube cube ::
+
+   cubicweb-ctl newcube <mycube>
+
+This will create a new cube in ``/path/to/forest/cubicweb/cubes/<mycube>``
+for a Mercurial forest installation, or in ``/usr/share/cubicweb/cubes``
+for a debian packages installation, and then create an instance as 
+explained just above.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/faq.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,344 @@
+.. -*- coding: utf-8 -*-
+
+Frequently Asked Questions
+==========================
+
+[XXX 'copy answer from forum' means reusing text from
+http://groups.google.com/group/google-appengine/browse_frm/thread/c9476925f5f66ec6
+and
+http://groups.google.com/group/google-appengine/browse_frm/thread/d791ce17e2716147/eb078f8cfe8426e0
+and
+http://groups.google.com/group/google-appengine/browse_frm/thread/f48cf6099973aef5/c28cd6934dd72457
+]
+
+Why does not CubicWeb have a template language ?
+------------------------------------------------
+
+  There are enough template languages out there. You can use your
+  preferred template language if you want. [explain how to use a
+  template language]
+
+  `CubicWeb` does not define its own templating language as this was
+  not our goal. Based on our experience, we realized that
+  we could gain productivity by letting designers use design tools
+  and developpers develop without the use of the templating language
+  as an intermediary that could not be anyway efficient for both parties.
+  Python is the templating language that we use in `CubicWeb`, but again,
+  it does not prevent you from using a templating language.
+
+  The reason template languages are not used in this book is that
+  experience has proved us that using pure python was less cumbersome.
+
+Why do you think using pure python is better than using a template language ?
+-----------------------------------------------------------------------------
+
+  Python is an Object Oriented Programming language and as such it
+  already provides a consistent and strong architecture and syntax
+  a templating language would not reach.
+
+  When doing development, you need a real language and template
+  languages are not real languages.
+
+  Using Python enables developing applications for which code is
+  easier to maintain with real functions/classes/contexts
+  without the need of learning a new dialect. By using Python,
+  we use standard OOP techniques and this is a key factor in a
+  robust application.
+
+Why do you use the GPL license to prevent me from doing X ?
+-----------------------------------------------------------
+
+  GPL means that *if* you redistribute your application, you need to
+  redistribute it *and* the changes you made *and* the code _linked_
+  to it under the GPL licence.
+
+  Publishing a web site has nothing to do with redistributing
+  source code. A fair amount of companies use modified GPL code
+  for internal use. And someone could publish a `CubicWeb` component
+  under a BSD licence for others to plug into a GPL framework without
+  any problem. The only thing we are trying to prevent here is someone
+  taking the framework and packaging it as closed source to his own
+  clients.
+
+
+CubicWeb looks pretty recent. Is it stable ?
+--------------------------------------------
+
+  It is constantly evolving, piece by piece.  The framework has
+  evolved over the past seven years and data has been migrated from
+  one schema to the other ever since. There is a well-defined way to
+  handle data and schema migration.
+
+<<<<<<< /home/syt/src/fcubicweb/cubicweb_3.2/doc/book/en/annexes/faq.rst
+Why is the RQL query language looking similar to X ?
+----------------------------------------------------
+=======
+Why is the RQL query language looking similar to X ?
+-----------------------------------------------------
+>>>>>>> /tmp/faq.rst~other.MxOUAP
+
+  It may remind you of SQL but it is higher level than SQL, more like
+  SPARQL. Except that SPARQL did not exist when we started the project.
+  Having SPARQL has a query language has been in our backlog for years.
+
+  That RQL language is what is going to make a difference with django-
+  like frameworks for several reasons.
+
+  1. accessing data is *much* easier with it. One can write complex
+     queries with RQL that would be tedious to define and hard to maintain
+     using an object/filter suite of method calls.
+
+  2. it offers an abstraction layer allowing your applications to run
+     on multiple back-ends. That means not only various SQL backends
+     (postgresql, sqlite, mysql), but also multiple databases at the
+     same time, and also non-SQL data stores like LDAP directories and
+     subversion/mercurial repositories (see the `vcsfile`
+     component). Google App Engine is yet another supported target for
+     RQL.
+
+[copy answer from forum, explain why similar to sparql and why better
+  than django and SQL]
+
+which ajax library
+------------------
+[we use jquery and things on top of that]
+
+
+How to implement security?
+--------------------------
+
+  This is an example of how it works in our framework::
+
+    class Version(EntityType):
+    """a version is defining the content of a particular project's
+    release"""
+    # definition of attributes is voluntarily missing
+    permissions = {'read': ('managers', 'users', 'guests',),
+                   'update': ('managers', 'logilab', 'owners',),
+                   'delete': ('managers', ),
+                   'add': ('managers', 'logilab',
+                        ERQLExpression('X version_of PROJ, U in_group G, PROJ
+                        require_permission P, P name "add_version", P require_group G'),)}
+
+  The above means that permission to read a Version is granted to any
+  user that is part of one of the groups 'managers', 'users', 'guests'.
+  The 'add' permission is granted to users in group 'managers' or
+  'logilab' and to users in group G, if G is linked by a permission
+  entity named "add_version" to the version's project.
+  ::
+
+    class version_of(RelationType):
+        """link a version to its project. A version is necessarily linked
+        to one and only one project. """
+        # some lines voluntarily missing
+        permissions = {'read': ('managers', 'users', 'guests',), 
+                       'delete': ('managers', ),
+                       'add': ('managers', 'logilab',
+                            RRQLExpression('O require_permission P, P name "add_version",
+                            'U in_group G, P require_group G'),) }
+
+  You can find additional information in the section :ref:`security`.
+
+  [XXX what does the second example means in addition to the first one?]
+
+
+`Error while publishing rest text ...`
+--------------------------------------
+  While modifying the description of an entity, you get an error message in
+  the application `Error while publishing ...` for Rest text and plain text.
+  The server returns a traceback like as follows ::
+
+      2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text
+      Traceback (most recent call last):
+      File "/home/user/src/blogdemo/cubicweb/common/rest.py", line 217, in rest_publish
+      File "/usr/lib/python2.5/codecs.py", line 817, in open
+      file = __builtin__.open(filename, mode, buffering)
+      TypeError: __init__() takes at most 3 arguments (4 given)
+
+
+  This can be fixed by applying the patch described in :
+  http://code.google.com/p/googleappengine/issues/detail?id=48
+
+What are hooks used for?
+------------------------
+
+  Hooks are executed around (actually before or after) events.  The
+  most common events are data creation, update and deletion.  They
+  permit additional constraint checking (those not expressible at the
+  schema level), pre and post computations depending on data
+  movements.
+
+  As such, they are a vital part of the framework.
+
+  Other kinds of hooks, called Operations, are available
+  for execution just before commit.
+
+When should you define an HTML template rather than define a graphical component?
+---------------------------------------------------------------------------------
+
+  An HTML template cannot contain code, hence it is only about static
+  content.  A component is made of code and operations that apply on a
+  well defined context (request, result set). It enables much more
+  dynamic views.
+
+What is the difference between `AppRsetObject` and `AppObject` ?
+----------------------------------------------------------------
+
+  `AppRsetObject` instances are selected on a request and a result
+  set. `AppObject` instances are directly selected by id.
+
+How to update a database after a schema modification?
+-----------------------------------------------------
+
+  It depends on what has been modified in the schema.
+
+  * Update of an attribute permissions and properties: 
+    ``synchronize_eschema('MyEntity')``.
+
+  * Update of a relation permissions and properties: 
+    ``synchronize_rschema('MyRelation')``.
+
+  * Add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
+
+  * Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
+
+
+How to create an anonymous user?
+--------------------------------
+
+  This allows to bypass authentication for your site. In the
+  ``all-in-one.conf`` file of your instance, define the anonymous user
+  as follows ::
+
+    # login of the CubicWeb user account to use for anonymous user (if you want to
+    # allow anonymous)
+    anonymous-user=anon
+
+    # password of the CubicWeb user account matching login
+    anonymous-password=anon
+
+  You also must ensure that this `anon` user is a registered user of
+  the DB backend. If not, you can create through the administation
+  interface of your instance by adding a user with the role `guests`.
+  This could be the admin account (for development
+  purposes, of course).
+
+.. note::
+    While creating a new instance, you can decide to allow access
+    to anonymous user, which will automatically execute what is
+    decribed above.
+
+
+How to change the application logo?
+-----------------------------------
+
+  There are two ways of changing the logo.
+
+  1. The easiest way to use a different logo is to replace the existing
+     ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
+     By default all application will look for a ``logo.png`` to be
+     rendered in the logo section.
+
+     .. image:: ../images/lax-book.06-main-template-logo.en.png
+
+  2. In your cube directory, you can specify which file to use for the logo.
+     This is configurable in ``mycube/data/external_resources``: ::
+
+       LOGO = DATADIR/path/to/mylogo.gif
+
+     where DATADIR is ``mycubes/data``.
+
+
+How to configure LDAP source?
+-------------------------------
+
+  Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
+  Configuring an LDAP source is about declaring that source in your
+  instance configuration file such as: ::
+
+    [ldapuser]
+    adapter=ldapuser
+    # ldap host
+    host=myhost
+    # base DN to lookup for usres
+    user-base-dn=ou=People,dc=mydomain,dc=fr
+    # user search scope
+    user-scope=ONELEVEL
+    # classes of user
+    user-classes=top,posixAccount
+    # attribute used as login on authentication
+    user-login-attr=uid
+    # name of a group in which ldap users will be by default
+    user-default-group=users
+    # map from ldap user attributes to cubicweb attributes
+    user-attrs-map=gecos:email,uid:login
+
+  Any change applied to configuration file requires to restart your
+  application.
+
+I get NoSelectableObject exceptions: how do I debug selectors ?
+---------------------------------------------------------------
+
+  You just need to put the appropriate context manager around view/component
+  selection: ::
+
+    from cubicweb.common.selectors import traced_selection
+    with traced_selection():
+        comp = self.vreg.select_object('contentnavigation', 'wfhistory',
+                                       self.req, rset, context='navcontentbottom')
+
+  This will yield additional WARNINGs, like this: ::
+
+    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
+
+How to format an entity date attribute?
+---------------------------------------
+
+  If your schema has an attribute of type Date or Datetime, you might
+  want to format it. First, you should define your preferred format using
+  the site configuration panel ``http://appurl/view?vid=systemepropertiesform``
+  and then set ``ui.date`` and/or ``ui.datetime``.
+  Then in the view code, use::
+    
+    self.format_date(entity.date_attribute)
+
+Can PostgreSQL and CubicWeb authentication work with kerberos ?
+----------------------------------------------------------------
+
+  If you have postgresql set up to accept kerberos authentication, you can set
+  the db-host, db-name and db-user parameters in the `sources` configuration
+  file while leaving the password blank. It should be enough for your instance
+  to connect to postgresql with a kerberos ticket.
+
+  
+How to load data from a script?
+-------------------------------
+
+  The following script aims at loading data within a script assuming pyro-nsd is
+  running and your application is configured with ``pyro-server=yes``, otherwise
+  you would not be able to use dbapi. ::
+
+    from cubicweb import dbapi
+
+    cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
+    cur = cnx.cursor()
+    for name in ('Personal', 'Professional', 'Computers'):
+        cur.execute('INSERT Blog B: B name %s', name)
+    cnx.commit()
+
+What is the CubicWeb datatype corresponding to GAE datastore's UserProperty?
+----------------------------------------------------------------------------
+
+  If you take a look at your application schema and
+  click on "display detailed view of metadata" you will see that there
+  is a Euser entity in there. That's the one that is modeling users. The
+  thing that corresponds to a UserProperty is a relationship between
+  your entity and the Euser entity. As in ::
+
+    class TodoItem(EntityType):
+       text = String()
+       todo_by = SubjectRelation('Euser')
+
+  [XXX check that cw handle users better by
+  mapping Google Accounts to local Euser entities automatically]
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,26 @@
+.. -*- coding: utf-8 -*-
+
+.. _Part4:
+
+--------------------
+Part IV - Appendixes
+--------------------
+
+The following chapters are reference material.
+ 
+.. toctree::
+   :maxdepth: 1
+
+   faq
+   cookbook
+   cubicweb-ctl
+   rql/index
+   mercurial
+
+(X)HTML tricks to apply
+-----------------------
+
+Some web browser (Firefox for example) are not happy with empty `<div>`
+(by empty we mean that there is no content in the tag, but there
+could be attributes), so we should always use `<div></div>` even if
+it is empty and not use `<div/>`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/mercurial.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,133 @@
+.. -*- coding: utf-8 -*-
+
+.. _MercurialPresentation:
+
+Introducing Mercurial
+=====================
+
+Introduction
+````````````
+Mercurial_ manages a distributed repository containing revisions
+trees (each revision indicates the changes required to obtain the
+next, and so on). Locally, we have a repository containing revisions
+tree, and a working directory. It is possible
+to put in its working directory, one of the versions of its local repository,
+modify and then push it in its repository. 
+It is also possible to get revisions from another repository or to export
+its own revisions from the local repository to another repository.
+
+.. _Mercurial: http://www.selenic.com/mercurial/
+
+In contrast to CVS/Subversion, we usually create a repository by
+project to manage.
+
+In a collaborative development, we usually create a central repository
+accessible to all developers of the project. These central repository is used
+as a reference. According to its needs, then everyone can have a local repository,
+that you will have to synchronize with the central repository from time to time.
+
+
+Major commands
+``````````````
+* Create a local repository::
+
+     hg clone ssh://myhost//home/src/repo
+
+* See the contents of the local repository (graphical tool in Tk)::
+
+     hgview
+
+* Add a sub-directory or file in the current directory::
+
+     hg add subdir
+
+* Move to the working directory a specific revision (or last
+  revision) from the local repository::
+
+     hg update [identifier-revision]
+     hg up [identifier-revision]
+
+* Get in its local repository, the tree of revisions contained in a
+  remote repository (this does not change the local directory)::
+
+     hg pull ssh://myhost//home/src/repo
+     hg pull -u ssh://myhost//home/src/repo # equivalent to pull + update
+
+* See what are the heads of branches of the local repository if a `pull`
+  returned a new branch::
+
+     hg heads
+
+* Submit the working directory in the local repository (and create a new
+  revision)::
+
+     hg commit
+     hg ci
+
+* Merge with the mother revision of local directory, another revision from
+  the local respository (the new revision will be then two mothers
+  revisions)::
+
+     hg merge identifier-revision
+
+* Export to a remote repository, the tree of revisions in its content
+  local respository (this does not change the local directory)::
+
+     hg push ssh://myhost//home/src/repo
+
+* See what local revisions are not in another repository::
+
+     hg outgoing ssh://myhost//home/src/repo
+
+* See what are the revisions of a repository not found locally::
+
+     hg incoming ssh://myhost//home/src/repo
+
+* See what is the revision of the local repository which has been taken out 
+  from the working directory and amended::
+
+     hg parent
+
+* See the differences between the working directory and the mother revision
+  of the local repository, possibly to submit them in the local repository::
+
+     hg diff
+     hg commit-tool
+     hg ct
+
+
+Best Practices
+``````````````
+* Remember to `hg pull -u` regularly, and particularly before
+   a `hg commit`.
+
+* Remember to `hg push` when your repository contains a version
+  relatively stable of your changes.
+
+* If a `hg pull -u` created a new branch head:
+
+   1. find its identifier with `hg head`
+   2. merge with `hg merge`
+   3. `hg ci`
+   4. `hg push`
+
+Installation of the forest extension
+````````````````````````````````````
+
+Set up the forest extension by getting a copy of the sources 
+from http://hg.akoha.org/hgforest/ and adding the following 
+lines to your ``~/.hgrc``: ::
+
+   [extensions]
+   hgext.forest=
+   # or, if forest.py is not in the hgext dir:
+   # forest=/path/to/forest.py
+
+
+More information
+````````````````
+
+For more information about Mercurial, please refer to the Mercurial project online documentation_.
+
+.. _documentation: http://www.selenic.com/mercurial/wiki/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/rql/implementation.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,169 @@
+
+
+Implementation
+--------------
+BNF grammar
+~~~~~~~~~~~
+
+The terminal elements are in capital letters, non-terminal in lowercase.
+The value of the terminal elements (between quotes) is a Python regular
+expression.
+::
+
+     statement:: = (select | delete | insert | update) ';'
+
+
+     # select specific rules
+     select      ::= 'DISTINCT'? E_TYPE selected_terms restriction? group? sort?
+
+     selected_terms ::= expression ( ',' expression)*
+
+     group       ::= 'GROUPBY' VARIABLE ( ',' VARIABLE)*
+
+     sort        ::= 'ORDERBY' sort_term ( ',' sort_term)*
+
+     sort_term   ::=  VARIABLE sort_method =?
+
+     sort_method ::= 'ASC' | 'DESC'
+
+
+     # delete specific rules
+     delete ::= 'DELETE' (variables_declaration | relations_declaration) restriction?
+
+
+     # insert specific rules
+     insert ::= 'INSERT' variables_declaration ( ':' relations_declaration)? restriction?
+
+
+     # update specific rules
+     update ::= 'SET' relations_declaration restriction
+
+
+     # common rules
+     variables_declaration ::= E_TYPE VARIABLE (',' E_TYPE VARIABLE)*
+
+     relations_declaration ::= simple_relation (',' simple_relation)*
+
+     simple_relation ::= VARIABLE R_TYPE expression
+
+     restriction ::= 'WHERE' relations
+
+     relations   ::= relation (LOGIC_OP relation)*
+                   | '(' relations')'
+
+     relation    ::= 'NOT'? VARIABLE R_TYPE COMP_OP? expression
+                   | 'NOT'? R_TYPE VARIABLE 'IN' '(' expression (',' expression)* ')'
+                   
+     expression  ::= var_or_func_or_const (MATH_OP var_or_func_or_const) *
+                   | '(' expression ')'
+
+     var_or_func_or_const ::= VARIABLE | function | constant
+
+     function    ::= FUNCTION '(' expression ( ',' expression) * ')'
+
+     constant    ::= KEYWORD | STRING | FLOAT | INT
+
+     # tokens
+     LOGIC_OP ::= ',' | 'OR' | 'AND'
+     MATH_OP  ::= '+' | '-' | '/' | '*'
+     COMP_OP  ::= '>' | '>=' | '=' | '<=' | '<' | '~=' | 'LIKE'
+
+     FUNCTION ::= 'MIN' | 'MAX' | 'SUM' | 'AVG' | 'COUNT' | 'UPPER' | 'LOWER'
+
+     VARIABLE ::= '[A-Z][A-Z0-9]*'
+     E_TYPE   ::= '[A-Z]\w*'
+     R_TYPE   ::= '[a-z_]+'
+
+     KEYWORD  ::= 'TRUE' | 'FALSE' | 'NULL' | 'TODAY' | 'NOW'
+     STRING   ::= "'([^'\]|\\.)*'" |'"([^\"]|\\.)*\"'
+     FLOAT    ::= '\d+\.\d*'
+     INT      ::= '\d+'
+
+
+Internal representation (syntactic tree)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The tree research does not contain the selected variables 
+(e.g. there is only what follows "WHERE").
+
+The insertion tree does not contain the variables inserted or relations
+defined on these variables (e.g. there is only what follows "WHERE").
+
+The removal tree does not contain the deleted variables and relations
+(e.g. there is only what follows the "WHERE").
+
+The update tree does not contain the variables and relations updated
+(e.g. there is only what follows the "WHERE").
+
+::
+
+     Select         ((Relationship | And | Or)?, Group?, Sort?)
+     Insert         (Relations | And | Or)?
+     Delete         (Relationship | And | Or)?
+     Update         (Relations | And | Or)?
+
+     And            ((Relationship | And | Or), (Relationship | And | Or))
+     Or             ((Relationship | And | Or), (Relationship | And | Or))
+
+     Relationship   ((VariableRef, Comparison))
+
+     Comparison     ((Function | MathExpression | Keyword | Constant | VariableRef) +)
+
+     Function       (())
+     MathExpression ((MathExpression | Keyword | Constant | VariableRef), (MathExpression | Keyword | Constant | VariableRef))
+
+     Group          (VariableRef +)
+     Sort           (SortTerm +)
+     SortTerm       (VariableRef +)
+
+     VariableRef    ()
+     Variable       ()
+     Keyword        ()
+     Constant       ()
+
+
+Known limitations
+~~~~~~~~~~~~~~~~~
+
+- The current implementation does not support linking two relations of type 'is'
+  with a OR. I do not think that the negation is supported on this type of
+  relation (XXX FIXME to be confirmed).
+
+- Relations defining the variables must be left to those using them.  For
+  example::
+
+     Point P where P abs X, P ord Y, P value X+Y
+
+  is valid, but::
+
+     Point P where P abs X, P value X+Y, P ord Y
+
+  is not.
+
+- missing proper explicit type conversion,  COALESCE and certainly other things...
+
+- writing a rql query require knowledge of the schema used (with real relation
+  names and entities, not those viewing in the user interface). On the other
+  hand, we can not really bypass that, and it is the job of a user interface to
+  hide the RQL.
+
+
+Topics
+~~~~~~
+
+It would be convenient to express the schema matching
+relations (non-recursive rules)::
+
+     Document class Type <-> Document occurence_of Fiche class Type
+     Sheet class Type    <-> Form collection Collection class Type
+    
+Therefore 1. becomes::
+
+     Document X where
+     X class C, C name 'Cartoon'
+     X owned_by U, U login 'syt'
+     X available true
+
+I'm not sure that we should handle this at RQL level ...
+
+There should also be a special relation 'anonymous'.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/rql/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,12 @@
+Relation Query Language (RQL)
+=============================
+
+This chapter describes the Relation Query Language syntax and its implementation in CubicWeb.
+
+.. toctree::
+   :maxdepth: 1
+
+   intro
+   language
+   dbapi
+   implementation
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/rql/intro.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,66 @@
+
+Introduction
+------------
+
+Goals of RQL
+~~~~~~~~~~~~
+
+The goal is to have a language emphasizing the way of browsing
+relations. As such, attributes will be regarded as cases of
+special relations (in terms of implementation, the language
+user should see virtually no difference between an attribute and a
+relation).
+
+RQL is inspired by SQL but is the highest level. A knowledge of the 
+`CubicWeb` schema defining the application is necessary.
+
+Comparison with existing languages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SQL
+```
+RQL builds on the features of SQL but is at a higher level
+(the current implementation of RQL generates SQL). For that it is limited
+to the way of browsing relations and introduces variables. 
+The user does not need to know the model underlying SQL, but the `CubicWeb` 
+schema defining the application.
+
+Versa
+`````
+We should look in more detail, but here are already some ideas for
+the moment ... Versa_ is the language most similar to what we wanted
+to do, but the model underlying data being RDF, there is some
+number of things such as namespaces or handling of the RDF types which 
+does not interest us. On the functionality level, Versa_ is very comprehensive
+including through many functions of conversion and basic types manipulation,
+which may need to be guided at one time or another. 
+Finally, the syntax is a little esoteric.
+
+Sparql
+``````
+The query language most similar to RQL is SPARQL_, defined by the W3C to serve
+for the semantic web. 
+
+
+The different types of queries
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Search (`Any`)
+   Extract entities and attributes of entities.
+
+Insert entities (`INSERT`)
+   Insert new entities or relations in the database.
+   It can also directly create relationships for the newly created entities.
+
+Update entities, create relations (`SET`)
+   Update existing entities in the database,
+   or create relations between existing entities.
+
+Delete entities or relationship (`DELETE`)
+   Remove entities or relations existing in the database.
+
+   
+
+
+.. _Versa: http://uche.ogbuji.net/tech/rdf/versa/
+.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/rql/language.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,377 @@
+.. -*- coding: utf-8 -*-
+
+.. _RQL:
+
+RQL syntax
+----------
+
+Reserved keywords
+~~~~~~~~~~~~~~~~~
+The keywords are not case sensitive.
+
+::
+
+     DISTINCT, INSERT, SET, DELETE,
+     WHERE, AND, OR, NOT, EXISTS,
+     IN, LIKE, UNION, WITH, BEING,
+     TRUE, FALSE, NULL, TODAY, NOW,
+     LIMIT, OFFSET,
+     HAVING, GROUPBY, ORDERBY, ASC, DESC
+
+
+Variables and Typing
+~~~~~~~~~~~~~~~~~~~~
+
+With RQL, we do not distinguish between entities and attributes. The
+value of an attribute is considered an entity of a particular type (see
+below), linked to one (real) entity by a relation called the name of
+the attribute.
+
+Entities and values to browse and/or select are represented in
+the query by *variables* that must be written in capital letters.
+
+There is a special type **Any**, referring to a non specific type.
+
+We can restrict the possible types for a variable using the
+special relation **is**.
+The possible type(s) for each variable is derived from the schema
+according to the constraints expressed above and thanks to the relations between
+each variable.
+
+Built-in types
+``````````````
+
+The base types supported are string (between double or single quotes),
+integers or floats (the separator is '.'), dates and
+boolean. We expect to receive a schema in which types String,
+Int, Float, Date and Boolean are defined.
+
+* `String` (literal: between double or single quotes).
+* `Int`, `Float` (separator being'.').
+* `Date`, `Datetime`, `Time` (literal: string YYYY/MM/DD [hh:mm] or keywords
+  `TODAY` and `NOW`).
+* `Boolean` (keywords `TRUE` and `FALSE`).
+* `Keyword` NULL.
+
+
+Operators
+~~~~~~~~~
+
+Logical Operators
+`````````````````
+::
+
+     AND, OR, NOT, ','
+
+  ',' is equivalent to 'AND' but with the smallest among the priority
+  of logical operators (see :ref:`PriorityOperators`).
+
+Mathematical Operators
+````````````````````
+::
+
+     +, -, *, /
+
+Comparison operators
+````````````````````
+::
+
+     =, <, <=, >=, >, ~=, IN, LIKE
+
+* The operator `=` is the default operator.
+
+* The operator `LIKE` equivalent to `~=` can be used with the
+  special character `%` in a string to indicate that the chain 
+  must start or finish by a prefix/suffix:
+  ::
+
+     Any X WHERE X name ~= 'Th%'
+     Any X WHERE X name LIKE '%lt'
+
+* The operator `IN` provides a list of possible values:
+  ::
+  
+    Any X WHERE X name IN ( 'chauvat', 'fayolle', 'di mascio', 'thenault')
+
+
+XXX nico: "A trick <> 'bar'" wouldn't it be more convenient than 
+"NOT A trick 'bar'" ?
+
+.. _PriorityOperators:
+
+Operators priority
+``````````````````
+
+1. '*', '/'
+
+2. '+', '-'
+
+3. 'not'
+
+4 'and'
+
+5 'or'
+
+6 ','
+
+
+Search Query
+~~~~~~~~~~~~
+
+   [ `DISTINCT`] <entity type> V1 (, V2) \ *
+   [ `GROUPBY` V1 (V2) \*] [ `ORDERBY` <orderterms>]
+   [ `LIMIT` <value>] [ `OFFSET` <value>]
+   [ `WHERE` <restriction>]
+   [ `WITH` V1 (, V2) \ * BEING (<query>)]
+   [ `HAVING` <restriction>]
+   [ `UNION` <query>]
+
+:entity type:
+   Type of selected variables.
+   The special type `Any` is equivalent to not specify a type.
+:restriction:
+   list of conditions to test successively 
+     `V1 relation V2 | <static value>`
+:orderterms:
+   Definition of the selection order: variable or column number followed by
+   sorting method ( `ASC`, `DESC`), ASC is the default.
+:note for grouped queries:
+   For grouped queries (e.g., a clause `GROUPBY`), all
+   selected variables must be aggregated or grouped.
+
+
+Sorting and groups
+``````````````````
+
+- For grouped queries (e.g. with a GROUPBY clause), all
+  selected variables should be grouped.
+
+- To group and/or sort by attributes, we can do: "X,L user U, U
+  login L GROUPBY L, X ORDERBY L"
+
+- If the sorting method (SORT_METHOD) is not specified, then the sorting is
+  ascendant.
+
+- Aggregate Functions: COUNT, MIN, MAX, AVG, SUM
+
+
+Negation
+````````
+
+* A query such as `Document X WHERE NOT X owned_by U` means "the
+  documents have no relation `owned_by`".
+* But the query `Document X WHERE NOT X owned_by U, U login "syt"`
+  means "the documents have no relation `owned_by` with the user
+  syt". They may have a relation "owned_by" with another user.
+
+Identity
+````````
+
+You can use the special relation `identity` in a query to 
+add an identity constraint between two variables. This is equivalent
+to ``is`` in python::
+
+   Any A WHERE A comments B, A identity B
+
+return all objects that comment themselves. The relation
+`identity` is especially useful when defining the rules for securities
+with `RQLExpressions`.
+
+
+Limit / offset
+``````````````
+::
+    
+    Any P ORDERBY N LIMIT 5 OFFSET 10 WHERE P is Person, P firstname N
+
+Function calls
+``````````````
+::
+    
+    Any UPPER(N) WHERE P firstname N
+
+Functions on string: UPPER, LOWER
+    
+Exists
+``````
+::
+    
+    Any X ORDERBY PN,N
+    WHERE X num N, X version_of P, P name PN, 
+          EXISTS(X in_state S, S name IN ("dev", "ready"))
+          OR EXISTS(T tags X, T name "priority")
+
+
+Optional relations (Left outer join)
+````````````````````````````````````
+
+* They allow you to select entities related or not to another.
+
+* You must use the `?` behind the variable to specify that the relation
+  toward it is optional:
+
+   - Anomalies of a project attached or not to a version ::
+
+       Any X, V WHERE X concerns P, P eid 42, X corrected_in V?
+
+   - All cards and the project they document if necessary ::
+
+       Any C, P WHERE C is Card, P? documented_by C
+
+    Any T,P,V WHERE T is Ticket, T concerns P, T done_in V?
+    
+    
+Having
+``````
+::
+    
+    Any X GROUPBY X WHERE X knows Y HAVING COUNT(Y) > 10
+
+Subqueries
+``````````
+::
+
+    (Any X WHERE X is Person) UNION (Any X WHERE X is Company)
+    
+
+     DISTINCT Any W, REF
+        WITH W, REF BEING 
+            (
+	      (Any W, REF WHERE W is Workcase, W ref REF, 
+                                 W concerned_by D, D name "Logilab")
+               UNION 
+              (Any W, REF WHERE W is Workcase, W ref REF, '
+                                W split_into WP, WP name "WP1")
+            )
+
+
+Examples
+````````
+
+- *Search for the object of identifier 53*
+  ::
+
+        Any WHERE X
+        X eid 53
+
+- *Search material such as comics, owned by syt and available*
+  ::
+
+        Any X WHERE X is Document
+        X occurence_of F, F class C, C name 'Comics'
+        X owned_by U, U login 'syt'
+        X available TRUE
+
+- *Looking for people working for eurocopter interested in training*
+  ::
+
+        Any P WHERE
+        P is Person, P work_for S, S name 'Eurocopter'
+        P interested_by T, T name 'training'
+
+- *Search note less than 10 days old written by jphc or ocy*
+  ::
+
+        Any N WHERE
+        N is Note, N written_on D, D day> (today -10),
+        N written_by P, P name 'jphc' or P name 'ocy'
+
+- *Looking for people interested in training or living in Paris*
+  ::
+
+        Any P WHERE
+        P is Person, (P interested_by T, T name 'training') OR
+        (P city 'Paris')
+
+- *The name and surname of all people*
+  ::
+
+        Any N, P WHERE
+        X is Person, X name N, X first_name P
+
+  Note that the selection of several entities generally force
+  the use of "Any" because the type specification applies otherwise
+  to all the selected variables. We could write here
+  ::
+
+        String N, P WHERE
+        X is Person, X name N, X first_name P
+
+
+  Note: You can not specify several types with * ... where X is FirstType or X is SecondType*.
+  To specify several types explicitely, you have to do
+
+  ::
+
+        Any X where X is in (FirstType, SecondType)
+
+
+Insertion query
+~~~~~~~~~~~~~~~~
+
+    `INSERT` <entity type> V1 (, <entity type> V2) \ * `:` <assignments>
+    [ `WHERE` <restriction>]
+
+:assignments:
+   list of relations to assign in the form `V1 relationship V2 | <static value>`
+
+The restriction can define variables used in assignments.
+
+Caution, if a restriction is specified, the insertion is done for 
+*each line result returned by the restriction*.
+
+- *Insert a new person named 'foo'*
+  ::
+
+        INSERT Person X: X name 'foo'
+
+- *Insert a new person named 'foo', another called 'nice' and a 'friend' relation
+  between them*
+  ::
+
+        INSERT Person X, Person Y: X name 'foo', Y name 'nice', X friend Y
+
+- *Insert a new person named 'foo' and a 'friend' relation with an existing 
+  person called 'nice'*
+  ::
+
+        INSERT Person X: X name 'foo', X friend  Y WHERE name 'nice'
+
+Update and relation creation queries
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    `SET` <assignements>
+    [ `WHERE` <restriction>]
+
+Caution, if a restriction is specified, the update is done *for
+each result line returned by the restriction*.
+
+- *Renaming of the person named 'foo' to 'bar' with the first name changed*
+  ::
+
+        SET X name 'bar', X first_name 'original' WHERE X is Person, X name 'foo'
+
+- *Insert a relation of type 'know' between objects linked by 
+  the relation of type 'friend'*
+  ::
+
+        SET X know Y  WHERE X friend Y
+
+
+Deletion query
+~~~~~~~~~~~~~~
+    `DELETE` (<entity type> V) | (V1 relation v2 ),...
+    [ `WHERE` <restriction>]
+
+Caution, if a restriction is specified, the deletion is made *for
+each line result returned by the restriction*.
+
+- *Deletion of the person named 'foo'*
+  ::
+
+        DELETE Person X WHERE X name 'foo'
+
+- *Removal of all relations of type 'friend' from the person named 'foo'*
+  ::
+
+        DELETE X friend Y WHERE X is Person, X name 'foo'
+
--- a/doc/book/en/conf.py	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/en/conf.py	Thu May 14 12:50:34 2009 +0200
@@ -29,14 +29,14 @@
 templates_path = ['.templates']
 
 # The suffix of source filenames.
-source_suffix = '.txt'
+source_suffix = '.rst'
 
 # The master toctree document.
 master_doc = 'index'
 
 # General substitutions.
 project = 'Cubicweb'
-copyright = '2008, Logilab'
+copyright = '2008-2009, Logilab'
 
 # The default replacements for |version| and |release|, also used in various
 # other places throughout the built documents.
@@ -44,7 +44,7 @@
 # The short X.Y version.
 version = '0.54'
 # The full version, including alpha/beta/rc tags.
-release = '3.0'
+release = '3.2'
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
@@ -53,7 +53,7 @@
 today_fmt = '%B %d, %Y'
 
 # List of documents that shouldn't be included in the build.
-#unused_docs = []
+unused_docs = ['D070-modules-cbw-api.en',]
 
 # List of directories, relative to source directories, that shouldn't be searched
 # for source files.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/cubes/available-cubes.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,64 @@
+
+Available cubes
+---------------
+
+An application is based on several basic cubes. In the set of available
+basic cubes we can find for example :
+
+Base entity types
+~~~~~~~~~~~~~~~~~
+* addressbook_: PhoneNumber and PostalAddress
+* card_: Card, generic documenting card
+* event_: Event (define events, display them in calendars)
+* file_: File (to allow users to upload and store binary or text files)
+* link_: Link (to collect links to web resources)
+* mailinglist_: MailingList (to reference a mailing-list and the URLs
+  for its archives and its admin interface)
+* person_: Person (easily mixed with addressbook)
+* task_: Task (something to be done between start and stop date)
+* zone_: Zone (to define places within larger places, for example a
+  city in a state in a country)
+
+
+Classification
+~~~~~~~~~~~~~~
+* folder_: Folder (to organize things but grouping them in folders)
+* keyword_: Keyword (to define classification schemes)
+* tag_: Tag (to tag anything)
+
+Other features
+~~~~~~~~~~~~~~
+* basket_: Basket (like a shopping cart)
+* blog_: a blogging system uxing Blog and BlogEntry entity types
+* comment_: system to attach comment threads to entities)
+* email_: archiving management for emails (`Email`, `Emailpart`,
+  `Emailthread`), trigger action in cubicweb through email
+
+
+
+
+
+.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
+.. _basket: http://www.cubicweb.org/project/cubicweb-basket
+.. _card: http://www.cubicweb.org/project/cubicweb-card
+.. _blog: http://www.cubicweb.org/project/cubicweb-blog
+.. _comment: http://www.cubicweb.org/project/cubicweb-comment
+.. _email: http://www.cubicweb.org/project/cubicweb-email
+.. _event: http://www.cubicweb.org/project/cubicweb-event
+.. _file: http://www.cubicweb.org/project/cubicweb-file
+.. _folder: http://www.cubicweb.org/project/cubicweb-folder
+.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
+.. _link: http://www.cubicweb.org/project/cubicweb-link
+.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
+.. _person: http://www.cubicweb.org/project/cubicweb-person
+.. _tag: http://www.cubicweb.org/project/cubicweb-tag
+.. _task: http://www.cubicweb.org/project/cubicweb-task
+.. _zone: http://www.cubicweb.org/project/cubicweb-zone
+
+To declare the use of a component, once installed, add the name of the component
+to the variable `__use__` in the file `__pkginfo__.py` of your own component.
+
+.. note::
+  The listed cubes above are available as debian-packages on `CubicWeb's forge`_.
+
+.. _`CubicWeb's forge`: http://www.cubicweb.org/project?vtitle=All%20cubicweb%20projects
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/cubes/cc-newcube.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,46 @@
+Creating a new cube from scratch using :command:`cubicweb-ctl newcube`
+----------------------------------------------------------------------
+
+Let's start by creating the cube environment in which we will develop ::
+
+  cd ~/hg
+
+  cubicweb-ctl newcube mycube
+
+  # answer questions 
+  hg init moncube
+  cd mycube
+  hg add .
+  hg ci
+
+If all went well, you should see the cube you just create in the list
+returned by `cubicweb-ctl list` in the section *Available components*,
+and if it is not the case please refer to :ref:`ConfigurationEnv`.
+
+To use a cube, you have to list it in the variable ``__use__``
+of the file ``__pkginfo__.py`` of the instance.
+This variable is used for the instance packaging (dependencies
+handled by system utility tools such as APT) and the usable cubes
+at the time the base is created (import_erschema('MyCube') will
+not properly work otherwise).
+
+.. note::
+    Please note that if you do not wish to use default directory
+    for your cubes library, then you want to use the option
+    --directory to specify where you would like to place
+    the source code of your cube:
+    ``cubicweb-ctl newcube --directory=/path/to/cubes/library cube_name``
+
+    
+Usage of :command:`cubicweb-ctl liveserver`
+-------------------------------------------
+
+To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
+which allows to create an instance in memory (using an SQLite database by 
+default) and make it accessible through a web server ::
+
+  cubicweb-ctl live-server mycube
+
+or by using an existing database (SQLite or Postgres)::
+
+  cubicweb-ctl live-server -s myfile_sources mycube
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/cubes/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,11 @@
+Cubes
+=====
+
+This chapter describes how to define your own cubes and reuse already available cubes.
+
+.. toctree::
+   :maxdepth: 1
+
+   layout.rst
+   cc-newcube.rst
+   available-cubes.rst
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/cubes/layout.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,138 @@
+
+.. _foundationsCube:
+
+.. _cubelayout:
+
+Standard structure for a cube
+-----------------------------
+
+A cube is structured as follows:
+
+::
+
+  mycube/
+  |
+  |-- data/
+  |   |-- cubes.mycube.css
+  |   |-- cubes.mycube.js
+  |   `-- external_resources
+  |
+  |-- debian/
+  |   |-- changelog
+  |   |-- compat
+  |   |-- control
+  |   |-- copyright
+  |   |-- cubicweb-mycube.prerm
+  |   `-- rules
+  |
+  |-- entities.py
+  |
+  |-- i18n/
+  |   |-- en.po
+  |   `-- fr.po
+  |
+  |-- __init__.py
+  |
+  |-- MANIFEST.in
+  |
+  |-- migration/
+  |   |-- postcreate.py
+  |   `-- precreate.py
+  |
+  |-- __pkginfo__.py
+  |
+  |-- schema.py
+  |
+  |-- setup.py
+  |
+  |-- site_cubicweb.py
+  |
+  |-- hooks.py
+  |
+  |-- test/
+  |   |-- data/
+  |   |   `-- bootstrap_cubes
+  |   |-- pytestconf.py
+  |   |-- realdb_test_mycube.py
+  |   `-- test_mycube.py
+  |
+  `-- views.py
+
+
+We can use subpackages instead of python modules for ``views.py``, ``entities.py``,
+``schema.py`` or ``hooks.py``. For example, we could have:
+
+::
+
+  mycube/
+  |
+  |-- entities.py
+  |-- hooks.py
+  `-- views/
+      |-- forms.py
+      |-- primary.py
+      `-- widgets.py
+
+
+where :
+
+* ``schema`` contains the schema definition (server side only)
+* ``entities`` contains the entities definition (server side and web interface)
+* ``sobjects`` contains hooks and/or views notifications (server side only)
+* ``views`` contains the web interface components (web interface only)
+* ``test`` contains tests related to the application (not installed)
+* ``i18n`` contains message catalogs for supported languages (server side and
+  web interface)
+* ``data`` contains data files for static content (images, css, javascripts)
+  ...(web interface only)
+* ``migration`` contains initialization files for new instances (``postcreate.py``)
+  and a file containing dependencies of the component depending on the version
+  (``depends.map``)
+* ``debian`` contains all the files managing debian packaging (you will find
+  the usual files ``control``, ``rules``, ``changelog``... not installed)
+* file ``__pkginfo__.py`` provides component meta-data, especially the distribution
+  and the current version (server side and web interface) or sub-cubes used by
+  the cube.
+
+
+At least you should have:
+
+* the file ``__pkginfo__.py``
+* the schema definition
+  XXX false, we may want to have cubes which are only adding a service,
+  no persistent data (eg embedding for instance)
+
+
+
+The :file:`__init__.py` and :file:`site_cubicweb.py` files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :file:`__pkginfo__.py` file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX contains metadata describing your cubes
+    distname / modname
+    version / numversion
+    __use__
+    __recommend__
+
+
+:file:`migration/precreate.py` and :file:`migration/postcreate.py`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX detail steps of instance creation
+
+
+External resources such as image, javascript and css files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX naming convention external_resources file
+
+
+Out-of the box testing
+~~~~~~~~~~~~~~~~~~~~~~
+XXX MANIFEST.in, __pkginfo__.include_dirs, debian
+
+
+
+Packaging and distribution
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX MANIFEST.in, __pkginfo__.include_dirs, debian
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/baseschema.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,38 @@
+
+Pre-defined schemas in the library
+----------------------------------
+
+The library defines a set of entity schemas that are required by the system
+or commonly used in `CubicWeb` applications.
+
+
+Entity types used to store the schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* `CWEType`, entity type
+* `CWRType`, relation type
+* `CWRelation`, relation definition
+* `CWAttribute`, attribute relation definition
+* `CWConstraint`,  `CWConstraintType`, `RQLExpression`
+
+Entity types used to manage users and permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* `CWUser`, system users
+* `CWGroup`, users groups
+* `CWPermission`, used to configure the security of the application
+
+Entity types used to manage workflows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* `State`, workflow state
+* `Transition`, workflow transition
+* `TrInfo`, record of a transition trafic for an entity 
+
+Other entity types
+~~~~~~~~~~~~~~~~~~
+* `CWCache`
+* `CWProperty`, used to configure the application
+
+* `EmailAddress`, email address, used by the system to send notifications
+  to the users and also used by others optionnals schemas
+
+* `Bookmark`, an entity type used to allow a user to customize his links within
+  the application
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/define-workflows.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,159 @@
+.. -*- coding: utf-8 -*-
+
+.. _Workflow:
+
+An Example: Workflow definition
+===============================
+
+General
+-------
+
+A workflow describes how certain entities have to evolve between 
+different states. Hence we have a set of states, and a "transition graph", 
+i.e. a list of possible transitions from one state to another state.
+
+We will define a simple workflow for a blog, with only the following 
+two states: `submitted` and `published`. So first, we create a simple 
+`CubicWeb` in ten minutes (see :ref:`BlogTenMinutes`).
+
+Set-up a workflow
+-----------------
+
+We want to create a workflow to control the quality of the BlogEntry 
+submitted on your application. When a BlogEntry is created by a user
+its state should be `submitted`. To be visible to all, it has to
+be in the state `published`. To move it from `submitted` to `published`,
+we need a transition that we can call `approve_blogentry`.
+
+A BlogEntry state should not be modifiable by every user.
+So we have to define a group of users, `moderators`, and 
+this group will have appropriate permissions to publish a BlogEntry.
+
+There are two ways to create a workflow: from the user interface,
+or by defining it in ``migration/postcreate.py``. 
+This script is executed each time a new ``cubicweb-ctl db-init`` is done. 
+We strongly recommand to create the workflow in ``migration/postcreate.py``
+and we will now show you how. Read `Under the hood`_ to understand why.
+
+Update the schema
+~~~~~~~~~~~~~~~~~
+If we want a State for our BlogEntry, we have to define a relation
+``in_state`` in the schema of BlogEntry. So we add
+the line ``in_state (...)``::
+
+  class BlogEntry(EntityType):
+      title = String(maxsize=100, required=True)
+      publish_date = Date(default='TODAY')
+      text_format = String(meta=True, internationalizable=True, maxsize=50,
+                           default='text/rest', constraints=[format_constraint])
+      text = String(fulltextindexed=True)
+      category = String(vocabulary=('important','business'))
+      entry_of = SubjectRelation('Blog', cardinality='?*')
+      in_state = SubjectRelation('State', cardinality='1*')
+
+As you updated the schema, you have to re-execute ``cubicweb-ctl db-init``
+to initialize the database and migrate your existing entities.
+
+[WRITE ABOUT MIGRATION]
+
+Create states, transitions and group permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``postcreate.py`` script is executed in a special environment, adding
+several `CubicWeb` primitives that can be used.
+They are all defined in the ``class ServerMigrationHelper``.
+We will only discuss the methods we use to create a workflow in this example.
+
+To define our workflow for BlogDemo, please add the following lines
+to ``migration/postcreate.py``::
+  
+  _ = unicode
+
+  moderators = add_entity('CWGroup', name=u"moderators")
+
+This adds the `moderators` user group.
+
+::
+
+  submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
+  published = add_state(_('published'), 'BlogEntry')
+
+``add_state`` expects as first argument the name of the state you want
+to create, then the entity type on which the state can be applied,
+and an optional argument to say if it is supposed to be the initial state
+of the entity type.
+
+::
+
+  add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
+
+
+``add_transition`` expects 
+
+  * as the first argument the name of the
+    transition, then the entity type on which the transition can be applied,
+  * then the list of states on which the transition can be trigged,
+  * the target state of the transition, 
+  * and the permissions
+    (e.g. a list of user groups who can apply the transition; the user
+    has to belong to at least one of the listed group to perform the action).
+
+::
+
+  checkpoint()
+
+.. note::
+  Do not forget to add the `_()` in front of all states and transitions names while creating
+  a workflow so that they will be identified by the i18n catalog scripts.
+
+In addition to the user group condition, we could have added a RQL condition. 
+In this case, the user can only perform the action if 
+the two conditions are satisfied. 
+
+If we use a RQL condition on a transition, we can use the following 
+variables:
+
+* `%(eid)s`, object's eid
+* `%(ueid)s`, user executing the query eid
+* `%(seid)s`, the object's current state eid
+
+
+.. image:: images/lax-book.03-transitions-view.en.png
+
+You can notice that in the action box of a BlogEntry, the state
+is now listed as well as the possible transitions defined by the workflow.
+The transitions will only be displayed for users having the right permissions.
+In our example, the transition `approve_blogentry` will only be displayed 
+for the users belonging to the group `moderators` or `managers`.
+
+
+Under the hood
+~~~~~~~~~~~~~~
+
+A workflow is a collection of entities of type ``State`` and of type ``Transition``
+which are standard `CubicWeb` entity types.
+For instance, the following lines::
+
+  submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
+  published = add_state(_('published'), 'BlogEntry')
+
+will create two entities of type ``State``, one with name 'submitted', and the other
+with name 'published'. Whereas::
+
+  add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
+ 
+will create an entity of type ``Transition`` with name 'approve_blogentry' which will
+be linked to the ``State`` entities created before.
+As a consequence, we could use the administration interface to do these operations.
+But it is not recommanded because it will be uselessly complicated
+and will be only local to your instance.
+
+
+Indeed, if you create the states and transitions through the user interface,
+next time you initialize the database
+you will have to re-create all the entities. 
+The user interface should only be a reference for you to view the states 
+and transitions, but is not the appropriate interface to define your
+application workflow.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/definition.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,440 @@
+.. -*- coding: utf-8 -*-
+
+Yams *schema*
+-------------
+
+The **schema** is the core piece of a `CubicWeb` application as it defines
+the handled data model. It is based on entity types that are either already
+defined in the `CubicWeb` standard library; or more specific types, that
+`CubicWeb` expects to find in one or more Python files under the directory
+`schema`.
+
+At this point, it is important to make clear the difference between
+*relation type* and *relation definition*: a *relation type* is only a relation
+name with potentially other additionnal properties (see XXXX), whereas a 
+*relation definition* is a complete triplet 
+"<subject entity type> <relation type> <object entity type>". 
+A relation type could have been implied if none is related to a 
+relation definition of the schema.
+
+
+All `CubicWeb` built-in types are available : `String`, `Int`, `Float`,
+`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte` 
+and `Password`.
+They are implicitely imported (as well as the special the function "_"
+for translation :ref:`internationalization`).
+
+The instance schema of an application is defined on all appobjects by a .schema
+class attribute set on registration.  It's an instance of
+:class:`yams.schema.Schema`.
+
+Entity type
+~~~~~~~~~~~
+It's an instance of :class:`yams.schema.EntitySchema`
+
+XXX meta
+XXX permission
+XXX yams inheritance
+
+Relation type
+~~~~~~~~~~~~~
+It's an instance of :class:`yams.schema.RelationSchema`
+
+In addition to the permissions, the properties of the relation types
+(shared also by all definition of relation of this type) are :
+
+
+* `inlined` : boolean handling the physical optimization for archiving
+  the relation in the subject entity table, instead of creating a specific
+  table for the relation. This applies to the relation when the cardinality
+  of subject->relation->object is 0..1 (`?`) or 1..1 (`1`)
+
+* `symmetric` : boolean indicating that the relation is symmetrical, which
+  means `X relation Y` implies `Y relation X`
+
+XXX meta
+XXX permission
+
+
+Relation definition
+~~~~~~~~~~~~~~~~~~~
+Relation definition are represented in yams using an internal structure only exposed through the :mod:`api <yams.schema>`.
+
+Properties
+``````````
+Properties defined below are accessible through the following api:
+
+  RelationSchema.rproperties()
+  RelationSchema.rproperty(subjtype, objtype, property name)
+
+* Optional properties for attributes and relations : 
+
+  - `description` : a string describing an attribute or a relation. By default
+    this string will be used in the editing form of the entity, which means
+    that it is supposed to help the end-user and should be flagged by the
+    function `_` to be properly internationalized.
+
+  - `constraints` : a list of conditions/constraints that the relation has to
+    satisfy (c.f. `Contraints`_)
+
+  - `cardinality` : a two character string which specify the cardinality of the
+    relation. The first character defines the cardinality of the relation on
+    the subject, and the second on the object. When a relation can have 
+    multiple subjects or objects, the cardinality applies to all,
+    not on a one-to-one basis (so it must be consistent...). The possible
+    values are inspired from regular expression syntax :
+
+    * `1`: 1..1
+    * `?`: 0..1
+    * `+`: 1..n
+    * `*`: 0..n
+
+  - `meta` : boolean indicating that the relation is a meta-relation (false by
+    default)
+
+* optional properties for attributes : 
+
+  - `required` : boolean indicating if the attribute is required (false by default)
+
+  - `unique` : boolean indicating if the value of the attribute has to be unique
+    or not within all entities of the same type (false by default)
+
+  - `indexed` : boolean indicating if an index needs to be created for this 
+    attribute in the database (false by default). This is useful only if
+    you know that you will have to run numerous searches on the value of this
+    attribute.
+
+  - `default` : default value of the attribute. In case of date types, the values
+    which could be used correspond to the RQL keywords `TODAY` and `NOW`.
+  
+  - `vocabulary` : specify static possible values of an attribute
+
+* optional properties of type `String` : 
+
+  - `fulltextindexed` : boolean indicating if the attribute is part of
+    the full text index (false by default) (*applicable on the type `Byte`
+    as well*)
+
+  - `internationalizable` : boolean indicating if the value of the attribute
+    is internationalizable (false by default)
+
+  - `maxsize` : integer providing the maximum size of the string (no limit by default)
+
+* optional properties for relations : 
+
+  - `composite` : string indicating that the subject (composite == 'subject')
+    is composed of the objects of the relations. For the opposite case (when
+    the object is composed of the subjects of the relation), we just set 
+    'object' as value. The composition implies that when the relation
+    is deleted (so when the composite is deleted), the composed are also deleted.
+
+  - `fti_container`: XXX feed me
+
+Constraints
+```````````
+By default, the available constraint types are :
+
+* `SizeConstraint` : allows to specify a minimum and/or maximum size on
+  string (generic case of `maxsize`)
+
+* `BoundConstraint` : allows to specify a minimum and/or maximum value on 
+  numeric types
+
+* `UniqueConstraint` : identical to "unique=True"
+
+* `StaticVocabularyConstraint` : identical to "vocabulary=(...)"
+
+* `RQLConstraint` : allows to specify a RQL query that has to be satisfied
+  by the subject and/or the object of the relation. In this query the variables
+  `S` and `O` are reserved for the entities subject and object of the 
+  relation.
+
+* `RQLVocabularyConstraint` : similar to the previous type of constraint except
+  that it does not express a "strong" constraint, which means it is only used to
+  restrict the values listed in the drop-down menu of editing form, but it does
+  not prevent another entity to be selected.
+
+XXX note about how to add new constraint
+
+
+The security model
+~~~~~~~~~~~~~~~~~~
+
+The security model of `cubicWeb` is based on `Access Control List`. 
+The main principles are:
+
+* users and groups of users
+* a user belongs to at least one group of user
+* permissions (read, update, create, delete)
+* permissions are assigned to groups (and not to users)
+
+For `CubicWeb` in particular:
+
+* we associate rights at the enttities/relations schema level
+* for each entity, we distinguish four kind of permissions: read,
+  add, update and delete
+* for each relation, we distinguish three king of permissions: read,
+  add and delete (we can not modify a relation)
+* the basic groups are: Administrators, Users and Guests
+* by default, users belongs to the group Users
+* there is a virtual group called `Owners users` to which we
+  can associate only deletion and update permissions
+* we can not add users to the `Owners users` group, they are
+  implicetely added to it according to the context of the objects
+  they own
+* the permissions of this group are only be checked on update/deletion
+  actions if all the other groups the user belongs does not provide
+  those permissions
+
+Setting permissions is done with the attribute `permissions` of entities and
+relation types. It defines a dictionary where the keys are the access types
+(action), and the values are the authorized groups or expressions.
+
+For an entity type, the possible actions are `read`, `add`, `update` and
+`delete`.
+
+For a relation type, the possible actions are `read`, `add`, and `delete`.
+
+For each access type, a tuple indicates the name of the authorized groups and/or
+one or multiple RQL expressions to satisfy to grant access. The access is
+provided once the user is in the listed groups or one of the RQL condition is
+satisfied.
+
+The standard user groups
+````````````````````````
+
+* `guests`
+
+* `users`
+
+* `managers`
+
+* `owners` : virtual group corresponding to the entity's owner.
+  This can only be used for the actions `update` and `delete` of an entity
+  type.
+
+It is also possible to use specific groups if they are defined in the precreate 
+of the cube (``migration/precreate.py``).
+
+
+Use of RQL expression for writing rights
+`````````````````````````````````````````
+It is possible to define RQL expression to provide update permission 
+(`add`, `delete` and `update`) on relation and entity types.
+
+RQL expression for entity type permission :
+
+* you have to use the class `RQLExpression`
+
+* the used expression corresponds to the WHERE statement of an RQL query
+
+* in this expression, the variables X and U are pre-defined references
+  respectively on the current entity (on which the action is verified) and
+  on the user who send the request
+
+* it is possible to use, in this expression, a special relation 
+  "has_<ACTION>_permission" where the subject is the user and the 
+  object is a any variable, meaning that the user needs to have
+  permission to execute the action <ACTION> on the entities related
+  to this variable 
+
+For RQL expressions on a relation type, the principles are the same except 
+for the following :
+
+* you have to use the class `RQLExpression` in the case of a non-final relation
+
+* in the expression, the variables S, O and U are pre-defined references
+  to respectively the subject and the object of the current relation (on
+  which the action is being verified) and the user who executed the query
+
+* we can also defined rights on attributes of an entity (non-final relation),
+  knowing that : 
+
+  - to defines RQL expression, we have to use the class `RQLExpression`
+    in which X represents the entity the attribute belongs to
+
+  - the permissions `add` and `delete` are equivalent. Only `add`/`read`
+    are actually taken in consideration.
+
+:Note on the use of RQL expression for `add` permission:
+
+  Potentially, the use of an RQL expression to add an entity or a relation
+  can cause problems for the user interface, because if the expression uses
+  the entity or the relation to create, then we are not able to verify the 
+  permissions before we actually add the entity (please note that this is
+  not a problem for the RQL server at all, because the permissions checks are
+  done after the creation). In such case, the permission check methods 
+  (check_perm, has_perm) can indicate that the user is not allowed to create 
+  this entity but can obtain the permission. 
+  To compensate this problem, it is usually necessary, for such case,
+  to use an action that reflects the schema permissions but which enables
+  to check properly the permissions so that it would show up if necessary.
+
+  
+Use of RQL expression for reading rights
+````````````````````````````````````````
+
+The principles are the same but with the following restrictions :
+
+* we can not use `RRQLExpression` on relation types for reading
+
+* special relations "has_<ACTION>_permission" can not be used
+
+
+
+
+Defining your schema using yams
+-------------------------------
+
+Entity type definition
+~~~~~~~~~~~~~~~~~~~~~~
+
+An entity type is defined by a Python class which inherits from `EntityType`.
+The class definition contains the description of attributes and relations
+for the defined entity type.
+The class name corresponds to the entity type name. It is exepected to be
+defined in the module ``mycube.schema``.
+
+
+For example ::
+
+  class Person(EntityType):
+    """A person with the properties and the relations necessary for my
+    application"""
+
+    last_name = String(required=True, fulltextindexed=True)
+    first_name = String(required=True, fulltextindexed=True)
+    title = String(vocabulary=('Mr', 'Mrs', 'Miss'))
+    date_of_birth = Date()
+    works_for = SubjectRelation('Company', cardinality='?*')
+
+
+The entity described above defines three attributes of type String,
+last_name, first_name and title, an attribute of type Date for the date of
+birth and a relation that connects a `Person` to another entity of type
+`Company` through the semantic `works_for`.
+
+The name of the Python attribute corresponds to the name of the attribute
+or the relation in `CubicWeb` application.
+
+An attribute is defined in the schema as follows::
+    
+    attr_name = attr_type(properties*)
+
+where `attr_type` is one of the type listed above and `properties` is
+a list of  the attribute needs to statisfy (see :ref:`properties`
+for more details). 
+
+
+* relations can be defined by using `ObjectRelation` or `SubjectRelation`.
+  The first argument of `SubjectRelation` or `ObjectRelation` gives respectively
+  the object/subject entity type of the relation. This could be :  
+
+  * a string corresponding to an entity type
+
+  * a tuple of string corresponding to multiple entity types
+
+  * special string such as follows :
+
+    - "**" : all types of entities
+    - "*" : all types of non-meta entities 
+    - "@" : all types of meta entities but not system entities (e.g. used for
+      the basic schema description)
+
+* it is possible to use the attribute `meta` to flag an entity type as a `meta`
+  (e.g. used to describe/categorize other entities)
+
+Inheritance
+```````````
+XXX feed me
+
+
+Definition of relations
+~~~~~~~~~~~~~~~~~~~~~~~
+
+XXX add note about defining relation type / definition
+
+A relation is defined by a Python class heriting `RelationType`. The name
+of the class corresponds to the name of the type. The class then contains
+a description of the properties of this type of relation, and could as well 
+contain a string for the subject and a string for the object. This allows to create
+new definition of associated relations, (so that the class can have the 
+definition properties from the relation) for example ::
+
+  class locked_by(RelationType):
+    """relation on all entities indicating that they are locked"""
+    inlined = True
+    cardinality = '?*'
+    subject = '*'
+    object = 'CWUser'
+
+In the case of simultaneous relations definitions, `subject` and `object`
+can both be equal to the value of the first argument of `SubjectRelation`
+and `ObjectRelation`.
+
+When a relation is not inlined and not symmetrical, and it does not require
+specific permissions, its definition (by using `SubjectRelation` and
+`ObjectRelation`) is all we need.
+
+
+Definition of permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to that the entity type `CWPermission` from the standard library
+allow to build very complex and dynamic security architecture. The schema of
+this entity type is as follow : ::
+
+    class CWPermission(MetaEntityType):
+	"""entity type that may be used to construct some advanced security configuration
+	"""
+	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
+	require_group = SubjectRelation('CWGroup', cardinality='+*',
+					description=_('groups to which the permission is granted'))
+	require_state = SubjectRelation('State',
+				    description=_("entity'state in which the permission is applyable"))
+	# can be used on any entity
+	require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
+					    description=_("link a permission to the entity. This "
+							  "permission should be used in the security "
+							  "definition of the entity's type to be useful."))
+
+
+Example of configuration ::
+
+
+    ...
+
+    class Version(EntityType):
+	"""a version is defining the content of a particular project's release"""
+
+	permissions = {'read':   ('managers', 'users', 'guests',),
+		       'update': ('managers', 'logilab', 'owners',),
+		       'delete': ('managers', ),
+		       'add':    ('managers', 'logilab',
+				  ERQLExpression('X version_of PROJ, U in_group G,'
+						 'PROJ require_permission P, P name "add_version",'
+						 'P require_group G'),)}
+
+    ...
+
+    class version_of(RelationType):
+	"""link a version to its project. A version is necessarily linked to one and only one project.
+	"""
+	permissions = {'read':   ('managers', 'users', 'guests',),
+		       'delete': ('managers', ),
+		       'add':    ('managers', 'logilab',
+				  RRQLExpression('O require_permission P, P name "add_version",'
+						 'U in_group G, P require_group G'),)
+		       }
+	inlined = True
+
+This configuration indicates that an entity `CWPermission` named
+"add_version" can be associated to a project and provides rights to create
+new versions on this project to specific groups. It is important to notice that :
+
+* in such case, we have to protect both the entity type "Version" and the relation
+  associating a version to a project ("version_of")
+
+* because of the genricity of the entity type `CWPermission`, we have to execute
+  a unification with the groups and/or the states if necessary in the expression
+  ("U in_group G, P require_group G" in the above example)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,13 @@
+Data model
+==========
+
+This chapter describes how you define a schema and how to make it evolves as the time goes.
+
+.. toctree::
+   :maxdepth: 1
+
+   definition
+   metadata
+   baseschema
+
+..    define-workflows
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/inheritance.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+XXX WRITME
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/datamodel/metadata.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,43 @@
+
+Meta-data
+----------
+
+Each entity type has at least the following meta-relations:
+
+eid
+~~~
+Each entity in *CubicWeb* has an associated identifier which is unique
+in an instance. We usually call this identifier `eid`.
+
+`creation_date` and `modification_date`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Date and time of the creation / lastest modification of an entity.
+
+
+`created_by`
+~~~~~~~~~~~~
+relation to the :ref:`users <CWUser>` who has created the entity
+
+`owned_by`
+~~~~~~~~~~
+relation to :ref:`users <CWUser>` whom the entity belongs; usually the creator but not
+necessary, and it could have multiple owners notably for permission control
+
+`is`
+~~~~~
+relation to the :ref:`entity type <CWEType>` of which type the entity is.
+
+`is_instance`
+~~~~~~~~~~~~~
+relation to the :ref:`entity types <CWEType>` of which type the entity is an instance of.
+
+
+Special relations
+-----------------
+`has_text`
+~~~~~~~~~~
+query the full text index (only for entities having fulltextindexed attributes)
+
+`identity`
+~~~~~~~~~~
+XXX
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devcore/appobject.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,86 @@
+
+  
+The `AppObject` class
+~~~~~~~~~~~~~~~~~~~~~
+
+In general:
+
+* we do not inherit directly from this class but from a more specific
+  class such as `AnyEntity`, `EntityView`, `AnyRsetView`,
+  `Action`...
+
+* to be recordable, a subclass has to define its own register (attribute
+  `__registry__`) and its identifier (attribute `id`). Usually we do not have
+  to take care of the register, only the identifier `id`.
+
+We can find a certain number of attributes and methods defined in this class
+and common to all the application objects.
+
+At the recording, the following attributes are dynamically added to
+the *subclasses*:
+
+* `vreg`, the `vregistry` of the application
+* `schema`, the application schema
+* `config`, the application configuration
+
+We also find on instances, the following attributes:
+
+* `req`, `Request` instance
+* `rset`, the *result set* associated to the object if necessary
+* `cursor`, rql cursor on the session
+
+
+:URL handling:
+  * `build_url(method=None, **kwargs)`, returns an absolute URL based on
+    the given arguments. The *controller* supposed to handle the response,
+    can be specified through the special parameter `method` (the connection
+    is theoretically done automatically :).
+
+  * `datadir_url()`, returns the directory of the application data
+    (contains static files such as images, css, js...)
+
+  * `base_url()`, shortcut to `req.base_url()`
+
+  * `url_quote(value)`, version *unicode safe* of the function `urllib.quote`
+
+:Data manipulation:
+
+  * `etype_rset(etype, size=1)`, shortcut to `vreg.etype_rset()`
+
+  * `eid_rset(eid, rql=None, descr=True)`, returns a *result set* object for
+    the given eid
+  * `entity(row, col=0)`, returns the entity corresponding to the data position
+    in the *result set* associated to the object
+
+  * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but
+    also call the method `complete()` on the entity before returning it
+
+:Data formatting:
+  * `format_date(date, date_format=None, time=False)` returns a string for a
+    mx date time according to application's configuration
+  * `format_time(time)` returns a string for a mx date time according to
+    application's configuration
+
+:And more...:
+
+  * `external_resource(rid, default=_MARKER)`, access to a value defined in the
+    configuration file `external_resource`
+
+  * `tal_render(template, variables)`, renders a precompiled page template with
+    variables in the given dictionary as context
+
+.. note::
+  When we inherit from `AppObject` (even not directly), you *always* have to use
+  **super()** to get the methods and attributes of the superclasses, and not
+  use the class identifier.
+  For example, instead of writting: ::
+
+      class Truc(PrimaryView):
+          def f(self, arg1):
+              PrimaryView.f(self, arg1)
+
+  You'd better write: ::
+
+      class Truc(PrimaryView):
+          def f(self, arg1):
+              super(Truc, self).f(arg1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devcore/dbapi.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,29 @@
+
+
+API Python/RQL
+~~~~~~~~~~~~~~
+
+The Python API developped to interface with RQL is inspired from the standard db-api,
+with a Connection object having the methods cursor, rollback and commit essentially.
+The most important method is the `execute` method of a cursor :
+
+`execute(rqlstring, args=None, eid_key=None, build_descr=True)`
+
+:rqlstring: the RQL query to execute (unicode)
+:args: if the query contains substitutions, a dictionary containing the values to use
+:eid_key:
+   an implementation detail of the RQL cache implies that if a substitution
+   is used to introduce an eid *susceptible to raise the ambiguities in the query
+   type resolution*, then we have to specify the corresponding key in the dictionary
+   through this argument
+
+
+The `Connection` object owns the methods `commit` and `rollback`. You *should
+never need to use them* during the development of the web interface based on
+the `CubicWeb` framework as it determines the end of the transaction depending
+on the query execution success.
+
+.. note::
+  While executing update queries (SET, INSERT, DELETE), if a query generates
+  an error related to security, a rollback is automatically done on the current
+  transaction.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devcore/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,18 @@
+Core APIs
+=========
+
+.. toctree::
+   :maxdepth: 1
+
+   vreg.rst
+   selection.rst
+   appobject.rst
+   selectors.rst
+   dbapi.rst
+
+   
+:mod:`Configuration <cubicweb.cwconfig>`
+----------------------------------------
+
+.. automodule:: cubicweb.cwconfig
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devcore/selectors.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,101 @@
+Base selectors
+--------------
+
+Selectors are scoring functions that are called by the view dispatcher to tell
+whenever a view can be applied to a given result set of a request. Selector sets
+are the glue that tie views to the data model. Using them appropriately is an
+essential part of the construction of well behaved cubes.
+
+
+`CubicWeb` provides its own set of selectors that you can use and here is a
+description of some of the most common used:
+
+Of course you will write your own set of selectors as you get familiar with the
+framework.
+
+
+:yes([score=1]):
+  Return the score given as parameter (default to 1). Usually used for appobjects
+  which can be selected whatever the context, or also sometimes to add arbitrary
+  points to a score. Take care, `yes(0)` could be named 'no'...
+
+
+Rset selectors
+~~~~~~~~~~~~~~
+:none_rset():
+  Return 1 if the result set is None.
+
+:any_rset():
+  Return 1 for any result set, whatever the number of rows in it.
+
+:nonempty_rset():
+  Return 1 for non empty result set.
+
+:empty_rset():
+  Return 1 for empty result set.
+
+:one_line_rset():
+  Return 1 if the result set is of size 1 or if a row is specified.
+
+:two_lines_rset():
+  Return 1 if the result set has *at least* two rows.
+
+:two_cols_rset():
+  Return 1 if the result set is not empty and has *at least* two columns per
+  row.
+
+:paginated_rset():
+  Return 1 if the result set has more rows the specified by the
+  `navigation.page-size` property.
+
+:sorted_rset():
+  Return 1 if the result set has an ORDERBY clause.
+
+:one_etype_rset():
+  Return 1 if the result set has entities which are all of the same type in a
+  given column (default to column 0).
+
+:non_final_entity():
+  Return 1 if the result set contains entities in a given column (the first one
+  by default), and no "final" values such as string of int.
+
+:implements(<iface or etype>, ...):
+  Return positive score if entities in the result set are of the given entity
+  type or implements given interface.  If multiple arguments are given, matching
+  one of them is enough. Returned score reflects the "distance" between expected
+  type or interface and matched entities. Entity types are usually given as
+  string, the corresponding class will be fetched from the vregistry.
+
+:two_etypes_rset(): XXX
+:entity_implements(): XXX
+:relation_possible(): XXX
+:partial_relation_possible(): XXX
+:may_add_relation(): XXX
+:partial_may_add_relation(): XXX
+:has_related_entities(): XXX
+:partial_has_related_entities(): XXX
+:has_permission(): XXX
+:has_add_permission(): XXX
+:rql_condition(): XXX
+:but_etype(): XXX
+:score_entity(): XXX
+
+Request selectors
+~~~~~~~~~~~~~~~~~~
+:anonymous_user():
+  Return 1 if user isn't authenticated (eg is the anonymous user).
+
+:authenticated_user():
+  Return 1 if user is authenticated.
+
+:match_user_groups(): XXX
+:match_search_state(): XXX
+:match_form_params(): XXX
+
+Other selectors
+~~~~~~~~~~~~~~~
+:match_kwargs(): XXX
+:match_context_prop(): XXX
+:appobject_selectable(): XXX
+:specified_etype_implements(): XXX
+:primary_view(): XXX
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devcore/vreg.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,151 @@
+The VRegistry
+--------------
+
+The recording process on startup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Details of the recording process
+````````````````````````````````
+
+* par défaut on enregistre automatiquement tout les objets
+
+* si certains objets doivent remplacer d'autres objets ou être inclus
+  conditionnellement,
+  - enregistrement explicite en définissant la fonction `registration_callback(vreg)`
+  - appel des méthodes d'enregistrement des objets sur le vreg
+
+* suppression de l'ancien système quand il ne restera plus de réference au
+  module registerers dans le code des cubes existants.
+
+
+Examples
+
+.. code-block:: python
+
+   # web/views/basecomponents.py
+   def registration_callback(vreg):
+      vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+      if 'see_also' in vreg.schema:
+          vreg.register(SeeAlsoVComponent)
+
+   # goa/appobjects/sessions.py
+   def registration_callback(vreg):
+      vreg.register(SessionsCleaner)
+      vreg.register(GAEAuthenticationManager, clear=True)
+      vreg.register(GAEPersistentSessionManager, clear=True)
+
+
+API d'enregistrement des objets
+```````````````````````````````
+.. code-block:: python
+
+   register(obj, registryname=None, oid=None, clear=False)
+
+   register_all(objects, modname, butclasses=())
+
+   unregister(obj, registryname=None)
+
+   register_and_replace(obj, replaced, registryname=None)
+
+   register_if_interface_found(obj, ifaces, **kwargs)
+
+
+Runtime objects selection
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Defining selectors
+``````````````````
+The object's selector is defined by itsd `__select__` class attribute.
+
+When two selectors are combined using the `&` operator (former `chainall`), it
+means that both should return a positive score. On success, the sum of scores is returned.
+
+When two selectors are combined using the `|` operator (former `chainfirst`), it
+means that one of them should return a positive score. On success, the first
+positive score is returned.
+
+Of course you can use paren to balance expressions.
+
+
+For instance, if you're selecting the primary (eg `id = 'primary'`) view (eg
+`__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects
+will probably be selectable:
+
+* the default primary view (`__select__ = implements('Any')`), meaning that the object is selectable for any kind of entity type
+
+* the specific `Card` primary view (`__select__ = implements('Card')`, meaning that the object is selectable for Card entities
+
+Other primary views specific to other entity types won't be selectable in this
+case. Among selectable objects, the implements selector will return a higher score
+to the second view since it's more specific, so it will be selected as expected.
+
+
+Example
+````````
+
+Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe
+vers les entrées de ce blog, non vers l'entité blog elle-même.
+
+L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité
+qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation
+par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce
+qu'on veut.
+
+La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient
+plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler
+la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel
+à limited_rql)
+
+Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité,
+l'autre pour un rset qui contient plusieurs entité.
+
+Donc... On a déja dans web/views/boxes.py la classe RSSIconBox qui fonctionne. Son
+sélecteur ::
+
+  class RSSIconBox(ExtResourcesBoxTemplate):
+    """just display the RSS icon on uniform result set"""
+    __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
+
+
+indique qu'il prend en compte :
+
+* les conditions d'apparition de la boite (faut remonter dans les classes parentes
+  pour voir le détail)
+* non_final_entity, qui filtre sur des rset contenant une liste d'entité non finale
+
+ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique
+pour le 1er cas ::
+
+  class EntityRSSIconBox(RSSIconBox):
+    """just display the RSS icon on uniform result set for a single entity"""
+    __select__ = RSSIconBox.__select__ & one_line_rset()
+
+
+Ici, on ajoute le selector one_line_rset, qui filtre sur des result set de taille 1. Il faut
+savoir que quand on chaine des selecteurs, le score final est la somme des scores
+renvoyés par chaque sélecteur (sauf si l'un renvoie zéro, auquel cas l'objet est
+non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector
+rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la
+classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un
+score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée.
+
+Voili voilou, il reste donc pour finir tout ça :
+
+* à définir le contenu de la méthode call de EntityRSSIconBox
+* fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur
+  AnyEntity
+* surcharger cette methode dans blog.Blog
+
+
+When to use selectors?
+```````````````````````
+
+Il faut utiliser les sélecteurs pour faire des choses différentes en
+fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la
+nature de `self.rset` dans un objet, il faut très sérieusement se
+poser la question s'il ne vaut pas mieux avoir deux objets différent
+avec des sélecteurs approprié.
+
+Debugging
+`````````
+XXX explain traced_selection context manager
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/hooks.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,32 @@
+.. -*- coding: utf-8 -*-
+
+.. _hooks:
+
+Hooks
+=====
+
+XXX FILLME
+
+*Hooks* are executed before or after updating an entity or a relation in the
+repository.
+
+Their prototypes are as follows: 
+    
+    * after_add_entity     (session, entity)
+    * after_update_entity  (session, entity)
+    * after_delete_entity  (session, eid)
+    * before_add_entity    (session, entity)
+    * before_update_entity (session, entity)
+    * before_delete_entity (session, eid)
+
+    * after_add_relation     (session, fromeid, rtype, toeid)
+    * after_delete_relation  (session, fromeid, rtype, toeid)
+    * before_add_relation    (session, fromeid, rtype, toeid)
+    * before_delete_relation (session, fromeid, rtype, toeid)
+    
+    * server_startup
+    * server_shutdown
+    
+    * session_open
+    * session_close
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,14 @@
+.. -*- coding: utf-8 -*-
+
+Repository customization
+++++++++++++++++++++++++
+.. toctree::
+   :maxdepth: 1
+
+   sessions
+   hooks
+   notifications
+   operations
+   tasks
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/notifications.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,6 @@
+.. -*- coding: utf-8 -*-
+
+Notifications management
+========================
+
+XXX FILLME
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/operations.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Repository operations
+======================
+
+[WRITE ME]
+
+* repository operations
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/sessions.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Sessions
+========
+
+[WRITE ME]
+
+* authentication and management of sessions
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devrepo/tasks.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Tasks
+=========
+
+[WRITE ME]
+
+* repository tasks
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/controllers.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,4 @@
+Controllers
+-----------
+
+XXX the view controller, other controllers
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/css.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,29 @@
+.. -*- coding: utf-8 -*-
+
+CSS Stylesheet
+---------------
+Conventions
+~~~~~~~~~~~
+
+XXX external_resources variable
+    naming convention
+    request.add_css
+
+
+Extending / overriding existing styles
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We cannot modify the order in which the application is reading the CSS. In
+the case we want to create new CSS style, the best is to define it a in a new
+CSS located under ``myapp/data/`` and use those new styles while writing
+customized views and templates.
+
+If you want to modify an existing CSS styling property, you will have to use
+``!important`` declaration to override the existing property. The application
+apply a higher priority on the default CSS and you can not change that.
+Customized CSS will not be read first.
+
+
+CubicWeb stylesheets
+~~~~~~~~~~~~~~~~~~~~
+XXX explain diffenrent files and main classes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/facets.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+The facets system
+-----------------
+XXX feed me
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/form.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,29 @@
+Form construction
+------------------
+
+Forms
+~~~~~
+XXX feed me
+:Vocabulary control on relations:
+
+  * `vocabulary(rtype, x='subject', limit=None)`, called by the
+    editing views, it returns a list of couples (label, eid) of entities
+    that could be related to the entity by the relation `rtype`
+  * `subject_relation_vocabulary(rtype, limit=None)`, called internally 
+    by  `vocabulary` in the case of a subject relation
+  * `object_relation_vocabulary(rtype, limit=None)`, called internally 
+    by  `vocabulary` in the case of an object relation
+  * `relation_vocabulary(rtype, targettype, x, limit=None)`, called
+    internally by `subject_relation_vocabulary` and `object_relation_vocabulary`
+
+Fields
+~~~~~~
+XXX feed me
+
+Widgets
+~~~~~~~
+XXX feed me
+
+Renderers
+~~~~~~~~~
+XXX feed me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/httpcaching.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+HTTP cache management
+---------------------
+XXX feedme
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,20 @@
+Web development
+===============
+
+In this chapter, we will core api for web development in the CubicWeb framework.
+
+.. toctree::
+   :maxdepth: 1
+
+   request
+   publisher
+   controllers
+   property
+   rtags
+   views
+   form
+   facets
+   httpcaching
+   js
+   css
+   internationalization
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/internationalization.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,110 @@
+.. -*- coding: utf-8 -*-
+
+.. _internationalisation:
+
+
+Internationalisation
+---------------------
+
+Cubicweb fully supports the internalization of it's content and interface.
+
+Cubicweb's interface internationalization is based on the translation project `GNU gettext`_.
+
+.. _`GNU gettext`: http://www.gnu.org/software/gettext/
+
+Cubicweb' internalization involves two steps:
+
+* in your Python code and cubicweb-tal templates : mark translatable strings
+
+* in your application : handle the translation catalog
+
+String internationalization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the Python code and cubicweb-tal templates translatable strings can be
+marked in one of the following ways :
+
+ * by using the *built-in* function `_` ::
+
+     class PrimaryView(EntityView):
+         """the full view of an non final entity"""
+         id = 'primary'
+         title = _('primary')
+
+  OR
+
+ * by using the equivalent request's method ::
+
+     class NoResultView(EmptyRsetView):
+         """default view when no result has been found"""
+         id = 'noresult'
+
+         def call(self, **kwargs):
+             self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
+                 % self.req._('No result matching query'))
+
+The goal of the *built-in* function `_` is only **to mark the
+translatable strings**, it will only return the string to translate
+it-self, but not its translation (it's actually refering to the `unicode` builtin).
+
+In the other hand the request's method `self.req._` is meant to retrieve the
+proper translation of translation strings in the requested language.
+
+Finally you can also use the `__` attribute of request object to get a
+translation for a string *which should not itself added to the catalog*,
+usually in case where the actual msgid is created by string interpolation ::
+
+  self.req.__('This %s' % etype)
+
+In this example `req.__` is used instead of `req._` so we don't have 'This %s' in
+messages catalogs.
+
+
+Translations in cubicweb-tal template can also be done with TAL tags
+`i18n:content` and `i18n:replace`.
+
+.. note::
+
+   We dont need to mark the translation strings of entities/relations
+   used by a particular application's schema as they are generated
+   automatically.
+
+
+Handle the translation catalog
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once the internationalization is done in your application's code, you need
+to populate and update the translation catalog. Cubicweb provides the
+following commands for this purpose:
+
+
+* `i18nlibupdate` updates Cubicweb framework's translation
+  catalogs. Unless you work on the framework development, you don't
+  need to use this command.
+
+* `i18nupdate` updates the translation catalogs of *one particular
+  component* (or of all components). After this command is
+  executed you must update the translation files *.po* in the "i18n"
+  directory of your template. This command will of course not remove
+  existing translations still in use.
+
+* `i18ncompile` recompile the translation catalogs of *one particular
+  instance* (or of all instances) after the translation catalogs of
+  its components have been updated. This command is automatically
+  called every time you create or update your instance. The compiled
+  catalogs (*.mo*) are stored in the i18n/<lang>/LC_MESSAGES of
+  application where `lang` is the language identifier ('en' or 'fr'
+  for exemple).
+
+
+Example
+```````
+You have added and/or modified some translation strings in your application
+(after creating a new view or modifying the application's schema for exemple).
+To update the translation catalogs you need to do:
+
+1. `cubicweb-ctl i18nupdate <component>`
+2. Edit the <component>/xxx.po  files and add missing translations (empty `msgstr`)
+3. `hg ci -m "updated i18n catalogs"`
+4. `cubicweb-ctl i18ncompile <myapplication>`
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/js.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,18 @@
+.. -*- coding: utf-8 -*-
+
+Javascript
+----------
+
+XXX jquery...
+
+Conventions
+~~~~~~~~~~~
+
+XXX external_resources variable
+    naming convention
+    request.add_js
+
+
+CubicWeb javascrip api
+~~~~~~~~~~~~~~~~~~~~~~
+XXX explain diffenrent files and main functions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/property.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,12 @@
+The property mecanims
+---------------------
+XXX CWProperty and co
+
+
+Property API
+~~~~~~~~~~~~
+XXX feed me
+
+Registering and using your own property
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX feed me
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/publisher.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,4 @@
+Publisher
+---------
+
+XXX cubicweb.web.application; coop diagram for execution of a http query
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/request.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,59 @@
+
+
+The `Request` class (`cubicweb.web`)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A request instance is created when an HTTP request is sent to the web server.
+It contains informations such as form parameters, user authenticated, etc.
+
+**Globally, a request represents a user query, either through HTTP or not
+(we also talk about RQL queries on the server side for example).**
+
+An instance of `Request` has the following attributes:
+
+* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
+  user
+* `form`, dictionary containing the values of a web form
+* `encoding`, character encoding to use in the response
+
+But also:
+
+:Session data handling:
+  * `session_data()`, returns a dictionary containing all the session data
+  * `get_session_data(key, default=None)`, returns a value associated to the given
+    key or the value `default` if the key is not defined
+  * `set_session_data(key, value)`, assign a value to a key
+  * `del_session_data(key)`,  suppress the value associated to a key
+
+
+:Cookies handling:
+  * `get_cookie()`, returns a dictionary containing the value of the header
+    HTTP 'Cookie'
+  * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`,
+    with a minimal 5 minutes length of duration by default (`maxage` = None
+    returns a *session* cookie which will expire when the user closes the browser
+    window)
+  * `remove_cookie(cookie, key)`, forces a value to expire
+
+:URL handling:
+  * `url()`, returns the full URL of the HTTP request
+  * `base_url()`, returns the root URL of the web application
+  * `relative_path()`, returns the relative path of the request
+
+:And more...:
+  * `set_content_type(content_type, filename=None)`, adds the header HTTP
+    'Content-Type'
+  * `get_header(header)`, returns the value associated to an arbitrary header
+    of the HTTP request
+  * `set_header(header, value)`, adds an arbitrary header in the response
+  * `cursor()` returns a RQL cursor on the session
+  * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()``
+  * `property_value(key)`, properties management (`CWProperty`)
+  * dictionary `data` to store data to share informations between components
+    *while a request is executed*
+
+Please note that this class is abstract and that a concrete implementation
+will be provided by the *frontend* web used (in particular *twisted* as of
+today). For the views or others that are executed on the server side,
+most of the interface of `Request` is defined in the session associated
+to the client.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/rtags.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,14 @@
+Configuring the generated interface
+-----------------------------------
+
+
+The "Relation tags" structure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. automodule:: `cubicweb.rtags`
+   :members:
+
+
+The `uicfg` module (:mod:`cubicweb.web.uicfg`)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. automodule:: `cubicweb.web.uicfg`
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/devweb/views.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,232 @@
+Views
+-----
+
+This chapter aims to describe the concept of a `view` used all along
+the development of a web application and how it has been implemented
+in `CubicWeb`.
+
+We'll start with a description of the interface providing you with a basic
+understanding of the classes and methods available, then detail the view
+selection principle which makes `CubicWeb` web interface very flexible.
+
+A `View` is an object applied to another object such as an entity.
+
+Basic class for views
+---------------------
+
+Class `View` (`cubicweb.view`)
+`````````````````````````````````````
+
+This class is an abstraction of a view class, used as a base class for every
+renderable object such as views, templates, graphic components, etc.
+
+A `View` is instantiated to render a result set or part of a result set. `View`
+subclasses may be parametrized 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 views
+      must not be embeded in the main template for HTML pages)
+    * 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.
+
+A view writes to its output stream thanks to its attribute `w` (`UStreamIO`).
+
+The basic interface for views is as follows (remember that the result set has a
+tabular structure with rows and columns, hence cells):
+
+* `dispatch(**context)`, render the view by calling `call` or
+  `cell_call` depending on the given parameters
+* `call(**kwargs)`, call the view for a complete result set or null (default 
+  implementation calls `cell_call()` on each cell of the result set)
+* `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set
+* `url()`, returns the URL enabling us to get the view with the current
+  result set 
+* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier 
+  `__vid` on the given result set. It is possible to give a view identifier
+  of fallback that will be used if the view requested is not applicable to the
+  result set
+  
+* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except
+  the flow is automatically passed in the parameters
+  
+* `html_headers()`, returns a list of HTML headers to set by the main template
+
+* `page_title()`, returns the title to use in the HTML header `title`
+
+
+Other basic view classes
+````````````````````````
+Here are some of the subclasses of `View` defined in `cubicweb.common.view`
+that are more concrete as they relate to data rendering within the application:
+
+* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
+* `StartupView`, start view that does not require a result set to apply to
+* `AnyRsetView`, view applied to any result set 
+* `EmptyRsetView`, view applied to an empty result set
+
+
+Examples of views class 
+-----------------------
+
+- Using `templatable`, `content_type` and HTTP cache configuration
+
+.. code-block:: python
+    
+
+    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
+    
+
+
+- Using custom selector
+
+.. code-block:: python
+    
+
+    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')
+        __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
+
+
+Example of a view customization
+-------------------------------
+
+[FIXME] XXX Example needs to be rewritten as it shows how to modify cell_call which
+contredicts our advise of not modifying it.
+
+We'll show you now an example of a ``primary`` view and how to customize it.
+
+If you want to change the way a ``BlogEntry`` is displayed, just override 
+the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` ::
+
+.. code-block:: python
+
+   from cubicweb.view import EntityView
+   from cubicweb.selectors import implements
+  
+   class BlogEntryPrimaryView(EntityView):
+       id = 'primary'
+       __select__ =implements('Blog')
+       
+       def cell_call(self, row, col):
+           entity = self.entity(row, col)
+           self.w(u'<h1>%s</h1>' % entity.title)
+           self.w(u'<p>published on %s in category %s</p>' % \
+                  (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
+           self.w(u'<p>%s</p>' % entity.text)
+
+The above source code defines a new primary view (`line 03`) for
+``BlogEntry`` (`line 05`). 
+
+Since views are applied to result sets which can be tables of
+data, we have to recover the entity from its (row,col)-coordinates (`line 08`).
+We will get to this in more detail later.
+
+The view method ``self.w()`` is used to output data. Here `lines
+09-12` output HTML tags and values of the entity's attributes.
+
+When displaying the same blog entry as before, you will notice that the
+page is now looking much nicer. [FIXME: it is not clear to what this refers.]
+
+.. image:: ../../images/lax-book.09-new-view-blogentry.en.png
+   :alt: blog entries now look much nicer
+
+Let us now improve the primary view of a blog
+
+.. code-block:: python
+
+ class BlogPrimaryView(EntityView):
+     id = 'primary'
+     __select__ =implements('Blog')
+
+     def cell_call(self, row, col):
+         entity = self.entity(row, col)
+         self.w(u'<h1>%s</h1>' % entity.title)
+         self.w(u'<p>%s</p>' % entity.description)
+         rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
+         self.wview('primary', rset)
+
+In the above source code, `lines 01-08` are similar to the previous
+view we defined. [FIXME: defined where ?]
+
+At `line 09`, a simple request is made to build a result set with all
+the entities linked to the current ``Blog`` entity by the relationship
+``entry_of``. The part of the framework handling the request knows
+about the schema and infer that such entities have to be of the
+``BlogEntry`` kind and retrieves them.
+
+The request returns a selection of data called a result set. At 
+`line 10` the view 'primary' is applied to this result set to output
+HTML. 
+
+**This is to be compared to interfaces and protocols in object-oriented
+languages. Applying a given view called 'a_view' to all the entities
+of a result set only requires to have for each entity of this result set,
+an available view called 'a_view' which accepts the entity.**
+
+Assuming we added entries to the blog titled `MyLife`, displaying it
+now allows to read its description and all its entries.
+
+.. image:: ../../images/lax-book.10-blog-with-two-entries.en.png
+   :alt: a blog and all its entries
+
+**Before we move forward, remember that the selection/view principle is
+at the core of `CubicWeb`. Everywhere in the engine, data is requested
+using the RQL language, then HTML/XML/text/PNG is output by applying a
+view to the result set returned by the query. That is where most of the
+flexibility comes from.**
+
+[WRITE ME]
+
+* implementing interfaces, calendar for blog entries
+* show that a calendar view can export data to ical
+
+We will implement the `cubicweb.interfaces.ICalendarable` interfaces on
+entities.BlogEntry and apply the OneMonthCalendar and iCalendar views
+to result sets like "Any E WHERE E is BlogEntry"
+
+* create view "blogentry table" with title, publish_date, category
+
+We will show that by default the view that displays 
+"Any E,D,C WHERE E publish_date D, E category C" is the table view.
+Of course, the same can be obtained by calling
+self.wview('table',rset)
+
+* in view blog, select blogentries and apply view "blogentry table"
+* demo ajax by filtering blogentry table on category
+
+we did the same with 'primary', but with tables we can turn on filters
+and show that ajax comes for free.
+[FILLME]
+
+
+
+XML views, binaries...
+----------------------
+For views generating other formats than HTML (an image generated dynamically
+for example), and which can not simply be included in the HTML page generated
+by the main template (see above), you have to:
+
+* set the attribute `templatable` of the class to `False`
+* set, through the attribute `content_type` of the class, the MIME type generated
+  by the view to `application/octet-stream`
+
+For views dedicated to binary content creation (like dynamically generated 
+images), we have to set the attribute `binary` of the class to `True` (which
+implies that `templatable == False`, so that the attribute `w` of the view could be
+replaced by a binary flow instead of unicode).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/data-as-objects.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,95 @@
+Access to persistent data
+--------------------------
+
+XXX is provided by the :class:`Entity <cubicweb.entity.entity>` class
+
+An entity class is bound to a schema entity type.  Descriptors are added when
+classes are registered in order to initialize the class according to its schema:
+
+* we can access the defined attributes in the schema thanks to the attributes of
+  the same name on instances (typed value)
+
+* we can access the defined relations in the schema thanks to the relations of
+  the same name on instances (entities instances list)
+
+
+:Formatting and output generation:
+
+  * `view(vid, **kwargs)`, applies the given view to the entity
+
+  * `absolute_url(**kwargs)`, returns an absolute URL to access the primary view
+    of an entity
+    
+  * `rest_path()`, returns a relative REST URL to get the entity
+
+  * `format(attr)`, returns the format (MIME type) of the field given un parameter
+
+  * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`, 
+    returns a string enabling the display of an attribute value in a given format
+    (the value is automatically recovered if necessary)
+
+:Data handling:
+
+  * `as_rset()`, converts the entity into an equivalent result set simulating the 
+     request `Any X WHERE X eid _eid_`
+
+  * `complete(skip_bytes=True)`, executes a request that recovers in one time
+    all the missing attributes of an entity
+
+  * `get_value(name)`, returns the value associated to the attribute name given
+    in parameter
+
+  * `related(rtype, x='subject', limit=None, entities=False)`, returns a list
+    of entities related to the current entity by the relation given in parameter
+
+  * `unrelated(rtype, targettype, x='subject', limit=None)`, returns a result set
+    corresponding to the entities not related to the current entity by the
+    relation given in parameter and satisfying its constraints
+
+  * `set_attributes(**kwargs)`, updates the attributes list with the corresponding
+    values given named parameters
+
+  * `copy_relations(ceid)`, copies the relations of the entities having the eid
+    given in the parameters on the current entity
+
+  * `last_modified(view)`, returns the date the object has been modified
+    (used by HTTP cache handling)
+
+  * `delete()` allows to delete the entity
+
+  
+Tne :class:`AnyEntity` class
+----------------------------
+  
+To provide a specific behavior for each entity, we have to define
+a class inheriting from `cubicweb.entities.AnyEntity`. In general, we
+define this class in a module of `mycube.entities` package of an application
+so that it will be available on both server and client side.
+
+The class `AnyEntity` is loaded dynamically from the class `Entity` 
+(`cubciweb.entity`). We define a sub-class to add methods or to
+specialize the handling of a given entity type
+
+The methods defined for `AnyEntity` or `Entity` are the following ones:
+  
+:Standard meta-data (Dublin Core):
+
+  * `dc_title()`, returns a unicode string corresponding to the meta-data
+    `Title` (used by default the first attribute non-meta of the entity
+    schema)
+
+  * `dc_long_title()`, same as dc_title but can return a more
+    detailled title
+
+  * `dc_description(format='text/plain')`, returns a unicode string 
+    corresponding to the meta-data `Description` (look for a description
+    attribute by default)
+
+  * `dc_authors()`, returns a unicode string corresponding to the meta-data 
+    `Authors` (owners by default)
+
+  * `dc_date(date_format=None)`, returns a unicode string corresponding to 
+    the meta-data `Date` (update date by default)
+
+  * `dc_type(form='')`, returns a string to display the entity type by 
+    specifying the preferred form (`plural` for a plural form)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,13 @@
+Data as objects
+===============
+
+In this chapter, we will introduce the objects that are used to handle
+the data stored in the database.
+
+.. toctree::
+   :maxdepth: 1
+
+   data-as-objects
+   load-sort
+   interfaces
+   more
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/interfaces.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,16 @@
+Interfaces
+----------
+
+XXX how to define a cw interface
+
+Declaration of interfaces implemented by a class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+XXX __implements__
+
+
+Interfaces defined in the library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: cubicweb.interface
+   :members:
+`````````````
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/load-sort.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,38 @@
+
+Loaded attributes and default sorting management
+````````````````````````````````````````````````
+
+* The class attribute `fetch_attrs` allows to defined in an entity class
+  a list of names of attributes or relations that should be automatically
+  loaded when we recover the entities of this type. In the case of relations,
+  we are limited to *subject of cardinality `?` or `1`* relations.
+
+* The class method `fetch_order(attr, var)` expects an attribute (or relation)
+  name as a parameter and a variable name, and it should return a string
+  to use in the requirement `ORDER BY` of an RQL query to automatically
+  sort the list of entities of such type according to this attribute, or
+  `None` if we do not want to sort on the attribute given in the parameter.
+  By default, the entities are sorted according to their creation date.
+
+* The class method `fetch_unrelated_order(attr, var)` is similar to the 
+  method `fetch_order` except that it is essentially used to control
+  the sorting of drop-down lists enabling relations creation in 
+  the editing view of an entity.
+
+The function `fetch_config(fetchattrs, mainattr=None)` simplifies the
+definition of the attributes to load and the sorting by returning a 
+list of attributes to pre-load (considering automatically the attributes
+of `AnyEntity`) and a sorting function based on the main attribute
+(the second parameter if specified otherwisethe first attribute from
+the list `fetchattrs`).
+This function is defined in `cubicweb.entities`.
+
+For example: ::
+
+  class Transition(AnyEntity):
+    """..."""
+    id = 'Transition'
+    fetch_attrs, fetch_order = fetch_config(['name'])
+
+Indicates that for the entity type "Transition", you have to pre-load
+the attribute `name` and sort by default on this attribute.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/more.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,12 @@
+Navigation on deletion
+----------------------
+XXX after_deletion_path, pre_web_edit
+
+Controlling output url
+---------------------
+XXX
+
+Controling notification references
+----------------------------------
+XXX
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,20 @@
+.. _Part2:
+
+---------------------
+Part II - Development
+---------------------
+
+This part is about developing web applications with the `CubicWeb` framework.
+
+.. toctree::
+   :maxdepth: 2
+
+   cubes/index
+   datamodel/index
+   entityclasses/index
+   devcore/index
+   devweb/index
+   devrepo/index
+   testing/index
+   migration/index
+   webstdlib/index
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/migration/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,213 @@
+.. -*- coding: utf-8 -*-
+
+.. _migration:
+
+Migration
+=========
+
+One of the main concept in `CubicWeb` is to create incremental applications.
+For this purpose, multiple actions are provided to facilitate the improvement
+of an application, and in particular to handle the changes to be applied
+to the data model, without loosing existing data.
+
+The current version of an application model is provided in the file
+`__pkginfo__.py` as a tuple of 3 integers.
+
+Migration scripts management
+----------------------------
+
+Migration scripts has to be located in the directory `migration` of your
+application and named accordingly:
+
+::
+
+  <version n° X.Y.Z>[_<description>]_<mode>.py
+
+in which : 
+
+* X.Y.Z is the model version number to which the script enables to migrate.
+
+* *mode* (between the last "_" and the extension ".py") is used for 
+  distributed installation. It indicates to which part
+  of the application (RQL server, web server) the script applies.
+  Its value could be :
+
+  * `common`, applies to the RQL server as well as the web server and updates
+    files on the hard drive (configuration files migration for example).
+
+  * `web`, applies only to the web server and updates files on the hard drive.
+
+  * `repository`, applies only to the RQL server and updates files on the
+    hard drive.
+
+  * `Any`, applies only to the RQL server and updates data in the database
+    (schema and data migration for example).
+
+Again in the directory `migration`, the file `depends.map` allows to indicate
+that for the migration to a particular model version, you always have to first 
+migrate to a particular `CubicWeb` version. This file can contain comments (lines
+starting by `#`) and a dependancy is listed as follows: ::
+  
+  <model version n° X.Y.Z> : <cubicweb version n° X.Y.Z>
+
+For example: ::
+
+  0.12.0: 2.26.0
+  0.13.0: 2.27.0
+  # 0.14 works with 2.27 <= cubicweb <= 2.28 at least
+  0.15.0: 2.28.0
+
+Base context
+------------
+
+The following identifiers are pre-defined in migration scripts:
+
+* `config`, instance configuration
+
+* `interactive_mode`, boolean indicating that the script is executed in
+  an interactive mode or not 
+
+* `appltemplversion`, application model version of the instance
+
+* `templversion`, installed application model version
+
+* `cubicwebversion`, installed cubicweb version
+
+* `confirm(question)`, function asking the user and returning true
+  if the user answers yes, false otherwise (always returns true in
+  non-interactive mode)
+
+* the function `_`, it is equivalent to `unicode` allowing to flag the strings
+  to internationalize in the migration scripts.
+
+In the `repository` scripts, the following identifiers are also defined:
+
+* `checkpoint`, request confirming and executing a "commit" at checking point
+
+* `repo_schema`, instance persisting schema (e.g. instance schema of the
+  current migration)
+
+* `newschema`, installed schema on the file system (e.g. schema of 
+  the updated model and cubicweb)
+
+* `sqlcursor`, SQL cursor for very rare cases where it is really
+   necessary or beneficial to go through the sql
+
+* `repo`, repository object
+
+                        
+Schema migration
+----------------
+The following functions for schema migration are available in `repository`
+scripts:
+
+* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new
+  attribute to an existing entity type. If the attribute type is not specified, 
+  then it is extracted from the updated schema.
+        
+* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an
+  existing entity type.
+
+* `rename_attribute(etype, oldname, newname, commit=True)`, renames an attribute
+            
+* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type.
+  If `auto` is True, all the relations using this entity type and having a known
+  entity type on the other hand will automatically be added.
+
+* `drop_entity_type(etype, commit=True)`, removes an entity type and all the 
+  relations using it.
+
+* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type
+            
+* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation
+  type. If `addrdef` is True, all the relations definitions of this type will
+  be added.
+
+* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
+  definitions of this type.
+
+* `rename_relation(oldname, newname, commit=True)`, renames a relation.
+
+* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
+  relation definition.
+
+* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes
+  a relation definition.
+
+* `synchronize_permissions(ertype, commit=True)`, synchronizes permissions on
+  an entity type or relation type.
+        
+* `synchronize_rschema(rtype, commit=True)`, synchronizes properties and permissions
+  on a relation type.
+                
+* `synchronize_eschema(etype, commit=True)`, synchronizes properties and persmissions
+  on an entity type.
+    
+* `synchronize_schema(commit=True)`, synchronizes the persisting schema with the
+  updated schema (but without adding or removing new entity types, relations types 
+  or even relations definitions).
+        
+* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes
+  properties of a relation definition by using the named parameters of the properties
+  to change.
+
+* `set_widget(etype, rtype, widget, commit=True)`, changes the widget used for the
+  relation <rtype> of entity type <etype>.
+
+* `set_size_constraint(etype, rtype, size, commit=True)`, changes the size constraints
+  for the relation <rtype> of entity type <etype>.
+
+Data migration
+--------------
+The following functions for data migration are available in `repository` scripts:
+
+* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
+  query, either to interrogate or update. A result set object is returned.  
+
+* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
+  type. The attribute and relation values are specified using the named and
+  positionned parameters.
+
+Workflow creation
+-----------------
+
+The following functions for workflow creation are available in `repository`
+scripts:
+
+* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, adds a new state
+  in the workflow.
+    
+* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`, 
+  adds a new transition in the workflow.
+
+You can find more details about workflows in the chapter :ref:`Workflow` .
+
+Configuration migration
+-----------------------
+
+The following functions for configuration migration are available in all 
+scripts:
+
+* `option_renamed(oldname, newname)`, indicates that an option has been renamed
+
+* `option_group_change(option, oldgroup, newgroup)`, indicates that an option does not
+  belong anymore to the same group.
+
+* `option_added(oldname, newname)`, indicates that an option has been added.
+
+* `option_removed(oldname, newname)`, indicates that an option has been deleted.
+
+
+Others migration functions
+--------------------------
+Those functions are only used for low level operations that could not be 
+accomplished otherwise or to repair damaged databases during interactive 
+session. They are available in `repository` scripts:
+
+* `sqlexec(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query
+* `add_entity_type_table(etype, commit=True)`
+* `add_relation_type_table(rtype, commit=True)`
+* `uninline_relation(rtype, commit=True)`
+
+
+[FIXME] Add explanation on how to use cubicweb-ctl shell
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/testing/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,44 @@
+.. -*- coding: utf-8 -*-
+
+Tests
+=====
+
+.. toctree::
+   :maxdepth: 1
+
+
+Unit tests
+----------
+
+`CubicWeb` framework provides essentially two Python test classes in the
+module `cubicweb.devtools.apptest`:
+
+* `EnvBasedTC`, to simulate a complete environment (web + repository)
+* `RepositoryBasedTC`, to simulate a repository environment only
+
+Thos two classes almost have the same interface and offers numerous methods to
+write tests rapidely and efficiently.
+
+XXX FILLME describe API
+
+In most of the cases, you will inherit `EnvBasedTC` to write Unittest or
+functional tests for your entities, views, hooks, etc...
+
+
+Email notifications tests
+-------------------------
+When running tests potentially generated e-mails are not really
+sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`. 
+This list is reset at each test *setUp* (by the setUp of classes `EnvBasedTC`
+and `RepositoryBasedTC`).
+
+	
+You can test your notifications by analyzing the contents of this list, which
+contains objects with two attributes:
+* `recipients`, the list of recipients
+* `msg`, object email.Message
+
+
+Automatic testing
+-----------------
+XXXFILLME
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/autoform.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,31 @@
+The automatic entity form (:mod:`cubicweb.web.views.autoform`)
+---------------------------------------------------------------
+
+It is possible to manage attributes/relations in the simple or multiple
+editing form thanks to the following *rtags*: 
+
+* `primary`, indicates that an attribute or a relation has to be
+  inserted **in the simple or multiple editing forms**. In the case of
+  a relation, the related entity editing form will be included in the
+  editing form and represented as a combobox. Each item of the
+  combobox is a link to an existing entity.
+
+* `secondary`, indicates that an attribute or a relation has to be
+  inserted **in the simple editing form only**. In the case of a
+  relation, the related entity editing form will be included in the
+  editing form and represented as a combobox. Each item of the combobox
+  is a link to an existing entity.
+
+* `inlineview`, includes the target entity's form in the editing form
+  of the current entity. It allows to create the target entity in the
+  same time as the current entity.
+
+* `generic`, indicates that a relation has to be inserted in the simple
+  editing form, in the generic box of relation creation.
+
+* `generated`, indicates that an attribute is dynamically computed
+  or other,  and that it should not be displayed in the editing form.
+
+If necessary, it is possible to overwrite the method  
+`relation_category(rtype, x='subject')` to dynamically compute
+a relation editing category.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/basetemplates.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,189 @@
+.. -*- coding: utf-8 -*-
+
+.. _templates:
+
+Templates
+=========
+
+[WRITE ME]
+
+* talk about main templates, etc.
+
+
+
+Look at ``cubicweb/web/views/basetemplates.py`` and you will
+find the base templates used to generate HTML for your application.
+
+A page is composed as indicated on the schema below :
+
+.. image:: ../../images/lax-book.06-main-template-layout.en.png
+
+In this section we will go through a couple of the primary templates
+you must be interested in, that is to say, the HTMLPageHeader,
+the HTMLPageFooter and the TheMainTemplate.
+
+
+HTMLPageHeader
+--------------
+
+Customize header
+~~~~~~~~~~~~~~~~
+
+Let's now move the search box in the header and remove the login form
+from the header. We'll show how to move it to the left column of the application.
+
+Let's say we do not want anymore the login menu to be in the header, but we 
+prefer it to be in the left column just below the logo. As the left column is
+rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_. 
+
+First, to remove the login menu, we just need to comment out the display of the
+login component such as follows : ::
+
+  class MyHTMLPageHeader(HTMLPageHeader):
+    
+      def main_header(self, view):
+          """build the top menu with authentification info and the rql box"""
+          self.w(u'<table id="header"><tr>\n')
+          self.w(u'<td id="firstcolumn">')
+          self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+          self.w(u'</td>\n')
+          # appliname and breadcrumbs
+          self.w(u'<td id="headtext">')
+          comp = self.vreg.select_component('appliname', self.req, self.rset)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w)
+          comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w, view=view)
+          self.w(u'</td>')
+          # logged user and help
+          #self.w(u'<td>\n')
+          #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+          #comp.dispatch(w=self.w)
+          #self.w(u'</td><td>')
+
+          self.w(u'<td>')
+          helpcomp = self.vreg.select_component('help', self.req, self.rset)
+          if helpcomp: # may not be available if Card is not defined in the schema
+              helpcomp.dispatch(w=self.w)
+          self.w(u'</td>')
+          # lastcolumn
+          self.w(u'<td id="lastcolumn">')
+          self.w(u'</td>\n')
+          self.w(u'</tr></table>\n')
+          self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+                        title=False, message=False)
+
+
+
+.. image:: ../../images/lax-book.06-header-no-login.en.png
+
+Let's now move the search box in the top-right header area. To do so, we will
+first create a method to get the search box display and insert it in the header
+table.
+
+::
+
+ from cubicweb.web.views.basetemplates import HTMLPageHeader
+ class MyHTMLPageHeader(HTMLPageHeader):
+    def main_header(self, view):
+        """build the top menu with authentification info and the rql box"""
+        self.w(u'<table id="header"><tr>\n')
+        self.w(u'<td id="firstcolumn">')
+        self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+        self.w(u'</td>\n')
+        # appliname and breadcrumbs
+        self.w(u'<td id="headtext">')
+        comp = self.vreg.select_component('appliname', self.req, self.rset)
+        if comp and comp.propval('visible'):
+            comp.dispatch(w=self.w)
+        comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+        if comp and comp.propval('visible'):
+            comp.dispatch(w=self.w, view=view)
+        self.w(u'</td>')
+        
+        # logged user and help
+        #self.w(u'<td>\n')
+        #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+        #comp.dispatch(w=self.w)
+        #self.w(u'</td><td>')
+        
+        # search box
+        self.w(u'<td>')
+        self.get_searchbox(view, 'left')
+        self.w(u'</td>')
+
+        self.w(u'<td>')
+        helpcomp = self.vreg.select_component('help', self.req, self.rset)
+        if helpcomp: # may not be available if Card is not defined in the schema
+            helpcomp.dispatch(w=self.w)
+        self.w(u'</td>')
+        # lastcolumn
+        self.w(u'<td id="lastcolumn">')
+        self.w(u'</td>\n')
+        self.w(u'</tr></table>\n')
+        self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+                      title=False, message=False)
+
+    def get_searchbox(self, view, context):
+        boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
+                                                 view=view, context=context))
+        if boxes:
+            for box in boxes:
+                if box.id == 'search_box':
+                    box.dispatch(w=self.w, view=view)
+
+ 
+
+
+HTMLPageFooter
+--------------
+
+If you want to change the footer for example, look
+for HTMLPageFooter and override it in your views file as in : 
+::
+
+  form cubicweb.web.views.basetemplates import HTMLPageFooter
+  class MyHTMLPageFooter(HTMLPageFooter):
+      def call(self, **kwargs):
+          self.w(u'<div class="footer">')
+          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
+          self.w(u'</div>')
+
+Updating a view does not require any restart of the server. By reloading
+the page you can see your new page footer.
+
+
+TheMainTemplate
+---------------
+.. _TheMainTemplate:
+
+TheMainTemplate is responsible for the general layout of the entire application. 
+It defines the template of ``id = main`` that is used by the application.
+
+The default main template (`cubicweb.web.views.basetemplates.TheMainTemplate`)
+builds the page based on the following pattern:
+
+.. image:: ../../images/main_template_layout.png
+
+The rectangle containing `view.dispatch()` represents the area where the content
+view has to be displayed. The others represents sub-templates called to complete
+the page. A default implementation of those is provided in 
+`cubicweb.views.basetemplates`. You can, of course, overload those sub-templates
+to implement your own customization of the HTML page.
+
+We can also control certain aspects of the main template thanks to the following
+forms parameters:
+
+* `__notemplate`, if present (whatever the value assigned), only the content view
+  is returned
+* `__force_display`, if present and its value is not null, no navigation 
+  whatever the number of entities to display
+* `__method`, if the result set to render contains only one entity and this 
+  parameter is set, it refers to a method to call on the entity by passing it
+  the dictionary of the forms parameters, before going the classic way (through
+  step 1 and 2 described juste above)
+
+The MainTemplate is a bit complex as it tries to accomodate many
+different cases. We are now about to go through it and cutomize entirely
+our application.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/baseviews.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,66 @@
+.. -*- coding: utf-8 -*-
+
+Base views (:mod:`cubicweb.web.views.baseviews`)
+------------------------------------------------
+
+`CubicWeb` provides a lot of standard views. You can find them in
+``cubicweb/web/views/``.
+
+A certain number of views are used to build the web interface, which apply
+to one or more entities. Their identifier is what distinguish them from
+each others and the main ones are:
+
+HTML views
+~~~~~~~~~~
+*oneline*
+    This is a hyper linked *text* view. Similar to the `secondary` view, 
+    but called when we want the view to stand on a single line, or just 
+    get a brief view. By default this view uses the
+    parameter `MAX_LINE_CHAR` to control the result size.
+
+*secondary*
+    This is a combinaison of an icon and a *oneline* view.
+    By default it renders the two first attributes of the entity as a 
+    clickable link redirecting to the primary view.
+
+*incontext, outofcontext*
+    Similar to the `secondary` view, but called when an entity is considered
+    as in or out of context. By default it respectively returns the result of 
+    `textincontext` and `textoutofcontext` wrapped in a link leading to 
+    the primary view of the entity.
+
+List
+`````
+*list*
+    This view displays a list of entities by creating a HTML list (`<ul>`) 
+    and call the view `listitem` for each entity of the result set.
+
+*listitem*
+    This view redirects by default to the `outofcontext` view.
+
+
+Special views
+`````````````
+
+*noresult*
+    This view is the default view used when no result has been found
+    (e.g. empty result set).
+
+*final*
+    Display the value of a cell without trasnformation (in case of a non final
+    entity, we see the eid). Applicable on any result set.
+
+*null*
+    This view is the default view used when nothing needs to be rendered.
+    It is always applicable and it does not return anything
+
+Text views
+~~~~~~~~~~
+*text*
+    This is the simplest text view for an entity. It displays the 
+    title of an entity. It should not contain HTML.
+
+*textincontext, textoutofcontext*
+    Similar to the `text` view, but called when an entity is considered out or
+    in context. By default it returns respectively the result of the 
+    methods `.dc_title` and `.dc_long_title` of the entity.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/boxes.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,34 @@
+Boxes (:mod:`cubicweb.web.views.boxes`)
+---------------------------------------------------------------
+
+*sidebox*
+  This view displays usually a side box of some related entities 
+  in a primary view.
+
+The action box
+~~~~~~~~~~~~~~~
+
+The ``add_related`` is an automatic menu in the action box that allows to create
+an entity automatically related to the initial entity (context in
+which the box is displayed). By default, the links generated in this
+box are computed from the schema properties of the displayed entity,
+but it is possible to explicitely specify them thanks to the
+`cubicweb.web.uicfg.rmode` *relation tag*:
+
+* `link`, indicates that a relation is in general created pointing
+  to an existing entity and that we should not to display a link
+  for this relation
+
+* `create`, indicates that a relation is in general created pointing
+  to new entities and that we should display a link to create a new
+  entity and link to it automatically
+
+  
+
+If necessary, it is possible to overwrite the method  
+`relation_mode(rtype, targettype, x='subject')` to dynamically
+compute a relation creation category.
+
+Please note that if at least one action belongs to the `addrelated` category,
+the automatic behavior is desactivated in favor of an explicit behavior
+(e.g. display of `addrelated` category actions only).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/breadcrumbs.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+Breadcrumbs (:mod:`cubicweb.web.views.ibreadcrumbs`)
+----------------------------------------------------
+XXX feedme
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/editcontroller.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,118 @@
+.. -*- coding: utf-8 -*-
+The 'edit' controller (:mod:`cubicweb.web.views.editcontroller`)
+----------------------------------------------------------------
+
+Editing control
+~~~~~~~~~~~~~~~~
+
+Re-requisites: the parameters related to entities to edit are
+specified as follows ::
+
+  <field name>:<entity eid>
+
+where entity eid could be a letter in case of an entity to create. We
+name those parameters as *qualified*.
+
+1. Retrieval of entities to edit by looking for the forms parameters
+   starting by `eid:` and also having a parameter `__type` associated
+   (also *qualified* by eid)
+
+2. For all the attributes and the relations of an entity to edit:
+
+   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
+      qualified in the case of a relation where the entity is object
+   2. if found, the value returned is considered as the initial value
+      for this relaiton and we then look for the new value(s)  in the parameter
+      <relation name> (qualified)
+   3. if the value returned is different from the initial value, an database update
+      request is done
+
+3. For each entity to edit:
+
+   1. if a qualified parameter `__linkto` is specified, its value has to be
+      a string (or a list of string) such as: ::
+
+        <relation type>:<eids>:<target>
+
+      where <target> is either `subject` or `object` and each eid could be
+      separated from the others by a `_`. Target specifies if the *edited entity*
+      is subject or object of the relation and each relation specified will
+      be inserted.
+
+    2. if a qualified parameter `__clone_eid` is specified for an entity, the
+       relations of the specified entity passed as value of this parameter are
+       copied on the edited entity.
+
+    3. if a qualified parameter `__delete` is specified, its value must be
+       a string or a list of string such as follows: ::
+
+          <ssubjects eids>:<relation type>:<objects eids>
+
+       where each eid subject or object can be seperated from the other
+       by `_`. Each relation specified will be deleted.
+
+    4. if a qualified parameter `__insert` is specified, its value should
+       follow the same pattern as `__delete`, but each relation specified is
+       inserted.
+
+4. If the parameters `__insert` and/or `__delete` are found not qualified,
+   they are interpreted as explained above (independantly from the number
+   of entities edited).
+
+5. If no entity is edited but the form contains the parameters `__linkto`
+   and `eid`, this one is interpreted by using the value specified for `eid`
+   to designate the entity on which to add the relations.
+
+
+.. note::
+
+   * If the parameter `__action_delete` is found, all the entities specified
+     as to be edited will be deleted.
+
+   * If the parameter`__action_cancel` is found, no action is completed.
+
+   * If the parameter `__action_apply` is found, the editing is applied
+     normally but the redirection is done on the form
+     (see :ref:`RedirectionControl`).
+
+   * The parameter `__method` is also supported as for the main template
+     (XXX not very consistent, maybe __method should be dealed in the view
+     controller).
+
+   * If no entity is found to be edited and if there is no parameter
+     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
+     `__insert`, an error is raised.
+
+   * Using the parameter `__message` in the form will allow to use its value
+     as a message to provide the user once the editing is completed.
+
+
+.. _RedirectionControl:
+
+Redirection control
+~~~~~~~~~~~~~~~~~~~
+Once editing is completed, there is still an issue left: where should we go
+now? If nothing is specified, the controller will do his job but it does not
+mean we will be happy with the result. We can control that by using the
+following parameters:
+
+* `__redirectpath`: path of the URL (relative to the root URL of the site,
+  no form parameters
+
+* `__redirectparams`: forms parameters to add to the path
+
+* `__redirectrql`: redirection RQL request
+
+* `__redirectvid`: redirection view identifier
+
+* `__errorurl`: initial form URL, used for redirecting in case a validation
+  error is raised during editing. If this one is not specified, an error page
+  is displayed instead of going back to the form (which is, if necessary,
+  responsible for displaying the errors)
+
+* `__form_id`: initial view form identifier, used if `__action_apply` is
+  found
+
+In general we use either `__redirectpath` and `__redirectparams` or
+`__redirectrql` and `__redirectvid`.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/editforms.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+Standard forms (:mod:`cubicweb.web.views.editforms`)
+----------------------------------------------------
+XXX feed me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/embedding.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Embedding external pages (:mod:`cubicweb.web.views.embedding`)
+---------------------------------------------------------------
+
+including external content
+
+XXX feeed me
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/facets.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+Facets (:mod:`cubicweb.web.views.facets`)
+-----------------------------------------
+XXX feed me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/idownloadable.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,3 @@
+The 'download' view (:mod:`cubicweb.web.views.idownloadable`)
+---------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,27 @@
+Standard features for web interface development
+===============================================
+
+This chapter describes generic web features built as CubicWeb application objects.
+
+They are used for CubicWeb default automatic interface, but you're free to use
+them or not for you're own application.
+
+.. toctree::
+   :maxdepth: 1
+
+   basetemplates
+   primary
+   baseviews
+   startup
+   boxes
+   table
+   xmlrss
+   autoform
+   editforms
+   editcontroller
+   urlpublish
+   breadcrumbs
+   facets
+   wdoc
+   embedding
+   idownloadable
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/primary.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,117 @@
+The default 'primary' view (:mod:`cubicweb.web.views.primary`)
+---------------------------------------------------------------
+
+The primary view of an entity is the view called by default when a single
+entity is in the result set and needs to be displayed. 
+
+This view is supposed to render a maximum of informations about the entity.
+
+
+
+Rendering methods and attributes for ``PrimaryView``
+----------------------------------------------------
+
+By default, `CubicWeb` provides a primary view for each new entity type
+you create. The first view you might be interested in modifying.
+
+Let's have a quick look at the EntityView ``PrimaryView`` as well as
+its rendering method
+
+.. code-block:: python
+    
+    class PrimaryView(EntityView):
+        """the full view of an non final entity"""
+        id = 'primary'
+        title = _('primary')
+        show_attr_label = True
+        show_rel_label = True
+        skip_none = True
+        skip_attrs = ('eid', 'creation_date', 'modification_date')
+        skip_rels = ()
+        main_related_section = True
+
+        ...
+
+    def cell_call(self, row, col):
+        self.row = row
+        self.render_entity(self.complete_entity(row, col))
+
+    def render_entity(self, entity):
+        """return html to display the given entity"""
+        siderelations = []
+        self.render_entity_title(entity)
+        self.render_entity_metadata(entity)
+        # entity's attributes and relations, excluding meta data
+        # if the entity isn't meta itself
+        self.w(u'<div>')
+        self.w(u'<div class="mainInfo">')
+        self.render_entity_attributes(entity, siderelations)
+        self.w(u'</div>')
+        self.content_navigation_components('navcontenttop')
+        if self.main_related_section:
+            self.render_entity_relations(entity, siderelations)
+        self.w(u'</div>')
+        # side boxes
+        self.w(u'<div class="primaryRight">')
+        self.render_side_related(entity, siderelations)
+        self.w(u'</div>')
+        self.w(u'<div class="clear"></div>')
+        self.content_navigation_components('navcontentbottom')
+
+    ...
+
+``cell_call`` is executed for each entity of a result set and apply ``render_entity``.
+
+The methods you want to modify while customizing a ``PrimaryView`` are:
+
+*render_entity_title(self, entity)* 
+    Renders the entity title based on the assumption that the method 
+    ``def content_title(self)`` is implemented for the given entity type.
+
+*render_entity_metadata(self, entity)*
+    Renders the entity metadata based on the assumption that the method
+    ``def summary(self)`` is implemented for the given entity type.
+
+*render_entity_attributes(self, entity, siderelations)*
+    Renders all the attribute of an entity with the exception of attribute
+    of type `Password` and `Bytes`.
+
+*content_navigation_components(self, context)*
+    This method is applicable only for entity type implementing the interface 
+    `IPrevNext`. This interface is for entities which can be linked to a previous
+    and/or next entity. This methods will render the navigation links between
+    entities of this type, either at the top or at the bottom of the page
+    given the context (navcontent{top|bottom}).
+
+*render_entity_relations(self, entity, siderelations)*
+    Renders all the relations of the entity in the main section of the page.
+        
+*render_side_related(self, entity, siderelations)*
+    Renders all the relations of the entity in a side box. This is equivalent
+    to *render_entity_relations* in addition to render the relations
+    in a box.
+
+Also, please note that by setting the following attributes in you class,
+you can already customize some of the rendering:
+
+*show_attr_label*
+    Renders the attribute label next to the attribute value if set to True.
+    Otherwise, does only display the attribute value.
+
+*show_rel_label* 
+    Renders the relation label next to the relation value if set to True.
+    Otherwise, does only display the relation value.
+
+*skip_none*
+    Does not render an attribute value that is None if set to True.
+
+*main_related_section*
+    Renders the relations of the entity if set to True.
+
+A good practice is for you to identify the content of your entity type for which
+the default rendering does not answer your need so that you can focus on the specific
+method (from the list above) that needs to be modified. We do not recommand you to
+overwrite ``render_entity`` as you might potentially loose the benefits of the side
+boxes handling.
+
+.. XXX talk about uicfg.rdisplay
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/startup.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,13 @@
+Startup views (:mod:`cubicweb.web.views.startup`)
+-------------------------------------------------
+Usual selector: no_rset or yes.
+
+Views that don't apply to a result set
+
+*index*
+    This view defines the home page of your application. It does not require
+    a result set to apply to.
+
+*schema*
+    A view dedicated to the display of the schema of the application
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/table.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,10 @@
+Table views (:mod:`cubicweb.web.views.table`)
+----------------------------------------------
+
+*table*
+    Creates a HTML table (`<table>`) and call the view `cell` for each cell of
+    the result set. Applicable on any result set.
+
+*cell*
+    By default redirects to the `final` view if this is a final entity or
+    `outofcontext` view otherwise
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/urlpublish.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,6 @@
+.. -*- coding: utf-8 -*-
+URL Rewriting (:mod:`cubicweb.web.views.urlpublish`) and (:mod:`cubicweb.web.views.urlrewrite`)
+------------------------------------------------------------------------------------------------
+
+XXX feed me
+show how urls are mapped to selections and views and explain URLRewriting 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/wdoc.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,7 @@
+.. -*- coding: utf-8 -*-
+
+Online documentation system (:mod:`cubicweb.web.views.wdoc`)
+-------------------------------------------------------------
+
+XXX  describe the on-line documentation system
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/webstdlib/xmlrss.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,10 @@
+XML and RSS views (:mod:`cubicweb.web.views.xmlrss`)
+----------------------------------------------------
+
+*rss*
+    Creates a RSS/XML view and call the view `rssitem` for each entity of
+    the result set.
+
+*rssitem*
+    Create a RSS/XML view for each entity based on the results of the dublin core
+    methods of the entity (`dc_*`)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,62 @@
+.. -*- coding: utf-8 -*-
+
+.. _contents:
+
+=====================================================
+`CubicWeb` - The Semantic Web is a construction game!
+=====================================================
+
+`CubicWeb` is a semantic web application framework, licensed under the LGPL,
+that empowers developers to efficiently build web applications by reusing
+components (called `cubes`) and following the well known object-oriented design
+principles.
+
+Its main features are:
+
+* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application, 
+* a query language name :ref:`RQL <RQL>` similar to W3C's SPARQL, 
+* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation, 
+* a library of reusable :ref:`components <cubes>` (data model and views) that fulfill common needs,
+* the power and flexibility of the Python_ programming language,
+* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
+
+Built since 2000 from an R&D effort still continued, supporting 100,000s of
+daily visits at some production sites, `CubicWeb` is a proven end to end solution
+for semantic web application development that promotes quality, reusability and
+efficiency.
+
+The unbeliever will read the :ref:`Tutorial`.
+
+The hacker will join development at the forge_.
+
+The impatient developper will move right away to :ref:`SetUpEnv`.
+
+.. _Logilab: http://www.logilab.fr/
+.. _forge: http://www.cubicweb.org/project/
+.. _Python: http://www.python.org/
+
+The book
+========
+
+.. toctree::
+   :maxdepth: 2
+
+   intro/index
+   development/index
+   admin/index
+   annexes/index
+
+
+
+Table of Contents
+-----------------
+
+Complete :ref:`TOC`.
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
--- a/doc/book/en/index.txt	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _contents:
-
-=====================================================
-`CubicWeb` - The Semantic Web is a construction game!
-=====================================================
-
-`CubicWeb` is a semantic web application framework, licensed under the LGPL,
-that empowers developers to efficiently build web applications by reusing
-components (called `cubes`) and following the well known object-oriented design
-principles.
-
-Its main features are:
-
-* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application, 
-* a query language name :ref:`RQL <RQL>` similar to W3C's SPARQL, 
-* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation, 
-* a library of reusable :ref:`components <cubes>` (data model and views) that fulfill common needs,
-* the power and flexibility of the Python_ programming language,
-* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
-
-Built since 2000 from an R&D effort still continued, supporting 100,000s of
-daily visits at some production sites, `CubicWeb` is a proven end to end solution
-for semantic web application development that promotes quality, reusability and
-efficiency.
-
-The unbeliever will read the :ref:`Overview`.
-
-The hacker will join development at the forge_.
-
-The impatient will go strait away to :ref:`QuickInstall`.
-
-The impatient developper will move right away to :ref:`MiseEnPlaceEnv`.
-
-.. _Logilab: http://www.logilab.fr/
-.. _forge: http://www.cubicweb.org/project/
-.. _Python: http://www.python.org/
-
-Table of contents
-=================
-
-
-.. toctree::
-   :maxdepth: 2
-
-   A000-introduction.en.txt
-   B000-development.en.txt
-   C000-administration.en.txt
-   D000-annex.en.txt
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/book-map.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,10 @@
+.. -*- coding: utf-8 -*-
+
+Book map
+========
+
+[XXX WRITE ME]
+
+* explain how to use this book and what chapters to read in what order depending on the
+  objectives of the reader
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/concepts/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,265 @@
+.. -*- coding: utf-8 -*-
+
+The Core Concepts of CubicWeb
+=============================
+
+.. toctree::
+   :maxdepth: 1
+
+------------------------------
+
+This section aims to provide you the keys of success with *CubicWeb*
+by clarifying the terms specific to our framework. If you want to do anything
+serious with CubicWeb, you should understand concepts in those lines.
+
+*CubicWeb* defines its own terminology. To make sure there is no confusion
+while reading this book, we strongly recommand you take time to go through
+the following definitions that are the basics to understand while
+developing with *CubicWeb*.
+
+
+.. _Cube:
+
+Cubes
+-----
+** Construct your application by assembling cubes **
+
+A cube provides a specific functionality, or a complete *CubicWeb*
+application usually by assembling other cubes.
+
+It's usually composed of a data model, some logic to manipulate it and some parts
+of web interface.
+
+You can decide to write your own set of cubes if you wish to re-use the
+entity types you develop or/and if you have specific needs not covered by
+cubes are available from the `CubicWeb Forge`_ under a free software license.
+
+Available cubes on your system are defined in the directory
+:file:`/usr/share/cubicweb/cubes` when using a system wide installation.  For people
+using the mercurial repository of cubicweb, the :file:`/path/to/forest/cubicweb/cubes`
+directory is used. You can specify additional location using the :envvar:`CW_CUBES_PATH`
+environment variable, using ':' as separator.
+
+.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
+
+
+
+Instances
+----------
+** *CubicWeb* framework is a server/client application framework**
+
+An instance is a specific installation of one or multiple cubes. All the required
+configuration files necessary for the well being of your web application are
+grouped in an instance. This will refer to the cube(s) your application is based
+on.  For example logilab.org and our intranet are two instances of a single cube
+`jpl`
+
+We recommand not to define schema, entities or views in the instance
+file system itself but in the cube, in order to maintain re-usability of
+entities and their views. We strongly recommand to develop cubes which
+could be used in other instances (modular approach).
+
+An instance usually usually consists into a web interface which is talking to a
+rql repository, itself connected to a SQL database, all into a single
+process. You can have some more complicated configurations using several web
+front-ends talking to a rql repository using `Pyro`_, databases replication...
+
+.. image:: ../../images/archi_globale.en.png
+
+The term application is sometimes used to talk about an instance and sometimes to
+talk of a cube depending on the context.  So we would like to avoid using this
+term and try to use *cube* and *instance* instead.
+
+Data Repository
+~~~~~~~~~~~~~~~
+The repository (Be carefull not to get confused with a Mercurial repository or a
+debian repository!) manages all interactions with various data sources by
+providing access to them using uniformly using the Relation Query Language (RQL).  The
+web interface and the repository communicate using this language.
+
+Usually, the web server and repository sides are integrated in the same process and
+interact directly, without the need for distant calls using Pyro. But, it is
+important to note that those two sides, client/server, are disjointed and it is
+possible to execute a couple of calls in distinct processes to balance the load
+of your web site on one or more machines.
+
+
+A data source is a container of data integrated in the *CubicWeb* repository. A
+repository has at least one source, named `system`, which contains the schema of
+the application, plain-text index and other vital informations for the
+system. You'll find source for SQL databases, LDAP servers, other RQL
+repositories and even mercurial /svn repositories or `Google App Engine`'s
+datastore.
+
+Web interface
+~~~~~~~~~~~~~
+By default the web server provides a generated interface based on its schema.
+Entities can be created, displayed, updated and deleted. As display views are not
+very fancy, it is usually necessary to develop your own.
+
+Instances are defined on your system in the directory :file:`/etc/cubicweb.d` when
+using a system wide installation.  For people using the mercurial repository of
+cubicweb, the :file:`etc` directory is searched in the user home directory. You can
+also specify an alternative directory using the :envvar:`CW_REGISTRY` environment
+variable.
+
+
+
+Schema
+------
+** *CubicWeb* is schema driven **
+
+The schema describes the persistent data model using entities and
+relations. It is modeled with a comprehensive language made of Python classes based on
+the `yams`_ library.
+
+When you create a new cubicweb instance, the schema is stored in the database,
+and it will usually evolves as you upgrade cubicweb and used cubes.
+
+*CubicWeb* provides a certain number of system entities included
+sytematically (necessary for the core of *CubicWeb*, notably the schema itself).
+You will also find a library of cubes which defines more piece of schema for standard needs.
+necessary.
+
+*CubicWeb* add some metadata to every entity type, such as the eid (a global
+  identifier, unique into an instance), entity's creation date...
+
+
+Attributes may be of the following types:
+  `String`, `Int`, `Float`, `Boolean`, `Date`, `Time`, `Datetime`,
+  `Interval`, `Password`, `Bytes`.
+
+New in 3.2: RichString
+
+see :ref:`yams.BASE_TYPES`
+
+Data level security is defined by setting permissions on entity and relation types.
+  
+A schema consist of parts detailed below.
+
+
+Entity type
+~~~~~~~~~~~
+An *entity type* defines set of attributes and is used in some relations. It may
+have some permissions telling who can read/add/update/delete entities of this type.
+
+Relation type
+~~~~~~~~~~~~~
+A *relation type* is used to define a semantic relation between two entity types.
+It may have some permissions telling who can read/add/delete relation of this type.
+
+In *CubicWeb* relations are ordered and binary: by convention we name the first
+item of a relation the `subject` and the second the `object`.
+
+Relation definition
+~~~~~~~~~~~~~~~~~~~
+A *relation definition* is a 3-uple (*subject entity type*, *relation type*, *object
+entity type*), with an associated set of property such as cardinality, constraints...
+
+
+
+Dynamic objects for reusable components
+---------------------------------------
+** Dynamic objects management or how CubicWeb provides really reusable components **
+
+Application objects
+~~~~~~~~~~~~~~~~~~~
+Beside a few core functionalities, almost every feature of the framework is
+acheived by dynamic objects (`application objects` or `appobjects`) stored in a
+two-levels registry (the `vregistry`). Each object is affected to a registry with
+an identifier in this registry. You may have more than one object sharing an
+identifier in the same registry, At runtime, appobjects are selected in the
+vregistry according to the context.
+
+Application objects are stored in the registry using a two level hierarchy :
+
+  object's `__registry__` : object's `id` : [list of app objects]
+
+The base class of appobjects is `AppRsetObject` (module `cubicweb.appobject`).
+
+The `vregistry`
+~~~~~~~~~~~~~~~
+At startup, the `registry` or registers base, inspects a number of directories
+looking for compatible classes definition. After a recording process, the objects
+are assigned to registers so that they can be selected dynamically while the
+application is running.
+
+Selectors
+~~~~~~~~~
+Each appobject has a selector, which is used to score how well it suits to a
+given context by returning a score.  A score of 0 means the object doesn't apply
+to the context. The score is used to choose the most pertinent object: the "more"
+the appobject suits the context the higher the score.
+
+CubicWeb provides a set of basic selectors which may be parametrized and combined
+using binary `&` and `|` operators to provide a custom selector (which can be
+itself reused...).
+
+There is 3 current ways to retreive some appobject from the repository:
+
+* get the most appropriate objects by specifying a registry and an identifier. In
+  that case, the object with the greatest score is selected. There should always
+  be a single appobject with a greater score than others.
+
+* get all appobjects applying to a context by specifying a registry.In
+  that case, every objects with the a postive score are selected.
+
+* get the object within a particular registry/identifier. In that case no
+  selection process is involved, the vregistry will expect to find a single
+  object in that cell.
+
+Selector sets are the glue that tie views to the data model. Using them
+appropriately is an essential part of the construction of well behaved cubes.
+
+
+When no score is higher than the others, an exception is raised in development
+mode to let you know that the engine was not able to identify the view to
+apply. This error is silented in production mode and one of the objects with the
+higher score is picked. 
+
+If no object has a positive score, ``NoSelectableObject`` exception is raised.
+
+If no object is found for a particular registry and identifier,
+``ObjectNotFound`` exception is raised.
+
+In such cases you would need to review your design and make sure your views are
+properly defined.
+
+
+
+The RQL query language
+----------------------
+**No needs for a complicated ORM when you've a powerful query language**
+
+All the persistant data in a CubicWeb application is retreived and modified by using the
+Relation Query Language.
+
+This query language is inspired by SQL but is on a higher level in order to
+emphasize browsing relations.
+
+db-api
+~~~~~~
+The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
+XXX feed me
+
+Result set
+~~~~~~~~~~
+XXX feed me
+
+
+Views
+-----
+** *CubicWeb* is data driven **
+
+XXX feed me.
+
+
+Hooks
+-----
+** *CubicWeb* provides an extensible data repository **
+
+XXX feed me.
+
+
+.. _`Python Remote Object`: http://pyro.sourceforge.net/
+.. _`yams`: http://www.logilab.org/project/yams/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/foundations/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,33 @@
+.. -*- coding: utf-8 -*-
+
+`CubicWeb` Foundations
+======================
+
+A little history...
+-------------------
+
+`CubicWeb` is a web application framework developped by Logilab_ since 2001.
+
+Entirely written in Python, `CubicWeb` publishes data from all sorts
+of sources such as SQL database, LDAP directory and versioning system such
+as subversion.
+
+`CubicWeb` user interface was designed to let the final user a huge flexibility
+on how to select and how to display content. It allows to browse the knowledge
+database and to display the results with the best rendering according to
+the context.
+This interface flexibility gives back the user the control of the 
+rendering parameters that are usually reserved for developpers.
+
+
+We can list a couple of web applications developped with `CubicWeb`, an online
+public phone directory (see http://www.118000.fr/), a system for managing 
+digital studies and simulations for a research lab, a tool for shared children
+babysitting (see http://garde-partagee.atoukontact.fr/), a tool to manage
+software developpment (see http://www.logilab.org), an application for
+managing museums collections (see 
+http://collections.musees-haute-normandie.fr/collections/), etc.
+
+In 2008, `CubicWeb` was ported for a new type of source : the datastore 
+from `GoogleAppEngine`_.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,20 @@
+.. -*- coding: utf-8 -*-
+
+.. _Part1:
+
+-----------------------------------
+Part I - Introduction to `CubicWeb`
+-----------------------------------
+
+This first part of the book will present different reading path to
+discover the `CubicWeb` framework, provide a tutorial to get a quick
+overview of its features and list its key concepts.
+
+ 
+.. toctree::
+   :maxdepth: 2
+
+   book-map
+   foundations/index
+   concepts/index
+   tutorial/index
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/blog-less-ten-minutes.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,26 @@
+.. -*- coding: utf-8 -*-
+
+.. _BlogTenMinutes:
+
+Get a Blog running in less than ten minutes!
+--------------------------------------------
+
+You need to install the following packages (:ref:`DebianInstallation`)::
+
+    cubicweb, cubicweb-dev, cubicweb-blog
+
+Creation and initialization of your application by running::
+    
+    cubicweb-ctl create blog myblog
+
+Your application is now ready to go::
+
+    cubicweb-ctl start -D myblog
+
+This is it. Your blog is ready to you. Go to http://localhost:8080 and enjoy!
+
+As a developper, you'll want to know more about how to develop new
+cubes and cutomize the look of your application and this is what we
+talk about now. 
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/components.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,81 @@
+.. -*- coding: utf-8 -*-
+
+.. _cubes:
+
+Cubes
+-----
+
+Standard library
+~~~~~~~~~~~~~~~~
+
+A library of standard cubes are available from `CubicWeb Forge`_
+Cubes provide entities and views.
+
+The available application entities are:
+
+* addressbook: PhoneNumber and PostalAddress
+
+* basket: Basket (like a shopping cart)
+
+* blog: Blog (a *very* basic blog)
+
+* classfolder: Folder (to organize things but grouping them in folders)
+
+* classtags: Tag (to tag anything)
+
+* file: File (to allow users to upload and store binary or text files)
+
+* link: Link (to collect links to web resources)
+
+* mailinglist: MailingList (to reference a mailing-list and the URLs
+  for its archives and its admin interface)
+
+* person: Person (easily mixed with addressbook)
+
+* task: Task (something to be done between start and stop date)
+
+* zone: Zone (to define places within larger places, for example a
+  city in a state in a country)
+
+The available system entities are:
+
+* comment: Comment (to attach comment threads to entities)
+
+
+Adding comments to BlogDemo
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To import a cube in your application just change the line in the
+``__pkginfo__.py`` file and verify that the cube you are planning
+to use is listed by the command ``cubicweb-ctl list``.
+For example::
+
+    __use__ = ('comment',)
+
+will make the ``Comment`` entity available in your ``BlogDemo``
+application.
+
+Change the schema to add a relationship between ``BlogEntry`` and
+``Comment`` and you are done. Since the comment cube defines the
+``comments`` relationship, adding the line::
+
+    comments = ObjectRelation('Comment', cardinality='1*', composite='object')
+
+to the definition of a ``BlogEntry`` will be enough.
+
+Synchronize the data model
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you modified your data model, you need to synchronize the
+database with your model. For this purpose, `CubicWeb` provides
+a very useful command ``cubicweb-ctl shell blogdemo`` which
+launches an interactive migration Python shell. (see 
+:ref:`cubicweb-ctl` for more details))
+As you modified a relation from the `BlogEntry` schema,
+run the following command:
+::
+
+  synchronize_rschema('BlogEntry')
+  
+You can now start your application and add comments to each 
+`BlogEntry`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/conclusion.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,17 @@
+.. -*- coding: utf-8 -*-
+
+What's next?
+------------
+
+We demonstrated how from a straight out of the box `CubicWeb`
+installation, you can build your web-application based on a
+schema. It's all already there: views, templates, permissions,
+etc. The step forward is now for you to customize according
+to your needs.
+
+More than a web application, many features are available to
+extend your application, for example: RSS channel integration 
+(:ref:`rss`), hooks (:ref:`hooks`), support of sources such as 
+Google App Engine (:ref:`gaecontents`) and lots of others to 
+discover through our book.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/create-cube.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,273 @@
+.. -*- coding: utf-8 -*-
+
+Create your cube
+----------------
+
+The packages ``cubicweb`` and ``cubicweb-dev`` installs a command line tool
+for `CubicWeb` called ``cubicweb-ctl``. This tool provides a wide range of
+commands described in details in :ref:`cubicweb-ctl`. 
+
+Once your `CubicWeb` development environment is set up, you can create a new
+cube::
+
+  cubicweb-ctl newcube blog
+
+This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
+installation, ``/usr/share/cubicweb/cubes`` for debian packages installation) 
+a directory named ``blog`` reflecting the structure described in :ref:`cubesConcepts`.
+
+.. _DefineDataModel:
+
+Define your data model
+----------------------
+
+The data model or schema is the core of your `CubicWeb` application.
+It defines the type of content your application will handle.
+
+The data model of your cube ``blog`` is defined in the file ``schema.py``:
+
+::
+
+  class Blog(EntityType):
+    title = String(maxsize=50, required=True)
+    description = String()
+
+  class BlogEntry(EntityType):
+    title = String(required=True, fulltextindexed=True, maxsize=256)
+    publish_date = Date(default='TODAY')
+    content = String(required=True, fulltextindexed=True)
+    entry_of = SubjectRelation('Blog', cardinality='?*') 
+
+
+A Blog has a title and a description. The title is a string that is
+required by the class EntityType and must be less than 50 characters. 
+The description is a string that is not constrained.
+
+A BlogEntry has a title, a publish_date and a content. The title is a
+string that is required and must be less than 100 characters. The
+publish_date is a Date with a default value of TODAY, meaning that
+when a BlogEntry is created, its publish_date will be the current day
+unless it is modified. The content is a string that will be indexed in
+the full-text index and has no constraint.
+
+A BlogEntry also has a relationship ``entry_of`` that links it to a
+Blog. The cardinality ``?*`` means that a BlogEntry can be part of
+zero or one Blog (``?`` means `zero or one`) and that a Blog can
+have any number of BlogEntry (``*`` means `any number including
+zero`). For completeness, remember that ``+`` means `one or more`.
+
+
+Create your instance
+--------------------
+
+To use this cube as an application and create a new instance named ``blogdemo``, do::
+  
+  cubicweb-ctl create blog blogdemo
+
+
+This command will create the corresponding database and initialize it.
+
+Welcome to your web application
+-------------------------------
+
+Start your application in debug mode with the following command: ::
+
+  cubicweb-ctl start -D blogdemo
+
+
+You can now access your web application to create blogs and post messages
+by visiting the URL http://localhost:8080/.
+
+A login form will appear. By default, the application will not allow anonymous
+users to enter the application. To login, you need then use the admin account
+you created at the time you initialized the database with ``cubicweb-ctl
+create``.
+
+.. image:: ../../images/login-form.png
+
+
+Once authenticated, you can start playing with your application 
+and create entities.
+
+.. image:: ../../images/blog-demo-first-page.png
+
+Please notice that so far, the `CubicWeb` franework managed all aspects of 
+the web application based on the schema provided at first.
+
+
+Add entities
+------------
+
+We will now add entities in our web application.
+
+Add a Blog
+~~~~~~~~~~
+
+Let us create a few of these entities. Click on the `[+]` at the left of the
+link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
+``everything about technology`` as the description, then validate the form by
+clicking on ``Validate``.
+
+.. image:: ../../images/cbw-create-blog.en.png
+   :alt: from to create blog
+
+Click on the logo at top left to get back to the home page, then
+follow the Blog link that will list for you all the existing Blog.
+You should be seeing a list with a single item ``Tech-blog`` you
+just created.
+
+.. image:: ../../images/cbw-list-one-blog.en.png
+   :alt: displaying a list of a single blog
+
+Clicking on this item will get you to its detailed description except
+that in this case, there is not much to display besides the name and
+the phrase ``everything about technology``.
+
+Now get back to the home page by clicking on the top-left logo, then
+create a new Blog called ``MyLife`` and get back to the home page
+again to follow the Blog link for the second time. The list now
+has two items.
+
+.. image:: ../../images/cbw-list-two-blog.en.png
+   :alt: displaying a list of two blogs
+
+Add a BlogEntry
+~~~~~~~~~~~~~~~
+
+Get back to the home page and click on [+] at the left of the link
+BlogEntry. Call this new entry ``Hello World`` and type in some text
+before clicking on ``Validate``. You added a new blog entry without
+saying to what blog it belongs. There is a box on the left entitled
+``actions``, click on the menu item ``modify``. You are back to the form
+to edit the blog entry you just created, except that the form now has
+another section with a combobox titled ``add relation``. Chose
+``entry_of`` in this menu and a second combobox appears where you pick
+``MyLife``. 
+
+You could also have, at the time you started to fill the form for a
+new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the 
+combobox titled ``add relation`` would have showed up.
+
+
+.. image:: ../../images/cbw-add-relation-entryof.en.png
+   :alt: editing a blog entry to add a relation to a blog
+
+Validate the changes by clicking ``Validate``. The entity BlogEntry
+that is displayed now includes a link to the entity Blog named
+``MyLife``.
+
+.. image:: ../../images/cbw-detail-one-blogentry.en.png
+   :alt: displaying the detailed view of a blogentry
+
+Note that all of this was handled by the framework and that the only input
+that was provided so far is the schema. To get a graphical view of the schema,
+point your browser to the URL http://localhost:8080/schema
+
+.. image:: ../../images/cbw-schema.en.png
+   :alt: graphical view of the schema (aka data-model)
+
+
+.. _DefineViews:
+
+Define your entity views
+------------------------
+
+Each entity defined in a model inherits default views allowing
+different rendering of the data. You can redefine each of them
+according to your needs and preferences. So let's see how the
+views are defined.
+
+
+The view selection principle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A view is defined by a Python class which includes: 
+  
+  - an identifier (all objects in `CubicWeb` are entered in a registry
+    and this identifier will be used as a key)
+  
+  - a filter to select the result sets it can be applied to
+
+A view has a set of methods complying
+with the `View` class interface (`cubicweb.common.view`).
+
+`CubicWeb` provides a lot of standard views for the type `EntityView`;
+for a complete list, read the code in directory ``cubicweb/web/views/``.
+
+A view is applied on a `result set` which contains a set of
+entities we are trying to display. `CubicWeb` uses a selector
+mechanism which computes for each available view a score: 
+the view with the highest score is then used to display the given `result set`.
+The standard library of selectors is in 
+``cubicweb.common.selector`` and a library of methods used to
+compute scores is available in ``cubicweb.vregistry.vreq``.
+
+It is possible to define multiple views for the same identifier
+and to associate selectors and filters to allow the application
+to find the best way to render the data. 
+
+For example, the view named ``primary`` is the one used to display
+a single entity. We will now show you how to customize this view.
+
+
+View customization
+~~~~~~~~~~~~~~~~~~
+
+If you wish to modify the way a `BlogEntry` is rendered, you will have to 
+overwrite the `primary` view defined in the module ``views`` of the cube
+``cubes/blog/views.py``.
+
+We can for example add in front of the publication date a prefix specifying
+that the date we see is the publication date.
+
+To do so, please apply the following changes:
+
+.. code-block:: python
+
+  from cubicweb.web.views import baseviews
+
+
+  class BlogEntryPrimaryView(baseviews.PrimaryView):
+
+    accepts = ('BlogEntry',)
+
+    def render_entity_title(self, entity):
+        self.w(u'<h1>%s</h1>' % html_escape(entity.dc_title()))
+
+    def content_format(self, entity):
+        return entity.view('reledit', rtype='content_format')
+
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+
+        # display entity attributes with prefixes
+        self.w(u'<h1>%s</h1>' % entity.title)
+        self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
+        self.w(u'<p>%s</p>' % entity.content)
+        
+        # display relations
+        siderelations = []
+        if self.main_related_section:
+            self.render_entity_relations(entity, siderelations)
+
+.. note::
+  When a view is modified, it is not required to restart the application
+  server. Save the Python file and reload the page in your web browser
+  to view the changes.
+
+You can now see that the publication date has a prefix.
+
+.. image:: ../../images/cbw-update-primary-view.en.png
+   :alt: modified primary view
+
+
+The above source code defines a new primary view for ``BlogEntry``. 
+
+Since views are applied to result sets and result sets can be tables of
+data, we have to recover the entity from its (row,col)-coordinates.
+The view has a ``self.w()`` method that is used to output data, in our
+example HTML output.
+
+You can find more details about views and selectors in :ref:`ViewDefinition`.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/index.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,31 @@
+.. -*- coding: utf-8 -*-
+
+.. _Tutorial:
+
+Tutorial
+========
+
+`CubicWeb` is a semantic web application framework that favors reuse and
+object-oriented design.
+
+A `cube` is a component that includes a model defining the data types and a set of
+views to display the data. 
+
+An application is a `cube`, but usually an application is built by assembling
+a few smaller cubes.
+
+An `instance` is a specific installation of an application and includes
+configuration files.
+
+
+This tutorial will show how to create a `cube` and how to use it as an
+application to run an `instance`.
+
+.. toctree::
+   :maxdepth: 2
+
+   blog-less-ten-minutes
+   create-cube
+   components
+   maintemplate
+   conclusion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/maintemplate.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,131 @@
+.. -*- coding: utf-8 -*-
+
+Templates
+---------
+
+Look at ``cubicweb/web/views/basetemplates.py`` and you will
+find the base templates used to generate HTML for your application.
+
+A page is composed as indicated on the schema below:
+
+.. image:: ../../images/lax-book.06-main-template-layout.en.png
+
+In this section we will demonstrate a change in one of the main
+interesting template from the three you will look for, 
+that is to say, the HTMLPageHeader, the HTMLPageFooter 
+and the TheMainTemplate.
+
+
+Customize a template
+~~~~~~~~~~~~~~~~~~~~
+
+Based on the diagram below, each template can be overriden
+by your customized template. To do so, we recommand you create
+a Python module ``blog.views.templates`` to keep it organized.
+In this module you will have to import the parent class you are
+interested as follows: ::
+  
+  from cubicweb.web.views.basetemplates import HTMLPageHeader, \
+                                    HTMLPageFooter, TheMainTemplate
+
+and then create your sub-class::
+
+  class MyBlogHTMLPageHeader(HTMLPageHeader):
+      ...  
+
+Customize header
+`````````````````
+
+Let's now move the search box in the header and remove the login form
+from the header. We'll show how to move it to the left column of the application.
+
+Let's say we do not want anymore the login menu to be in the header
+
+First, to remove the login menu, we just need to comment out the display of the
+login graphic component such as follows:
+
+.. code-block :: python
+
+  class MyBlogHTMLPageHeader(HTMLPageHeader):
+    
+      def main_header(self, view):
+          """build the top menu with authentification info and the rql box"""
+          self.w(u'<table id="header"><tr>\n')
+          self.w(u'<td id="firstcolumn">')
+          self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+          self.w(u'</td>\n')
+          # appliname and breadcrumbs
+          self.w(u'<td id="headtext">')
+          comp = self.vreg.select_component('appliname', self.req, self.rset)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w)
+          comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w, view=view)
+          self.w(u'</td>')
+          # logged user and help
+          #self.w(u'<td>\n')
+          #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+          #comp.dispatch(w=self.w)
+          #self.w(u'</td><td>')
+
+          self.w(u'<td>')
+          helpcomp = self.vreg.select_component('help', self.req, self.rset)
+          if helpcomp: # may not be available if Card is not defined in the schema
+              helpcomp.dispatch(w=self.w)
+          self.w(u'</td>')
+          # lastcolumn
+          self.w(u'<td id="lastcolumn">')
+          self.w(u'</td>\n')
+          self.w(u'</tr></table>\n')
+          self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+                        title=False, message=False)
+
+
+
+.. image:: ../../images/lax-book.06-header-no-login.en.png
+
+Customize footer
+````````````````
+
+If you want to change the footer for example, look
+for HTMLPageFooter and override it in your views file as in: ::
+
+..code-block :: python
+
+  from cubicweb.web.views.basetemplates import HTMLPageFooter
+
+  class MyHTMLPageFooter(HTMLPageFooter):
+
+      def call(self, **kwargs):
+          self.w(u'<div class="footer">')
+          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
+          self.w(u'</div>')
+
+Updating a view does not require any restart of the server. By reloading
+the page you can see your new page footer.
+
+
+TheMainTemplate
+```````````````
+
+.. _TheMainTemplate:
+
+The MainTemplate is a bit complex as it tries to accomodate many
+different cases. We are now about to go through it and cutomize entirely
+our application.
+
+TheMainTemplate is responsible for the general layout of the entire application. 
+It defines the template of ``id = main`` that is used by the application. Is 
+also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
+be used based on TheMainTemplate called SimpleMainTemplate which does not have 
+a top section.
+
+.. image:: ../../images/lax-book.06-simple-main-template.en.png
+
+XXX
+[WRITE ME]
+
+* customize MainTemplate and show that everything in the user
+  interface can be changed
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/toc.rst	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,25 @@
+.. -*- coding: utf-8 -*-
+
+.. _TOC:
+
+Table of contents
+=================
+
+
+.. toctree::
+   :numbered:
+
+   concepts/index
+   cubes/index
+   datamodel/index
+   entityclasses/index
+   devcore/index
+   devweb/index
+   devrepo/index
+   testing/index
+   migration/index
+   webstdlib/index
+   admin/index
+   rql/index
+   annexes/index
+
--- a/doc/book/fr/02-foundation.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/02-foundation.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -258,7 +258,7 @@
     réponse 
   * `cursor()` retourne un curseur RQL sur la session
   * `execute(*args, **kwargs)`, raccourci vers .cursor().execute()
-  * `property_value(key)`, gestion des propriétés (`EProperty`)
+  * `property_value(key)`, gestion des propriétés (`CWProperty`)
   * le dictionaire `data` pour stocker des données pour partager de
     l'information entre les composants *durant l'éxécution de la requête*.
 
--- a/doc/book/fr/04-01-schema-stdlib.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/04-01-schema-stdlib.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -11,10 +11,10 @@
 Schémas "systèmes"
 ``````````````````
 
-* `EUser`, utilisateurs du système
-* `EGroup`, groupes d'utilisateurs
-* `EEType`, types d'entité
-* `ERType`, types de relation
+* `CWUser`, utilisateurs du système
+* `CWGroup`, groupes d'utilisateurs
+* `CWEType`, types d'entité
+* `CWRType`, types de relation
 
 * `State`, état d'un workflow
 * `Transition`, transition d'un workflow
@@ -23,8 +23,8 @@
 * `EmailAddress`, adresse électronique, utilisé par le système de notification
   pour les utilisateurs et par d'autres schéma optionnels
 
-* `EProperty`, utilisé pour configurer l'application
-* `EPermission`, utilisé pour configurer la sécurité de l'application
+* `CWProperty`, utilisé pour configurer l'application
+* `CWPermission`, utilisé pour configurer la sécurité de l'application
 
 * `Card`, fiche documentaire générique
 * `Bookmark`, un type d'entité utilisé pour permetter à un utilisateur de
--- a/doc/book/fr/04-02-schema-definition.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/04-02-schema-definition.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -32,12 +32,12 @@
   
   - `modification_date` (`Datetime`)
   
-  - `created_by` (`EUser`) (quel utilisateur a créé l'entité)
+  - `created_by` (`CWUser`) (quel utilisateur a créé l'entité)
   
-  - `owned_by` (`EUser`) (à qui appartient l'entité, par défaut le
+  - `owned_by` (`CWUser`) (à qui appartient l'entité, par défaut le
      créateur mais pas forcément et il peut exister plusieurs propriétaires)
      
-  - `is` (`EEType`)
+  - `is` (`CWEType`)
 
   
 * il est également possible de définir des relations dont le type d'entité est
@@ -167,7 +167,7 @@
     inlined = True
     cardinality = '?*'
     subject = '*'
-    object = 'EUser'
+    object = 'CWUser'
 
 En plus des permissions, les propriétés propres aux types de relation (et donc
 partagés par toutes les définitions de relation de ce type) sont :
@@ -263,16 +263,16 @@
     'add'/'read' son pris en considération
 
 
-En plus de cela, le type d'entité `EPermission` de la librairie standard permet
+En plus de cela, le type d'entité `CWPermission` de la librairie standard permet
 de construire des modèles de sécurités très complexes et dynamiques. Le schéma
 de ce type d'entité est le suivant : ::
 
 
-    class EPermission(MetaEntityType):
+    class CWPermission(MetaEntityType):
 	"""entity type that may be used to construct some advanced security configuration
 	"""
 	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
-	require_group = SubjectRelation('EGroup', cardinality='+*',
+	require_group = SubjectRelation('CWGroup', cardinality='+*',
 					description=_('groups to which the permission is granted'))
 	require_state = SubjectRelation('State',
 				    description=_("entity'state in which the permission is applyable"))
@@ -311,7 +311,7 @@
 		       }
 	inlined = True
 
-Cette configuration suppose indique qu'une entité `EPermission` de nom
+Cette configuration suppose indique qu'une entité `CWPermission` de nom
 "add_version" peut-être associée à un projet et donner le droit de créer des
 versions sur ce projet à des groupes spécifiques. Il est important de noter les
 points suivants :
@@ -319,7 +319,7 @@
 * dans ce cas il faut protéger à la fois le type d'entité "Version" et la
   relation liant une version à un projet ("version_of")
 
-* du fait de la généricité du type d'entité `EPermission`, il faut effectuer
+* du fait de la généricité du type d'entité `CWPermission`, il faut effectuer
   l'unification avec les groupes et / ou les états le cas échéant dans
   l'expression ("U in_group G, P require_group G" dans l'exemple ci-dessus)
 
--- a/doc/book/fr/06-define-workflows.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/06-define-workflows.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -117,7 +117,7 @@
   
   _ = unicode
 
-  moderators      = add_entity('EGroup', name=u"moderators")
+  moderators      = add_entity('CWGroup', name=u"moderators")
 
   submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
   published = add_state(_('published'), 'BlogEntry')
--- a/doc/book/fr/09-instance-config.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/09-instance-config.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -29,7 +29,7 @@
 
 :`main.anonymous-user`, `main.anonymous-password`:
    login et mot de passe à utiliser pour se connecter au serveur RQL lors des
-   connexions HTTP anonymes. Il faut que le compte EUser associé existe.
+   connexions HTTP anonymes. Il faut que le compte CWUser associé existe.
 
 :`main.base-url`:
    url de base du site, à utiliser pour générer les urls des pages web
@@ -145,7 +145,7 @@
 
 Configuration Eproperties
 -------------------------
-D'autres paramètres de configuration sont sous la forme d'entités `EProperty`
+D'autres paramètres de configuration sont sous la forme d'entités `CWProperty`
 dans la base de données. Il faut donc les éditer via l'interface web ou par des
 requêtes rql.
 
--- a/doc/book/fr/12-ui-components.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/12-ui-components.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -9,6 +9,6 @@
 ---------------------
 XXXFILLME
 
-EProperty
+CWProperty
 ---------
 XXXFILLME
--- a/doc/book/fr/MERGE_ME-tut-create-app.fr.txt	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/MERGE_ME-tut-create-app.fr.txt	Thu May 14 12:50:34 2009 +0200
@@ -331,7 +331,7 @@
   
   _ = unicode
 
-  moderators      = add_entity('EGroup', name=u"moderators")
+  moderators      = add_entity('CWGroup', name=u"moderators")
 
   submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
   published = add_state(_('published'), 'BlogEntry')
--- a/doc/book/fr/conf.py	Thu May 14 12:50:14 2009 +0200
+++ b/doc/book/fr/conf.py	Thu May 14 12:50:34 2009 +0200
@@ -36,7 +36,7 @@
 
 # General substitutions.
 project = 'Cubicweb'
-copyright = '2008, Logilab'
+copyright = '2008-2009, Logilab'
 
 # The default replacements for |version| and |release|, also used in various
 # other places throughout the built documents.
--- a/doc/tools/generate_modules.py	Thu May 14 12:50:14 2009 +0200
+++ b/doc/tools/generate_modules.py	Thu May 14 12:50:34 2009 +0200
@@ -1,99 +1,12 @@
+"""generate list of modules for sphinx doc"""
+
 import sys
 
-"""
-Generates the chapter that list all the modules in CubicWeb
-in order to pull all the docstring.
-"""
-
-class ModuleGenerator:
-    HEADER = """.. -*- coding: utf-8 -*-
-
-============
-CubicWeb API
-============
-"""
-    EXCLUDE_DIRS = ('test', 'tests', 'examples', 'data', 'doc', '.hg', 'migration')  
-
-    def __init__(self, output_fn, mod_names):
-        self.mod_names =  mod_names
-        self.fn = open(output_fn, 'w')
-        self.fn.write(self.HEADER)
-
-    def done(self):
-        self.fn.close()
-        
-    def gen_module(self, mod_name):
-        mod_entry = """
-:mod:`%s`
-%s
-
-.. automodule:: %s
-   :members:
-""" % (mod_name, '='*(len(':mod:``'+mod_name)), mod_name)
-        self.fn.write(mod_entry)
-
-    def find_modules(self):
-        import os
-        modules = []
-        for mod_name in self.mod_names:
-            for root, dirs, files in os.walk(mod_name):
-                if self.keep_module(root):
-                    for name in files:
-                        if name == "__init__.py":
-                            if self.format_mod_name(root, mod_name) not in modules:
-                                modules.append(self.format_mod_name(root, mod_name))
-                        else:
-                            if name.endswith(".py") and name != "__pkginfo__.py" and "__init__.py" in files:
-                                filename = root + '/' + name.split('.py')[0]
-                                if self.format_mod_name(filename, mod_name) not in modules:
-                                    modules.append(self.format_mod_name(filename, mod_name))
-        return modules
+EXCLUDE_DIRS = ('test', 'tests', 'examples', 'data', 'doc', '.hg', 'migration')
+if __name__ == '__main__':
 
-    def gen_modules(self):
-        for module in self.find_modules():
-            self.gen_module(module)
-
-    def format_mod_name(self, path, mod_name):
-        mod_root = mod_name.split('/')[-1]
-        mod_end = path.split(mod_root)[-1]
-        return mod_root + mod_end.replace('/', '.')
-
-    def keep_module(self, mod_end):
-        """
-        Filter modules in order to exclude specific package directories.
-        """
-        for dir in self.EXCLUDE_DIRS:
-            if mod_end.find(dir) != -1:
-                return False
-        return True
-
-USAGE = """
-Two arguments required:
-    generate_modules [cubicweb-root] [file-out]
+    from logilab.common.sphinxutils import generate_modules_file
 
-[cubicweb-root] : full path to cubicweb forest
-[file-out] : rest file containing the list of modules for Sphinx
-"""
-def generate_modules_file(args):
-    if len(args) != 2:
-        print USAGE
-        sys.exit()
-    CW_ROOT = args[0]
-    OUTPUT = args[1]
-    modules = (CW_ROOT + '/cubicweb', \
-               CW_ROOT + '/indexer', \
-               CW_ROOT + '/logilab', \
-               CW_ROOT + '/rql', \
-               CW_ROOT + '/yams')
-
-    mg = ModuleGenerator(CW_ROOT + '/cubicweb/doc/book/en/' + OUTPUT, modules)
-    mg.find_modules()
-    mg.gen_modules()
-    mg.done()
-    print args
-
-
-
-if __name__ == '__main__':
-    generate_modules_file(sys.argv[1:])
-
+    gen = generate_modules_file(sys.argv[1:])
+    gen.set_docdir("cubicweb/doc/book/en")
+    gen.make(['cubicweb', '/indexer', '/logilab', '/rql', '/yams'], EXCLUDE_DIRS)
--- a/embedded/mx/DateTime/ARPA.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-""" This module provides a set of constructors and routines to convert
-    between DateTime[Delta] instances and ARPA representations of date
-    and time. The format is specified by RFC822 + RFC1123.
-
-    Note: Timezones are only interpreted by ParseDateTimeGMT(). All
-    other constructors silently ignore the time zone information.
-
-    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author. All Rights Reserved.
-
-"""
-import DateTime,Timezone
-import re,string
-
-# Grammar: RFC822 + RFC1123 + depreciated RFC850
-_litday = '(?P<litday>Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*'
-_litmonth = '(?P<litmonth>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'\
-            '[a-z]*'
-_date = ('(?:(?P<day>\d?\d)(?: +' + _litmonth + 
-         ' +|-(?P<month>\d?\d)-)(?P<year>(?:\d\d)?\d\d))')
-_zone = Timezone.zone
-_time = ('(?:(?P<hour>\d\d):(?P<minute>\d\d)'
-         '(?::(?P<second>\d\d))?(?: +'+_zone+')?)')
-#       Timezone information is made optional because some mail apps
-#       forget to add it (most of these seem to be spamming engines, btw).
-#       It defaults to UTC.
-
-_arpadate = '(?:'+ _litday + ',? )? *' + _date
-_arpadatetime = '(?:'+ _litday + ',? )? *' + _date + ' +' + _time
-
-#       We are not strict about the extra characters: some applications
-#       add extra information to the date header field. Additional spaces
-#       between the fields and extra characters in the literal day
-#       and month fields are also silently ignored.
-
-arpadateRE = re.compile(_arpadate)
-arpadatetimeRE = re.compile(_arpadatetime)
-
-# Translation tables
-litdaytable = {'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6 }
-litmonthtable = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
-                 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 }
-_days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
-                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
-
-def ParseDate(arpastring,parse_arpadate=arpadateRE.match,
-
-              strip=string.strip,atoi=string.atoi,atof=string.atof,
-              lower=string.lower):
-
-    """ParseDate(arpastring)
-
-       Returns a DateTime instance reflecting the given ARPA
-       date. Only the date part is parsed, any time part will be
-       ignored. The instance's time is set to 0:00:00.
-
-    """
-    s = strip(arpastring)
-    date = parse_arpadate(s)
-    if not date:
-        raise ValueError,'wrong format'
-    litday,day,litmonth,month,year = date.groups()
-    if len(year) == 2:
-        year = DateTime.add_century(atoi(year))
-    else:
-        year = atoi(year)
-    if litmonth:
-        litmonth = lower(litmonth)
-        try:
-            month = litmonthtable[litmonth]
-        except KeyError:
-            raise ValueError,'wrong month format'
-    else:
-        month = atoi(month)
-    day = atoi(day)
-    # litday and timezone are ignored
-    return DateTime.DateTime(year,month,day)
-
-def ParseDateTime(arpastring,parse_arpadatetime=arpadatetimeRE.match,
-
-                  strip=string.strip,atoi=string.atoi,atof=string.atof,
-                  lower=string.lower):
-
-    """ParseDateTime(arpastring)
-
-       Returns a DateTime instance reflecting the given ARPA date assuming
-       it is local time (timezones are silently ignored).
-    """
-    s = strip(arpastring)
-    date = parse_arpadatetime(s)
-    if not date:
-        raise ValueError,'wrong format or unknown time zone'
-    litday,day,litmonth,month,year,hour,minute,second,zone = date.groups()
-    if len(year) == 2:
-        year = DateTime.add_century(atoi(year))
-    else:
-        year = atoi(year)
-    if litmonth:
-        litmonth = lower(litmonth)
-        try:
-            month = litmonthtable[litmonth]
-        except KeyError:
-            raise ValueError,'wrong month format'
-    else:
-        month = atoi(month)
-    day = atoi(day)
-    hour = atoi(hour)
-    minute = atoi(minute)
-    if second is None:
-        second = 0.0
-    else:
-        second = atof(second)
-    # litday and timezone are ignored
-    return DateTime.DateTime(year,month,day,hour,minute,second)
-
-def ParseDateTimeGMT(arpastring,parse_arpadatetime=arpadatetimeRE.match,
-
-                     strip=string.strip,atoi=string.atoi,atof=string.atof,
-                     lower=string.lower):
-
-    """ParseDateTimeGMT(arpastring)
-
-       Returns a DateTime instance reflecting the given ARPA date converting
-       it to UTC (timezones are honored).
-    """
-    s = strip(arpastring)
-    date = parse_arpadatetime(s)
-    if not date:
-        raise ValueError,'wrong format or unknown time zone'
-    litday,day,litmonth,month,year,hour,minute,second,zone = date.groups()
-    if len(year) == 2:
-        year = DateTime.add_century(atoi(year))
-    else:
-        year = atoi(year)
-    if litmonth:
-        litmonth = lower(litmonth)
-        try:
-            month = litmonthtable[litmonth]
-        except KeyError:
-            raise ValueError,'wrong month format'
-    else:
-        month = atoi(month)
-    day = atoi(day)
-    hour = atoi(hour)
-    minute = atoi(minute)
-    if second is None:
-        second = 0.0
-    else:
-        second = atof(second)
-    offset = Timezone.utc_offset(zone)
-    # litday is ignored
-    return DateTime.DateTime(year,month,day,hour,minute,second) - offset
-
-# Alias
-ParseDateTimeUTC = ParseDateTimeGMT
-
-def str(datetime,tz=None):
-
-    """str(datetime,tz=DateTime.tz_offset(datetime))
-
-    Returns the datetime instance as ARPA date string. tz can be given
-    as DateTimeDelta instance providing the time zone difference from
-    datetime's zone to UTC. It defaults to
-    DateTime.tz_offset(datetime) which assumes local time. """
-
-    if tz is None:
-        tz = datetime.gmtoffset()
-    return '%s, %02i %s %04i %02i:%02i:%02i %+03i%02i' % (
-        _days[datetime.day_of_week], datetime.day, 
-        _months[datetime.month], datetime.year,
-        datetime.hour, datetime.minute, datetime.second,
-        tz.hour,tz.minute)
-
-def strGMT(datetime):
-
-    """ strGMT(datetime)
-
-    Returns the datetime instance as ARPA date string assuming it
-    is given in GMT. """
-
-    return '%s, %02i %s %04i %02i:%02i:%02i GMT' % (
-        _days[datetime.day_of_week], datetime.day, 
-        _months[datetime.month], datetime.year,
-        datetime.hour, datetime.minute, datetime.second)
-
-def strUTC(datetime):
-
-    """ strUTC(datetime)
-
-    Returns the datetime instance as ARPA date string assuming it
-    is given in UTC. """
-
-    return '%s, %02i %s %04i %02i:%02i:%02i UTC' % (
-        _days[datetime.day_of_week], datetime.day, 
-        _months[datetime.month], datetime.year,
-        datetime.hour, datetime.minute, datetime.second)
-
-def _test():
-    import sys, os, rfc822
-    file = os.path.join(os.environ['HOME'], 'nsmail/Inbox')
-    f = open(file, 'r')
-    while 1:
-        m = rfc822.Message(f)
-        if not m:
-            break
-        print 'From:', m.getaddr('from')
-        print 'To:', m.getaddrlist('to')
-        print 'Subject:', m.getheader('subject')
-        raw = m.getheader('date')
-        try:
-            date = ParseDateTimeUTC(raw)
-            print 'Date:',strUTC(date)
-        except ValueError,why:
-            print 'PROBLEMS:',repr(raw),'-->',why
-            raw_input('...hit return to continue')
-        print
-        # Netscape mail file
-        while 1:
-            line = f.readline()
-            if line[:6] == 'From -':
-                break
-
-if __name__ == '__main__':
-    _test()
--- a/embedded/mx/DateTime/DateTime.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1054 +0,0 @@
-""" Python part of the low-level DateTime[Delta] type implementation.
-
-    Copyright (c) 1998-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author. All Rights Reserved.
-"""
-# Import the python implementation module
-from mxDateTime_python import *
-from mxDateTime_python import __version__
-
-# Singletons
-oneSecond = DateTimeDelta(0,0,0,1)
-oneMinute = DateTimeDelta(0,0,1)
-oneHour = DateTimeDelta(0,1)
-oneDay = DateTimeDelta(1)
-oneWeek = DateTimeDelta(7)
-Epoch = DateTimeFromAbsDateTime(1,0)
-
-# Shortcuts for pickle; for backward compatibility only (they are now
-# defined in __init__.py to further reduce the pickles length)
-def _DT(absdate,abstime):
-    return DateTimeFromAbsDateTime(absdate,abstime)
-def _DTD(seconds):
-    return DateTimeDeltaFromSeconds(seconds)
-
-# Module init
-class modinit:
-
-    global _time,_string,_math,_types
-    import time,string,math,types
-    _time = time
-    _string = string
-    _math = math
-    _types = types
-
-del modinit
-
-### Helpers
-
-def _isstring(arg,
-
-              isinstance=isinstance, types=_types):
-    
-    if isinstance(arg, types.StringType):
-        return 1
-    try:
-        if isinstance(arg, types.UnicodeType):
-            return 1
-    except AttributeError:
-        pass
-    return 0
-
-### Compatibility APIs
-
-# Aliases and functions to make 'from mx.DateTime import *' work much
-# like 'from time import *'
-
-def localtime(ticks=None,
-              # Locals:
-              time=_time.time,float=float,localtime=_time.localtime,
-              round=round,int=int,DateTime=DateTime,floor=_math.floor):
-
-    """localtime(ticks=None)
-
-       Construct a DateTime instance using local time from ticks.  If
-       ticks are not given, it defaults to the current time.  The
-       result is similar to time.localtime(). Fractions of a second
-       are rounded to the nearest micro-second.
-
-    """
-    if ticks is None:
-        ticks = time()
-    else:
-        ticks = float(ticks)
-    ticks = round(ticks, 6)
-    fticks = floor(ticks)
-    Y,M,D,h,m,s = localtime(fticks)[:6]
-    s = s + (ticks - fticks)
-    return DateTime(Y,M,D,h,m,s)
-
-def gmtime(ticks=None,
-           # Locals:
-           time=_time.time,float=float,gmtime=_time.gmtime,
-           round=round,int=int,DateTime=DateTime,floor=_math.floor):
-
-    """gmtime(ticks=None)
-
-       Construct a DateTime instance using UTC time from ticks.  If
-       ticks are not given, it defaults to the current time.  The
-       result is similar to time.gmtime(). Fractions of a second are
-       rounded to the nearest micro-second.
-
-    """
-    if ticks is None:
-        ticks = time()
-    else:
-        ticks = float(ticks)
-    ticks = round(ticks, 6)
-    fticks = floor(ticks)
-    Y,M,D,h,m,s = gmtime(ticks)[:6]
-    s = s + (ticks - fticks)
-    return DateTime(Y,M,D,h,m,s)
-
-def mktime((year,month,day,hour,minute,second,dow,doy,dst),
-           # Locals:
-           DateTime=DateTime):
-
-    """mktime((year,month,day,hour,minute,second,dow,doy,dst))
-
-       Same as the DateTime() constructor accept that the interface
-       used is compatible to the similar time.mktime() API.
-
-       Note that the tuple elements dow, doy and dst are not used in
-       any way.
-      
-    """
-    return DateTime(year,month,day,hour,minute,second)
-
-def ctime(datetime):
-
-    """ctime(datetime)
-
-       Returns a string representation of the given DateTime instance
-       using the current locale's default settings.
-
-    """
-    return datetime.strftime('%c')
-
-def today(hour=0,minute=0,second=0.0,
-          # Locals:
-          localtime=_time.localtime,time=_time.time,DateTime=DateTime):
-
-    """today(hour=0,minute=0,second=0.0)
-
-       Returns a DateTime instance for today (in local time) at the
-       given time (defaults to midnight).
-
-    """
-    Y,M,D = localtime(time())[:3]
-    return DateTime(Y,M,D,hour,minute,second)
-
-def TimeDelta(hours=0.0,minutes=0.0,seconds=0.0,
-              # Locals:
-              DateTimeDelta=DateTimeDelta):
-
-    """TimeDelta(hours=0.0,minutes=0.0,seconds=0.0)
-
-       Returns a DateTimeDelta-object reflecting the given time
-       delta. Seconds can be given as float to indicate fractions.
-
-    """
-    return DateTimeDelta(0,hours,minutes,seconds)
-
-def gm2local(datetime):
-
-    """ gm2local(datetime)
-
-        Convert a DateTime instance holding UTC time to a DateTime
-        instance using local time.
-
-    """
-    return localtime(datetime.gmticks())
-
-def local2gm(datetime):
-
-    """ local2gm(datetime)
-
-        Convert a DateTime instance holding local time to a DateTime
-        instance using UTC time.
-
-    """
-    return gmtime(datetime.ticks())
-
-# Alias
-gmt = utc
-
-# Default value for DateTimeFromTJD's tjd_myriad parameter
-current_myriad = localtime().tjd_myriad
-
-def DateTimeFromTJD(tjd,tjd_myriad=current_myriad):
-
-    """ DateTimeFromTJD(tjd[,myriad])
-
-        Return a DateTime instance for the given Truncated Julian Day.
-        myriad defaults to the TJD myriad current at package import
-        time.
-
-        Note that this version of Truncated Julian Day number does
-        real truncation of important information. It's use is
-        discouraged and unsupported.
-
-    """
-    return DateTimeFromAbsDays(tjd + tjd_myriad * 10000.0 - 1721425.0)
-
-def DateTimeFromJDN(jdn):
-
-    """ DateTimeFromJDN(jdn)
-
-        Return a DateTime instance for the given Julian Day Number.
-
-        References:
-        -----------
-        Gregorian 2000-01-01 12:00:00 corresponds to JDN 2451545.0.
-        Gregorian 1858-11-17 00:00:00.00 corresponds to JDN 2400000.5; MJD 0.0.
-        Julian -4712-01-01 12:00:00.00 corresponds to JDN 0.0.
-        Gregorian -4713-11-24 12:00:00.00 corresponds to JDN 0.0.
-
-    """
-    return DateTimeFromAbsDays(jdn - 1721425.5)
-
-def DateTimeFromMJD(mjd):
-
-    """ DateTimeFromMJD(mjd)
-
-        Return a DateTime instance for the given Modified Julian Day
-        (MJD). The MJD is calculated the same way as the JDN except
-        that 1858-11-17 00:00:00.00 is taken as origin of the scale.
-
-    """
-    return DateTimeFromAbsDays(mjd + 678575.0)
-
-def DateTimeFrom(*args, **kws):
-
-    """ DateTimeFrom(*args, **kws)
-
-        Generic DateTime instance constructor. Can handle parsing
-        strings, numbers and keywords.
-
-        XXX Add support for Unicode.
-
-    """
-    if len(args) == 1:
-        # Single argument
-        arg = args[0]
-        argtype = type(arg)
-        if _isstring(arg):
-            import Parser
-            return apply(Parser.DateTimeFromString, args, kws)
-        elif argtype is DateTimeType:
-            return arg
-        elif argtype is DateTimeDeltaType:
-            raise TypeError,'cannot convert DateTimeDelta to DateTime'
-        else:
-            try:
-                value = float(arg)
-            except (TypeError, ValueError):
-                value = int(arg)
-            assert not kws
-            return DateTimeFromTicks(value)
-
-    elif len(args) > 1:
-        # More than one argument
-        if len(args) == 2 and _isstring(args[0]) and _isstring(args[1]):
-            # interpret as date and time string
-            import Parser
-            return apply(Parser.DateTimeFromString,
-                         (args[0] + ' ' + args[1],),
-                         kws)
-
-        # Assume the arguments are the same as for DateTime()
-        return apply(DateTime, args, kws)
-
-    elif len(kws) > 0:
-        # Keyword arguments; add defaults... today at 0:00:00
-        hour = kws.get('hour',0)
-        minute = kws.get('minute',0)
-        second = kws.get('second',0)
-        today = now()
-        day = kws.get('day',today.day)
-        month = kws.get('month',today.month)
-        year = kws.get('year',today.year)
-        return DateTime(year,month,day,hour,minute,second)
-
-    else:
-        raise TypeError,'cannot convert arguments to DateTime'
-
-def DateTimeDeltaFrom(*args, **kws):
-
-    """ DateTimeDeltaFrom(*args, **kws)
-
-        Generic DateTimeDelta instance constructor. Can handle parsing
-        strings, numbers and keywords.
-
-        XXX Add support for Unicode.
-
-    """
-    if len(args) == 1:
-        # Single argument
-        arg = args[0]
-        if _isstring(arg):
-            import Parser
-            return apply(Parser.DateTimeDeltaFromString, args, kws)
-        elif type(arg) is DateTimeDeltaType:
-            return arg
-        elif type(arg) is DateTimeType:
-            raise TypeError,'cannot convert DateTime to DateTimeDelta'
-        else:
-            try:
-                value = float(arg)
-            except TypeError:
-                value = int(arg)
-            assert not kws
-            return DateTimeDeltaFromSeconds(value)
-
-    elif len(args) > 1:
-        # Assume the arguments are the same as for DateTimeDelta()
-        return apply(DateTimeDelta, args, kws)
-
-    elif len(kws) > 0:
-        # Keyword arguments; default: 00:00:00:00.00
-        hours = kws.get('hours',0)
-        minutes = kws.get('minutes',0)
-        seconds = kws.get('seconds',0.0)
-        days = kws.get('days',0)
-        return DateTimeDelta(days,hours,minutes,seconds)
-
-    else:
-        raise TypeError,'cannot convert arguments to DateTimeDelta'
-
-def TimeDeltaFrom(*args, **kws):
-
-    """ TimeDeltaFrom(*args, **kws)
-
-        Generic TimeDelta instance constructor. Can handle parsing
-        strings, numbers and keywords.
-
-        XXX Add support for Unicode.
-
-    """
-    if len(args) > 1:
-        # Assume the arguments are the same as for TimeDelta(): without
-        # days part !
-        return apply(DateTimeDelta, (0,)+args, kws)
-    else:
-        # Otherwise treat the arguments just like for DateTimeDelta
-        # instances.
-        return apply(DateTimeDeltaFrom, args, kws)
-
-def DateFromTicks(ticks,
-                  # Locals:
-                  DateTime=DateTime,localtime=_time.localtime):
-
-    """ DateFromTicks(ticks)
-
-        Constructs a DateTime instance pointing to the local time date
-        at 00:00:00.00 (midnight) indicated by the given ticks value.
-        The time part is ignored.
-
-    """
-    return apply(DateTime, localtime(ticks)[:3])
-
-def TimestampFromTicks(ticks,
-                       # Locals:
-                       DateTime=DateTime,localtime=_time.localtime):
-
-    """ TimestampFromTicks(ticks)
-
-        Constructs a DateTime instance pointing to the local date and
-        time indicated by the given ticks value.
-
-    """
-    return apply(DateTime, localtime(ticks)[:6])
-
-def TimeFromTicks(ticks,
-                  # Locals:
-                  DateTimeDelta=DateTimeDelta,localtime=_time.localtime):
-
-    """ TimeFromTicks(ticks)
-
-        Constructs a DateTimeDelta instance pointing to the local time
-        indicated by the given ticks value. The date part is ignored.
-
-    """
-    return apply(DateTimeDelta, (0,) + localtime(ticks)[3:6])
-
-# Aliases
-utctime = gmtime
-utc2local = gm2local
-local2utc = local2gm
-DateTimeFromTicks = localtime
-Date = DateTime
-Time = TimeDelta
-Timestamp = DateTime
-DateFrom = DateTimeFrom # XXX should only parse the date part !
-TimeFrom = TimeDeltaFrom
-TimestampFrom = DateTimeFrom
-GregorianDateTime = DateTime
-GregorianDate = Date
-JulianDate = JulianDateTime
-
-
-### For backward compatibility (these are depreciated):
-
-def gmticks(datetime):
-
-    """gmticks(datetime)
-
-       [DEPRECIATED: use the .gmticks() method]
-    
-       Returns a ticks value based on the values stored in
-       datetime under the assumption that they are given in UTC,
-       rather than local time.
-
-    """
-    return datetime.gmticks()
-
-# Alias
-utcticks = gmticks
-
-def tz_offset(datetime,
-              # Locals:
-              oneSecond=oneSecond):
-
-    """tz_offset(datetime)
-
-       [DEPRECIATED: use the .gmtoffset() method]
-    
-       Returns a DateTimeDelta instance representing the UTC
-       offset for datetime assuming that the stored values refer
-       to local time. If you subtract this value from datetime,
-       you'll get UTC time.
-
-    """
-    return datetime.gmtoffset()
-
-### Constants (only English; see Locale.py for other languages)
-
-# Weekdays
-Monday =        0
-Tuesday =       1
-Wednesday =     2
-Thursday =      3
-Friday =        4
-Saturday =      5
-Sunday =        6
-# as mapping
-Weekday = {'Saturday': 5, 6: 'Sunday', 'Sunday': 6, 'Thursday': 3,
-           'Wednesday': 2, 'Friday': 4, 'Tuesday': 1, 'Monday': 0,
-           5: 'Saturday', 4: 'Friday', 3: 'Thursday', 2: 'Wednesday',
-           1: 'Tuesday', 0: 'Monday'}
-
-# Months
-January =       1
-February =      2
-March =         3
-April =         4
-May =           5
-June =          6
-July =          7
-August =        8 
-September =     9
-October =       10
-November =      11
-December =      12
-# as mapping
-Month = {2: 'February', 3: 'March', None: 0, 'July': 7, 11: 'November',
-    'December': 12, 'June': 6, 'January': 1, 'September': 9, 'August':
-    8, 'March': 3, 'November': 11, 'April': 4, 12: 'December', 'May':
-    5, 10: 'October', 9: 'September', 8: 'August', 7: 'July', 6:
-    'June', 5: 'May', 4: 'April', 'October': 10, 'February': 2, 1:
-    'January', 0: None}
-
-# Limits (see also the range checks in mxDateTime.c)
-MaxDateTime = DateTime(5867440,12,31) 
-MinDateTime = DateTime(-5851455,1,1)
-MaxDateTimeDelta = DateTimeDeltaFromSeconds(2147483647 * 86400.0)
-MinDateTimeDelta = -MaxDateTimeDelta
-
-###
-
-class RelativeDateTime:
-
-    """RelativeDateTime(years=0,months=0,days=0,
-                  hours=0,minutes=0,seconds=0,
-                  year=0,month=0,day=0,
-                  hour=None,minute=None,second=None,
-                  weekday=None,weeks=None)
-
-       Returns a RelativeDateTime instance for the specified relative
-       time. The constructor handles keywords, so you'll only have to
-       give those parameters which should be changed when you add the
-       relative to an absolute DateTime instance.
-
-       Adding RelativeDateTime instances is supported with the
-       following rules: deltas will be added together, right side
-       absolute values override left side ones.
-
-       Adding RelativeDateTime instances to DateTime instances will
-       return DateTime instances with the appropriate calculations
-       applied, e.g. to get a DateTime instance for the first of next
-       month, you'd call now() + RelativeDateTime(months=+1,day=1).
-
-    """
-    years = 0
-    months = 0
-    days = 0
-    year = None
-    month = 0
-    day = 0
-    hours = 0
-    minutes = 0
-    seconds = 0
-    hour = None
-    minute = None
-    second = None
-    weekday = None
-
-    # cached hash value
-    _hash = None
-
-    # For Zope security:
-    __roles__ = None
-    __allow_access_to_unprotected_subobjects__ = 1
-
-    def __init__(self,
-                 years=0,months=0,days=0,
-                 hours=0,minutes=0,seconds=0,
-                 year=None,month=None,day=None,
-                 hour=None,minute=None,second=None,
-                 weekday=None,weeks=0):
-        
-        self.years = years
-        self.months = months
-        self.days = days + weeks*7
-        self.year = year
-        self.month = month
-        self.day = day
-        self.hours = hours
-        self.minutes = minutes
-        self.seconds = seconds
-        self.hour = hour
-        self.minute = minute
-        self.second = second
-        if weekday is not None:
-            #  Make sure we've got a 2-tuple
-            assert len(weekday) == 2
-            self.weekday = weekday
-
-    def __add__(self,other,
-                # Locals:
-                isinstance=isinstance):
-
-        if isinstance(other,RelativeDateTime):
-            # RelativeDateTime (self) + RelativeDateTime (other)
-
-            r = RelativeDateTime()
-            # date deltas
-            r.years = self.years + other.years
-            r.months = self.months + other.months
-            r.days = self.days + other.days
-            # absolute entries of other override those in self, if given
-            r.year = other.year or self.year
-            r.month = other.month or self.month
-            r.day = other.day or self.day
-            r.weekday = other.weekday or self.weekday
-            # time deltas
-            r.hours = self.hours + other.hours
-            r.minutes = self.minutes + other.minutes
-            r.seconds = self.seconds + other.seconds
-            # absolute entries of other override those in self, if given
-            r.hour = other.hour or self.hour
-            r.minute = other.minute or self.minute
-            r.second = other.second or self.second
-            return r
-
-        else:
-            raise TypeError,"can't add the two types"
-
-    def __radd__(self,other,
-                 # Locals:
-                 isinstance=isinstance,DateTimeType=DateTimeType,
-                 DateTime=DateTime,DateTimeDelta=DateTimeDelta):
-
-        if isinstance(other,DateTimeType):
-            # DateTime (other) + RelativeDateTime (self)
-
-            # date
-            if self.year is None:
-                year = other.year + self.years
-            else:
-                year = self.year + self.years
-            if self.month is None:
-                month = other.month + self.months
-            else:
-                month = self.month + self.months
-            if self.day is None:
-                day = other.day
-            else:
-                day = self.day
-            if day < 0:
-                # fix negative day values
-                month = month + 1
-                day = day + 1
-            day = day + self.days
-            # time
-            if self.hour is None:
-                hour = other.hour + self.hours
-            else:
-                hour = self.hour + self.hours
-            if self.minute is None:
-                minute = other.minute + self.minutes
-            else:
-                minute = self.minute + self.minutes
-            if self.second is None:
-                second = other.second + self.seconds
-            else:
-                second = self.second + self.seconds
-
-            # Refit into proper ranges:
-            if month < 1 or month > 12:
-                month = month - 1
-                yeardelta, monthdelta = divmod(month, 12)
-                year = year + yeardelta
-                month = monthdelta + 1
-
-            # Make sure we have integers
-            year = int(year)
-            month = int(month)
-            day = int(day)
-
-            if self.weekday is None:
-                return DateTime(year, month, 1) + \
-                       DateTimeDelta(day-1,hour,minute,second)
-            
-            # Adjust to the correct weekday
-            day_of_week,index = self.weekday
-            d = DateTime(year, month, 1) + \
-                DateTimeDelta(day-1,hour,minute,second)
-            if index == 0:
-                # 0 index: next weekday if no match
-                return d + (day_of_week - d.day_of_week)
-            elif index > 0:
-                # positive index (1 == first weekday of month)
-                first = d - (d.day - 1)
-                diff = day_of_week - first.day_of_week
-                if diff >= 0:
-                    return first + (diff + (index-1) * 7)
-                else:
-                    return first + (diff + index * 7)
-            else:
-                # negative index (-1 == last weekday of month)
-                last = d + (d.days_in_month - d.day)
-                diff = day_of_week - last.day_of_week
-                if diff <= 0:
-                    return last + (diff + (index+1) * 7)
-                else:
-                    return last + (diff + index * 7)
-            
-        else:
-            raise TypeError,"can't add the two types"
-
-    def __sub__(self,other):
-
-        if isinstance(other,RelativeDateTime):
-            # RelativeDateTime (self) - RelativeDateTime (other)
-
-            r = RelativeDateTime()
-            # date deltas
-            r.years = self.years - other.years
-            r.months = self.months - other.months
-            r.days = self.days - other.days
-            # absolute entries of other override those in self, if given
-            r.year = other.year or self.year
-            r.month = other.month or self.month
-            r.day = other.day or self.day
-            r.weekday = other.weekday or self.weekday
-            # time deltas
-            r.hours = self.hours - other.hours
-            r.minutes = self.minutes - other.minutes
-            r.seconds = self.seconds - other.seconds
-            # absolute entries of other override those in self, if given
-            r.hour = other.hour or self.hour
-            r.minute = other.minute or self.minute
-            r.second = other.second or self.second
-
-            return r
-
-        else:
-            raise TypeError,"can't subtract the two types"
-
-    def __rsub__(self,other,
-                 # Locals:
-                 isinstance=isinstance,DateTimeType=DateTimeType):
-
-        if isinstance(other,DateTimeType):
-            # DateTime (other) - RelativeDateTime (self)
-            return other + self.__neg__()
-
-        else:
-            raise TypeError,"can't subtract the two types"
-
-    def __neg__(self):
-
-        # - RelativeDateTime(self)
-
-        r = RelativeDateTime()
-        # negate date deltas
-        r.years = - self.years
-        r.months = - self.months
-        r.days = - self.days
-        # absolute entries don't change
-        r.year = self.year
-        r.month = self.month
-        r.day = self.day
-        r.weekday = self.weekday
-        # negate time deltas
-        r.hours = - self.hours
-        r.minutes = - self.minutes
-        r.seconds = - self.seconds
-        # absolute entries don't change
-        r.hour = self.hour
-        r.minute = self.minute
-        r.second = self.second
-
-        return r
-
-    def __nonzero__(self):
-
-        # RelativeDateTime instances are considered false in case
-        # they do not define any alterations
-        if (self.year is None and
-            self.years == 0 and
-            self.month is None and
-            self.months == 0 and
-            self.day is None and
-            self.weekday is None and
-            self.days == 0 and
-            self.hour is None and
-            self.hours == 0 and
-            self.minute is None and
-            self.minutes == 0 and
-            self.second is None and
-            self.seconds == 0):
-            return 0
-        else:
-            return 1
-
-    def __mul__(self,other):
-
-        # RelativeDateTime (self) * Number (other)
-        factor = float(other)
-
-        r = RelativeDateTime()
-        # date deltas
-        r.years = factor * self.years
-        r.months = factor * self.months
-        r.days = factor * self.days
-        # time deltas
-        r.hours = factor * self.hours
-        r.minutes = factor * self.minutes
-        r.seconds = factor * self.seconds
-        return r
-
-    __rmul__ = __mul__
-
-    def __div__(self,other):
-
-        # RelativeDateTime (self) / Number (other)
-        return self.__mul__(1/float(other))
-
-    def __eq__(self, other):
-
-        if isinstance(self, RelativeDateTime) and \
-           isinstance(other, RelativeDateTime):
-            # RelativeDateTime (self) == RelativeDateTime (other)
-            if (self.years == other.years and
-                self.months == other.months and
-                self.days == other.days and
-                self.year == other.year and
-                self.day == other.day and
-                self.hours == other.hours and
-                self.minutes == other.minutes and
-                self.seconds == other.seconds and
-                self.hour == other.hour and
-                self.minute == other.minute and
-                self.second == other.second and
-                self.weekday == other.weekday):
-                return 1
-            else:
-                return 0
-        else:
-            raise TypeError,"can't compare the two types"
-
-    def __hash__(self):
-
-        if self._hash is not None:
-            return self._hash
-        x = 1234
-        for value in (self.years, self.months, self.days,
-                      self.year, self.day,
-                      self.hours, self.minutes, self.seconds,
-                      self.hour, self.minute, self.second,
-                      self.weekday):
-            if value is None:
-                x = 135051820 ^ x
-            else:
-                x = hash(value) ^ x
-        self._hash = x
-        return x
-
-    def __str__(self,
-
-                join=_string.join):
-
-        l = []
-        append = l.append
-
-        # Format date part
-        if self.year is not None:
-            append('%04i-' % self.year)
-        elif self.years:
-            append('(%0+5i)-' % self.years)
-        else:
-            append('YYYY-')
-        if self.month is not None:
-            append('%02i-' % self.month)
-        elif self.months:
-            append('(%0+3i)-' % self.months)
-        else:
-            append('MM-')
-        if self.day is not None:
-            append('%02i' % self.day)
-        elif self.days:
-            append('(%0+3i)' % self.days)
-        else:
-            append('DD')
-        if self.weekday:
-            append(' %s:%i' % (Weekday[self.weekday[0]][:3],self.weekday[1]))
-        append(' ')
-        
-        # Normalize relative time values to avoid fractions
-        hours = self.hours
-        minutes = self.minutes
-        seconds = self.seconds
-        hours_fraction = hours - int(hours)
-        minutes = minutes + hours_fraction * 60.0
-        minutes_fraction = minutes - int(minutes)
-        seconds = seconds + minutes_fraction * 6.0
-        seconds_fraction = seconds - int(seconds)
-
-        if 0:
-            # Normalize to standard time ranges
-            if seconds > 60.0:
-                extra_minutes, seconds = divmod(seconds, 60.0)
-                minutes = minutes + extra_minutes
-            elif seconds < -60.0:
-                extra_minutes, seconds = divmod(seconds, -60.0)
-                minutes = minutes - extra_minutes
-            if minutes >= 60.0:
-                extra_hours, minutes = divmod(minutes, 60.0)
-                hours = hours + extra_hours
-            elif minutes <= -60.0:
-                extra_hours, minutes = divmod(minutes, -60.0)
-                hours = hours - extra_hours
-
-        # Format time part
-        if self.hour is not None:
-            append('%02i:' % self.hour)
-        elif hours:
-            append('(%0+3i):' % hours)
-        else:
-            append('HH:')
-        if self.minute is not None:
-            append('%02i:' % self.minute)
-        elif minutes:
-            append('(%0+3i):' % minutes)
-        else:
-            append('MM:')
-        if self.second is not None:
-            append('%02i' % self.second)
-        elif seconds:
-            append('(%0+3i)' % seconds)
-        else:
-            append('SS')
-            
-        return join(l,'')
-
-    def __repr__(self):
-
-        return "<%s instance for '%s' at 0x%x>" % ( 
-            self.__class__.__name__, 
-            self.__str__(), 
-            id(self))
-
-# Alias
-RelativeDate = RelativeDateTime
-
-def RelativeDateTimeFrom(*args, **kws):
-
-    """ RelativeDateTimeFrom(*args, **kws)
-
-        Generic RelativeDateTime instance constructor. Can handle
-        parsing strings and keywords.
-
-    """
-    if len(args) == 1:
-        # Single argument
-        arg = args[0]
-        if _isstring(arg):
-            import Parser
-            return apply(Parser.RelativeDateTimeFromString, args, kws)
-        elif isinstance(arg, RelativeDateTime):
-            return arg
-        else:
-            raise TypeError,\
-                  'cannot convert argument to RelativeDateTime'
-
-    else:
-        return apply(RelativeDateTime,args,kws)
-
-def RelativeDateTimeDiff(date1,date2,
-
-                         floor=_math.floor,int=int,divmod=divmod,
-                         RelativeDateTime=RelativeDateTime):
-
-    """ RelativeDateTimeDiff(date1,date2)
-
-        Returns a RelativeDateTime instance representing the difference
-        between date1 and date2 in relative terms.
-
-        The following should hold: 
-        
-        date2 + RelativeDateDiff(date1,date2) == date1 
-
-        for all dates date1 and date2.
-
-        Note that due to the algorithm used by this function, not the
-        whole range of DateTime instances is supported; there could
-        also be a loss of precision.
-
-        XXX There are still some problems left (thanks to Carel
-        Fellinger for pointing these out):
-
-        29 1 1901 ->  1 3 1901 = 1 month
-        29 1 1901 ->  1 3 1900 = -10 month and -28 days, but
-        29 1 1901 -> 28 2 1900 = -11 month and -1 day
-
-        and even worse:
-
-        >>> print RelativeDateDiff(Date(1900,3,1),Date(1901,2,1))
-        YYYY-(-11)-DD HH:MM:SS
-
-        with:
-
-        >>> print Date(1901,1,29) + RelativeDateTime(months=-11)
-        1900-03-01 00:00:00.00
-        >>> print Date(1901,2,1) + RelativeDateTime(months=-11)
-        1900-03-01 00:00:00.00
-
-    """
-    diff = date1 - date2
-    if diff.days == 0:
-        return RelativeDateTime()
-    date1months = date1.year * 12 + (date1.month - 1)
-    date2months = date2.year * 12 + (date2.month - 1)
-    #print 'months',date1months,date2months
-
-    # Calculate the months difference
-    diffmonths = date1months - date2months
-    #print 'diffmonths',diffmonths
-    if diff.days > 0:
-        years,months = divmod(diffmonths,12)
-    else:
-        years,months = divmod(diffmonths,-12)
-        years = -years
-    date3 = date2 + RelativeDateTime(years=years,months=months)
-    diff3 = date1 - date3
-    days = date1.absdays - date3.absdays
-    #print 'date3',date3,'diff3',diff3,'days',days
-
-    # Correction to ensure that all relative parts have the same sign
-    while days * diff.days < 0:
-        if diff.days > 0:
-            diffmonths = diffmonths - 1
-            years,months = divmod(diffmonths,12)
-        else:
-            diffmonths = diffmonths + 1
-            years,months = divmod(diffmonths,-12)
-            years = -years
-        #print 'diffmonths',diffmonths
-        date3 = date2 + RelativeDateTime(years=years,months=months)
-        diff3 = date1 - date3
-        days = date1.absdays - date3.absdays
-        #print 'date3',date3,'diff3',diff3,'days',days
-
-    # Drop the fraction part of days
-    if days > 0:
-        days = int(floor(days))
-    else:
-        days = int(-floor(-days))
-
-    return RelativeDateTime(years=years,
-                            months=months,
-                            days=days,
-                            hours=diff3.hour,
-                            minutes=diff3.minute,
-                            seconds=diff3.second)
-
-# Aliases
-RelativeDateDiff = RelativeDateTimeDiff
-Age = RelativeDateTimeDiff
-
-###
-
-_current_year = now().year
-_current_century, _current_year_in_century = divmod(_current_year, 100)
-_current_century = _current_century * 100
-
-def add_century(year,
-
-                current_year=_current_year,
-                current_century=_current_century):
-    
-    """ Sliding window approach to the Y2K problem: adds a suitable
-        century to the given year and returns it as integer.
-
-        The window used depends on the current year (at import time).
-        If adding the current century to the given year gives a year
-        within the range current_year-70...current_year+30 [both
-        inclusive], then the current century is added. Otherwise the
-        century (current + 1 or - 1) producing the least difference is
-        chosen.
-
-    """
-    if year > 99:
-        # Take it as-is
-        return year
-    year = year + current_century
-    diff = year - current_year
-    if diff >= -70 and diff <= 30:
-        return year
-    elif diff < -70:
-        return year + 100
-    else:
-        return year - 100
-
-# Reference formulas for JDN taken from the Calendar FAQ:
-
-def gregorian_jdn(year,month,day):
-
-    # XXX These require proper integer division.
-    a = (14-month)/12
-    y = year+4800-a
-    m = month + 12*a - 3
-    return day + (306*m+5)/10 + y*365 + y/4 - y/100 + y/400 - 32045
-
-def julian_jdn(year,month,day):
-
-    # XXX These require proper integer division.
-    a = (14-month)/12
-    y = year+4800-a
-    m = month + 12*a - 3
-    return day + (306*m+5)/10 + y*365 + y/4 - 32083
--- a/embedded/mx/DateTime/ISO.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,366 +0,0 @@
-""" This module provides a set of constructors and routines to convert
-    between DateTime[Delta] instances and ISO representations of date
-    and time.
-
-    Note: Timezones are only interpreted by ParseDateTimeGMT(). All
-    other constructors silently ignore the time zone information.
-
-    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author.
-
-"""
-import DateTime,Timezone
-import re,string
-
-# Grammar: ISO 8601 (not all, but what we need from it)
-_year = '(?P<year>\d?\d\d\d)'
-_month = '(?P<month>\d?\d)'
-_day = '(?P<day>\d?\d)'
-_hour = '(?P<hour>\d?\d)'
-_minute = '(?P<minute>\d?\d)'
-_second = '(?P<second>\d?\d(?:\.\d+)?)'
-_sign = '(?P<sign>[-+])'
-_week = 'W(?P<week>\d?\d)'
-_zone = Timezone.isozone
-
-_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?'
-_date = _year + '-?' + '(?:' + _month + '-?' + _day + '?)?'
-_time = _hour + ':?' + _minute + ':?' + _second + '?(?:' + _zone + ')?'
-
-isodatetimeRE = re.compile(_date + '(?:[ T]' + _time + ')?$')
-isodateRE = re.compile(_date + '$')
-isotimeRE = re.compile(_time + '$')
-isodeltaRE = re.compile(_sign + '?' + _time + '$')
-isoweekRE = re.compile(_weekdate + '$')
-isoweektimeRE = re.compile(_weekdate + '(?:[ T]' + _time + ')?$')
-
-def WeekTime(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0):
-
-    """Week(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0)
-
-       Returns a DateTime instance pointing to the given ISO week and
-       day.  isoday defaults to 1, which corresponds to Monday in the
-       ISO numbering. The time part is set as given.
-
-    """
-    d = DateTime.DateTime(year,1,1,hour,minute,second)
-    if d.iso_week[0] == year:
-        # 1.1. belongs to year (backup to Monday)
-        return d + (-d.day_of_week + 7 * (isoweek-1) + isoday-1)
-    else:
-        # 1.1. belongs to year-1 (advance to next Monday)
-        return d + (7-d.day_of_week + 7 * (isoweek-1) + isoday-1)
-
-# Alias
-Week = WeekTime
-
-# Aliases for the other constructors (they all happen to already use
-# ISO format)
-Date = DateTime.Date
-Time = DateTime.Time
-TimeDelta = DateTime.TimeDelta
-
-def ParseDateTime(isostring,parse_isodatetime=isodatetimeRE.match,
-
-                  strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseDateTime(isostring)
-
-       Returns a DateTime instance reflecting the given ISO date. A
-       time part is optional and must be delimited from the date by a
-       space or 'T'.
-
-       Time zone information is parsed, but not evaluated.
-
-    """
-    s = strip(isostring)
-    date = parse_isodatetime(s)
-    if not date:
-        raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS'
-    year,month,day,hour,minute,second,zone = date.groups()
-    year = atoi(year)
-    if month is None:
-        month = 1
-    else:
-        month = atoi(month)
-    if day is None:
-        day = 1
-    else:
-        day = atoi(day)
-    if hour is None:
-        hour = 0
-    else:
-        hour = atoi(hour)
-    if minute is None:
-        minute = 0
-    else:
-        minute = atoi(minute)
-    if second is None:
-        second = 0.0
-    else:
-        second = atof(second)
-    return DateTime.DateTime(year,month,day,hour,minute,second)
-
-def ParseDateTimeGMT(isostring,parse_isodatetime=isodatetimeRE.match,
-
-                     strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseDateTimeGMT(isostring)
-
-       Returns a DateTime instance in UTC reflecting the given ISO
-       date. A time part is optional and must be delimited from the
-       date by a space or 'T'. Timezones are honored.
-
-    """
-    s = strip(isostring)
-    date = parse_isodatetime(s)
-    if not date:
-        raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS'
-    year,month,day,hour,minute,second,zone = date.groups()
-    year = atoi(year)
-    if month is None:
-        month = 1
-    else:
-        month = atoi(month)
-    if day is None:
-        day = 1
-    else:
-        day = atoi(day)
-    if hour is None:
-        hour = 0
-    else:
-        hour = atoi(hour)
-    if minute is None:
-        minute = 0
-    else:
-        minute = atoi(minute)
-    if second is None:
-        second = 0.0
-    else:
-        second = atof(second)
-    offset = Timezone.utc_offset(zone)
-    return DateTime.DateTime(year,month,day,hour,minute,second) - offset
-
-# Alias
-ParseDateTimeUTC = ParseDateTimeGMT
-
-def ParseDate(isostring,parse_isodate=isodateRE.match,
-
-              strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseDate(isostring)
-
-       Returns a DateTime instance reflecting the given ISO date. A
-       time part may not be included.
-
-    """
-    s = strip(isostring)
-    date = parse_isodate(s)
-    if not date:
-        raise ValueError,'wrong format, use YYYY-MM-DD'
-    year,month,day = date.groups()
-    year = atoi(year)
-    if month is None:
-        month = 1
-    else:
-        month = atoi(month)
-    if day is None:
-        day = 1
-    else:
-        day = atoi(day)
-    return DateTime.DateTime(year,month,day)
-
-def ParseWeek(isostring,parse_isoweek=isoweekRE.match,
-
-              strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseWeek(isostring)
-
-       Returns a DateTime instance reflecting the given ISO date. A
-       time part may not be included.
-
-    """
-    s = strip(isostring)
-    date = parse_isoweek(s)
-    if not date:
-        raise ValueError,'wrong format, use yyyy-Www-d, e.g. 1998-W01-1'
-    year,week,day = date.groups()
-    year = atoi(year)
-    if week is None:
-        week = 1
-    else:
-        week = atoi(week)
-    if day is None:
-        day = 1
-    else:
-        day = atoi(day)
-    return Week(year,week,day)
-
-def ParseWeekTime(isostring,parse_isoweektime=isoweektimeRE.match,
-
-                  strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseWeekTime(isostring)
-
-       Returns a DateTime instance reflecting the given ISO date. A
-       time part is optional and must be delimited from the date by a
-       space or 'T'.
-
-    """
-    s = strip(isostring)
-    date = parse_isoweektime(s)
-    if not date:
-        raise ValueError,'wrong format, use e.g. "1998-W01-1 12:00:30"'
-    year,week,day,hour,minute,second,zone = date.groups()
-    year = atoi(year)
-    if week is None:
-        week = 1
-    else:
-        week = atoi(week)
-    if day is None:
-        day = 1
-    else:
-        day = atoi(day)
-    if hour is None:
-        hour = 0
-    else:
-        hour = atoi(hour)
-    if minute is None:
-        minute = 0
-    else:
-        minute = atoi(minute)
-    if second is None:
-        second = 0.0
-    else:
-        second = atof(second)
-    return WeekTime(year,week,day,hour,minute,second)
-
-def ParseTime(isostring,parse_isotime=isotimeRE.match,
-
-              strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseTime(isostring)
-
-       Returns a DateTimeDelta instance reflecting the given ISO time.
-       Hours and minutes must be given, seconds are
-       optional. Fractions of a second may also be used,
-       e.g. 12:23:12.34.
-
-    """
-    s = strip(isostring)
-    time = parse_isotime(s)
-    if not time:
-        raise ValueError,'wrong format, use HH:MM:SS'
-    hour,minute,second,zone = time.groups()
-    hour = atoi(hour)
-    minute = atoi(minute)
-    if second is not None:
-        second = atof(second)
-    else:
-        second = 0.0
-    return DateTime.TimeDelta(hour,minute,second)
-
-def ParseTimeDelta(isostring,parse_isodelta=isodeltaRE.match,
-
-                   strip=string.strip,atoi=string.atoi,atof=string.atof):
-
-    """ParseTimeDelta(isostring)
-
-       Returns a DateTimeDelta instance reflecting the given ISO time
-       as delta. Hours and minutes must be given, seconds are
-       optional. Fractions of a second may also be used,
-       e.g. 12:23:12.34. In addition to the ISO standard a sign may be
-       prepended to the time, e.g. -12:34.
-
-    """
-    s = strip(isostring)
-    time = parse_isodelta(s)
-    if not time:
-        raise ValueError,'wrong format, use [-]HH:MM:SS'
-    sign,hour,minute,second,zone = time.groups()
-    hour = atoi(hour)
-    minute = atoi(minute)
-    if second is not None:
-        second = atof(second)
-    else:
-        second = 0.0
-    if sign and sign == '-':
-        return -DateTime.TimeDelta(hour,minute,second)
-    else:
-        return DateTime.TimeDelta(hour,minute,second)
-
-def ParseAny(isostring):
-
-    """ParseAny(isostring)
-
-       Parses the given string and tries to convert it to a
-       DateTime[Delta] instance.
-
-    """
-    try:
-        return ParseDateTime(isostring)
-    except ValueError:
-        pass
-    try:
-        return ParseWeekTime(isostring)
-    except ValueError:
-        pass
-    try:
-        return ParseTimeDelta(isostring)
-    except ValueError:
-        raise ValueError,'unsupported format: "%s"' % isostring
-
-def str(datetime,tz=None):
-
-    """str(datetime,tz=DateTime.tz_offset(datetime))
-
-       Returns the datetime instance as ISO date string. tz can be
-       given as DateTimeDelta instance providing the time zone
-       difference from datetime's zone to UTC. It defaults to
-       DateTime.tz_offset(datetime) which assumes local time.
-
-    """
-    if tz is None:
-        tz = datetime.gmtoffset()
-    return '%04i-%02i-%02i %02i:%02i:%02i%+03i%02i' % (
-        datetime.year, datetime.month, datetime.day, 
-        datetime.hour, datetime.minute, datetime.second,
-        tz.hour,tz.minute)
-
-def strGMT(datetime):
-
-    """strGMT(datetime)
-
-       Returns the datetime instance as ISO date string assuming it is
-       given in GMT.
-
-    """
-    return '%04i-%02i-%02i %02i:%02i:%02i+0000' % (
-        datetime.year, datetime.month, datetime.day, 
-        datetime.hour, datetime.minute, datetime.second)
-
-def strUTC(datetime):
-
-    """strUTC(datetime)
-
-       Returns the datetime instance as ISO date string assuming it is
-       given in UTC.
-
-    """
-    return '%04i-%02i-%02i %02i:%02i:%02i+0000' % (
-        datetime.year, datetime.month, datetime.day, 
-        datetime.hour, datetime.minute, datetime.second)
-
-# Testing
-if __name__ == '__main__':
-    e = DateTime.Date(1900,1,1)
-    for i in range(100000):
-        d = e + i
-        year,week,day = d.iso_week
-        c = WeekTime(year,week,day)
-        if d != c:
-            print ' Check %s (given; %i) != %s (parsed)' % (d,d.day_of_week,c)
-        elif i % 1000 == 0:
-            print d,'ok'
--- a/embedded/mx/DateTime/Parser.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1225 +0,0 @@
-# -*- coding: latin-1 -*-
-
-""" Date/Time string parsing module.
-
-    Note about the Y2K problems:
-
-       The parser can only handle years with at least 2 digits. 2
-       digit year values get expanded by adding the century using
-       DateTime.add_century(), while 3 digit year get converted
-       literally. To have 2 digit years also be interpreted literally,
-       add leading zeros, e.g. year 99 must be written as 099 or 0099.
-
-    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author. All Rights Reserved.
-
-"""
-import types,re,string
-import DateTime,ISO,ARPA,Timezone
-
-# Enable to produce debugging output
-_debug = 0
-
-# REs for matching date and time parts in a string; These REs
-# parse a superset of ARPA, ISO, American and European style dates.
-# Timezones are supported via the Timezone submodule.
-
-_year = '(?P<year>-?\d+\d(?!:))'
-_fullyear = '(?P<year>-?\d+\d\d(?!:))'
-_year_epoch = '(?:' + _year + '(?P<epoch> *[ABCDE\.]+)?)'
-_fullyear_epoch = '(?:' + _fullyear + '(?P<epoch> *[ABCDE\.]+)?)'
-_relyear = '(?:\((?P<relyear>[-+]?\d+)\))'
-
-_month = '(?P<month>\d?\d(?!:))'
-_fullmonth = '(?P<month>\d\d(?!:))'
-_litmonth = ('(?P<litmonth>'
-             'jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|'
-             'mär|mae|mrz|mai|okt|dez|'
-             'fev|avr|juin|juil|aou|aoû|déc|'
-             'ene|abr|ago|dic|'
-             'out'
-             ')[a-z,\.;]*')
-litmonthtable = {
-    # English
-    'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
-    'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12,
-    # German
-    'mär':3, 'mae':3, 'mrz':3, 'mai':5, 'okt':10, 'dez':12,
-    # French
-    'fev':2, 'avr':4, 'juin':6, 'juil':7, 'aou':8, 'aoû':8,
-    'déc':12,
-    # Spanish
-    'ene':1, 'abr':4, 'ago':8, 'dic':12,
-    # Portuguese
-    'out':10,
-    }
-_relmonth = '(?:\((?P<relmonth>[-+]?\d+)\))'
-
-_day = '(?P<day>\d?\d(?!:))'
-_usday = '(?P<day>\d?\d(?!:))(?:st|nd|rd|th|[,\.;])?'
-_fullday = '(?P<day>\d\d(?!:))'
-_litday = ('(?P<litday>'
-           'mon|tue|wed|thu|fri|sat|sun|'
-           'die|mit|don|fre|sam|son|'
-           'lun|mar|mer|jeu|ven|sam|dim|'
-           'mie|jue|vie|sab|dom|'
-           'pri|seg|ter|cua|qui'
-           ')[a-z]*')
-litdaytable = {
-    # English
-    'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6,
-    # German
-    'die':1, 'mit':2, 'don':3, 'fre':4, 'sam':5, 'son':6,
-    # French
-    'lun':0, 'mar':1, 'mer':2, 'jeu':3, 'ven':4, 'sam':5, 'dim':6,
-    # Spanish
-    'mie':2, 'jue':3, 'vie':4, 'sab':5, 'dom':6,
-    # Portuguese
-    'pri':0, 'seg':1, 'ter':2, 'cua':3, 'qui':4,
-    }
-_relday = '(?:\((?P<relday>[-+]?\d+)\))'
-
-_hour = '(?P<hour>[012]?\d)'
-_minute = '(?P<minute>[0-6]\d)'
-_second = '(?P<second>[0-6]\d(?:[.,]\d+)?)'
-
-_days = '(?P<days>\d*\d(?:[.,]\d+)?)'
-_hours = '(?P<hours>\d*\d(?:[.,]\d+)?)'
-_minutes = '(?P<minutes>\d*\d(?:[.,]\d+)?)'
-_seconds = '(?P<seconds>\d*\d(?:[.,]\d+)?)'
-
-_reldays = '(?:\((?P<reldays>[-+]?\d+(?:[.,]\d+)?)\))'
-_relhours = '(?:\((?P<relhours>[-+]?\d+(?:[.,]\d+)?)\))'
-_relminutes = '(?:\((?P<relminutes>[-+]?\d+(?:[.,]\d+)?)\))'
-_relseconds = '(?:\((?P<relseconds>[-+]?\d+(?:[.,]\d+)?)\))'
-
-_sign = '(?:(?P<sign>[-+]) *)'
-_week = 'W(?P<week>\d?\d)'
-_zone = Timezone.zone
-_ampm = '(?P<ampm>[ap][m.]+)'
-
-_time = (_hour + ':' + _minute + '(?::' + _second + '|[^:]|$) *'
-         + _ampm + '? *' + _zone + '?')
-_isotime = _hour + ':?' + _minute + ':?' + _second + '? *' + _zone + '?'
-
-_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?'
-_eurodate = _day + '\.' + _month + '\.' + _year_epoch + '?'
-_usdate = _month + '/' + _day + '(?:/' + _year_epoch + '|[^/]|$)'
-_altusdate = _month + '-' + _day + '-' + _fullyear_epoch
-_isodate = _year + '-' + _month + '-?' + _day + '?(?!:)'
-_altisodate = _year + _fullmonth + _fullday + '(?!:)'
-_usisodate = _fullyear + '/' + _fullmonth + '/' + _fullday
-_litdate = ('(?:'+ _litday + ',? )? *' + 
-            _usday + ' *' + 
-            '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ] *' +
-            _year_epoch + '?')
-_altlitdate = ('(?:'+ _litday + ',? )? *' + 
-               _litmonth + '[ ,.a-z]+' +
-               _usday + 
-               '(?:[ a-z]+' + _year_epoch + ')?')
-_eurlitdate = ('(?:'+ _litday + ',?[ a-z]+)? *' + 
-               '(?:'+ _usday + '[ a-z]+)? *' +
-               _litmonth + 
-               '(?:[ ,.a-z]+' + _year_epoch + ')?')
-
-_relany = '[*%?a-zA-Z]+'
-
-_relisodate = ('(?:(?:' + _relany + '|' + _year + '|' + _relyear + ')-' +
-               '(?:' + _relany + '|' + _month + '|' + _relmonth + ')-' +
-               '(?:' + _relany + '|' + _day + '|' + _relday + '))')
-
-_asctime = ('(?:'+ _litday + ',? )? *' + 
-                _usday + ' *' + 
-                '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ]' +
-                '(?:[0-9: ]+)' + 
-                _year_epoch + '?')
-
-_relisotime = ('(?:(?:' + _relany + '|' + _hour + '|' + _relhours + '):' +
-               '(?:' + _relany + '|' + _minute + '|' + _relminutes + ')' +
-               '(?::(?:' + _relany + '|' + _second + '|' + _relseconds + '))?)')
-
-_isodelta1 = (_sign + '?' +
-              _days + ':' + _hours + ':' + _minutes + ':' + _seconds)
-_isodelta2 = (_sign + '?' + 
-              _hours + ':' + _minutes + ':' + _seconds)
-_isodelta3 = (_sign + '?' + 
-              _hours + ':' + _minutes)
-_litdelta = (_sign + '?' +
-             '(?:' + _days + ' *d[a-z]*[,; ]*)?' + 
-             '(?:' + _hours + ' *h[a-z]*[,; ]*)?' + 
-             '(?:' + _minutes + ' *m[a-z]*[,; ]*)?' +
-             '(?:' + _seconds + ' *s[a-z]*[,; ]*)?')
-_litdelta2 = (_sign + '?' +
-             '(?:' + _days + ' *d[a-z]*[,; ]*)?' + 
-              _hours + ':' + _minutes + '(?::' + _seconds + ')?')
-
-_timeRE = re.compile(_time, re.I)
-_isotimeRE = re.compile(_isotime, re.I)
-_isodateRE = re.compile(_isodate, re.I)
-_altisodateRE = re.compile(_altisodate, re.I)
-_usisodateRE = re.compile(_usisodate, re.I)
-_eurodateRE = re.compile(_eurodate, re.I)
-_usdateRE = re.compile(_usdate, re.I)
-_altusdateRE = re.compile(_altusdate, re.I)
-_litdateRE = re.compile(_litdate, re.I)
-_altlitdateRE = re.compile(_altlitdate, re.I)
-_eurlitdateRE = re.compile(_eurlitdate, re.I)
-_relisodateRE = re.compile(_relisodate, re.I)
-_asctimeRE = re.compile(_asctime, re.I)
-_isodelta1RE = re.compile(_isodelta1)
-_isodelta2RE = re.compile(_isodelta2)
-_isodelta3RE = re.compile(_isodelta3)
-_litdeltaRE = re.compile(_litdelta)
-_litdelta2RE = re.compile(_litdelta2)
-_relisotimeRE = re.compile(_relisotime, re.I)
-
-# Available date parsers
-_date_formats = ('euro',
-                 'usiso', 'us', 'altus',
-                 'iso', 'altiso', 
-                 'lit', 'altlit', 'eurlit',
-                 'unknown')
-
-# Available time parsers
-_time_formats = ('standard',
-                 'iso',
-                 'unknown')
-
-def _parse_date(text, formats=_date_formats, defaultdate=None,
-
-                int=int,float=float,lower=string.lower,
-                add_century=DateTime.add_century,
-                now=DateTime.now,us_formats=('us', 'altus'),
-                iso_formats=('iso', 'altiso', 'usiso')):
-
-    """ Parses the date part given in text and returns a tuple
-        (text,day,month,year,style) with the following
-        meanings:
-
-        * text gives the original text without the date part
-
-        * day,month,year give the parsed date
-
-        * style gives information about which parser was successful:
-          'euro' - the European date parser
-          'us' - the US date parser
-          'altus' - the alternative US date parser (with '-' instead of '/')
-          'iso' - the ISO date parser
-          'altiso' - the alternative ISO date parser (without '-')
-          'usiso' - US style ISO date parser (yyyy/mm/dd)
-          'lit' - the US literal date parser
-          'altlit' - the alternative US literal date parser
-          'eurlit' - the Eurpean literal date parser
-          'unknown' - no date part was found, defaultdate was used
-
-        formats may be set to a tuple of style strings specifying
-        which of the above parsers to use and in which order to try
-        them. Default is to try all of them in the above order.
-
-        defaultdate provides the defaults to use in case no date part
-        is found. Most other parsers default to the current year
-        January 1 if some of these date parts are missing.
-
-        If 'unknown' is not given in formats and the date cannot be
-        parsed, a ValueError is raised.
-
-    """
-    match = None
-    style = ''
-    
-    # Apply parsers in the order given in formats
-    for format in formats:
-
-        if format == 'euro':
-            # European style date
-            match = _eurodateRE.search(text)
-            if match is not None:
-                day,month,year,epoch = match.groups()
-                if year:
-                    if len(year) == 2:
-                        # Y2K problem:
-                        year = add_century(int(year))
-                    else:
-                        year = int(year)
-                else:
-                    if defaultdate is None:
-                        defaultdate = now()
-                    year = defaultdate.year
-                if epoch and 'B' in epoch:
-                    year = -year + 1
-                month = int(month)
-                day = int(day)
-                # Could have mistaken euro format for us style date
-                # which uses month, day order
-                if month > 12 or month == 0:
-                    match = None
-                    continue
-                break
-
-        elif format in iso_formats:
-            # ISO style date
-            if format == 'iso':
-                match = _isodateRE.search(text)
-            elif format == 'altiso':
-                match = _altisodateRE.search(text)
-                # Avoid mistaking ISO time parts ('Thhmmss') for dates
-                if match is not None:
-                    left, right = match.span()
-                    if left > 0 and \
-                       text[left - 1:left] == 'T':
-                        match = None
-                        continue
-            else:
-                match = _usisodateRE.search(text)
-            if match is not None:
-                year,month,day = match.groups()
-                if len(year) == 2:
-                    # Y2K problem:
-                    year = add_century(int(year))
-                else:
-                    year = int(year)
-                # Default to January 1st
-                if not month:
-                    month = 1
-                else:
-                    month = int(month)
-                if not day:
-                    day = 1
-                else:
-                    day = int(day)
-                break
-
-        elif format in us_formats:
-            # US style date
-            if format == 'us':
-                match = _usdateRE.search(text)
-            else:
-                match = _altusdateRE.search(text)
-            if match is not None:
-                month,day,year,epoch = match.groups()
-                if year:
-                    if len(year) == 2:
-                        # Y2K problem:
-                        year = add_century(int(year))
-                    else:
-                        year = int(year)
-                else:
-                    if defaultdate is None:
-                        defaultdate = now()
-                    year = defaultdate.year
-                if epoch and 'B' in epoch:
-                    year = -year + 1
-                # Default to 1 if no day is given
-                if day:
-                    day = int(day)
-                else:
-                    day = 1
-                month = int(month)
-                # Could have mistaken us format for euro style date
-                # which uses day, month order
-                if month > 12 or month == 0:
-                    match = None
-                    continue
-                break
-
-        elif format == 'lit':
-            # US style literal date
-            match = _litdateRE.search(text)
-            if match is not None:
-                litday,day,litmonth,month,year,epoch = match.groups()
-                break
-
-        elif format == 'altlit':
-            # Alternative US style literal date
-            match = _altlitdateRE.search(text)
-            if match is not None: 
-                litday,litmonth,day,year,epoch = match.groups()
-                month = '<missing>'
-                break
-
-        elif format == 'eurlit':
-            # European style literal date
-            match = _eurlitdateRE.search(text)
-            if match is not None: 
-                litday,day,litmonth,year,epoch = match.groups()
-                month = '<missing>'
-                break
-
-        elif format == 'unknown':
-            # No date part: use defaultdate
-            if defaultdate is None:
-                defaultdate = now()
-            year = defaultdate.year
-            month = defaultdate.month
-            day = defaultdate.day
-            style = format
-            break
-
-    # Check success
-    if match is not None:
-        # Remove date from text
-        left, right = match.span()
-        if 0 and _debug:
-            print 'parsed date:',repr(text[left:right]),\
-                  'giving:',year,month,day
-        text = text[:left] + text[right:]
-        style = format
-        
-    elif not style:
-        # Not recognized: raise an error
-        raise ValueError, 'unknown date format: "%s"' % text
-
-    # Literal date post-processing
-    if style in ('lit', 'altlit', 'eurlit'):
-        if 0 and _debug: print match.groups()
-        # Default to current year, January 1st
-        if not year:
-            if defaultdate is None:
-                defaultdate = now()
-            year = defaultdate.year
-        else:
-            if len(year) == 2:
-                # Y2K problem:
-                year = add_century(int(year))
-            else:
-                year = int(year)
-        if epoch and 'B' in epoch:
-            year = -year + 1
-        if litmonth:
-            litmonth = lower(litmonth)
-            try:
-                month = litmonthtable[litmonth]
-            except KeyError:
-                raise ValueError,\
-                      'wrong month name: "%s"' % litmonth
-        elif month:
-            month = int(month)
-        else:
-            month = 1
-        if day:
-            day = int(day)
-        else:
-            day = 1
-
-    #print '_parse_date:',text,day,month,year,style
-    return text,day,month,year,style
-
-def _parse_time(text, formats=_time_formats,
-
-                int=int,float=float,replace=string.replace):
-
-    """ Parses a time part given in text and returns a tuple
-        (text,hour,minute,second,offset,style) with the following
-        meanings:
-
-        * text gives the original text without the time part
-        * hour,minute,second give the parsed time
-        * offset gives the time zone UTC offset
-        * style gives information about which parser was successful:
-          'standard' - the standard parser
-          'iso' - the ISO time format parser
-          'unknown' - no time part was found
-
-        formats may be set to a tuple specifying the parsers to use:
-          'standard' - standard time format with ':' delimiter
-          'iso' - ISO time format (superset of 'standard')
-          'unknown' - default to 0:00:00, 0 zone offset
-
-        If 'unknown' is not given in formats and the time cannot be
-        parsed, a ValueError is raised.
-
-    """
-    match = None
-    style = ''
-
-    # Apply parsers in the order given in formats
-    for format in formats:
-
-        # Standard format
-        if format == 'standard':
-            match = _timeRE.search(text)
-            if match is not None:
-                hour,minute,second,ampm,zone = match.groups()
-                style = 'standard'
-                break
-
-        # ISO format
-        if format == 'iso':
-            match =  _isotimeRE.search(text)
-            if match is not None:
-                hour,minute,second,zone = match.groups()
-                ampm = None
-                style = 'iso'
-                break
-
-        # Default handling
-        elif format == 'unknown':
-            hour,minute,second,offset = 0,0,0.0,0
-            style = 'unknown'
-            break
-
-    if not style:
-        # If no default handling should be applied, raise an error
-        raise ValueError, 'unknown time format: "%s"' % text
-
-    # Post-processing
-    if match is not None:
-        if zone:
-            # Convert to UTC offset
-            offset = Timezone.utc_offset(zone)
-        else:
-            offset = 0
-        hour = int(hour)
-        if ampm:
-            if ampm[0] in ('p', 'P'):
-                # 12pm = midday
-                if hour < 12:
-                    hour = hour + 12
-            else:
-                # 12am = midnight 
-                if hour >= 12:
-                    hour = hour - 12
-        if minute:
-            minute = int(minute)
-        else:
-            minute = 0
-        if not second:
-            second = 0.0
-        else:
-            if ',' in second:
-                second = replace(second, ',', '.')
-            second = float(second)
-
-        # Remove time from text
-        left,right = match.span()
-        if 0 and _debug: 
-            print 'parsed time:',repr(text[left:right]),\
-                  'giving:',hour,minute,second,offset
-        text = text[:left] + text[right:]
-
-    #print '_parse_time:',text,hour,minute,second,offset,style
-    return text,hour,minute,second,offset,style
-
-###
-
-def DateTimeFromString(text, formats=_date_formats, defaultdate=None,
-                       time_formats=_time_formats,
-
-                       DateTime=DateTime):
-
-    """ DateTimeFromString(text, [formats, defaultdate])
-    
-        Returns a DateTime instance reflecting the date and time given
-        in text. In case a timezone is given, the returned instance
-        will point to the corresponding UTC time value. Otherwise, the
-        value is set as given in the string.
-
-        formats may be set to a tuple of strings specifying which of
-        the following parsers to use and in which order to try
-        them. Default is to try all of them in the order given below:
-
-          'euro' - the European date parser
-          'us' - the US date parser
-          'altus' - the alternative US date parser (with '-' instead of '/')
-          'iso' - the ISO date parser
-          'altiso' - the alternative ISO date parser (without '-')
-          'usiso' - US style ISO date parser (yyyy/mm/dd)
-          'lit' - the US literal date parser
-          'altlit' - the alternative US literal date parser
-          'eurlit' - the Eurpean literal date parser
-          'unknown' - if no date part is found, use defaultdate
-
-        defaultdate provides the defaults to use in case no date part
-        is found. Most of the parsers default to the current year
-        January 1 if some of these date parts are missing.
-
-        If 'unknown' is not given in formats and the date cannot
-        be parsed, a ValueError is raised.
-
-        time_formats may be set to a tuple of strings specifying which
-        of the following parsers to use and in which order to try
-        them. Default is to try all of them in the order given below:
-
-          'standard' - standard time format HH:MM:SS (with ':' delimiter)
-          'iso' - ISO time format (superset of 'standard')
-          'unknown' - default to 00:00:00 in case the time format
-                      cannot be parsed
-
-        Defaults to 00:00:00.00 for time parts that are not included
-        in the textual representation.
-
-        If 'unknown' is not given in time_formats and the time cannot
-        be parsed, a ValueError is raised.
-
-    """
-    origtext = text
-    formats = tuple(formats)
-
-    if formats is _date_formats or \
-       'iso' in formats or \
-       'altiso' in formats:
-
-        # First try standard order (parse time, then date)
-        if formats[0] not in ('iso', 'altiso'):
-            text,hour,minute,second,offset,timestyle = _parse_time(
-                origtext,
-                time_formats)
-            text,day,month,year,datestyle = _parse_date(
-                text,
-                formats + ('unknown',),
-                defaultdate)
-            if 0 and _debug:
-                print 'tried time/date on %s, date=%s, time=%s' % (origtext,
-                                                                   datestyle,
-                                                                   timestyle)
-        else:
-            timestyle = 'iso'
-            
-        # If this fails, try the ISO order (date, then time)
-        if timestyle in ('iso', 'unknown'):
-            text,day,month,year,datestyle = _parse_date(
-                origtext,
-                formats,
-                defaultdate)
-            text,hour,minute,second,offset,timestyle = _parse_time(
-                text,
-                time_formats)
-            if 0 and _debug:
-                print 'tried ISO on %s, date=%s, time=%s' % (origtext,
-                                                             datestyle,
-                                                             timestyle)
-    else:
-        # Standard order: time part, then date part
-        text,hour,minute,second,offset,timestyle = _parse_time(
-            origtext,
-            time_formats)
-        text,day,month,year,datestyle = _parse_date(
-            text,
-            formats,
-            defaultdate)
-
-    if (datestyle == 'unknown' and 'unknown' not in formats) or \
-       (timestyle == 'unknown' and 'unknown' not in time_formats):
-        raise ValueError,\
-              'Failed to parse "%s": found "%s" date, "%s" time' % \
-              (origtext, datestyle, timestyle)
-    
-    try:
-        return DateTime.DateTime(year,month,day,hour,minute,second) - offset
-    except DateTime.RangeError, why:
-        raise DateTime.RangeError,\
-              'Failed to parse "%s": %s' % (origtext, why)
-
-def DateFromString(text, formats=_date_formats, defaultdate=None,
-
-                   DateTime=DateTime):
-
-    """ DateFromString(text, [formats, defaultdate])
-    
-        Returns a DateTime instance reflecting the date given in
-        text. A possibly included time part is ignored.
-
-        formats and defaultdate work just like for
-        DateTimeFromString().
-
-    """
-    _text,day,month,year,datestyle = _parse_date(text, formats, defaultdate)
-
-    if datestyle == 'unknown' and \
-       'unknown' not in formats:
-        raise ValueError,\
-              'Failed to parse "%s": found "%s" date' % \
-              (origtext, datestyle)
-
-    try:
-        return DateTime.DateTime(year,month,day)
-    except DateTime.RangeError, why:
-        raise DateTime.RangeError,\
-              'Failed to parse "%s": %s' % (text, why)
-
-def validateDateTimeString(text, formats=_date_formats):
-
-    """ validateDateTimeString(text, [formats, defaultdate])
-
-        Validates the given text and returns 1/0 depending on whether
-        text includes parseable date and time values or not.
-
-        formats works just like for DateTimeFromString() and defines
-        the order of date/time parsers to apply. It defaults to the
-        same list of parsers as for DateTimeFromString().
-
-        XXX Undocumented !
-    
-    """
-    formats = list(formats)
-    if 'unknown' in formats:
-        formats.remove('unknown')
-    try:
-        DateTimeFromString(text, formats)
-    except (DateTime.RangeError, ValueError), why:
-        return 0
-    return 1
-
-def validateDateString(text, formats=_date_formats):
-
-    """ validateDateString(text, [formats, defaultdate])
-
-        Validates the given text and returns 1/0 depending on whether
-        text includes a parseable date value or not.
-
-        formats works just like for DateTimeFromString() and defines
-        the order of date/time parsers to apply. It defaults to the
-        same list of parsers as for DateTimeFromString().
-    
-        XXX Undocumented !
-    
-    """
-    formats = list(formats)
-    if 'unknown' in formats:
-        formats.remove('unknown')
-    try:
-        DateFromString(text, formats)
-    except (DateTime.RangeError, ValueError), why:
-        return 0
-    return 1
-
-def TimeFromString(text, formats=_time_formats,
-
-                   DateTime=DateTime):
-
-    """ TimeFromString(text, [formats])
-    
-        Returns a DateTimeDelta instance reflecting the time given in
-        text. A possibly included date part is ignored.
-
-        formats may be set to a tuple of strings specifying which of
-        the following parsers to use and in which order to try
-        them. Default is to try all of them in the order given below:
-
-          'standard' - standard time format with ':' delimiter
-          'iso' - ISO time format (superset of 'standard')
-          'unknown' - default to 00:00:00 in case the time format
-                      cannot be parsed
-
-        Defaults to 00:00:00.00 for parts that are not included in the
-        textual representation.
-        
-    """
-    _text,hour,minute,second,offset,timestyle = _parse_time(
-        text,
-        formats)
-
-    if timestyle == 'unknown' and \
-       'unknown' not in formats:
-        raise ValueError,\
-              'Failed to parse "%s": found "%s" time' % \
-              (text, timestyle)
-
-    try:
-        dtd = DateTime.DateTimeDelta(0.0, hour, minute, second)
-    except DateTime.RangeError, why:
-        raise DateTime.RangeError,\
-              'Failed to parse "%s": %s' % (text, why)
-    else:
-        # XXX What to do with offset ?
-        return dtd
-
-#
-# XXX Still missing: validateTimeString(), validateDateTimeDeltaString()
-#                    and validateTimeDeltaString()
-#
-
-def DateTimeDeltaFromString(text,
-
-                            float=float,DateTime=DateTime):
-
-    """ DateTimeDeltaFromString(text)
-    
-        Returns a DateTimeDelta instance reflecting the delta given in
-        text. Defaults to 0:00:00:00.00 for parts that are not
-        included in the textual representation or cannot be parsed.
-
-    """
-    match = _isodelta1RE.search(text)
-    if match is not None:
-        sign, days, hours, minutes, seconds = match.groups()
-    else:
-        match = _litdelta2RE.search(text)
-        if match is not None:
-            sign, days, hours, minutes, seconds = match.groups()
-        else:
-            match = _isodelta2RE.search(text)
-            if match is not None:
-                sign, hours, minutes, seconds = match.groups()
-                days = None
-            else:
-                match = _isodelta3RE.search(text)
-                if match is not None:
-                    sign, hours, minutes = match.groups()
-                    days = None
-                    seconds = None
-                else:
-                    match = _litdeltaRE.search(text)
-                    if match is not None:
-                        sign, days, hours, minutes, seconds = match.groups()
-
-                    else:
-                        # Not matched:
-                        return DateTime.DateTimeDelta(0.0)
-
-    # Conversions
-    if days:
-        days = float(days)
-    else:
-        days = 0.0
-    if hours:
-        hours = float(hours)
-    else:
-        hours = 0.0
-    if minutes:
-        minutes = float(minutes)
-    else:
-        minutes = 0.0
-    if seconds:
-        seconds = float(seconds)
-    else:
-        seconds = 0.0
-    if sign != '-':
-        sign = 1
-    else:
-        sign = -1
-
-    try:
-        dtd = DateTime.DateTimeDelta(days,hours,minutes,seconds)
-    except DateTime.RangeError, why:
-        raise DateTime.RangeError,\
-              'Failed to parse "%s": %s' % (text, why)
-    else:
-        if sign < 0:
-            return -dtd
-        else:
-            return dtd
-
-# Aliases
-TimeDeltaFromString = DateTimeDeltaFromString
-
-###
-
-def _parse_reldate(text,
-
-                   int=int,float=float):
-
-    match = _relisodateRE.search(text)
-    if match is not None:
-        groups = match.groups()
-        if 0 and _debug: print groups
-        year,years,month,months,day,days = groups
-        if year:
-            year = int(year)
-        if years:
-            years = float(years)
-        else:
-            years = 0
-        if month:
-            month = int(month)
-        if months:
-            months = float(months)
-        else:
-            months = 0
-        if day:
-            day = int(day)
-        if days:
-            days = float(days)
-        else:
-            days = 0
-        return year,years,month,months,day,days
-    else:
-        return None,0,None,0,None,0
-
-def _parse_reltime(text,
-
-                   int=int,float=float):
-
-    match = _relisotimeRE.search(text)
-    if match is not None:
-        groups = match.groups()
-        if 0 and _debug: print groups
-        hour,hours,minute,minutes,second,seconds = groups
-        if hour:
-            hour = int(hour)
-        if hours:
-            hours = float(hours)
-        else:
-            hours = 0
-        if minute:
-            minute = int(minute)
-        if minutes:
-            minutes = float(minutes)
-        else:
-            minutes = 0
-        if second:
-            second = int(second)
-        if seconds:
-            seconds = float(seconds)
-        else:
-            seconds = 0
-        return hour,hours,minute,minutes,second,seconds
-    else:
-        return None,0,None,0,None,0
-
-def RelativeDateTimeFromString(text,
-
-                               RelativeDateTime=DateTime.RelativeDateTime):
-
-    """ RelativeDateTimeFromString(text)
-    
-        Returns a RelativeDateTime instance reflecting the relative
-        date and time given in text.
-
-        Defaults to wildcards for parts or values which are not
-        included in the textual representation or cannot be parsed.
-
-        The format used in text must adhere to the following syntax:
-
-                        [YYYY-MM-DD] [HH:MM[:SS]]
-
-        with the usual meanings. Values which should not be altered
-        may be replaced with '*', '%', '?' or any combination of
-        letters, e.g. 'YYYY'. Relative settings must be enclosed in
-        parenthesis if given and should include a sign, e.g. '(+0001)'
-        for the year part. All other settings are interpreted as
-        absolute values.
-
-        Date and time parts are both optional as a whole. Seconds in
-        the time part are optional too. Everything else (including the
-        hyphens and colons) is mandatory.
-
-    """
-    year,years,month,months,day,days = _parse_reldate(text)
-    hour,hours,minute,minutes,second,seconds = _parse_reltime(text)
-    return RelativeDateTime(year=year,years=years,
-                            month=month,months=months,
-                            day=day,days=days,
-                            hour=hour,hours=hours,
-                            minute=minute,minutes=minutes,
-                            second=second,seconds=seconds)
-
-def RelativeDateFromString(text,
-
-                           RelativeDateTime=DateTime.RelativeDateTime):
-
-    """ RelativeDateFromString(text)
-    
-        Same as RelativeDateTimeFromString(text) except that only the
-        date part of text is taken into account.
-
-    """
-    year,years,month,months,day,days = _parse_reldate(text)
-    return RelativeDateTime(year=year,years=years,
-                            month=month,months=months,
-                            day=day,days=days)
-
-def RelativeTimeFromString(text,
-
-                           RelativeDateTime=DateTime.RelativeDateTime):
-
-    """ RelativeTimeFromString(text)
-    
-        Same as RelativeDateTimeFromString(text) except that only the
-        time part of text is taken into account.
-
-    """
-    hour,hours,minute,minutes,second,seconds = _parse_reltime(text)
-    return RelativeDateTime(hour=hour,hours=hours,
-                            minute=minute,minutes=minutes,
-                            second=second,seconds=seconds)
-
-### Tests
-
-def _test():
-
-    import sys
-
-    t = DateTime.now()
-
-    print 'Testing DateTime Parser...'
-
-    l = [
-
-        # Literal formats
-        ('Sun Nov  6 08:49:37 1994', '1994-11-06 08:49:37.00'),
-        ('sun nov  6 08:49:37 1994', '1994-11-06 08:49:37.00'),
-        ('sUN NOV  6 08:49:37 1994', '1994-11-06 08:49:37.00'),
-        ('Sunday, 06-Nov-94 08:49:37 GMT', '1994-11-06 08:49:37.00'),
-        ('Sun, 06 Nov 1994 08:49:37 GMT', '1994-11-06 08:49:37.00'),
-        ('06-Nov-94 08:49:37', '1994-11-06 08:49:37.00'),
-        ('06-Nov-94', '1994-11-06 00:00:00.00'),
-        ('06-NOV-94', '1994-11-06 00:00:00.00'),
-        ('November 19 08:49:37', '%s-11-19 08:49:37.00' % t.year),
-        ('Nov. 9', '%s-11-09 00:00:00.00' % t.year),
-        ('Sonntag, der 6. November 1994, 08:49:37 GMT', '1994-11-06 08:49:37.00'),
-        ('6. November 2001, 08:49:37', '2001-11-06 08:49:37.00'),
-        ('sep 6', '%s-09-06 00:00:00.00' % t.year),
-        ('sep 6 2000', '2000-09-06 00:00:00.00'),
-        ('September 29', '%s-09-29 00:00:00.00' % t.year),
-        ('Sep. 29', '%s-09-29 00:00:00.00' % t.year),
-        ('6 sep', '%s-09-06 00:00:00.00' % t.year),
-        ('29 September', '%s-09-29 00:00:00.00' % t.year),
-        ('29 Sep.', '%s-09-29 00:00:00.00' % t.year),
-        ('sep 6 2001', '2001-09-06 00:00:00.00'),
-        ('Sep 6, 2001', '2001-09-06 00:00:00.00'),
-        ('September 6, 2001', '2001-09-06 00:00:00.00'),
-        ('sep 6 01', '2001-09-06 00:00:00.00'),
-        ('Sep 6, 01', '2001-09-06 00:00:00.00'),
-        ('September 6, 01', '2001-09-06 00:00:00.00'),
-        ('30 Apr 2006 20:19:00', '2006-04-30 20:19:00.00'),
-        
-        # ISO formats
-        ('1994-11-06 08:49:37', '1994-11-06 08:49:37.00'),
-        ('010203', '2001-02-03 00:00:00.00'),
-        ('2001-02-03 00:00:00.00', '2001-02-03 00:00:00.00'),
-        ('2001-02 00:00:00.00', '2001-02-01 00:00:00.00'),
-        ('2001-02-03', '2001-02-03 00:00:00.00'),
-        ('2001-02', '2001-02-01 00:00:00.00'),
-        ('20000824/2300', '2000-08-24 23:00:00.00'),
-        ('20000824/0102', '2000-08-24 01:02:00.00'),
-        ('20000824', '2000-08-24 00:00:00.00'),
-        ('20000824/020301', '2000-08-24 02:03:01.00'),
-        ('20000824 020301', '2000-08-24 02:03:01.00'),
-        ('-20000824 020301', '-2000-08-24 02:03:01.00'),
-        ('20000824T020301', '2000-08-24 02:03:01.00'),
-        ('20000824 020301', '2000-08-24 02:03:01.00'),
-        ('2000-08-24 02:03:01.00', '2000-08-24 02:03:01.00'),
-        ('T020311', '%s 02:03:11.00' % t.date),
-        ('2003-12-9', '2003-12-09 00:00:00.00'),
-        ('03-12-9', '2003-12-09 00:00:00.00'),
-        ('003-12-9', '0003-12-09 00:00:00.00'),
-        ('0003-12-9', '0003-12-09 00:00:00.00'),
-        ('2003-1-9', '2003-01-09 00:00:00.00'),
-        ('03-1-9', '2003-01-09 00:00:00.00'),
-        ('003-1-9', '0003-01-09 00:00:00.00'),
-        ('0003-1-9', '0003-01-09 00:00:00.00'),
-
-        # US formats
-        ('06/11/94 08:49:37', '1994-06-11 08:49:37.00'),
-        ('11/06/94 08:49:37', '1994-11-06 08:49:37.00'),
-        ('9/23/2001', '2001-09-23 00:00:00.00'),
-        ('9-23-2001', '2001-09-23 00:00:00.00'),
-        ('9/6', '%s-09-06 00:00:00.00' % t.year),
-        ('09/6', '%s-09-06 00:00:00.00' % t.year),
-        ('9/06', '%s-09-06 00:00:00.00' % t.year),
-        ('09/06', '%s-09-06 00:00:00.00' % t.year),
-        ('9/6/2001', '2001-09-06 00:00:00.00'),
-        ('09/6/2001', '2001-09-06 00:00:00.00'),
-        ('9/06/2001', '2001-09-06 00:00:00.00'),
-        ('09/06/2001', '2001-09-06 00:00:00.00'),
-        ('9-6-2001', '2001-09-06 00:00:00.00'),
-        ('09-6-2001', '2001-09-06 00:00:00.00'),
-        ('9-06-2001', '2001-09-06 00:00:00.00'),
-        ('09-06-2001', '2001-09-06 00:00:00.00'),
-        ('2002/05/28 13:10:56.1147 GMT+2', '2002-05-28 13:10:56.11'),
-        ('1970/01/01', '1970-01-01 00:00:00.00'),
-        ('20021025 12:00 PM', '2002-10-25 12:00:00.00'),
-        ('20021025 12:30 PM', '2002-10-25 12:30:00.00'),
-        ('20021025 12:00 AM', '2002-10-25 00:00:00.00'),
-        ('20021025 12:30 AM', '2002-10-25 00:30:00.00'),
-        ('20021025 1:00 PM', '2002-10-25 13:00:00.00'),
-        ('20021025 2:00 AM', '2002-10-25 02:00:00.00'),
-        ('Thursday, February 06, 2003 12:40 PM', '2003-02-06 12:40:00.00'),
-        ('Mon, 18 Sep 2006 23:03:00', '2006-09-18 23:03:00.00'),
-
-        # European formats
-        ('6.11.2001, 08:49:37', '2001-11-06 08:49:37.00'),
-        ('06.11.2001, 08:49:37', '2001-11-06 08:49:37.00'),
-        ('06.11. 08:49:37', '%s-11-06 08:49:37.00' % t.year),
-        #('21/12/2002', '2002-12-21 00:00:00.00'),
-        #('21/08/2002', '2002-08-21 00:00:00.00'),
-        #('21-08-2002', '2002-08-21 00:00:00.00'),
-        #('13/01/03', '2003-01-13 00:00:00.00'),
-        #('13/1/03', '2003-01-13 00:00:00.00'),
-        #('13/1/3', '2003-01-13 00:00:00.00'),
-        #('13/01/3', '2003-01-13 00:00:00.00'),
-
-        # Time only formats
-        ('01:03', '%s 01:03:00.00' % t.date),
-        ('01:03:11', '%s 01:03:11.00' % t.date),
-        ('01:03:11.50', '%s 01:03:11.50' % t.date),
-        ('01:03:11.50 AM', '%s 01:03:11.50' % t.date),
-        ('01:03:11.50 PM', '%s 13:03:11.50' % t.date),
-        ('01:03:11.50 a.m.', '%s 01:03:11.50' % t.date),
-        ('01:03:11.50 p.m.', '%s 13:03:11.50' % t.date),
-
-        # Invalid formats
-        ('6..2001, 08:49:37', '%s 08:49:37.00' % t.date),
-        ('9//2001', 'ignore'),
-        ('06--94 08:49:37', 'ignore'),
-        ('20000824020301', 'ignore'),
-        ('20-03 00:00:00.00', 'ignore'),
-        ('9/2001', 'ignore'),
-        ('9-6', 'ignore'),
-        ('09-6', 'ignore'),
-        ('9-06', 'ignore'),
-        ('09-06', 'ignore'),
-        ('20000824/23', 'ignore'),
-        ('November 1994 08:49:37', 'ignore'),
-        ('Nov. 94', 'ignore'),
-        ('Mon, 18 Sep 2006 23:03:00 +1234567890', 'ignore'),
-
-        ]
-
-    # Add Unicode versions
-    try:
-        unicode
-    except NameError:
-        pass
-    else:
-        k = []
-        for text, result in l:
-            k.append((unicode(text), result))
-        l.extend(k)
-
-    for text, reference in l:
-        try:
-            value = DateTimeFromString(text)
-        except:
-            if reference is None:
-                continue
-            else:
-                value = str(sys.exc_info()[1])
-        valid_datetime = validateDateTimeString(text)
-        valid_date = validateDateString(text)
-        if str(value) != reference and \
-           not reference == 'ignore':
-            print 'Failed to parse "%s"' % text
-            print '  expected: %s' % (reference or '<exception>')
-            print '  parsed:   %s' % value
-        elif _debug:
-            print 'Parsed "%s" successfully' % text
-        if _debug:
-            if not valid_datetime:
-                print '  "%s" failed date/time validation' % text
-            if not valid_date:
-                print '  "%s" failed date validation' % text
-
-    et = DateTime.now()
-    print 'done. (after %f seconds)' % ((et-t).seconds)
-
-    ###
-
-    print 'Testing DateTimeDelta Parser...'
-
-    t = DateTime.now()
-    l = [
-
-        # Literal formats
-        ('Sun Nov  6 08:49:37 1994', '08:49:37.00'),
-        ('1 day, 8 hours, 49 minutes, 37 seconds', '1:08:49:37.00'),
-        ('10 days, 8 hours, 49 minutes, 37 seconds', '10:08:49:37.00'),
-        ('8 hours, 49 minutes, 37 seconds', '08:49:37.00'),
-        ('49 minutes, 37 seconds', '00:49:37.00'),
-        ('37 seconds', '00:00:37.00'),
-        ('37.5 seconds', '00:00:37.50'),
-        ('8 hours later', '08:00:00.00'),
-        ('2 days', '2:00:00:00.00'),
-        ('2 days 23h', '2:23:00:00.00'),
-        ('2 days 23:57', '2:23:57:00.00'),
-        ('2 days 23:57:13', '2:23:57:13.00'),
-        ('', '00:00:00.00'),
-        
-        # ISO formats
-        ('1994-11-06 08:49:37', '08:49:37.00'),
-        ('10:08:49:37', '10:08:49:37.00'),
-        ('08:49:37', '08:49:37.00'),
-        ('08:49', '08:49:00.00'),
-        ('-10:08:49:37', '-10:08:49:37.00'),
-        ('-08:49:37', '-08:49:37.00'),
-        ('-08:49', '-08:49:00.00'),
-        ('- 10:08:49:37', '-10:08:49:37.00'),
-        ('- 08:49:37', '-08:49:37.00'),
-        ('- 08:49', '-08:49:00.00'),
-        ('10:08:49:37.5', '10:08:49:37.50'),
-        ('08:49:37.5', '08:49:37.50'),
-        ('10:8:49:37', '10:08:49:37.00'),
-        ('8:9:37', '08:09:37.00'),
-        ('8:9', '08:09:00.00'),
-        ('8', '00:00:00.00'),
-
-        # Invalid formats
-        #('', None),
-        #('8', None),
-
-        ]
-
-    for text, reference in l:
-        try:
-            value = DateTimeDeltaFromString(text)
-        except:
-            if reference is None:
-                continue
-            else:
-                value = str(sys.exc_info()[1])
-        if str(value) != reference and \
-           not reference == 'ignore':
-            print 'Failed to parse "%s"' % text
-            print '  expected: %s' % (reference or '<exception>')
-            print '  parsed:   %s' % value
-        elif _debug:
-            print 'Parsed "%s" successfully' % text
-
-    et = DateTime.now()
-    print 'done. (after %f seconds)' % ((et-t).seconds)
-
-    ###
-
-    print 'Testing Time Parser...'
-
-    t = DateTime.now()
-    l = [
-
-        # Standard formats
-        ('08:49:37 AM', '08:49:37.00'),
-        ('08:49:37 PM', '20:49:37.00'),
-        ('12:00:00 AM', '00:00:00.00'),
-        ('12:00:00 PM', '12:00:00.00'),
-        ('8:09:37', '08:09:37.00'),
-        ('8:09', '08:09:00.00'),
-        
-        # ISO formats
-        ('08:49:37', '08:49:37.00'),
-        ('08:49', '08:49:00.00'),
-        ('08:49:37.5', '08:49:37.50'),
-        ('08:49:37,5', '08:49:37.50'),
-        ('08:09', '08:09:00.00'),
-
-        # Invalid formats
-        ('', None),
-        ('8:9:37', 'XXX Should give an exception'),
-        ('08:9:37', 'XXX Should give an exception'),
-        ('8:9', None),
-        ('8', None),
-
-        ]
-
-    for text, reference in l:
-        try:
-            value = TimeFromString(text, formats=('standard', 'iso'))
-        except:
-            if reference is None:
-                continue
-            else:
-                value = str(sys.exc_info()[1])
-        if str(value) != reference and \
-           not reference == 'ignore':
-            print 'Failed to parse "%s"' % text
-            print '  expected: %s' % (reference or '<exception>')
-            print '  parsed:   %s' % value
-        elif _debug:
-            print 'Parsed "%s" successfully' % text
-
-    et = DateTime.now()
-    print 'done. (after %f seconds)' % ((et-t).seconds)
-
-if __name__ == '__main__':
-    _test()
--- a/embedded/mx/DateTime/Timezone.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-# -*- coding: latin-1 -*-
-
-""" Timezone information.
-
-    XXX This module still has prototype status and is undocumented.
-
-    XXX Double check the offsets given in the zonetable below.
-
-    XXX Add TZ environment variable parsing functions. The REs are already
-        there.
-
-    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author. All Rights Reserved.
-
-"""
-import DateTime
-import re,string
-
-### REs
-
-# time zone parsing
-isozone = ('(?P<zone>[+-]\d\d:?(?:\d\d)?|Z)')
-zone = ('(?P<zone>[A-Z]+|[+-]\d\d?:?(?:\d\d)?)')
-zoneoffset = ('(?:'
-              '(?P<zonesign>[+-])?'
-              '(?P<hours>\d\d?)'
-              ':?'
-              '(?P<minutes>\d\d)?'
-              '(?P<extra>\d+)?'
-              ')'
-              )
-
-# TZ environment variable parsing
-dstswitchtime = ('(?P<hour>\d\d?):?'
-                 '(?P<minute>\d\d)?:?'
-                 '(?P<second>\d\d)?')
-dstswitch = ('(?:'
-              '(?P<doy>\d+)|'
-              '(?:J(?P<jdoy>\d+))|'
-              '(?:M(?P<month>\d+).(?P<week>\d+).(?P<day>\d+))'
-             ')'
-             '(?:/' + dstswitchtime + ')?'
-             )
-
-# XXX Doesn't work since re doesn't like multiple occurrences of
-#     group names.
-#tz = ('(?::(?P<filename>.+))|'
-#      '(?P<std>[A-Z]+)' + zoneoffset + 
-#      '(?:'
-#       '(?P<dst>[A-Z]+)' + zoneoffset + '?'+
-#       '(?:[;,]' + dstswitch + '[;,]' + dstswitch + ')'
-#      ')?'
-#      )
-
-# Compiled RE objects
-isozoneRE = re.compile(zone)
-zoneRE = re.compile(zone)
-zoneoffsetRE = re.compile(zoneoffset)
-#tzRE= re.compile(tz)
-
-### Time zone offset table
-#
-# The offset given here represent the difference between UTC and the
-# given time zone.
-#
-# Additions and corrections are always welcome :-)
-#
-# Note that some zone names are ambiguous, e.g. IST can refer to Irish
-# Summer Time, Indian Standard Time, Israel Standard Time. We've
-# usualy chosen meaning with the most wide-spread use.
-#
-zonetable = {
-              # Timezone abbreviations
-              # Std     Summer
-
-              # Standards
-              'UT':0,
-              'UTC':0,
-              'GMT':0,
-
-              # A few common timezone abbreviations
-              'CET':1,  'CEST':2, 'CETDST':2, # Central European
-              'MET':1,  'MEST':2, 'METDST':2, # Mean European
-              'MEZ':1,  'MESZ':2,             # Mitteleuropäische Zeit
-              'EET':2,  'EEST':3, 'EETDST':3, # Eastern Europe
-              'WET':0,  'WEST':1, 'WETDST':1, # Western Europe
-              'MSK':3,  'MSD':4,  # Moscow
-              'IST':5.5,          # India
-              'JST':9,            # Japan
-              'KST':9,            # Korea
-              'HKT':8,            # Hong Kong
-
-              # US time zones
-              'AST':-4, 'ADT':-3, # Atlantic
-              'EST':-5, 'EDT':-4, # Eastern
-              'CST':-6, 'CDT':-5, # Central
-              'MST':-7, 'MDT':-6, # Midwestern
-              'PST':-8, 'PDT':-7, # Pacific
-
-              # Australian time zones
-              'CAST':9.5, 'CADT':10.5, # Central
-              'EAST':10,  'EADT':11,   # Eastern
-              'WAST':8,   'WADT':9,    # Western
-              'SAST':9.5, 'SADT':10.5, # Southern
-
-              # US military time zones
-              'Z': 0,
-              'A': 1,
-              'B': 2,
-              'C': 3,
-              'D': 4,
-              'E': 5,
-              'F': 6,
-              'G': 7,
-              'H': 8,
-              'I': 9,
-              'K': 10,
-              'L': 11,
-              'M': 12,
-              'N':-1,
-              'O':-2,
-              'P':-3,
-              'Q':-4,
-              'R':-5,
-              'S':-6,
-              'T':-7,
-              'U':-8,
-              'V':-9,
-              'W':-10,
-              'X':-11,
-              'Y':-12
-              }    
-
-def utc_offset(zone,
-
-               atoi=string.atoi,zoneoffset=zoneoffsetRE,
-               zonetable=zonetable,zerooffset=DateTime.DateTimeDelta(0),
-               oneMinute=DateTime.oneMinute,upper=string.upper):
-
-    """ utc_offset(zonestring)
-
-        Return the UTC time zone offset as DateTimeDelta instance.
-
-        zone must be string and can either be given as +-HH:MM,
-        +-HHMM, +-HH numeric offset or as time zone
-        abbreviation. Daylight saving time must be encoded into the
-        zone offset.
-
-        Timezone abbreviations are treated case-insensitive.
-
-    """
-    if not zone:
-        return zerooffset
-    uzone = upper(zone)
-    if zonetable.has_key(uzone):
-        return zonetable[uzone]*DateTime.oneHour
-    offset = zoneoffset.match(zone)
-    if not offset:
-        raise ValueError,'wrong format or unknown time zone: "%s"' % zone
-    zonesign,hours,minutes,extra = offset.groups()
-    if extra:
-        raise ValueError,'illegal time zone offset: "%s"' % zone
-    offset = int(hours or 0) * 60 + int(minutes or 0)
-    if zonesign == '-':
-        offset = -offset
-    return offset*oneMinute
-
--- a/embedded/mx/DateTime/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-""" mxDateTime - Date and time handling routines and types
-
-    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
-    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
-    See the documentation for further information on copyrights,
-    or contact the author. All Rights Reserved.
-"""
-from DateTime import *
-from DateTime import __version__
-
-## mock strptime implementation
-from datetime import datetime
-
-def strptime(datestr, formatstr, datetime=datetime):
-    """mocked strptime implementation"""
-    date = datetime.strptime(datestr, formatstr)
-    return DateTime(date.year, date.month, date.day,
-                    date.hour, date.minute, date.second)
-
-# don't expose datetime directly
-del datetime
--- a/embedded/mx/DateTime/mxDateTime_python.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,614 +0,0 @@
-"""
-    Python implementation courtesy of Drew Csillag (StarMedia Network, Inc.)
-
-    This version has been somewhat modified by MAL. It is still fairly
-    rough though and not necessarily high performance... 
-
-    XXX Still needs testing and checkup !!!
-
-    WARNING: Using this file is only recommended if you really must
-    use it for some reason. It is not being actively maintained !
-
-"""
-
-__version__ = '1.2.0 [Python]'
-
-import time,types,exceptions,math
-
-### Errors
-
-class Error(exceptions.StandardError):
-    pass
-
-class RangeError(Error):
-    pass
-
-### Constants (internal use only)
-
-month_offset=(
-    (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365),
-    (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366),
-    )
-
-days_in_month=(
-    (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
-    (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
-    )
-
-### Helpers
-
-def _IS_LEAPYEAR(d):
-    return ((d.year % 4 == 0)
-            and (
-                (d.year % 100 != 0)
-                or (d.year % 400 == 0)
-                )
-            )
-
-def _YEAROFFSET(d):
-    return (
-        (d.year - 1) * 365
-        + (d.year - 1) / 4
-        - (d.year - 1) / 100
-        + (d.year - 1) / 400
-        )
-
-class _EmptyClass:
-    pass
-
-def createEmptyObject(Class,
-                      _EmptyClass=_EmptyClass):
-
-    o = _EmptyClass()
-    o.__class__ = Class
-    return o
-
-### DateTime class
-
-class DateTime:
-
-    def __init__(self, year, month=1, day=1, hour=0, minute=0, second=0.0):
-
-        second=1.0 * second
-        if month <= 0:
-            raise RangeError, "year out of range (>0)"
-
-        #calculate absolute date
-        leap = (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0))
-
-        #Negative values indicate days relative to the years end
-        if month < 0:
-            month = month + 13 
-
-        if not (month >= 1 and month <= 12):
-            raise RangeError, "month out of range (1-12)"
-
-        #Negative values indicate days relative to the months end
-        if (day < 0):
-            day = day + days_in_month[leap][month - 1] + 1;
-
-        if not (day >= 1 and day <= days_in_month[leap][month - 1]):
-            raise RangeError, "day out of range"
-
-        year = year - 1
-        yearoffset = year * 365 + year / 4 - year / 100 + year / 400
-        year = year + 1
-        absdate = day + month_offset[leap][month - 1] + yearoffset;
-
-        self.absdate = absdate
-        self.year = year
-        self.month = month
-        self.day = day
-        self.day_of_week = (absdate - 1) % 7
-        self.day_of_year = absdate - yearoffset
-        self.days_in_month = days_in_month[leap][month - 1]
-        comdate = absdate - 693594
-
-        if not (hour >=0 and hour <= 23):
-            raise RangeError, "hour out of range (0-23)"
-        if not (minute >= 0 and minute <= 59):
-            raise RangeError, "minute out of range (0-59)"
-        if not (second >= 0.0 and
-                (second < 60.0 or 
-                 (hour == 23 and minute == 59 and second < 61.0))):
-            raise RangeError, "second out of range (0.0 - <60.0; <61.0 for 23:59)"
-
-        self.abstime = (hour * 3600 + minute * 60) + second
-        self.hour = hour
-        self.minute = minute
-        self.second = second
-        self.dst = -1
-        self.tz = "???"
-        self.is_leapyear = leap
-        self.yearoffset = yearoffset
-        self.iso_week = (self.year, self.day, self.day_of_week)
-
-        if comdate < 0.0:
-            comdate = comdate - self.abstime / 86400.0
-        else:
-            comdate = comdate + self.abstime / 86400.0
-
-        self.comdate = comdate
-
-    def COMDate(self):
-        return self.comdate
-    
-    def __str__(self):
-        return "%04d-%02d-%02d %02d:%02d:%05.2f" % (
-            self.year, self.month, self.day, self.hour, self.minute,
-            self.second)
-    
-    def __getattr__(self, attr):
-        if attr == 'mjd':
-            return (self - mjd0).days
-        elif attr == 'jdn':
-            return (self - jdn0).days
-        elif attr == 'tjd':
-            return (self - jdn0).days % 10000
-        elif attr == 'tjd_myriad':
-            return int((self - jdn0).days) / 10000 + 240
-        elif attr == 'absdays':
-            return self.absdate - 1 + self.abstime / 86400.0
-        else:
-            try:
-                return self.__dict__[attr]
-            except:
-                raise AttributeError, attr
-
-    def __mul__(self, other):
-        raise TypeError, "bad operand type(s) for *"
-
-    def __div__(self, other):
-        raise TypeError, "bad operand type(s) for /"
-    
-    def strftime(self, format_string="%c"):
-        "localtime([seconds]) -> (tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst)"
-        # The map prevents a deprecation warning on Python 2.5.1 (Mac)
-        # DeprecationWarning: integer argument expected, got float
-        items = [int(item) for item in self.tuple()]
-        return time.strftime(format_string, items)
-
-    # Alias
-    Format = strftime
-    
-    def tuple(self):
-        return (self.year, self.month, self.day, self.hour, self.minute,
-                self.second, self.day_of_week, self.day_of_year, -1)
-        #return time.localtime(self.ticks())
-
-    def absvalues(self):
-        return self.absdate, self.abstime
-    
-    def __float__(self):
-        return self.ticks()
-
-    def __int__(self):
-        return int(self.ticks)
-    
-    def ticks(self, offset=0.0, dst=-1):
-        tticks=time.mktime(self.year, self.month, self.day, self.hour,
-                           self.minute, self.second, self.day_of_week, 0, dst)
-        if tticks == -1:
-            raise OverflowError, "cannot convert value to a time value"
-        ticks = (1.0*tticks) + (self.abstime - int(self.abstime)) - offset
-        return ticks
-
-    def gmticks(self, offset=0.0):
-        from mx.DateTime import tz_offset
-        return (self-tz_offset(self)).ticks()
-
-    def gmtoffset(self):
-        gmtime = DateTime(*time.gmtime()[:6])
-        return - (now() - gmtime)
-    
-    def __repr__(self):
-        return "<DateTime object for '%d-%02d-%02d %02d:%02d:%05.2f' at %x>"% (
-            self.year, self.month, self.day, self.hour, self.minute,
-            self.second, id(self))
-
-    def __cmp__(self, other,
-                cmp=cmp):
-
-        if isinstance(other,DateTime):
-            cmpdate = cmp(self.absdate,other.absdate)
-            if cmpdate == 0:
-                return cmp(self.abstime,other.abstime)
-            else:
-                return cmpdate
-        elif type(other) == types.NoneType:
-            return -1
-        elif type(other) == types.StringType:
-            return -1
-        elif type(other) in (types.FloatType, types.LongType, types.IntType):
-            return 1
-        return -1
-
-    def __hash__(self):
-        return hash(self.tuple())
-    
-    def __add__(self, other):
-        abstime=self.abstime
-        absdate=self.absdate
-
-        didadd=0
-        
-        if type(other) == types.InstanceType:
-            if other.__class__ == DateTimeDelta:
-                abstime = abstime + other.seconds
-                didadd=1
-            elif other.__class__ == DateTime:
-                raise TypeError, "DateTime + DateTime is not supported"
-            else:
-                return other.__class__.__radd__(other, self)
-            
-        elif type(other) == types.IntType or type(other) == types.FloatType:
-            abstime = abstime + other * 86400.0
-            didadd=1
-
-        if not didadd:
-            raise TypeError, "cannot add these two types"
-
-        if abstime >= 86400.0:
-            days = abstime / 86400.0
-            absdate = absdate + days
-            abstime = abstime - (86400.0 * int(days))
-            #print "absdate, abstime = ", absdate, abstime
-        elif abstime < 0.0:
-            days = int(((-abstime - 1) / 86400.0)) + 1
-            #days = int(-abstime / 86400.0)
-            absdate = absdate - days
-            abstime = abstime + 86400.0 * int(days)
-
-        if absdate < 1:
-            raise RangeError, "underflow while adding"
-
-        return DateTimeFromAbsDateTime(absdate, abstime)
-
-    def __radd__(self, other):
-        return DateTime.__add__(other, self)
-    
-    def __sub__(self, other):
-        abstime=self.abstime
-        absdate=self.absdate
-
-        didsub=0
-        if type(other) == types.InstanceType:
-            if other.__class__ == DateTimeDelta:
-                abstime = abstime - other.seconds
-                didsub = 1
-            elif other.__class__ == DateTime:
-                absdate = absdate - other.absdate
-                abstime = abstime - other.abstime
-                return DateTimeDelta(absdate,0.0,0.0,abstime)
-            else:
-                return other.__rsub__(self)
-
-        elif type(other) == types.IntType or type(other) == types.FloatType:
-            abstime = abstime - other * 86400.0;
-            didsub=1
-
-        if not didsub:
-            raise TypeError, "cannot subtract these two types"
-
-        if abstime >= 86400.0:
-            days = abstime / 86400.0
-            absdate = absdate + days
-            abstime = abstime - (86400.0 * days)
-            #print "absdate, abstime = ", absdate, abstime
-        elif abstime < 0.0:
-            #print "abstime < 0"
-            days = int( ((-abstime - 1) / 86400.0) + 1)
-            #days = -abstime / 86400.0
-            absdate = absdate - int(days)
-            abstime = (1.0*abstime) + (86400.0 * days)
-            #print "absdate, abstime", absdate, abstime
-        if absdate < 1:
-            raise RangeError, "underflow while adding"
-
-        return DateTimeFromAbsDateTime(absdate, abstime)
-
-# Constants
-mjd0 = DateTime(1858, 11, 17)
-jdn0 = DateTime(-4713, 1, 1, 12, 0, 0.0)
-
-# Other DateTime constructors
-
-def DateTimeFromCOMDate(comdate):
-
-    absdate = int(comdate)
-    abstime = (comdate - float(absdate)) * 86400.0
-    if abstime < 0.0:
-        abstime = -abstime
-    absdate = absdate + 693594;
-    dt = DateTimeFromAbsDateTime(absdate, abstime)
-    dt.comdate = comdate
-    return dt
-    
-def DateTimeFromAbsDateTime(absdate, abstime):
-
-    # Create the object without calling its default constructor
-    dt = createEmptyObject(DateTime)
-
-    # Init. the object
-    abstime=1.0 * abstime
-    if abstime < 0 and abstime > -0.001: abstime = 0.0
-    if not (absdate > 0):
-        raise RangeError, "absdate out of range (>0)"
-    if not (abstime >= 0.0 and abstime <= 86400.0):
-        raise RangeError, "abstime out of range (0.0 - 86400.0) <%s>" % abstime
-
-    dt.absdate=absdate
-    dt.abstime=abstime
-
-    #calculate com date
-    comdate = 1.0 * (dt.absdate - 693594)
-    if comdate < 0.0:
-        comdate = comdate - dt.abstime / 86400.0
-    else:
-        comdate = comdate + dt.abstime / 86400.0
-    dt.comdate = comdate
-
-    #calculate the date
-    #print "absdate=", absdate
-    year = int((1.0 * absdate) / 365.2425)
-
-    #newApproximation:
-    while 1:
-        #print "year=", year
-        yearoffset = year * 365 + year / 4 - year / 100 + year / 400
-        #print "yearoffset=", yearoffset
-        #print "absdate=", absdate
-        if yearoffset >= absdate:
-            year = year - 1
-            #print "year = ", year
-            continue #goto newApproximation
-
-        year = year + 1
-        leap = (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0))
-        dayoffset = absdate - yearoffset
-        #print "dayoffset=", dayoffset
-        if dayoffset > 365 and leap == 0:
-            #print "dayoffset=", dayoffset
-            continue #goto newApproximation
-
-        monthoffset = month_offset[leap]
-        for month in range(1, 13):
-            if monthoffset[month] >= dayoffset:
-                break
-        dt.year = year
-        dt.month = month
-        dt.day = dayoffset - month_offset[leap][month-1]
-        dt.day_of_week = (dt.absdate - 1) % 7
-        dt.day_of_year = dayoffset
-        break
-    
-    #calculate the time
-    inttime = int(abstime)
-    hour = inttime / 3600
-    minute = (inttime % 3600) / 60
-    second = abstime - 1.0 * (hour*3600 + minute*60)
-    dt.hour = hour;
-    dt.minute = minute;
-    dt.second = second;
-    dt.days_in_month = days_in_month[leap][month - 1]
-    dt.dst = -1
-    dt.tz = "???"
-    dt.is_leapyear = leap
-    dt.yearoffset = yearoffset
-    return dt
-
-def now(
-        time=time.time,float=float,localtime=time.localtime,
-        round=round,int=int,DateTime=DateTime,floor=math.floor):
-    ticks = time()
-    Y,M,D,h,m,s = localtime(ticks)[:6]
-    s = s + (ticks - floor(ticks))
-    return DateTime(Y,M,D,h,m,s)
-
-def utc(
-        time=time.time,float=float,gmtime=time.gmtime,
-        round=round,int=int,DateTime=DateTime,floor=math.floor):
-
-    ticks = time()
-    Y,M,D,h,m,s = gmtime(ticks)[:6]
-    s = s + (ticks - floor(ticks))
-    return DateTime(Y,M,D,h,m,s)
-
-# Aliases
-Date = Timestamp = DateTime
-
-# XXX Calendars are not supported:
-def notSupported(*args,**kws):
-    raise Error,'calendars are not supported by the Python version of mxDateTime'
-JulianDateTime = notSupported
-
-### DateTimeDelta class
-               
-class DateTimeDelta:
-
-    def __init__(self, days=0, hours=0, minutes=0, seconds=0):
-
-        seconds = seconds + (days * 86400.0 + hours * 3600.0 + minutes * 60.0)
-        self.seconds = seconds
-        if seconds < 0.0:
-            seconds = -seconds
-        day = long(seconds / 86400.0)
-        seconds = seconds - (86400.0 * day)
-        wholeseconds = int(seconds)
-        hour = wholeseconds / 3600
-        minute = (wholeseconds % 3600) / 60
-        second = seconds - (hour * 3600.0 + minute * 60.0)
-        self.day = day
-        self.hour = hour
-        self.minute = minute
-        self.second = second
-        seconds=self.seconds
-        self.minutes = seconds / 60.0
-        self.hours = seconds / 3600.0
-        self.days = seconds / 86400.0
-
-    def __str__(self):
-        if self.day != 0:
-            if self.seconds >= 0.0:
-                r="%s:%02d:%02d:%05.2f" % (
-                    self.day, self.hour, self.minute, self.second)
-            else:
-                r="-%s:%02d:%02d:%05.2f" % (
-                    self.day, self.hour, self.minute, self.second)
-        else:
-            if self.seconds >= 0.0:
-                r="%02d:%02d:%05.2f" % (self.hour, self.minute, self.second)
-            else:
-                r="-%02d:%02d:%05.2f" % (self.hour, self.minute, self.second)
-        return r
-            
-    def absvalues(self):
-        days=self.seconds / 86400
-        seconds=self.seconds - (days * 86400.0)
-        return days, seconds
-
-    def tuple(self):
-        return (self.day, self.hour, self.minute, self.second)
-
-    def strftime(self, format_string):
-        raise NotImplementedError
-    
-    def __int__(self):
-        return int(self.seconds)
-
-    def __float__(self):
-        return self.seconds
-    
-    def __cmp__(self, other, accuracy=0.0):
-        if (type(other) == types.InstanceType
-            and other.__class__ == DateTimeDelta):
-
-            diff=self.seconds - other.seconds
-            if abs(diff) > accuracy:
-                if diff > 0: return 1
-                return -1
-            
-        elif type(other) == types.FloatType:
-            diff=self.seconds - other
-            if abs(diff) > accuracy:
-                if diff > 0: return 1
-                return -1
-            
-        elif type(other) == types.IntType:
-            diff=self.seconds - other
-            if abs(diff) > accuracy:
-                if diff > 0: return 1
-                return -1
-            
-        return 0
-    
-    def __getattr__(self, attr):
-        seconds=self.__dict__['seconds']
-        if attr in ('hour', 'minute', 'second', 'day'):
-            if seconds >= 0.0:
-                return self.__dict__[attr]
-            else:
-                return -self.__dict__[attr]
-        else:
-            try:
-                return self.__dict__[attr]
-            except:
-                raise AttributeError, attr
-
-    def __div__(self, other):
-        if type(other) in (types.IntType, types.FloatType):
-            return DateTimeDelta(0.0,0.0,0.0,self.seconds / other)
-        elif (type(other) == types.InstanceType
-              and isinstance(other,DateTimeDelta)):
-            return DateTimeDelta(0.0,0.0,0.0,self.seconds / other.seconds)
-        raise TypeError, "bad operand types for /"
-    
-    def __mul__(self, other):
-        if type(other) == types.IntType or type(other) == types.FloatType:
-            return DateTimeDelta(0.0,0.0,0.0,self.seconds * other)
-        else:
-            #print "type", type(other)
-            raise TypeError, "cannot multiply these two types"
-
-    def __rmul__(self, other):
-        return self.__mul__(other)
-    
-    def __neg__(self):
-        return DateTimeDelta(0.0,0.0,0.0,-self.seconds)
-        
-    def __repr__(self):
-        if self.day != 0:
-            if self.seconds >= 0.0:
-                strval="%s:%02d:%02d:%05.2f" % (self.day, self.hour,
-                                                 self.minute, self.second)
-            else:
-                strval="-%s:%02d:%02d:%05.2f" % (self.day, self.hour,
-                                                  self.minute, self.second)
-        else:
-            if self.seconds >= 0.0:
-                strval="%02d:%02d:%05.2f" % (self.hour, self.minute,
-                                            self.second)
-            else:
-                strval="-%02d:%02d:%05.2f" % (self.hour, self.minute,
-                                             self.second)
-        return "<DateTimeDelta object for '%s' at %x>" % (strval, id(self))
-    
-    def __abs__(self):
-        if self.seconds < 0:
-            return -self
-        return self
-
-    def __nonzero__(self):
-        return self.seconds != 0.0
-    
-    def __add__(self, other):
-        if type(other) == types.InstanceType:
-            if isinstance(other,DateTime):
-                return other + self
-            elif isinstance(other,DateTimeDelta):
-                return DateTimeDelta(0.0,0.0,0.0,self.seconds + other.seconds)
-
-    # What about __radd__ ?
-        
-# Other DateTimeDelta constructors
-
-def TimeDelta(hour=0.0, minute=0.0, second=0.0):
-    return DateTimeDelta(0.0, hours, minutes, seconds)
-
-Time=TimeDelta
-
-def DateTimeDeltaFromSeconds(seconds):
-    return DateTimeDelta(0.0,0.0,0.0,seconds)
-
-def DateTimeDeltaFromDays(days):
-    return DateTimeDelta(days)
-
-### Types
-
-DateTimeType = DateTime
-DateTimeDeltaType = DateTimeDelta
-
-### Functions
-
-def cmp(a,b,acc):
-
-    if isinstance(a,DateTime) and isinstance(b,DateTime):
-        diff = a.absdays - b.absdays
-        if (diff >= 0 and diff <= acc) or (diff < 0 and -diff <= acc):
-            return 0
-        elif diff < 0:
-            return 1
-        else:
-            return -1
-
-    elif isinstance(a,DateTimeDelta) and isinstance(b,DateTimeDelta):
-        diff = a.days - b.days
-        if (diff >= 0 and diff <= acc) or (diff < 0 and -diff <= acc):
-            return 0
-        elif diff < 0:
-            return 1
-        else:
-            return -1
-
-    else:
-        raise TypeError,"objects must be DateTime[Delta] instances"
--- a/entities/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/entities/__init__.py	Thu May 14 12:50:34 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,38 +23,9 @@
     """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',
+    id = 'Any'
+    __implements__ = (IBreadCrumbs, IFeed)
 
-        # 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'),
-        }
-
-    __implements__ = (IBreadCrumbs, IFeed)
-    
     @classmethod
     def selected(cls, etype):
         """the special Any entity is used as the default factory, so
@@ -67,7 +38,7 @@
         usercls.id = etype
         usercls.__initialize__()
         return usercls
-    
+
     fetch_attrs = ('modification_date',)
     @classmethod
     def fetch_order(cls, attr, var):
@@ -75,7 +46,7 @@
         this type are fetched
         """
         return cls.fetch_unrelated_order(attr, var)
-    
+
     @classmethod
     def fetch_unrelated_order(cls, attr, var):
         """class method used to control sort order when multiple entities of
@@ -87,30 +58,15 @@
         return None
 
     @classmethod
-    def __initialize__(cls): 
+    def __initialize__(cls):
         super(ANYENTITY, cls).__initialize__() # XXX
-        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')
+        # XXX move this away once the old widgets have been dropped!
+        eschema = cls.e_schema
+        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)
 
-    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
-    
     # meta data api ###########################################################
 
     def dc_title(self):
@@ -128,7 +84,7 @@
     def dc_long_title(self):
         """return a more detailled title for this entity"""
         return self.dc_title()
-    
+
     def dc_description(self, format='text/plain'):
         """return a suitable description for this entity"""
         if self.e_schema.has_subject_relation('description'):
@@ -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)"""
@@ -166,10 +121,10 @@
                                  'internationalizable'):
                 return self.req._(self.req.user.property_value('ui.language'))
         return self.req._(self.vreg.property_value('ui.language'))
-        
+
     @property
     def creator(self):
-        """return the EUser entity which has created this entity, or None if
+        """return the CWUser entity which has created this entity, or None if
         unknown or if the curent user doesn't has access to this euser
         """
         try:
@@ -202,9 +157,79 @@
 
     def rss_feed_url(self):
         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'):
         """return a widget to view or edit a relation
@@ -223,127 +248,30 @@
             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
+    @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)
 
-    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 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.autoform 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.autoform 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:
             try:
@@ -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.attr_metadata(attr, 'format') == 'text/html'
+            return self.req.property_value('ui.default-text-format') == 'text/html'
+        return False
 
 # XXX:  store a reference to the AnyEntity class since it is hijacked in goa
 #       configuration and we need the actual reference to avoid infinite loops
--- a/entities/authobjs.py	Thu May 14 12:50:14 2009 +0200
+++ b/entities/authobjs.py	Thu May 14 12:50:34 2009 +0200
@@ -5,37 +5,26 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+
 from logilab.common.decorators import cached
 
 from cubicweb import Unauthorized
 from cubicweb.entities import AnyEntity, fetch_config
 
-class EGroup(AnyEntity):
-    id = 'EGroup'
+class CWGroup(AnyEntity):
+    id = 'CWGroup'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = dict(in_group='create')
+    fetch_unrelated_order = fetch_order
 
     def db_key_name(self):
         """XXX goa specific"""
         return self.get('name')
 
-    
-class EUser(AnyEntity):
-    id = 'EUser'
+class CWUser(AnyEntity):
+    id = 'CWUser'
     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'),
-                  }
-    
+    fetch_unrelated_order = fetch_order
+
     # used by repository to check if  the user can log in or not
     AUTHENTICABLE_STATES = ('activated',)
 
@@ -43,12 +32,12 @@
     def __init__(self, *args, **kwargs):
         groups = kwargs.pop('groups', None)
         properties = kwargs.pop('properties', None)
-        super(EUser, self).__init__(*args, **kwargs)
+        super(CWUser, self).__init__(*args, **kwargs)
         if groups is not None:
             self._groups = groups
         if properties is not None:
             self._properties = properties
-            
+
     @property
     def groups(self):
         try:
@@ -56,7 +45,7 @@
         except AttributeError:
             self._groups = set(g.name for g in self.in_group)
             return self._groups
-        
+
     @property
     def properties(self):
         try:
@@ -75,7 +64,7 @@
         except ValueError:
             self.warning('incorrect value for eproperty %s of user %s', key, self.login)
         return self.vreg.property_value(key)
-    
+
     def matching_groups(self, groups):
         """return the number of the given group(s) in which the user is
 
@@ -97,13 +86,13 @@
         """ checks if user is an anonymous user"""
         #FIXME on the web-side anonymous user is detected according
         # to config['anonymous-user'], we don't have this info on
-        # the server side. 
+        # the server side.
         return self.groups == frozenset(('guests', ))
 
     def owns(self, eid):
         if hasattr(self.req, 'unsafe_execute'):
             # use unsafe_execute on the repository side, in case
-            # session's user doesn't have access to EUser
+            # session's user doesn't have access to CWUser
             execute = self.req.unsafe_execute
         else:
             execute = self.req.execute
@@ -115,7 +104,7 @@
     owns = cached(owns, keyarg=1)
 
     def has_permission(self, pname, contexteid=None):
-        rql = 'Any P WHERE P is EPermission, U eid %(u)s, U in_group G, '\
+        rql = 'Any P WHERE P is CWPermission, U eid %(u)s, U in_group G, '\
               'P name %(pname)s, P require_group G'
         kwargs = {'pname': pname, 'u': self.eid}
         cachekey = None
@@ -127,12 +116,12 @@
             return self.req.execute(rql, kwargs, cachekey)
         except Unauthorized:
             return False
-    
+
     # presentation utilities ##################################################
-    
+
     def name(self):
         """construct a name using firstname / surname or login if not defined"""
-        
+
         if self.firstname and self.surname:
             return self.req._('%(firstname)s %(surname)s') % {
                 'firstname': self.firstname, 'surname' : self.surname}
@@ -150,5 +139,5 @@
         return self.get('login')
 
 from logilab.common.deprecation import class_renamed
-Euser = class_renamed('Euser', EUser)
-Euser.id = 'Euser'
+EUser = class_renamed('EUser', CWUser)
+EGroup = class_renamed('EGroup', CWGroup)
--- a/entities/lib.py	Thu May 14 12:50:14 2009 +0200
+++ b/entities/lib.py	Thu May 14 12:50:34 2009 +0200
@@ -7,11 +7,12 @@
 __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 import UnknownProperty
+from cubicweb.entity import _marker
 from cubicweb.entities import AnyEntity, fetch_config
 
 def mangle_email(address):
@@ -25,19 +26,15 @@
     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())
         return self.display_address()
-    
+
     @property
     def email_of(self):
         return self.reverse_use_email and self.reverse_use_email[0]
-    
+
     @cached
     def canonical_form(self):
         if self.canonical:
@@ -91,39 +88,32 @@
 Emailaddress.id = 'Emailaddress'
 
 
-class EProperty(AnyEntity):
-    id = 'EProperty'
+class CWProperty(AnyEntity):
+    id = 'CWProperty'
 
     fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
-
-    widgets = {
-        'pkey' : "PropertyKeyWidget",
-        'value' : "PropertyValueWidget",
-        }
-    
     rest_attr = 'pkey'
 
     def typed_value(self):
         return self.vreg.typed_value(self.pkey, self.value)
-        
-    def dc_description(self):
-        return self.req._(self.vreg.property_info(self.pkey)['help'])
+
+    def dc_description(self, format='text/plain'):
+        try:
+            return self.req._(self.vreg.property_info(self.pkey)['help'])
+        except UnknownProperty:
+            return u''
 
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
         """
         return 'view', {}
-        
+
 
 class Bookmark(AnyEntity):
     """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)
@@ -140,27 +130,14 @@
         return self.absolute_url() + '/follow'
 
 
-class Card(AnyEntity):
-    """customized class for Card entities"""
-    id = 'Card'
-    rest_attr = 'wikiid'
-    
-    fetch_attrs, fetch_order = fetch_config(['title'])
-
-    def dc_title(self):
-        return self.title
-
-    def dc_description(self, format='text/plain'):
-        return self.synopsis or u''
-
-class ECache(AnyEntity):
+class CWCache(AnyEntity):
     """Cache"""
-    id = 'ECache'
-    
+    id = 'CWCache'
     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	Thu May 14 12:50:14 2009 +0200
+++ b/entities/schemaobjs.py	Thu May 14 12:50:34 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"
@@ -14,20 +14,13 @@
 from cubicweb.entities import AnyEntity, fetch_config
 
 
-class EEType(AnyEntity):
-    id = 'EEType'
+class CWEType(AnyEntity):
+    id = 'CWEType'
     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)
-    
+
     def dc_long_title(self):
         stereotypes = []
         _ = self.req._
@@ -44,18 +37,13 @@
         return self.get('name')
 
 
-class ERType(AnyEntity):
-    id = 'ERType'
+class CWRType(AnyEntity):
+    id = 'CWRType'
     fetch_attrs, fetch_order = fetch_config(['name'])
-    __rtags__ = {
-        ('final',         '*', 'subject'): 'generated',
-        
-        ('relation_type', '*', 'object') : 'create',
-        }
-    
+
     def dc_title(self):
         return self.req._(self.name)
-    
+
     def dc_long_title(self):
         stereotypes = []
         _ = self.req._
@@ -73,7 +61,7 @@
 
     def inlined_changed(self, inlined):
         """check inlining is necessary and possible:
-        
+
         * return False if nothing has changed
         * raise ValidationError if inlining is'nt possible
         * eventually return True
@@ -97,21 +85,16 @@
         return self.get('name')
 
 
-class ENFRDef(AnyEntity):
-    id = 'ENFRDef'
+class CWRelation(AnyEntity):
+    id = 'CWRelation'
     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' % (
             self.from_entity[0].name,
-            self.relation_type[0].name, 
+            self.relation_type[0].name,
             self.to_entity[0].name)
-    
+
     def dc_long_title(self):
         card = self.cardinality
         scard, ocard = u'', u''
@@ -130,12 +113,12 @@
         """
         if self.relation_type:
             return self.relation_type[0].rest_path(), {}
-        return super(ENFRDef, self).after_deletion_path()
+        return super(CWRelation, self).after_deletion_path()
 
 
-class EFRDef(ENFRDef):
-    id = 'EFRDef'
-    
+class CWAttribute(CWRelation):
+    id = 'CWAttribute'
+
     def dc_long_title(self):
         card = self.cardinality
         scard = u''
@@ -143,38 +126,34 @@
             scard = '+'
         return u'%s %s%s %s' % (
             self.from_entity[0].name,
-            scard, self.relation_type[0].name, 
+            scard, self.relation_type[0].name,
             self.to_entity[0].name)
 
 
-class EConstraint(AnyEntity):
-    id = 'EConstraint'
+class CWConstraint(AnyEntity):
+    id = 'CWConstraint'
     fetch_attrs, fetch_order = fetch_config(['value'])
 
     def dc_title(self):
         return '%s(%s)' % (self.cstrtype[0].name, self.value or u'')
-        
+
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
         """
         if self.reverse_constrained_by:
             return self.reverse_constrained_by[0].rest_path(), {}
-        return super(EConstraint, self).after_deletion_path()
+        return super(CWConstraint, self).after_deletion_path()
 
     @property
     def type(self):
         return self.cstrtype[0].name
 
-        
+
 class RQLExpression(AnyEntity):
     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'')
 
@@ -185,17 +164,17 @@
             values = getattr(self, 'reverse_%s' % rel)
             if values:
                 return values[0]
-            
+
     @cached
     def _rqlexpr(self):
         if self.exprtype == 'ERQLExpression':
             return ERQLExpression(self.expression, self.mainvars, self.eid)
         #if self.exprtype == 'RRQLExpression':
         return RRQLExpression(self.expression, self.mainvars, self.eid)
-    
+
     def check_expression(self, *args, **kwargs):
         return self._rqlexpr().check(*args, **kwargs)
-    
+
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -205,20 +184,15 @@
         return super(RQLExpression, self).after_deletion_path()
 
 
-class EPermission(AnyEntity):
-    id = 'EPermission'
+class CWPermission(AnyEntity):
+    id = 'CWPermission'
     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)
         return self.req._(self.name)
-    
+
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -226,4 +200,4 @@
         permissionof = getattr(self, 'reverse_require_permission', ())
         if len(permissionof) == 1:
             return permissionof[0].rest_path(), {}
-        return super(EPermission, self).after_deletion_path()
+        return super(CWPermission, self).after_deletion_path()
--- a/entities/test/data/bootstrap_packages	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/entities/test/unittest_base.py	Thu May 14 12:50:14 2009 +0200
+++ b/entities/test/unittest_base.py	Thu May 14 12:50:34 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
@@ -12,7 +10,7 @@
 from cubicweb import ValidationError
 from cubicweb.interfaces import IMileStone, IWorkflowable
 from cubicweb.entities import AnyEntity
-from cubicweb.entities.authobjs import EUser
+from cubicweb.entities.authobjs import CWUser
 from cubicweb.web.widgets import AutoCompletionWidget
 
 
@@ -20,59 +18,45 @@
 
     def setup_database(self):
         self.member = self.create_user('member')
-    
-                     
-    
+
+
+
 class MetadataTC(BaseEntityTC):
 
     def test_creator(self):
         self.login(u'member')
-        card = self.add_entity('Card', title=u"hello")
+        entity = self.add_entity('Bookmark', title=u"hello", path=u'project/cubicweb')
         self.commit()
-        self.assertEquals(card.creator.eid, self.member.eid)
-        self.assertEquals(card.dc_creator(), u'member')
+        self.assertEquals(entity.creator.eid, self.member.eid)
+        self.assertEquals(entity.dc_creator(), u'member')
 
     def test_type(self):
-        self.assertEquals(self.member.dc_type(), 'euser')
+        self.assertEquals(self.member.dc_type(), 'cwuser')
 
-    def test_custom_widget(self):
-        class EUser2(EUser):
-            widgets = {
-                'login' : 'AutoCompletionWidget',
-                }
-        clear_cache(self.vreg, 'etype_class')
-        self.vreg.register_vobject_class(EUser2)
-        p = self.entity('EUser U WHERE U login "member"')
-        self.failUnless(isinstance(p, EUser2))
-        w = p.get_widget('login')
-        self.failUnless(isinstance(w, AutoCompletionWidget))
-
-    def test_format_vocabulary(self):
-        card = self.add_entity('Card', title=u"hello")
-        self.assertEquals(card.default_content_format(), 'text/html')
-        self.execute('INSERT EProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
-        self.commit()
-        self.assertEquals(card.default_content_format(), 'text/rest')
-        
+    def test_entity_meta_attributes(self):
+        # XXX move to yams
+        self.assertEquals(self.schema['CWUser'].meta_attributes(), {})
+        self.assertEquals(dict((str(k), v) for k, v in self.schema['State'].meta_attributes().iteritems()),
+                          {'description_format': ('format', 'description')})
 
 
-class EUserTC(BaseEntityTC):
+class CWUserTC(BaseEntityTC):
     def test_dc_title_and_name(self):
-        e = self.entity('EUser U WHERE U login "member"')
+        e = self.entity('CWUser U WHERE U login "member"')
         self.assertEquals(e.dc_title(), 'member')
         self.assertEquals(e.name(), 'member')
-        self.execute(u'SET X firstname "bouah" WHERE X is EUser, X login "member"')
+        self.execute(u'SET X firstname "bouah" WHERE X is CWUser, X login "member"')
         self.assertEquals(e.dc_title(), 'member')
         self.assertEquals(e.name(), u'bouah')
-        self.execute(u'SET X surname "lôt" WHERE X is EUser, X login "member"')
+        self.execute(u'SET X surname "lôt" WHERE X is CWUser, X login "member"')
         self.assertEquals(e.dc_title(), 'member')
         self.assertEquals(e.name(), u'bouah lôt')
 
-    
+
 class StateAndTransitionsTC(BaseEntityTC):
-        
+
     def test_transitions(self):
-        user = self.entity('EUser X')
+        user = self.entity('CWUser X')
         e = self.entity('State S WHERE S name "activated"')
         trs = list(e.transitions(user))
         self.assertEquals(len(trs), 1)
@@ -86,12 +70,12 @@
         e = self.entity('State S WHERE S name "activated"')
         trs = list(e.transitions(user))
         self.assertEquals(len(trs), 0)
-        user = self.entity('EUser X')
+        user = self.entity('CWUser X')
         self.assert_(not user.can_pass_transition('deactivate'))
         self.assert_(not user.can_pass_transition('activate'))
-        
+
     def test_transitions_with_dest_specfied(self):
-        user = self.entity('EUser X')
+        user = self.entity('CWUser X')
         e = self.entity('State S WHERE S name "activated"')
         e2 = self.entity('State S WHERE S name "deactivated"')
         trs = list(e.transitions(user, e2.eid))
@@ -100,20 +84,20 @@
         self.assertEquals(trs[0].destination().name, u'deactivated')
         trs = list(e.transitions(user, e.eid))
         self.assertEquals(len(trs), 0)
-    
+
     def test_transitions_maybe_passed(self):
         self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
                      'X expression "X owned_by U", T condition X '
                      'WHERE T name "deactivate"')
         self._test_deactivated()
-        
+
     def test_transitions_maybe_passed_using_has_update_perm(self):
         self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
                      'X expression "U has_update_permission X", T condition X '
                      'WHERE T name "deactivate"')
         self._test_deactivated()
-        
-        
+
+
     def _test_deactivated(self):
         ueid = self.create_user('toto').eid
         self.create_user('tutu')
@@ -131,12 +115,12 @@
         self.assertRaises(ValidationError,
                           cu.execute, 'SET X in_state S WHERE X eid %(x)s, S name "activated"',
                           {'x': ueid}, 'x')
-    
+
 
     def test_transitions_selection(self):
         """
         ------------------------  tr1    -----------------
-        | state1 (Card, Bookmark) | ------> | state2 (Card) |
+        | state1 (CWGroup, Bookmark) | ------> | state2 (CWGroup) |
         ------------------------         -----------------
                   |  tr2    ------------------
                   `------>  | state3 (Bookmark) |
@@ -147,11 +131,11 @@
         state3 = self.add_entity('State', name=u'state3')
         tr1 = self.add_entity('Transition', name=u'tr1')
         tr2 = self.add_entity('Transition', name=u'tr2')
-        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is EEType, Y name "Card"' %
+        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "CWGroup"' %
                       (state1.eid, state2.eid))
-        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is EEType, Y name "Bookmark"' %
+        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Bookmark"' %
                       (state1.eid, state3.eid))
-        self.execute('SET X transition_of Y WHERE X eid %s, Y name "Card"' % tr1.eid)
+        self.execute('SET X transition_of Y WHERE X eid %s, Y name "CWGroup"' % tr1.eid)
         self.execute('SET X transition_of Y WHERE X eid %s, Y name "Bookmark"' % tr2.eid)
         self.execute('SET X allowed_transition Y WHERE X eid %s, Y eid %s' %
                       (state1.eid, tr1.eid))
@@ -161,36 +145,35 @@
                       (tr1.eid, state2.eid))
         self.execute('SET X destination_state Y WHERE X eid %s, Y eid %s' %
                       (tr2.eid, state3.eid))
-        self.execute('SET X initial_state Y WHERE Y eid %s, X name "Card"' % state1.eid)
+        self.execute('SET X initial_state Y WHERE Y eid %s, X name "CWGroup"' % state1.eid)
         self.execute('SET X initial_state Y WHERE Y eid %s, X name "Bookmark"' % state1.eid)
-        card = self.add_entity('Card', title=u't1')
-        bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view')
-        
-        transitions = list(state1.transitions(card))
+        group = self.add_entity('CWGroup', name=u't1')
+        transitions = list(state1.transitions(group))
         self.assertEquals(len(transitions), 1)
         self.assertEquals(transitions[0].name, 'tr1')
+        bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view')
         transitions = list(state1.transitions(bookmark))
         self.assertEquals(len(transitions), 1)
         self.assertEquals(transitions[0].name, 'tr2')
-        
+
 
     def test_transitions_selection2(self):
         """
         ------------------------  tr1 (Bookmark)   -----------------------
-        | state1 (Card, Bookmark) | -------------> | state2 (Card,Bookmark) |
+        | state1 (CWGroup, Bookmark) | -------------> | state2 (CWGroup,Bookmark) |
         ------------------------                -----------------------
-                  |  tr2 (Card)                     |
+                  |  tr2 (CWGroup)                     |
                   `---------------------------------/
         """
         state1 = self.add_entity('State', name=u'state1')
         state2 = self.add_entity('State', name=u'state2')
         tr1 = self.add_entity('Transition', name=u'tr1')
         tr2 = self.add_entity('Transition', name=u'tr2')
-        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is EEType, Y name "Card"' %
+        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "CWGroup"' %
                       (state1.eid, state2.eid))
-        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is EEType, Y name "Bookmark"' %
+        self.execute('SET X state_of Y WHERE X eid in (%s, %s), Y is CWEType, Y name "Bookmark"' %
                       (state1.eid, state2.eid))
-        self.execute('SET X transition_of Y WHERE X eid %s, Y name "Card"' % tr1.eid)
+        self.execute('SET X transition_of Y WHERE X eid %s, Y name "CWGroup"' % tr1.eid)
         self.execute('SET X transition_of Y WHERE X eid %s, Y name "Bookmark"' % tr2.eid)
         self.execute('SET X allowed_transition Y WHERE X eid %s, Y eid %s' %
                       (state1.eid, tr1.eid))
@@ -200,18 +183,17 @@
                       (tr1.eid, state2.eid))
         self.execute('SET X destination_state Y WHERE X eid %s, Y eid %s' %
                       (tr2.eid, state2.eid))
-        self.execute('SET X initial_state Y WHERE Y eid %s, X name "Card"' % state1.eid)
+        self.execute('SET X initial_state Y WHERE Y eid %s, X name "CWGroup"' % state1.eid)
         self.execute('SET X initial_state Y WHERE Y eid %s, X name "Bookmark"' % state1.eid)
-        card = self.add_entity('Card', title=u't1')
-        bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view')
-        
-        transitions = list(state1.transitions(card))
+        group = self.add_entity('CWGroup', name=u't1')
+        transitions = list(state1.transitions(group))
         self.assertEquals(len(transitions), 1)
         self.assertEquals(transitions[0].name, 'tr1')
+        bookmark = self.add_entity('Bookmark', title=u'111', path=u'/view')
         transitions = list(state1.transitions(bookmark))
         self.assertEquals(len(transitions), 1)
         self.assertEquals(transitions[0].name, 'tr2')
-        
+
 
 class EmailAddressTC(BaseEntityTC):
     def test_canonical_form(self):
@@ -240,34 +222,20 @@
         self.assertEquals(email.printable_value('address'), 'syt')
 
 
-class EUserTC(BaseEntityTC):
-    
+class CWUserTC(BaseEntityTC):
+
     def test_complete(self):
-        e = self.entity('EUser X WHERE X login "admin"')
+        e = self.entity('CWUser X WHERE X login "admin"')
         e.complete()
 
-        
+
     def test_matching_groups(self):
-        e = self.entity('EUser X WHERE X login "admin"')
+        e = self.entity('CWUser X WHERE X login "admin"')
         self.failUnless(e.matching_groups('managers'))
         self.failIf(e.matching_groups('xyz'))
         self.failUnless(e.matching_groups(('xyz', 'managers')))
         self.failIf(e.matching_groups(('xyz', 'abcd')))
 
-    def test_subject_in_state_vocabulary(self):
-        # on a new entity
-        e = self.etype_instance('EUser')
-        rschema = e.e_schema.subject_relation('in_state')
-        states = list(e.subject_in_state_vocabulary(rschema))
-        self.assertEquals(len(states), 1)
-        self.assertEquals(states[0][0], u'activated') # list of (combobox view, state eid)
-        # on an existant entity
-        e = self.entity('Any X WHERE X is EUser')
-        self.assertEquals(e.in_state[0].name, 'activated')
-        states = list(e.subject_in_state_vocabulary(rschema))
-        self.assertEquals(len(states), 1)
-        self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
-
     def test_workflow_base(self):
         e = self.create_user('toto')
         self.assertEquals(e.state, 'activated')
@@ -280,7 +248,7 @@
         e.change_state(deactivatedeid, u'deactivate 2')
         self.commit()
         # get a fresh user to avoid potential cache issues
-        e = self.entity('EUser X WHERE X eid %s' % e.eid)
+        e = self.entity('CWUser X WHERE X eid %s' % e.eid)
         self.assertEquals([tr.comment for tr in e.reverse_wf_info_for],
                           [None, 'deactivate 1', 'activate 1', 'deactivate 2'])
         self.assertEquals(e.latest_trinfo().comment, 'deactivate 2')
@@ -289,10 +257,11 @@
 class InterfaceTC(EnvBasedTC):
 
     def test_nonregr_subclasses_and_mixins_interfaces(self):
-        class MyUser(EUser):
+        class MyUser(CWUser):
             __implements__ = (IMileStone,)
+        self.vreg._loadedmods[__name__] = {}
         self.vreg.register_vobject_class(MyUser)
-        self.failUnless(implements(EUser, IWorkflowable))
+        self.failUnless(implements(CWUser, IWorkflowable))
         self.failUnless(implements(MyUser, IMileStone))
         self.failUnless(implements(MyUser, IWorkflowable))
 
@@ -303,7 +272,7 @@
         # clear selector cache
         clear_cache(self.vreg, 'etype_class')
         return self.vreg.etype_class(etype)
-        
+
     def test_etype_class_selection_and_specialization(self):
         # no specific class for Subdivisions, the default one should be selected
         eclass = self.select_eclass('SubDivision')
@@ -311,6 +280,7 @@
         #self.assertEquals(eclass.__bases__, (AnyEntity,))
         # build class from most generic to most specific and make
         # sure the most specific is always selected
+        self.vreg._loadedmods[__name__] = {}
         for etype in ('Company', 'Division', 'SubDivision'):
             class Foo(AnyEntity):
                 id = etype
@@ -324,6 +294,6 @@
         # check Division eclass is still selected for plain Division entities
         eclass = self.select_eclass('Division')
         self.assertEquals(eclass.id, 'Division')
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/entities/wfobjs.py	Thu May 14 12:50:14 2009 +0200
+++ b/entities/wfobjs.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """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"
@@ -17,10 +17,7 @@
     """
     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
 
@@ -47,7 +44,7 @@
 
     def destination(self):
         return self.destination_state[0]
-    
+
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -56,7 +53,7 @@
             return self.transition_of[0].rest_path(), {'vid': 'workflow'}
         return super(Transition, self).after_deletion_path()
 
-    
+
 class State(AnyEntity):
     """customized class for State entities
 
@@ -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, '
@@ -82,7 +75,7 @@
         for tr in rset.entities():
             if tr.may_be_passed(entity.eid, self.eid):
                 yield tr
-                
+
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -91,7 +84,7 @@
             return self.state_of[0].rest_path(), {'vid': 'workflow'}
         return super(State, self).after_deletion_path()
 
-    
+
 class TrInfo(AnyEntity):
     """customized class for Transition information entities
     """
@@ -104,7 +97,7 @@
     @property
     def previous_state(self):
         return self.from_state and self.from_state[0]
-    
+
     @property
     def new_state(self):
         return self.to_state[0]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/entity.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,965 @@
+"""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 import formwidgets, uicfg
+
+    def _dispatch_rtags(tags, rtype, role, stype, otype):
+        for tag in tags:
+            if tag in _MODE_TAGS:
+                uicfg.actionbox_appearsin_addmenu.tag_relation(
+                    (stype, rtype, otype, role), tag == 'create')
+            elif tag in _CATEGORY_TAGS:
+                uicfg.autoform_section.tag_relation((stype, rtype, otype, role),
+                                                    tag)
+            elif tag == 'inlineview':
+                uicfg.autoform_is_inlined.tag_relation((stype, rtype, otype, role), True)
+            else:
+                raise ValueError(tag)
+
+except ImportError:
+
+    _dispatch_rtags = None
+
+def _get_etype(bases, classdict):
+    try:
+        return classdict['id']
+    except KeyError:
+        for base in bases:
+            etype = getattr(base, 'id', None)
+            if etype and etype != 'Any':
+                return etype
+
+def _get_defs(attr, name, bases, classdict):
+    try:
+        yield name, classdict.pop(attr)
+    except KeyError:
+        for base in bases:
+            try:
+                value = getattr(base, attr)
+                delattr(base, attr)
+                yield base.__name__, value
+            except AttributeError:
+                continue
+
+class _metaentity(type):
+    """this metaclass sets the relation tags on the entity class
+    and deals with the `widgets` attribute
+    """
+    def __new__(mcs, name, bases, classdict):
+        # collect baseclass' rtags
+        etype = _get_etype(bases, classdict)
+        if etype and _dispatch_rtags is not None:
+            for name, rtags in _get_defs('__rtags__', name, bases, classdict):
+                warn('%s: __rtags__ is deprecated' % name, DeprecationWarning)
+                for relation, tags in rtags.iteritems():
+                    # tags must become an iterable
+                    if isinstance(tags, basestring):
+                        tags = (tags,)
+                    # relation must become a 3-uple (rtype, targettype, role)
+                    if isinstance(relation, basestring):
+                        _dispatch_rtags(tags, relation, 'subject', etype, '*')
+                        _dispatch_rtags(tags, relation, 'object', '*', etype)
+                    elif len(relation) == 1: # useful ?
+                        _dispatch_rtags(tags, relation[0], 'subject', etype, '*')
+                        _dispatch_rtags(tags, relation[0], 'object', '*', etype)
+                    elif len(relation) == 2:
+                        rtype, ttype = relation
+                        ttype = bw_normalize_etype(ttype) # XXX bw compat
+                        _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
+                        _dispatch_rtags(tags, rtype, 'object', ttype, etype)
+                    elif len(relation) == 3:
+                        rtype, ttype, role = relation
+                        ttype = bw_normalize_etype(ttype)
+                        if role == 'subject':
+                            _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
+                        else:
+                            _dispatch_rtags(tags, rtype, 'object', ttype, etype)
+                    else:
+                        raise ValueError('bad rtag definition (%r)' % (relation,))
+            for name, widgets in _get_defs('widgets', name, bases, classdict):
+                warn('%s: widgets is deprecated' % name, DeprecationWarning)
+                for rtype, wdgname in widgets.iteritems():
+                    if wdgname in ('URLWidget', 'EmbededURLWidget', 'RawDynamicComboBoxWidget'):
+                        warn('%s widget is deprecated' % wdgname, DeprecationWarning)
+                        continue
+                    if wdgname == 'StringWidget':
+                        wdgname = 'TextInput'
+                    widget = getattr(formwidgets, wdgname)
+                    assert hasattr(widget, 'render')
+                    uicfg.autoform_field_kwargs.tag_subject_of(
+                        (etype, rtype, '*'), {'widget': widget})
+        return super(_metaentity, mcs).__new__(mcs, name, bases, classdict)
+
+
+class Entity(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()
+
+    # class attributes that must be set in class definition
+    id = None
+    rest_attr = None
+    fetch_attrs = None
+    skip_copy_for = ()
+    # class attributes set automatically at registration time
+    e_schema = None
+
+    @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
+
+    @classmethod
+    @cached
+    def parent_classes(cls):
+        parents = [cls.vreg.etype_class(e.type) for e in cls.e_schema.ancestors()]
+        parents.append(cls.vreg.etype_class('Any'))
+        return parents
+
+    def __init__(self, req, rset=None, row=None, col=0):
+        AppRsetObject.__init__(self, req, rset, row, col)
+        dict.__init__(self)
+        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 attr_metadata(self, attr, metadata):
+        """return a metadata for an attribute (None if unspecified)"""
+        value = getattr(self, '%s_%s' % (attr, metadata), None)
+        if value is None and metadata == 'encoding':
+            value = self.vreg.property_value('ui.encoding')
+        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.attr_metadata(attr, 'format')
+            if attrformat:
+                return self.mtc_transform(value, attrformat, format,
+                                          self.req.encoding)
+        elif attrtype == 'Bytes':
+            attrformat = self.attr_metadata(attr, 'format')
+            if attrformat:
+                encoding = self.attr_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	Thu May 14 12:50:14 2009 +0200
+++ b/etwist/request.py	Thu May 14 12:50:34 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
@@ -39,11 +39,11 @@
     def base_url(self):
         """return the root url of the application"""
         return self._base_url
-    
+
     def http_method(self):
         """returns 'POST', 'GET', 'HEAD', etc."""
         return self._twreq.method
-    
+
     def relative_path(self, includeparams=True):
         """return the normalized path of the request (ie at least relative
         to the application's root, but some other normalization may be needed
@@ -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	Thu May 14 12:50:14 2009 +0200
+++ b/etwist/server.py	Thu May 14 12:50:34 2009 +0200
@@ -1,15 +1,16 @@
 """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 urlparse import urlsplit, urlunsplit
 
 from twisted.application import service, strports
 from twisted.internet import reactor, task, threads
@@ -18,7 +19,7 @@
 from twisted.web2 import static, resource, responsecode
 
 from cubicweb import ObjectNotFound
-from cubicweb.web import (AuthenticationError, NotFound, Redirect, 
+from cubicweb.web import (AuthenticationError, NotFound, Redirect,
                        RemoteCallFailed, DirectResponse, StatusResponse,
                        ExplicitLogin)
 from cubicweb.web.application import CubicWebPublisher
@@ -42,7 +43,15 @@
         start_task(interval, catch_error_func)
     # ensure no tasks will be further added
     repo._looping_tasks = ()
-    
+
+def host_prefixed_baseurl(baseurl, host):
+    scheme, netloc, url, query, fragment = urlsplit(baseurl)
+    netloc_domain = '.' + '.'.join(netloc.split('.')[-2:])
+    if host.endswith(netloc_domain):
+        netloc = host
+    baseurl = urlunsplit((scheme, netloc, url, query, fragment))
+    return baseurl
+
 
 class LongTimeExpiringFile(static.File):
     """overrides static.File and sets a far futre ``Expires`` date
@@ -62,8 +71,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)
@@ -71,7 +80,7 @@
 
 class CubicWebRootResource(resource.PostableResource):
     addSlash = False
-    
+
     def __init__(self, config, debug=None):
         self.appli = CubicWebPublisher(config, debug=debug)
         self.debugmode = debug
@@ -103,7 +112,7 @@
         interval = min(config['cleanup-session-time'] or 120,
                        config['cleanup-anonymous-session-time'] or 720) / 2.
         start_task(interval, self.appli.session_handler.clean_sessions)
-        
+
     def shutdown_event(self):
         """callback fired when the server is shutting down to properly
         clean opened sessions
@@ -116,7 +125,7 @@
             self.pyro_daemon.handleRequests(self.pyro_listen_timeout)
         except select.error:
             return
-        
+
     def locateChild(self, request, segments):
         """Indicate which resource to use to process down the URL's path"""
         if segments:
@@ -143,7 +152,7 @@
                     return static.File(fckeditordir), segments[1:]
         # Otherwise we use this single resource
         return self, ()
-    
+
     def render(self, request):
         """Render a page from the root resource"""
         # reload modified files (only in development or debug mode)
@@ -153,7 +162,7 @@
             return self.render_request(request)
         else:
             return threads.deferToThread(self.render_request, request)
-            
+
     def render_request(self, request):
         origpath = request.path
         host = request.host
@@ -163,10 +172,13 @@
             origpath = origpath[6:]
             request.uri = request.uri[6:]
             https = True
-            baseurl = self.https_url or self.base_url 
+            baseurl = self.https_url or self.base_url
         else:
             https = False
             baseurl = self.base_url
+        if self.config['use-request-subdomain']:
+            baseurl = host_prefixed_baseurl(baseurl, host)
+            self.warning('used baseurl is %s for this request', baseurl)
         req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl)
         if req.authmode == 'http':
             # activate realm-based auth
@@ -180,7 +192,7 @@
             return self.redirect(req, ex.location)
         if https and req.cnx.anonymous_connection:
             # don't allow anonymous on https connection
-            return self.request_auth(req)            
+            return self.request_auth(req)
         if self.url_rewriter is not None:
             # XXX should occur before authentication?
             try:
@@ -226,11 +238,6 @@
                 return self.request_auth(req, loggedout=True)
         except Redirect, ex:
             return self.redirect(req, ex.location)
-        if not result:
-            # no result, something went wrong...
-            self.error('no data (%s)', req)
-            # 500 Internal server error
-            return self.redirect(req, req.build_url('error'))
         # request may be referenced by "onetime callback", so clear its entity
         # cache to avoid memory usage
         req.drop_entity_cache()
@@ -242,11 +249,11 @@
         self.debug('redirecting to %s', location)
         # 303 See other
         return http.Response(code=303, headers=req.headers_out)
-        
+
     def request_auth(self, req, loggedout=False):
         if self.https_url and req.base_url() != self.https_url:
             req.headers_out.setHeader('location', self.https_url + 'login')
-            return http.Response(code=303, headers=req.headers_out)            
+            return http.Response(code=303, headers=req.headers_out)
         if self.config['auth-mode'] == 'http':
             code = responsecode.UNAUTHORIZED
         else:
@@ -260,7 +267,7 @@
             content = self.appli.need_login_content(req)
         return http.Response(code, req.headers_out, content)
 
-    
+
 # This part gets run when you run this file via: "twistd -noy demo.py"
 def main(appid, cfgname):
     """Starts an cubicweb  twisted server for an application
@@ -295,12 +302,12 @@
 
 # XXX set max file size to 100Mo: put max upload size in the configuration
 # line below for twisted >= 8.0, default param value for earlier version
-resource.PostableResource.maxSize = 100*1024*1024 
+resource.PostableResource.maxSize = 100*1024*1024
 def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
                   maxSize=100*1024*1024):
     if request.stream.length == 0:
         return defer.succeed(None)
-    
+
     ctype = request.headers.getHeader('content-type')
 
     if ctype is None:
@@ -318,7 +325,7 @@
     def error(f):
         f.trap(fileupload.MimeFormatError)
         raise http.HTTPError(responsecode.BAD_REQUEST)
-    
+
     if ctype.mediaType == 'application' and ctype.mediaSubtype == 'x-www-form-urlencoded':
         d = fileupload.parse_urlencoded(request.stream, keep_blank_values=True)
         d.addCallbacks(updateArgs, error)
@@ -360,7 +367,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/etwist/test/unittest_server.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,35 @@
+from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.etwist.server import host_prefixed_baseurl
+
+
+class HostPrefixedBaseURLTC(EnvBasedTC):
+
+    def _check(self, baseurl, host, waited):
+        self.assertEquals(host_prefixed_baseurl(baseurl, host), waited,
+                          'baseurl %s called through host %s should be considered as %s'
+                          % (baseurl, host, waited))
+
+    def test1(self):
+        self._check('http://www.cubicweb.org/hg/', 'code.cubicweb.org',
+                    'http://code.cubicweb.org/hg/')
+
+    def test2(self):
+        self._check('http://www.cubicweb.org/hg/', 'cubicweb.org',
+                    'http://www.cubicweb.org/hg/')
+
+    def test3(self):
+        self._check('http://cubicweb.org/hg/', 'code.cubicweb.org',
+                    'http://code.cubicweb.org/hg/')
+
+    def test4(self):
+        self._check('http://www.cubicweb.org/hg/', 'localhost',
+                    'http://www.cubicweb.org/hg/')
+
+    def test5(self):
+        self._check('http://www.cubicweb.org/cubes/', 'hg.code.cubicweb.org',
+                    'http://hg.code.cubicweb.org/cubes/')
+
+    def test6(self):
+        self._check('http://localhost:8080/hg/', 'code.cubicweb.org',
+                    'http://localhost:8080/hg/')
+
--- a/etwist/twconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/etwist/twconfig.py	Thu May 14 12:50:34 2009 +0200
@@ -2,13 +2,13 @@
 
 * the "twisted" configuration to get a web application running in a standalone
   twisted web server which talk to a repository server using Pyro
-  
+
 * the "all-in-one" configuration to get a web application running in a twisted
   web server integrating a repository server in the same process (only available
   if the repository part of the software is installed
 
 :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"
@@ -63,12 +63,12 @@
         ('pyro-server',
          {'type' : 'yn',
           # pyro is only a recommends by default, so don't activate it here
-          'default': False, 
+          'default': False,
           'help': 'run a pyro server',
           'group': 'main', 'inputlevel': 1,
           }),
         ) + WebConfiguration.options)
-    
+
     def server_file(self):
         return join(self.apphome, '%s-%s.py' % (self.appid, self.name))
 
@@ -91,6 +91,6 @@
         def pyro_enabled(self):
             """tell if pyro is activated for the in memory repository"""
             return self['pyro-server']
-        
+
 except ImportError:
     pass
--- a/etwist/twctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/etwist/twctl.py	Thu May 14 12:50:34 2009 +0200
@@ -1,4 +1,8 @@
 """cubicweb-clt handlers for twisted
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
 import sys
@@ -52,8 +56,8 @@
 class TWStopHandler(CommandHandler):
     cmdname = 'stop'
     cfgname = 'twisted'
-    
-    
+
+
 try:
     from cubicweb.server import serverctl
 
@@ -67,7 +71,7 @@
             """bootstrap this configuration"""
             serverctl.RepositoryCreateHandler.bootstrap(self, cubes, inputlevel)
             TWCreateHandler.bootstrap(self, cubes, inputlevel)
-            
+
     class AllInOneStartHandler(TWStartHandler):
         cmdname = 'start'
         cfgname = 'all-in-one'
@@ -80,4 +84,3 @@
 
 except ImportError:
     pass
-    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py	Thu May 14 12:50:34 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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,235 @@
+"""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 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')
+        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()
+
+
+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=CubicWebReSTParser(), 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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,255 @@
+"""provides simpleTAL extensions for CubicWeb
+
+: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 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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/test/unittest_rest.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,40 @@
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.apptest import EnvBasedTC
+
+from cubicweb.ext.rest import rest_publish
+
+class RestTC(EnvBasedTC):
+    def context(self):
+        return self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0)
+
+    def test_eid_role(self):
+        context = self.context()
+        self.assertEquals(rest_publish(context, ':eid:`%s`' % context.eid),
+                          '<p><a class="reference" href="http://testing.fr/cubicweb/cwuser/admin">#%s</a></p>\n' % context.eid)
+        self.assertEquals(rest_publish(context, ':eid:`%s:some text`' %  context.eid),
+                          '<p><a class="reference" href="http://testing.fr/cubicweb/cwuser/admin">some text</a></p>\n')
+
+    def test_bad_rest_no_crash(self):
+        data = rest_publish(self.context(), '''
+| card | implication     |
+--------------------------
+| 1-1  | N1 = N2         |
+| 1-?  | N1 <= N2        |
+| 1-+  | N1 >= N2        |
+| 1-*  | N1>0 => N2>0    |
+--------------------------
+| ?-?  | N1 # N2         |
+| ?-+  | N1 >= N2        |
+| ?-*  | N1 #  N2        |
+--------------------------
+| +-+  | N1>0 => N2>0 et |
+|      | N2>0 => N1>0    |
+| +-*  | N1>+ => N2>0    |
+--------------------------
+| *-*  | N1#N2           |
+--------------------------
+
+''')
+
+if __name__ == '__main__':
+    unittest_main()
--- a/gettext.py	Thu May 14 12:50:14 2009 +0200
+++ b/gettext.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/goa/__init__.py	Thu May 14 12:50:34 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
@@ -74,7 +17,7 @@
     pass
 else:
 
-    import os    
+    import os
     _SS = os.environ.get('SERVER_SOFTWARE')
     if _SS is None:
         MODE = 'test'
@@ -87,7 +30,7 @@
     from cubicweb.goa.gaesource import GAESource
     SOURCE_TYPES['gae'] = GAESource
 
-    
+
     def do_monkey_patch():
 
         # monkey patch yams Bytes validator since it should take a bytes string with gae
@@ -113,16 +56,16 @@
 
         # XXX monkey patch cubicweb.schema.CubicWebSchema to have string eid with
         #     optional cardinality (since eid is set after the validation)
-        
+
         import re
         from yams import buildobjs as ybo
-        
+
         def add_entity_type(self, edef):
             edef.name = edef.name.encode()
             assert re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name), repr(edef.name)
             eschema = super(CubicWebSchema, self).add_entity_type(edef)
             if not eschema.is_final():
-                # automatically add the eid relation to non final entity types 
+                # automatically add the eid relation to non final entity types
                 rdef = ybo.RelationDefinition(eschema.type, 'eid', 'Bytes',
                                               cardinality='?1', uid=True)
                 self.add_relation_def(rdef)
@@ -130,7 +73,7 @@
                 self.add_relation_def(rdef)
             self._eid_index[eschema.eid] = eschema
             return eschema
-        
+
         from cubicweb.schema import CubicWebSchema
         CubicWebSchema.add_entity_type = add_entity_type
 
@@ -150,9 +93,9 @@
             cubes = config['included-cubes'] + config['included-yams-cubes']
             return config.expand_cubes(cubes)
         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	Thu May 14 12:50:14 2009 +0200
+++ b/goa/appobjects/components.py	Thu May 14 12:50:34 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):
@@ -30,10 +23,8 @@
     to search for something to link to the edited eid
     """
     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)
@@ -52,7 +43,7 @@
     binary = True
     content_type = 'image/png'
     def call(self):
-        """display global schema information"""        
+        """display global schema information"""
         skipmeta = not int(self.req.form.get('withmeta', 0))
         if skipmeta:
             url = self.build_url('data/schema.png')
--- a/goa/appobjects/dbmgmt.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/appobjects/dbmgmt.py	Thu May 14 12:50:34 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()
@@ -53,15 +54,15 @@
         values.append('__session=%s' % cookie['__session'].value)
         self.w(u"<p>pass this flag to the client: --cookie='%s'</p>"
                % html_escape('; '.join(values)))
-        
-        
+
+
 
 class ContentInit(StartupView):
     """special management view to initialize content of a repository,
     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)
@@ -73,7 +74,7 @@
         status['stepid'] = stepid
         Put(status)
         self.msg(msg)
-        
+
     def call(self):
         status = _get_status('creation')
         if status.get('finished'):
@@ -148,7 +149,7 @@
                        '<b>delete all datastore content</b> so process can be '
                        'reinitialized</div>' % html_escape(self.req.base_url()))
         Put(status)
-        
+
     @property
     @cached
     def _migrhandler(self):
@@ -163,12 +164,12 @@
     def continue_link(self):
         self.w(u'<a href="%s">continue</a><br/>' % html_escape(self.req.url()))
 
-        
+
 class ContentClear(StartupView):
     id = 'contentclear'
-    require_groups = ('managers',)
-    skip_etypes = ('EGroup', 'EUser')
-    
+    __select__ = none_rset() & match_user_groups('managers')
+    skip_etypes = ('CWGroup', 'CWUser')
+
     def call(self):
         # XXX should use unsafe_execute with all hooks deactivated
         # XXX step by catching datastore errors?
--- a/goa/appobjects/gauthservice.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/appobjects/gauthservice.py	Thu May 14 12:50:34 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
+class GACWUserLink(UserLink):
 
     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(GACWUserLink, clear=True)
+        vreg.register(GAELogoutAction, clear=True)
--- a/goa/appobjects/sessions.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/appobjects/sessions.py	Thu May 14 12:50:34 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
@@ -40,10 +41,10 @@
     def __init__(self, *args, **kwargs):
         super(GAEAuthenticationManager, self).__init__(*args, **kwargs)
         self._repo = self.config.repository(vreg=self.vreg)
-        
+
     def authenticate(self, req, _login=None, _password=None):
         """authenticate user and return an established connection for this user
-        
+
         :raise ExplicitLogin: if authentication is required (no authentication
         info found or wrong user/password)
         """
@@ -74,7 +75,7 @@
     def __init__(self, *args, **kwargs):
         super(GAEPersistentSessionManager, self).__init__(*args, **kwargs)
         self._repo = self.config.repository(vreg=self.vreg)
-        
+
     def get_session(self, req, sessionid):
         """return existing session for the given session identifier"""
         # search a record for the given session
@@ -125,7 +126,7 @@
         record['anonymous_connection'] = cnx.anonymous_connection
         Put(record)
         return self._get_proxy(req, record, cnx, user)
-    
+
     def close_session(self, proxy):
         """close session on logout or on invalid session detected (expired out,
         corrupted...)
@@ -135,7 +136,7 @@
     def current_sessions(self):
         for record in Query('CubicWebSession').Run():
             yield ConnectionProxy(record)
-            
+
     def _get_proxy(self, req, record, cnx, user):
         proxy = ConnectionProxy(record, cnx, user)
         user.req = req
@@ -144,7 +145,7 @@
 
 
 class ConnectionProxy(object):
-    
+
     def __init__(self, record, cnx=None, user=None):
         self.__record = record
         self.__cnx = cnx
@@ -152,7 +153,7 @@
         self.__data = None
         self.__is_dirty = False
         self.sessionid = record.key().name()[4:] # remove 'key_' prefix
-        
+
     def __repr__(self):
         sstr = '<ConnectionProxy %s' % self.sessionid
         if self.anonymous_connection:
@@ -161,7 +162,7 @@
             sstr += ' for %s' % self.__user.login
         sstr += ', last used %s>' % strftime('%T', localtime(self.last_usage_time))
         return sstr
-        
+
     def __getattribute__(self, name):
         try:
             return super(ConnectionProxy, self).__getattribute__(name)
@@ -173,7 +174,7 @@
         self.__record['last_usage_time'] = value
     def _get_last_usage_time(self):
         return self.__record['last_usage_time']
-    
+
     last_usage_time = property(_get_last_usage_time, _set_last_usage_time)
 
     @property
@@ -181,7 +182,7 @@
         # use get() for bw compat if sessions without anonymous information are
         # found. Set default to True to limit lifetime of those sessions.
         return self.__record.get('anonymous_connection', True)
-        
+
     @property
     @cached
     def data(self):
@@ -193,7 +194,7 @@
                 self.exception('corrupted session data for session %s',
                                self.__cnx)
         return {}
-        
+
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         if pop:
@@ -205,20 +206,20 @@
                 return default
         else:
             return self.data.get(key, default)
-        
+
     def set_session_data(self, key, value):
         """set value associated to `key` in session data"""
         self.data[key] = value
         self.__is_dirty = True
-        
+
     def del_session_data(self, key):
         """remove value associated to `key` in session data"""
         try:
             del self.data[key]
             self.__is_dirty = True
         except KeyError:
-            pass    
-            
+            pass
+
     def commit(self):
         if self.__is_dirty:
             self.__save()
@@ -232,7 +233,7 @@
         if self.__cnx is not None:
             self.__cnx.close()
         Delete(self.__record)
-        
+
     def __save(self):
         if self.__is_dirty:
             self.__record['data'] = Blob(dumps(self.data))
@@ -242,7 +243,7 @@
     def user(self, req=None, props=None):
         """return the User object associated to this connection"""
         return self.__user
-        
+
 
 import logging
 from cubicweb import set_log_methods
@@ -254,8 +255,8 @@
 
 class SessionsCleaner(StartupView):
     id = 'cleansessions'
-    require_groups = ('managers',)
-    
+    __select__ = none_rset() & match_user_groups('managers')
+
     def call(self):
         # clean web session
         session_manager = application.SESSION_MANAGER
@@ -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	Thu May 14 12:50:14 2009 +0200
+++ b/goa/db.py	Thu May 14 12:50:34 2009 +0200
@@ -13,11 +13,11 @@
 * all methods returning `google.appengine.ext.db.Model` instance(s) will return
   `cubicweb.goa.db.Model` instance instead (though you should see almost no
   difference since those instances have the same api)
-  
+
 * class methods returning model instance take a `req` as first argument, unless
   they are called through an instance, representing the current request
   (accessible through `self.req` on almost all objects)
-  
+
 * XXX no instance.<modelname>_set attributes, use instance.reverse_<attr name>
       instead
 * XXX reference property always return a list of objects, not the instance
@@ -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
@@ -48,7 +47,7 @@
 from google.appengine.api.datastore_errors import BadKeyError
 
 # XXX remove this dependancy
-from google.appengine.ext import db 
+from google.appengine.ext import db
 
 
 def rset_from_objs(req, objs, attrs=('eid',), rql=None, args=None):
@@ -77,15 +76,15 @@
                 value = obj[attr]
                 descr = str(eschema.destination(attr))
             line.append(value)
-            linedescr.append(descr)            
+            linedescr.append(descr)
         rows.append(line)
         description.append(linedescr)
         for j, attr in enumerate(attrs):
             if attr == 'eid':
                 entity = vreg.etype_class(eschema.type)(req, rset, i, j)
-                rset._get_entity_cache_ = {(i, j): entity}        
+                rset._get_entity_cache_ = {(i, j): entity}
     rset.rowcount = len(rows)
-    req.decorate_rset(rset)    
+    req.decorate_rset(rset)
     return rset
 
 
@@ -103,7 +102,7 @@
         return wrapped(cls, req, *args, **kwargs)
     return iclassmethod(wrapper)
 
-    
+
 class gaedbmetaentity(metaentity):
     """metaclass for goa.db.Model classes: filter entity / db model part,
     put aside the db model part for later creation of db model class.
@@ -140,15 +139,15 @@
 class Model(entities.AnyEntity):
     id = 'Any'
     __metaclass__ = gaedbmetaentity
-    
+
     row = col = 0
-    
+
     @classmethod
     def __initialize__(cls):
         super(Model, cls).__initialize__()
         cls._attributes = frozenset(rschema for rschema in cls.e_schema.subject_relations()
                                     if rschema.is_final())
-    
+
     def __init__(self, *args, **kwargs):
         # db.Model prototype:
         #   __init__(self, parent=None, key_name=None, **kw)
@@ -169,22 +168,18 @@
                         val = val._dbmodel
                     kwargs[key] = val.key()
             self._gaeinitargs = (args, kwargs)
-            
+
     def __repr__(self):
         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)                
+                    value = Text(value)
             elif tschema == 'Password':
                 # if value is a Binary instance, this mean we got it
                 # from a query result and so it is already encrypted
@@ -208,7 +203,7 @@
                 value = self._cubicweb_to_datastore(attr, value)
             gaedict[attr] = value
         return gaedict
-    
+
     def to_gae_model(self):
         dbmodel = self._dbmodel
         dbmodel.update(self._to_gae_dict())
@@ -216,7 +211,7 @@
 
     @property
     @cached
-    def _dbmodel(self): 
+    def _dbmodel(self):
         if self.has_eid():
             assert self._gaeinitargs is None
             try:
@@ -245,7 +240,7 @@
             if '_app' in kwargs:
                 assert _app is None
                 _app = kwargs.pop('_app')
-            
+
             for key, value in kwargs.iteritems():
                 if key in self._attributes:
                     values['s_'+key] = value
@@ -272,10 +267,10 @@
         value will be prefixed by 'key_' to build the actual key name.
         """
         return None
-    
+
     def metainformation(self):
         return {'type': self.id, 'source': {'uri': 'system'}, 'extid': None}
-       
+
     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,
@@ -287,8 +282,7 @@
         if needcheck:
             return 'eid', False
         return mainattr, needcheck
-    
-    @use_mx_for_dates
+
     def get_value(self, name):
         try:
             value = self[name]
@@ -312,7 +306,7 @@
             return True
         except BadKeyError:
             return False
-        
+
     def complete(self, skip_bytes=True):
         pass
 
@@ -325,7 +319,7 @@
             objs = Query(str(targettype)).Run()
         return rset_from_objs(self.req, objs, ('eid',),
                               'Any X WHERE X is %s' % targettype)
-    
+
     def key(self):
         return Key(self.eid)
 
@@ -340,7 +334,7 @@
                                        'Any X WHERE X eid %(x)s', {'x': self.eid})
             self.row = self.col = 0
         return dbmodel
-    
+
     @needrequest
     def get(cls, req, keys):
         # if check if this is a dict.key call
@@ -399,7 +393,7 @@
 
     def dynamic_properties(self):
         raise NotImplementedError('use eschema')
-        
+
     def is_saved(self):
         return self.has_eid()
 
@@ -431,7 +425,7 @@
 IntegerProperty = db.IntegerProperty
 FloatProperty = db.FloatProperty
 ListProperty = db.ListProperty
-SelfReferenceProperty = db.SelfReferenceProperty 
+SelfReferenceProperty = db.SelfReferenceProperty
 UserProperty = db.UserProperty
 
 
--- a/goa/dbinit.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/dbinit.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """some utility functions for datastore initialization.
 
 :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"
@@ -15,7 +15,7 @@
     try:
         return _GROUP_CACHE[groupname]
     except KeyError:
-        key = Key.from_path('EGroup', 'key_' + groupname, parent=None)
+        key = Key.from_path('CWGroup', 'key_' + groupname, parent=None)
         try:
             group = Get(key)
         except datastore_errors.EntityNotFoundError:
@@ -29,7 +29,7 @@
 def create_user(login, password, groups):
     """create a cubicweb user"""
     from cubicweb.server.utils import crypt_password
-    user = Entity('EUser', name=login)
+    user = Entity('CWUser', name=login)
     user['s_login'] = unicode(login)
     user['s_upassword'] = crypt_password(password)
     set_user_groups(user, groups)
@@ -39,7 +39,7 @@
 def create_groups():
     """create initial cubicweb groups"""
     for groupname in ('managers', 'users', 'guests'):
-        group = Entity('EGroup', name='key_' + groupname)
+        group = Entity('CWGroup', name='key_' + groupname)
         group['s_name'] = unicode(groupname)
         Put(group)
         _GROUP_CACHE[groupname] = group
@@ -72,25 +72,25 @@
         dsrelation = 'o_' + rschema.type
         if not dsrelation in gaeentity:
             gaeentity[dsrelation] = None
-    
+
 def fix_entities(schema):
-    for etype in ('EUser', 'EGroup'):
+    for etype in ('CWUser', 'CWGroup'):
         eschema = schema.eschema(etype)
         for gaeentity in Query(etype).Run():
             init_relations(gaeentity, eschema)
-            # XXX o_is on EEType entity
-            gaeentity['s_is'] = Key.from_path('EEType', 'key_' + etype, parent=None)
+            # XXX o_is on CWEType entity
+            gaeentity['s_is'] = Key.from_path('CWEType', 'key_' + etype, parent=None)
             Put(gaeentity)
-    
+
 def init_persistent_schema(ssession, schema):
     execute = ssession.unsafe_execute
-    rql = ('INSERT EEType X: X name %(name)s, X description %(descr)s,'
+    rql = ('INSERT CWEType X: X name %(name)s, X description %(descr)s,'
            'X final FALSE, X meta %(meta)s')
-    eschema = schema.eschema('EEType')
-    execute(rql, {'name': u'EEType', 'descr': unicode(eschema.description),
+    eschema = schema.eschema('CWEType')
+    execute(rql, {'name': u'CWEType', 'descr': unicode(eschema.description),
                   'meta': eschema.meta})
     for eschema in schema.entities():
-        if eschema.is_final() or eschema == 'EEType':
+        if eschema.is_final() or eschema == 'CWEType':
             continue
         execute(rql, {'name': unicode(eschema), 'meta': eschema.meta,
                       'descr': unicode(eschema.description)})
@@ -98,11 +98,10 @@
 def insert_versions(ssession, config):
     execute = ssession.unsafe_execute
     # insert versions
-    execute('INSERT EProperty X: X pkey %(pk)s, X value%(v)s',
+    execute('INSERT CWProperty X: X pkey %(pk)s, X value%(v)s',
             {'pk': u'system.version.cubicweb',
              'v': unicode(config.cubicweb_version())})
     for cube in config.cubes():
-        execute('INSERT EProperty X: X pkey %(pk)s, X value%(v)s',
+        execute('INSERT CWProperty X: X pkey %(pk)s, X value%(v)s',
                 {'pk': u'system.version.%s' % cube,
                  'v': unicode(config.cube_version(cube))})
-    
--- a/goa/dbmyams.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/dbmyams.py	Thu May 14 12:50:34 2009 +0200
@@ -5,6 +5,9 @@
  - ReferenceProperty.verbose_name, collection_name, etc. (XXX)
 
 XXX proprify this knowing we'll use goa.db
+:organization: Logilab
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 
 from os.path import join
@@ -13,12 +16,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
@@ -72,7 +71,7 @@
     # XXX no equivalent to Django's `auto_now`
     return dbm2y_default_factory(prop, **kwargs)
 
-    
+
 def dbm2y_relation_factory(etype, prop, multiple=False):
     """called if `prop` is a `db.ReferenceProperty`"""
     if multiple:
@@ -87,8 +86,8 @@
     except AttributeError, ex:
         # hack, data_type is still _SELF_REFERENCE_MARKER
         return SubjectRelation(etype, cardinality=cardinality)
-    
-    
+
+
 DBM2Y_FACTORY = {
     basestring: dbm2y_string_factory,
     datastore_types.Text: dbm2y_string_factory,
@@ -111,7 +110,7 @@
         self.created = []
         self.loaded_files = []
         self._instantiate_handlers()
-        
+
     def finalize(self, register_base_types=False):
         return self._build_schema('google-appengine', register_base_types)
 
@@ -152,11 +151,11 @@
     def import_yams_cube_schema(self, templpath):
         for filepath in self.get_schema_files(templpath):
             self.handle_file(filepath)
-        
+
     @property
     def pyreader(self):
         return self._live_handlers['.py']
-        
+
 import os
 from cubicweb import CW_SOFTWARE_ROOT
 
@@ -172,7 +171,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,14 +179,14 @@
             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',
+    for erschema in ('CWGroup', 'CWEType', 'CWRType', 'RQLExpression',
                      'is_', 'is_instance_of',
                      'read_permission', 'add_permission',
                      'delete_permission', 'update_permission'):
-        loader.import_yams_schema(erschema, 'bootstrap')  
+        loader.import_yams_schema(erschema, 'bootstrap')
     loader.handle_file(join(SCHEMAS_LIB_DIRECTORY, 'base.py'))
     cubes = config['included-yams-cubes']
     for cube in reversed(config.expand_cubes(cubes)):
@@ -198,21 +197,20 @@
     if extrahook is not None:
         extrahook(loader)
     if config['use-google-auth']:
-        loader.defined['EUser'].remove_relation('upassword')
-        loader.defined['EUser'].permissions['add'] = ()
-        loader.defined['EUser'].permissions['delete'] = ()
-    for etype in ('EGroup', 'RQLExpression'):
+        loader.defined['CWUser'].remove_relation('upassword')
+        loader.defined['CWUser'].permissions['add'] = ()
+        loader.defined['CWUser'].permissions['delete'] = ()
+    for etype in ('CWGroup', 'RQLExpression'):
         read_perm_rel = loader.defined[etype].get_relations('read_permission').next()
         read_perm_rel.cardinality = '**'
-    # XXX not yet ready for EUser workflow
-    loader.defined['EUser'].remove_relation('in_state')
-    loader.defined['EUser'].remove_relation('wf_info_for')
-    # remove RQLConstraint('NOT O name "owners"') on EUser in_group EGroup
+    # XXX not yet ready for CWUser workflow
+    loader.defined['CWUser'].remove_relation('in_state')
+    loader.defined['CWUser'].remove_relation('wf_info_for')
+    # remove RQLConstraint('NOT O name "owners"') on CWUser in_group CWGroup
     # since "owners" group is not persistent with gae
-    loader.defined['EUser'].get_relations('in_group').next().constraints = []
+    loader.defined['CWUser'].get_relations('in_group').next().constraints = []
     # return the full schema including the cubes' schema
     for ertype in loader.defined.values():
         if getattr(ertype, 'inlined', False):
             ertype.inlined = False
     return loader.finalize()
-
--- a/goa/doc/devmanual_fr/chap_autres_composants_ui.txt	Thu May 14 12:50:14 2009 +0200
+++ b/goa/doc/devmanual_fr/chap_autres_composants_ui.txt	Thu May 14 12:50:34 2009 +0200
@@ -9,6 +9,6 @@
 ---------------------
 XXXFILLME
 
-EProperty
+CWProperty
 ---------
 XXXFILLME
--- a/goa/doc/devmanual_fr/chap_bases_framework_erudi.txt	Thu May 14 12:50:14 2009 +0200
+++ b/goa/doc/devmanual_fr/chap_bases_framework_erudi.txt	Thu May 14 12:50:34 2009 +0200
@@ -127,7 +127,7 @@
     réponse 
   * `cursor()` retourne un curseur RQL sur la session
   * `execute(*args, **kwargs)`, raccourci vers .cursor().execute()
-  * `property_value(key)`, gestion des propriétés (`EProperty`)
+  * `property_value(key)`, gestion des propriétés (`CWProperty`)
   * le dictionaire `data` pour stocker des données pour partager de
     l'information entre les composants *durant l'éxécution de la requête*.
 
--- a/goa/doc/devmanual_fr/chap_configuration_instance.txt	Thu May 14 12:50:14 2009 +0200
+++ b/goa/doc/devmanual_fr/chap_configuration_instance.txt	Thu May 14 12:50:34 2009 +0200
@@ -27,7 +27,7 @@
 
 :`main.anonymous-user`, `main.anonymous-password`:
    login et mot de passe à utiliser pour se connecter au serveur RQL lors des
-   connexions HTTP anonymes. Il faut que le compte EUser associé existe.
+   connexions HTTP anonymes. Il faut que le compte CWUser associé existe.
 
 :`main.base-url`:
    url de base du site, à utiliser pour générer les urls des pages web
@@ -143,7 +143,7 @@
 
 Configuration Eproperties
 -------------------------
-D'autres paramètres de configuration sont sous la forme d'entités `EProperty`
+D'autres paramètres de configuration sont sous la forme d'entités `CWProperty`
 dans la base de données. Il faut donc les éditer via l'interface web ou par des
 requêtes rql.
 
--- a/goa/doc/devmanual_fr/sect_definition_schema.txt	Thu May 14 12:50:14 2009 +0200
+++ b/goa/doc/devmanual_fr/sect_definition_schema.txt	Thu May 14 12:50:34 2009 +0200
@@ -27,8 +27,8 @@
   - `eid` (`Int`)
   - `creation_date` (`Datetime`)
   - `modification_date` (`Datetime`)
-  - `owned_by` (`EUser`)
-  - `is` (`EEType`)
+  - `owned_by` (`CWUser`)
+  - `is` (`CWEType`)
 
 * il est également possible de définir des relations dont le type d'entité est
   l'objet en utilisant `ObjectRelation` plutôt que `SubjectRelation`
@@ -158,7 +158,7 @@
     inlined = True
     cardinality = '?*'
     subject = '*'
-    object = 'EUser'
+    object = 'CWUser'
 
 En plus des permissions, les propriétés propres aux types de relation (et donc
 partagés par toutes les définitions de relation de ce type) sont :
@@ -254,16 +254,16 @@
     'add'/'read' son pris en considération
 
 
-En plus de cela, le type d'entité `EPermission` de la librairie standard permet
+En plus de cela, le type d'entité `CWPermission` de la librairie standard permet
 de construire des modèles de sécurités très complexes et dynamiques. Le schéma
 de ce type d'entité est le suivant : ::
 
 
-    class EPermission(MetaEntityType):
+    class CWPermission(MetaEntityType):
 	"""entity type that may be used to construct some advanced security configuration
 	"""
 	name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
-	require_group = SubjectRelation('EGroup', cardinality='+*',
+	require_group = SubjectRelation('CWGroup', cardinality='+*',
 					description=_('groups to which the permission is granted'))
 	require_state = SubjectRelation('State',
 				    description=_("entity'state in which the permission is applyable"))
@@ -302,7 +302,7 @@
 		       }
 	inlined = True
 
-Cette configuration suppose indique qu'une entité `EPermission` de nom
+Cette configuration suppose indique qu'une entité `CWPermission` de nom
 "add_version" peut-être associée à un projet et donner le droit de créer des
 versions sur ce projet à des groupes spécifiques. Il est important de noter les
 points suivants :
@@ -310,7 +310,7 @@
 * dans ce cas il faut protéger à la fois le type d'entité "Version" et la
   relation liant une version à un projet ("version_of")
 
-* du fait de la généricité du type d'entité `EPermission`, il faut effectuer
+* du fait de la généricité du type d'entité `CWPermission`, il faut effectuer
   l'unification avec les groupes et / ou les états le cas échéant dans
   l'expression ("U in_group G, P require_group G" dans l'exemple ci-dessus)
 
--- a/goa/doc/devmanual_fr/sect_stdlib_schemas.txt	Thu May 14 12:50:14 2009 +0200
+++ b/goa/doc/devmanual_fr/sect_stdlib_schemas.txt	Thu May 14 12:50:34 2009 +0200
@@ -9,10 +9,10 @@
 Schémas "systèmes"
 ``````````````````
 
-* `EUser`, utilisateurs du système
-* `EGroup`, groupes d'utilisateurs
-* `EEType`, types d'entité
-* `ERType`, types de relation
+* `CWUser`, utilisateurs du système
+* `CWGroup`, groupes d'utilisateurs
+* `CWEType`, types d'entité
+* `CWRType`, types de relation
 
 * `State`, état d'un workflow
 * `Transition`, transition d'un workflow
@@ -21,8 +21,8 @@
 * `EmailAddress`, adresse électronique, utilisé par le système de notification
   pour les utilisateurs et par d'autres schéma optionnels
 
-* `EProperty`, utilisé pour configurer l'application
-* `EPermission`, utilisé pour configurer la sécurité de l'application
+* `CWProperty`, utilisé pour configurer l'application
+* `CWPermission`, utilisé pour configurer la sécurité de l'application
 
 * `Card`, fiche documentaire générique
 * `Bookmark`, un type d'entité utilisé pour permetter à un utilisateur de
--- a/goa/gaesource.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/gaesource.py	Thu May 14 12:50:34 2009 +0200
@@ -1,24 +1,21 @@
 """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):
     # set default groups
     if guser is None:
@@ -39,11 +36,11 @@
             pass
         else:
             entity.clear_related_cache(rtype, role)
-    if gaesubject.kind() == 'EUser':
+    if gaesubject.kind() == 'CWUser':
         for asession in session.repo._sessions.itervalues():
             if asession.user.eid == subject:
                 asession.user.clear_related_cache(rtype, 'subject')
-    if gaeobject.kind() == 'EUser':
+    if gaeobject.kind() == 'CWUser':
         for asession in session.repo._sessions.itervalues():
             if asession.user.eid == object:
                 asession.user.clear_related_cache(rtype, 'object')
@@ -85,13 +82,13 @@
             gaeentity[relation] = related or None
     _mark_modified(session, gaeentity)
 
-    
+
 class DatastorePutOp(SingleOperation):
     """delayed put of entities to have less datastore write api calls
 
     * save all modified entities at precommit (should be the first operation
       processed, hence the 0 returned by insert_index())
-      
+
     * in case others precommit operations modify some entities, resave modified
       entities at commit. This suppose that no db changes will occurs during
       commit event but it should be the case.
@@ -106,10 +103,10 @@
             assert not eid in pending
             Put(gaeentity)
         modified.clear()
-        
+
     def commit_event(self):
         self._put_entities()
-        
+
     def precommit_event(self):
         self._put_entities()
 
@@ -117,12 +114,12 @@
 class GAESource(AbstractSource):
     """adapter for a system source on top of google appengine datastore"""
 
-    passwd_rql = "Any P WHERE X is EUser, X login %(login)s, X upassword P"
-    auth_rql = "Any X WHERE X is EUser, X login %(login)s, X upassword %(pwd)s"
-    _sols = ({'X': 'EUser', 'P': 'Password'},)
-    
+    passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
+    auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
+    _sols = ({'X': 'CWUser', 'P': 'Password'},)
+
     options = ()
-    
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         AbstractSource.__init__(self, repo, appschema, source_config,
                                 *args, **kwargs)
@@ -131,11 +128,11 @@
             self.authenticate = self.authenticate_gauth
         else:
             self.authenticate = self.authenticate_local
-            
+
     def reset_caches(self):
         """method called during test to reset potential source caches"""
         pass
-    
+
     def init_creating(self):
         pass
 
@@ -147,7 +144,7 @@
 
     def get_connection(self):
         return ConnectionWrapper()
-    
+
     # ISource interface #######################################################
 
     def compile_rql(self, rql):
@@ -155,22 +152,22 @@
         rqlst.restricted_vars = ()
         rqlst.children[0].solutions = self._sols
         return rqlst
-    
+
     def set_schema(self, schema):
         """set the application'schema"""
         self.interpreter = RQLInterpreter(schema)
         self.schema = schema
-        if 'EUser' in schema and not self.repo.config['use-google-auth']:
+        if 'CWUser' in schema and not self.repo.config['use-google-auth']:
             # rql syntax trees used to authenticate users
             self._passwd_rqlst = self.compile_rql(self.passwd_rql)
             self._auth_rqlst = self.compile_rql(self.auth_rql)
-                
+
     def support_entity(self, etype, write=False):
         """return true if the given entity's type is handled by this adapter
         if write is true, return true only if it's a RW support
         """
         return True
-    
+
     def support_relation(self, rtype, write=False):
         """return true if the given relation's type is handled by this adapter
         if write is true, return true only if it's a RW support
@@ -188,7 +185,7 @@
         # XXX http://code.google.com/appengine/docs/users/userobjects.html
         # use a reference property to automatically work with email address
         # changes after the propagation feature is implemented
-        key = Key.from_path('EUser', 'key_' + login, parent=None)
+        key = Key.from_path('CWUser', 'key_' + login, parent=None)
         try:
             euser = session.datastore_get(key)
             # XXX fix user. Required until we find a better way to fix broken records
@@ -198,14 +195,14 @@
             return str(key)
         except datastore_errors.EntityNotFoundError:
             # create a record for this user
-            euser = Entity('EUser', name='key_' + login)
+            euser = Entity('CWUser', name='key_' + login)
             euser['s_login'] = login
             _init_groups(guser, euser)
             Put(euser)
             return str(euser.key())
-        
+
     def authenticate_local(self, session, login, password):
-        """return EUser eid for the given login/password if this account is
+        """return CWUser eid for the given login/password if this account is
         defined in this source, else raise `AuthenticationError`
 
         two queries are needed since passwords are stored crypted, so we have
@@ -227,8 +224,8 @@
             return rset[0][0]
         except IndexError:
             raise AuthenticationError('bad password')
-    
-    def syntax_tree_search(self, session, union, args=None, cachekey=None, 
+
+    def syntax_tree_search(self, session, union, args=None, cachekey=None,
                            varmap=None):
         """return result from this source for a rql query (actually from a rql
         syntax tree and a solution dictionary mapping each used variable to a
@@ -238,25 +235,25 @@
         results, description = self.interpreter.interpret(union, args,
                                                           session.datastore_get)
         return results # XXX description
-                
+
     def flying_insert(self, table, session, union, args=None, varmap=None):
         raise NotImplementedError
-    
+
     def add_entity(self, session, entity):
         """add a new entity to the source"""
         # do not delay add_entity as other modifications, new created entity
         # needs an eid
         entity.put()
-        
+
     def update_entity(self, session, entity):
         """replace an entity in the source"""
         gaeentity = entity.to_gae_model()
         _mark_modified(session, entity.to_gae_model())
-        if gaeentity.kind() == 'EUser':
+        if gaeentity.kind() == 'CWUser':
             for asession in self.repo._sessions.itervalues():
                 if asession.user.eid == entity.eid:
                     asession.user.update(dict(gaeentity))
-                
+
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source"""
         # do not delay delete_entity as other modifications to ensure
@@ -273,7 +270,7 @@
         _radd(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
         _radd(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
         _clear_related_cache(session, gaesubj, rtype, gaeobj)
-            
+
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
@@ -283,7 +280,7 @@
         if not object in pending:
             _rdel(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1])
         _clear_related_cache(session, gaesubj, rtype, gaeobj)
-        
+
     # system source interface #################################################
 
     def eid_type_source(self, session, eid):
@@ -293,7 +290,7 @@
         except datastore_errors.BadKeyError:
             raise UnknownEid(eid)
         return key.kind(), 'system', None
-    
+
     def create_eid(self, session):
         return None # let the datastore generating key
 
@@ -306,15 +303,14 @@
         record from the entities table to the deleted_entities table
         """
         pass
-        
+
     def fti_unindex_entity(self, session, eid):
         """remove text content for entity with the given eid from the full text
         index
         """
         pass
-        
+
     def fti_index_entity(self, session, entity):
         """add text content of a created/modified entity to the full text index
         """
         pass
-
--- a/goa/goaconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/goaconfig.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """google appengine configuration
 
 :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"
@@ -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
 
@@ -32,7 +32,7 @@
     """repository and web application in the same twisted process"""
     name = 'app'
     repo_method = 'inmemory'
-    options = merge_options(( 
+    options = merge_options((
         ('included-cubes',
          {'type' : 'csv',
           'default': [],
@@ -75,7 +75,7 @@
           'anonymous access using the app.yaml file)',
           'group': 'main', 'inputlevel': 1,
           }),
-        
+
         ) + WebConfiguration.options + ServerConfiguration.options)
     options = [(optname, optdict) for optname, optdict in options
                if not optname in UNSUPPORTED_OPTIONS]
@@ -94,10 +94,10 @@
     # deactivate some hooks during [pre|post]create scripts execution
     # (unique values check, owned_by/created_by relations setup)
     free_wheel = True
-    
+
     if not os.environ.get('APYCOT_ROOT'):
         CUBES_DIR = join(CW_SOFTWARE_ROOT, '../cubes')
-    
+
     def __init__(self, appid, apphome=None):
         if apphome is None:
             apphome = 'data'
@@ -111,7 +111,7 @@
         if key == 'base-url':
             return self._base_url
         return super(GAEConfiguration, self).__getitem__(key)
-    
+
     # overriden from cubicweb base configuration
 
     @property
@@ -136,15 +136,15 @@
 
     def instance_md5_version(self):
         return ''
-    
+
     def _init_base_url(self):
         pass
-    
+
     # overriden from cubicweb server configuration
-    
+
     def sources(self):
         return {'system': {'adapter': 'gae'}}
-    
+
     def load_schema(self, schemaclasses=None, extrahook=None):
         try:
             return self._schema
@@ -155,10 +155,11 @@
     # goa specific
     def repo_session(self, sessionid):
         return self.repository()._sessions[sessionid]
-    
+
     def is_anonymous_user(self, login):
         if self['use-google-auth']:
             from google.appengine.api import users
             return users.get_current_user() is None
         else:
             return login == self.anonymous_user()[0]
+
--- a/goa/goactl.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/goactl.py	Thu May 14 12:50:34 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
@@ -36,7 +36,7 @@
     (join(CW_SOFTWARE_ROOT, 'embedded', 'mx'), 'mx'),
     ('/usr/share/fckeditor/', 'fckeditor'),
 
-    (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')), 
+    (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')),
     (join(CW_SOFTWARE_ROOT, 'web', 'wdoc'), join('cubes', 'shared', 'wdoc')),
     (join(CW_SOFTWARE_ROOT, 'i18n'), join('cubes', 'shared', 'i18n')),
     (join(CW_SOFTWARE_ROOT, 'goa', 'tools'), 'tools'),
@@ -47,28 +47,28 @@
     '__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',
-    
-    'common/appobject.py',
-    'common/entity.py',
-    'common/html4zope.py',
+    'view.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',
@@ -93,12 +93,12 @@
 
     'sobjects/__init__.py',
     'sobjects/notification.py',
-    
+
 # XXX would be necessary for goa.testlib but require more stuff to be added
 #     such as server.serverconfig and so on (check devtools.__init__)
 #    'devtools/__init__.py',
 #    'devtools/fake.py',
-    
+
     'web/__init__.py',
     'web/_exceptions.py',
     'web/action.py',
@@ -139,7 +139,7 @@
     'wsgi/__init__.py',
     'wsgi/handler.py',
     'wsgi/request.py',
-    
+
     'goa/__init__.py',
     'goa/db.py',
     'goa/dbinit.py',
@@ -149,9 +149,9 @@
     'goa/gaesource.py',
     'goa/rqlinterpreter.py',
     'goa/appobjects/__init__.py',
-    'goa/appobjects/components.py', 
-    'goa/appobjects/dbmgmt.py', 
-    'goa/appobjects/gauthservice.py', 
+    'goa/appobjects/components.py',
+    'goa/appobjects/dbmgmt.py',
+    'goa/appobjects/gauthservice.py',
     'goa/appobjects/sessions.py',
 
     'schemas/bootstrap.py',
@@ -178,7 +178,7 @@
     """
     name = 'newgapp'
     arguments = '<application directory>'
-    
+
     def run(self, args):
         if len(args) != 1:
             raise BadCommandUsage("exactly one argument is expected")
@@ -196,7 +196,7 @@
                 create_dir(split(subdirectory)[0])
             create_symlink(directory, join(appldir, subdirectory))
         create_init_file(join(appldir, 'logilab'), 'logilab')
-        # copy supported part of cubicweb 
+        # copy supported part of cubicweb
         create_dir(join(appldir, 'cubicweb'))
         for fpath in COPY_CW_FILES:
             target = join(appldir, 'cubicweb', fpath)
--- a/goa/goavreg.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/goavreg.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """goa specific registry
 
 :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"
@@ -19,7 +19,7 @@
     return 'cubes.%s.%s' % (cube, module)
 
 class GAERegistry(CubicWebRegistry):
-    
+
     def set_schema(self, schema):
         """disable reload hooks of cubicweb registry set_schema method"""
         self.schema = schema
@@ -37,7 +37,7 @@
         for cube in reversed(self.config.cubes()):
             self.load_cube(cube)
         self.load_application(applroot)
-        
+
     def load_directory(self, directory, cube, skip=()):
         for filename in listdir(directory):
             if filename[-3:] == '.py' and not filename in skip:
@@ -70,6 +70,3 @@
             # when using db.Model defined schema, the defined class is used as
             # entity class as well and so have to be registered
             self._import(_pkg_name(cube, 'schema'))
-
-
-    
--- a/goa/overrides/mttransforms.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/overrides/mttransforms.py	Thu May 14 12:50:34 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"
@@ -38,7 +38,7 @@
 
 HAS_PIL_TRANSFORMS = False
 HAS_PYGMENTS_TRANSFORMS = False
-    
+
 class html_to_text(Transform):
     inputs = HTML_MIMETYPES
     output = 'text/plain'
--- a/goa/overrides/rqlannotation.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/overrides/rqlannotation.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,13 @@
+"""
+: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"
+
 def set_qdata(getrschema, union, noinvariant):
     pass
-        
+
 class SQLGenAnnotator(object):
     def __init__(self, schema):
         self.schema = schema
@@ -9,5 +16,5 @@
     def annotate(self, rqlst):
         rqlst.has_text_query = False
         rqlst.need_distinct = False
-        
-   
+
+
--- a/goa/overrides/server_utils.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/overrides/server_utils.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
         pass
     def join(self):
         pass
-    
+
 class LoopTask(RepoThread):
     def cancel(self):
         pass
--- a/goa/rqlinterpreter.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/rqlinterpreter.py	Thu May 14 12:50:34 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
@@ -26,7 +23,7 @@
     return Key(key).kind()
 
 def poss_var_types(myvar, ovar, kind, solutions):
-    return frozenset(etypes[myvar] for etypes in solutions 
+    return frozenset(etypes[myvar] for etypes in solutions
                      if etypes[ovar] == kind)
 
 def expand_result(results, result, myvar, values, dsget=None):
@@ -87,7 +84,7 @@
             string.append('%s: %s' % (k, v))
     return '{%s}' % ', '.join(string)
 
-                         
+
 class EidMismatch(Exception):
     def __init__(self, varname, value):
         self.varname = varname
@@ -104,45 +101,45 @@
         self.operator = operator
         self.rtype = rel.r_type
         self.var = rel.children[0]
-        
+
     def __repr__(self):
         return '<%s for %s>' % (self.__class__.__name__, self.rel)
-    
+
     @property
     def rhs(self):
         return self.rel.children[1].children[0]
 
-        
+
 class MultipleRestriction(object):
     def __init__(self, restrictions):
         self.restrictions = restrictions
-        
+
     def resolve(self, solutions, fixed):
         return _resolve(self.restrictions, solutions, fixed)
 
-    
+
 class VariableSelection(Restriction):
     def __init__(self, rel, dsget, prefix='s'):
         Restriction.__init__(self, rel)
         self._dsget = dsget
         self._not = self.rel.neged(strict=True)
         self._prefix = prefix + '_'
-        
+
     def __repr__(self):
         return '<%s%s for %s>' % (self._prefix[0], self.__class__.__name__, self.rel)
-        
+
     @property
     def searched_var(self):
         if self._prefix == 's_':
             return self.var.name
         return self.rhs.name
-        
+
     @property
     def constraint_var(self):
         if self._prefix == 's_':
             return self.rhs.name
         return self.var.name
-        
+
     def _possible_values(self, myvar, ovar, entity, solutions, dsprefix):
         if self.rtype == 'identity':
             return (entity.key(),)
@@ -153,7 +150,7 @@
             value = [value]
         vartypes = poss_var_types(myvar, ovar, entity.kind(), solutions)
         return (v for v in value if v.kind() in vartypes)
-        
+
     def complete_and_filter(self, solutions, results):
         myvar = self.rhs.name
         ovar = self.var.name
@@ -176,8 +173,8 @@
                     expand_result(results, result, myvar, values, self._dsget)
         else:
             assert self.rhs.name in results[0]
-            self.object_complete_and_filter(solutions, results)           
-            
+            self.object_complete_and_filter(solutions, results)
+
     def filter(self, solutions, results):
         myvar = self.rhs.name
         ovar = self.var.name
@@ -190,10 +187,10 @@
                 newsols[key] = frozenset(v for v in values)
             if self._not:
                 if result[myvar].key() in newsols[key]:
-                    results.remove(result)                
+                    results.remove(result)
             elif not result[myvar].key() in newsols[key]:
                 results.remove(result)
-    
+
     def object_complete_and_filter(self, solutions, results):
         if self._not:
             raise NotImplementedError()
@@ -204,7 +201,7 @@
                                            solutions, 'o_')
             expand_result(results, result, myvar, values, self._dsget)
 
-    
+
 class EidRestriction(Restriction):
     def __init__(self, rel, dsget):
         Restriction.__init__(self, rel)
@@ -219,7 +216,7 @@
 
     def _get_value(self, fixed):
         return fixed[self.constraint_var].key()
-    
+
     def fill_query(self, fixed, query, operator=None):
         restr = '%s%s %s' % (self._prefix, self.rtype, operator or self.operator)
         query[restr] = self._get_value(fixed)
@@ -238,7 +235,7 @@
 
     def _get_value(self, fixed):
         return None
-    
+
     def resolve(self, solutions, fixed):
         if self.rtype == 'identity':
             raise NotImplementedError()
@@ -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('%'):
@@ -261,7 +255,7 @@
                 raise NotImplementedError('LIKE is only supported for prefix search')
             self.operator = '>'
             self.value = value[:-1]
-            
+
     def complete_and_filter(self, solutions, results):
         # check lhs var first in case this is a restriction
         assert self._not
@@ -269,7 +263,7 @@
         for result in results[:]:
             if result[myvar].get('s_'+rtype) == value:
                 results.remove(result)
-            
+
     def _get_value(self, fixed):
         return self.value
 
@@ -294,17 +288,13 @@
         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
     def operator(self):
         return 'in'
-            
+
 
 class TypeRestriction(AttributeRestriction):
     def __init__(self, var):
@@ -312,7 +302,7 @@
 
     def __repr__(self):
         return '<%s for %s>' % (self.__class__.__name__, self.var)
-    
+
     def resolve(self, solutions, fixed):
         objs = []
         for etype in frozenset(etypes[self.var.name] for etypes in solutions):
@@ -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))
@@ -342,7 +330,7 @@
         self.args = args
         self.term = term
         self._solution = self.term.stmt.solutions[0]
-        
+
     def compute(self, result):
         """return (entity type, value) to which self.term is evaluated according
         to the given result dictionnary and to query arguments (self.args)
@@ -353,7 +341,7 @@
         args = tuple(n.accept(self, result)[1] for n in node.children)
         value = self.functions[node.name](*args)
         return node.get_type(self._solution, self.args), value
-    
+
     def visit_variableref(self, node, result):
         value = result[node.name]
         try:
@@ -362,11 +350,11 @@
         except AttributeError:
             etype = self._solution[node.name]
         return etype, value
-    
+
     def visit_constant(self, node, result):
         return node.get_type(kwargs=self.args), node.eval(self.args)
-    
-        
+
+
 class RQLInterpreter(object):
     """algorithm:
     1. visit the restriction clauses and collect restriction for each subject
@@ -381,7 +369,7 @@
            for each solution in select'solutions:
                1. resolve variables which have attribute restriction
                2. resolve relation restriction
-               3. resolve selection and add to global results 
+               3. resolve selection and add to global results
     """
     def __init__(self, schema):
         self.schema = schema
@@ -391,15 +379,15 @@
                              'UPPER': lambda x: x.upper()}
         for cb in SQL_CONNECT_HOOKS.get('sqlite', []):
             cb(self)
-            
+
     # emulate sqlite connection interface so we can reuse stored procedures
     def create_function(self, name, nbargs, func):
         self._stored_proc[name] = func
-        
+
     def create_aggregate(self, name, nbargs, func):
         self._stored_proc[name] = func
 
-        
+
     def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
         rqlst = self.rqlhelper.parse(operation, annotate=True)
         try:
@@ -409,7 +397,7 @@
         else:
             results, description = self.interpret(rqlst, parameters)
         return ResultSet(results, operation, parameters, description, rqlst=rqlst)
-        
+
     def interpret(self, node, kwargs, dsget=None):
         if dsget is None:
             self._dsget = Get
@@ -429,7 +417,7 @@
             results += pres
             description += pdescr
         return results, description
-    
+
     def visit_select(self, node, extra):
         constraints = {}
         if node.where is not None:
@@ -453,7 +441,7 @@
         for varname, restrictions in constraints.iteritems():
             for restr in restrictions[:]:
                 if isinstance(restr, EidRestriction):
-                    assert not varname in fixed    
+                    assert not varname in fixed
                     try:
                         value = restr.resolve(kwargs)
                         fixed[varname] = value
@@ -467,7 +455,7 @@
                 if isinstance(restr, AttributeRestriction):
                     toresolve.setdefault(varname, []).append(restr)
                 elif isinstance(restr, NotRelationRestriction) or (
-                    isinstance(restr, RelationRestriction) and 
+                    isinstance(restr, RelationRestriction) and
                     not restr.searched_var in fixed and restr.constraint_var in fixed):
                     toresolve.setdefault(varname, []).append(restr)
                 else:
@@ -507,7 +495,7 @@
                     partres.append({varname: value})
             elif not varname in partres[0]:
                 # cartesian product
-                for res in partres:                    
+                for res in partres:
                     res[varname] = values[0]
                 for res in partres[:]:
                     for value in values[1:]:
@@ -515,14 +503,14 @@
                         res[varname] = value
                         partres.append(res)
             else:
-                # union 
+                # union
                 for res in varpartres:
                     for value in values:
                         res = res.copy()
                         res[varname] = value
                         partres.append(res)
         #print 'partres', len(partres)
-        #print partres                        
+        #print partres
         # Note: don't check for empty partres since constant selection may still
         # produce result at this point
         # sort to get RelationRestriction before AttributeSelection
@@ -581,14 +569,14 @@
                     append_result(res, descr, i, j, value, etype)
         #print '--------->', res
         return res, descr
-    
-    def visit_and(self, node, constraints, extra): 
+
+    def visit_and(self, node, constraints, extra):
         for child in node.children:
             child.accept(self, constraints, extra)
     def visit_exists(self, node, constraints, extra):
         extra['has_exists'] = True
         self.visit_and(node, constraints, extra)
-    
+
     def visit_not(self, node, constraints, extra):
         for child in node.children:
             child.accept(self, constraints, extra)
@@ -596,7 +584,7 @@
             extra.pop(node)
         except KeyError:
             raise NotImplementedError()
-        
+
     def visit_relation(self, node, constraints, extra):
         if node.is_types_restriction():
             return
@@ -612,7 +600,7 @@
             self._visit_non_final_neged_relation(rschema, node, constraints)
         else:
             self._visit_non_final_relation(rschema, node, constraints)
-                
+
     def _visit_non_final_relation(self, rschema, node, constraints, not_=False):
         lhs, rhs = node.get_variable_parts()
         for v1, v2, prefix in ((lhs, rhs, 's'), (rhs, lhs, 'o')):
@@ -623,14 +611,14 @@
             if nbrels > 1:
                 constraints.setdefault(v1.name, []).append(
                     RelationRestriction(node, self._dsget, prefix))
-                # just init an empty list for v2 variable to avoid a 
+                # just init an empty list for v2 variable to avoid a
                 # TypeRestriction being added for it
                 constraints.setdefault(v2.name, [])
                 break
         else:
             constraints.setdefault(rhs.name, []).append(
                 VariableSelection(node, self._dsget, 's'))
-                
+
     def _visit_non_final_neged_relation(self, rschema, node, constraints):
         lhs, rhs = node.get_variable_parts()
         for v1, v2, prefix in ((lhs, rhs, 's'), (rhs, lhs, 'o')):
@@ -665,16 +653,16 @@
                     AttributeInRestriction(node, extra['kwargs']))
             else:
                 raise NotImplementedError()
-        
+
     def _not_implemented(self, *args, **kwargs):
         raise NotImplementedError()
-    
+
     visit_or = _not_implemented
     # shouldn't occurs
     visit_set = _not_implemented
     visit_insert = _not_implemented
     visit_delete = _not_implemented
-        
+
 
 from logging import getLogger
 from cubicweb import set_log_methods
--- a/goa/skel/loader.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/skel/loader.py	Thu May 14 12:50:34 2009 +0200
@@ -4,7 +4,7 @@
     from cubicweb import goa
     from cubicweb.goa.goaconfig import GAEConfiguration
     from cubicweb.goa.dbinit import create_user, create_groups
-    
+
     # compute application's root directory
     APPLROOT = dirname(abspath(__file__))
     # apply monkey patches first
--- a/goa/skel/main.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/skel/main.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 to change anything here.
 
 :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"
--- a/goa/skel/views.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/skel/views.py	Thu May 14 12:50:34 2009 +0200
@@ -1,10 +1,8 @@
 # custom application views
-
-from mx.DateTime import DateTime
+from datetime import date
 
-from cubicweb.web.views import baseviews
-from cubicweb.web.views.boxes import BoxTemplate
-from cubicweb.web.views.calendar import MONTHNAMES
+from cubicweb.utils import last_day
+from cubicweb.web.views import baseviews, boxes, calendar
 from cubicweb.web.htmlwidgets import BoxLink, BoxWidget
 
 _ = unicode
@@ -12,15 +10,15 @@
 
 class BlogEntryPrimaryView(baseviews.PrimaryView):
     accepts = ('BlogEntry',)
-    
+
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         self.w(u'<h1>%s</h1>' % entity.dc_title())
         entity.view('metadata', w=self.w)
         self.w(entity.printable_value('text'))
-        
+
 
-class BlogArchiveBox(BoxTemplate):
+class BlogArchiveBox(boxes.BoxTemplate):
     """side box usually displaying some related entities in a primary view"""
     id = 'blog_archives_box'
     title = _('blog archives')
@@ -37,12 +35,12 @@
                 blogmonths.append( (year, month) )
         box = BoxWidget(_('Blog archives'), id=self.id)
         for year, month in blogmonths:
-            firstday = DateTime(year, month, 1)
-            lastday = DateTime(year, month, firstday.days_in_month)
+            firstday = date(year, month, 1)
+            lastday = last_day(firstday)
             rql = ('Any B WHERE B is BlogEntry, B creation_date >= "%s", B creation_date <= "%s"'
                    % (firstday.strftime('%Y-%m-%d'), lastday.strftime('%Y-%m-%d')))
             url = self.build_url(rql=rql)
-            label = u'%s %s' % (_(MONTHNAMES[month-1]), year)
+            label = u'%s %s' % (_(calendar.MONTHNAMES[month-1]), year)
             box.append( BoxLink(url, label) )
         box.render(self.w)
 
--- a/goa/test/data/views.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/data/views.py	Thu May 14 12:50:34 2009 +0200
@@ -21,7 +21,7 @@
 
 class MyIndex(StartupView):
     id = 'index'
-    
+
     def call(self):
         ctx = template.Context({'user': self.req.user})
         return INDEX_TEMPLATE.render(ctx)
--- a/goa/test/unittest_db.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_db.py	Thu May 14 12:50:34 2009 +0200
@@ -10,28 +10,28 @@
 
 class Blog(db.Model):
     data = db.BlobProperty()
-    
+
 class DBTest(GAEBasedTC):
     config = GAEConfiguration('toto')
     config.global_set_option('use-google-auth', False)
-    
+
     MODEL_CLASSES = (Blog,)
 
     def test_set_none_relation(self):
-        eprop = self.add_entity('EProperty', pkey=u'ui.language', value=u'en')
+        eprop = self.add_entity('CWProperty', pkey=u'ui.language', value=u'en')
         self.failUnless('s_for_user' in eprop._dbmodel)
         self.assertEquals(eprop._dbmodel['s_for_user'], None)
 
     def test_euser_key(self):
-        euser = self.add_entity('EUser', login=u'toto', upassword='toto')
+        euser = self.add_entity('CWUser', login=u'toto', upassword='toto')
         self.assertEquals(euser.key().name(), 'key_toto')
-        
+
     def test_egroup_key(self):
-        egroup = self.add_entity('EGroup', name=u'toto')
+        egroup = self.add_entity('CWGroup', name=u'toto')
         self.assertEquals(egroup.key().name(), 'key_toto')
 
     def test_password_encryption(self):
-        euser = self.add_entity('EUser', login=u'toto', upassword='toto')
+        euser = self.add_entity('CWUser', login=u'toto', upassword='toto')
         self.failUnless(euser.upassword != 'toto', euser.upassword)
         self.assertEquals(crypt_password('toto', euser.upassword[:2]), euser.upassword)
 
@@ -40,7 +40,7 @@
         text = u'e'*501
         entity = self.add_entity('State', name=u'test', description=text)
         self.assertIsInstance(entity.description, unicode)
-        self.failIf(isinstance(entity.description, Text)) 
+        self.failIf(isinstance(entity.description, Text))
         self.assertEquals(entity.description, text)
 
     def test_long_accentued_text(self):
@@ -48,7 +48,7 @@
         text = u'é'*500
         entity = self.add_entity('State', name=u'test', description=text)
         self.assertIsInstance(entity.description, unicode)
-        self.failIf(isinstance(entity.description, Text)) 
+        self.failIf(isinstance(entity.description, Text))
         self.assertEquals(entity.description, text)
 
     def test_blob(self):
@@ -56,10 +56,10 @@
         entity = self.add_entity('Blog', data=data)
         self.assertIsInstance(entity.data, Binary)
         value = entity.data.getvalue()
-        self.failIf(isinstance(value, Blob)) 
+        self.failIf(isinstance(value, Blob))
         self.assertEquals(value, data)
-        
-        
+
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/goa/test/unittest_editcontroller.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_editcontroller.py	Thu May 14 12:50:34 2009 +0200
@@ -8,27 +8,27 @@
 from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect
 
 from cubicweb.goa.goaconfig import GAEConfiguration
-from cubicweb.entities.authobjs import EUser
+from cubicweb.entities.authobjs import CWUser
 
 
 class EditControllerTC(GAEBasedTC):
-    
+
     config = GAEConfiguration('toto')
     config.global_set_option('use-google-auth', False)
     config.global_set_option('schema-type', 'yams')
     config.global_set_option('included-cubes', ())
     config.global_set_option('included-yams-cubes', ('blog',))
-    
+
     MODEL_CLASSES = ()
     from cubicweb.web.views import editcontroller
     from cubicweb.entities import lib
     LOAD_APP_MODULES = (editcontroller, lib)
-    
+
     def setUp(self):
         GAEBasedTC.setUp(self)
         self.req = self.request()
         self.ctrl = self.get_ctrl(self.req)
-        
+
     def get_ctrl(self, req):
         return self.vreg.select(self.vreg.registry_objects('controllers', 'edit'),
                                 req=req, appli=self)
@@ -69,13 +69,13 @@
         """check behaviour of this controller without any form parameter"""
         self.req.form = {}
         self.assertRaises(ValidationError, self.publish, self.req)
-        
+
     def test_validation_unique(self):
-        """test creation of two linked entities"""        
+        """test creation of two linked entities"""
         user = self.user
-        self.req.form = {'eid': 'X', '__type:X': 'EUser',
-                         'login:X': self.user.login, 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
+        self.req.form = {'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': self.user.login, 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
@@ -84,13 +84,13 @@
         """checking that a manager user can edit itself"""
         self.skip('missing actual gae support, retry latter')
         user = self.user
-        basegroups = [str(eid) for eid, in self.req.execute('EGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
-        groupeids = [eid for eid, in self.req.execute('EGroup G WHERE G name in ("managers", "users")')]
+        basegroups = [str(eid) for eid, in self.req.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
+        groupeids = [eid for eid, in self.req.execute('CWGroup G WHERE G name in ("managers", "users")')]
         groups = [str(eid) for eid in groupeids]
         stateeid = [eid for eid, in self.req.execute('State S WHERE S name "activated"')][0]
         self.req.form = {
             'eid':       user.eid,
-            '__type:'+user.eid:    'EUser',
+            '__type:'+user.eid:    'CWUser',
             'login:'+user.eid:     unicode(user.login),
             'firstname:'+user.eid: u'Th\xe9nault',
             'surname:'+user.eid:   u'Sylvain',
@@ -115,10 +115,10 @@
         user = self.create_user('user')
         cnx = self.login('user')
         req = self.request()
-        #self.assertEquals(self.ctrl.schema['EUser']._groups['read'],
+        #self.assertEquals(self.ctrl.schema['CWUser']._groups['read'],
         #                  ('managers', 'users'))
         req.form = {
-            'eid': user.eid, '__type:'+user.eid: 'EUser',
+            'eid': user.eid, '__type:'+user.eid: 'CWUser',
             '__maineid' : str(user.eid),
             'upassword:'+user.eid: 'tournicoton',
             'upassword-confirm:'+user.eid: 'tournicoton',
@@ -134,10 +134,10 @@
         relations (meaning no changes)
         """
         user = self.user
-        groupeids = [eid for eid, in self.req.execute('EGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
+        groupeids = [eid for eid, in self.req.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
         self.req.form = {
             'eid':       user.eid,
-            '__type:'+user.eid:    'EUser',
+            '__type:'+user.eid:    'CWUser',
             'login:'+user.eid:     unicode(user.login),
             'firstname:'+user.eid: u'Th\xe9nault',
             'surname:'+user.eid:   u'Sylvain',
@@ -155,23 +155,23 @@
         self.assertUnorderedIterableEquals([g.eid for g in e.in_group], groupeids)
         #stateeids = [eid for eid, in self.req.execute('State S WHERE S name "activated"')]
         #self.assertEquals([s.eid for s in e.in_state], stateeids)
-        
-        
+
+
     def test_create_multiple_linked(self):
-        gueid = self.req.execute('EGroup G WHERE G name "users"')[0][0]
+        gueid = self.req.execute('CWGroup G WHERE G name "users"')[0][0]
         self.req.form = {'eid': ['X', 'Y'],
-                         
-                         '__type:X': 'EUser',
+
+                         '__type:X': 'CWUser',
                          '__maineid' : 'X',
-                         'login:X': u'adim', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
+                         'login:X': u'adim', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
                          'surname:X': u'Di Mascio', 'edits-surname:X': '',
 
-                         'in_group:X': gueid, 'edits-in_group:X': INTERNAL_FIELD_VALUE, 
-                         
+                         'in_group:X': gueid, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE, 
+                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
                          }
         path, params = self.expect_redirect_publish()
         # should be redirected on the created person
@@ -180,17 +180,17 @@
         self.assertEquals(e.surname, 'Di Mascio')
         email = e.use_email[0]
         self.assertEquals(email.address, 'dima@logilab.fr')
-        
+
     def test_edit_multiple_linked(self):
         peid = self.create_user('adim').eid
         self.req.form = {'eid': [peid, 'Y'],
-                         '__type:%s'%peid: 'EUser',
+                         '__type:%s'%peid: 'CWUser',
                          'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: '',
-                         
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
                          'use_email:%s'%peid: 'Y', 'edits-use_email:%s'%peid: INTERNAL_FIELD_VALUE,
-                         
+
                          '__redirectrql': 'Any X WHERE X eid %s'%peid,
                          }
         path, params = self.expect_redirect_publish()
@@ -200,14 +200,14 @@
         self.assertEquals(e.surname, 'Di Masci')
         email = e.use_email[0]
         self.assertEquals(email.address, 'dima@logilab.fr')
-        
+
         emaileid = email.eid
         self.req.form = {'eid': [peid, emaileid],
-                         '__type:%s'%peid: 'EUser',
+                         '__type:%s'%peid: 'CWUser',
                          'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: 'Di Masci',
                          '__type:%s'%emaileid: 'EmailAddress',
                          'address:%s'%emaileid: u'adim@logilab.fr', 'edits-address:%s'%emaileid: 'dima@logilab.fr',
-                         'use_email:%s'%peid: emaileid, 'edits-use_email:%s'%peid: emaileid, 
+                         'use_email:%s'%peid: emaileid, 'edits-use_email:%s'%peid: emaileid,
                          '__redirectrql': 'Any X WHERE X eid %s'%peid,
                          }
         path, params = self.expect_redirect_publish()
@@ -220,28 +220,28 @@
         email = e.use_email[0]
         self.assertEquals(email.address, 'adim@logilab.fr')
 
-        
+
     def test_password_confirm(self):
         """test creation of two linked entities
-        """        
+        """
         user = self.user
         self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'EUser',
-                         'login:X': u'toto', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'edits-upassword:X': u'', 
+                         'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': u'toto', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
         self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'EUser',
-                         'login:X': u'toto', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'', 
+                         'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': u'toto', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
 
     def test_req_pending_insert(self):
         """make sure req's pending insertions are taken into account"""
-        tmpgroup = self.add_entity('EGroup', name=u"test")
+        tmpgroup = self.add_entity('CWGroup', name=u"test")
         user = self.user
         self.req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
         path, params = self.expect_redirect_publish()
@@ -254,7 +254,7 @@
     def test_req_pending_delete(self):
         """make sure req's pending deletions are taken into account"""
         user = self.user
-        groupeid = self.req.execute('INSERT EGroup G: G name "test", U in_group G WHERE U eid %(x)s',
+        groupeid = self.req.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',
                                     {'x': user.eid})[0][0]
         usergroups = [gname for gname, in
                       self.req.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
@@ -273,13 +273,13 @@
         def custom_login_edit(self, formparams, value, relations):
             formparams['login'] = value.upper()
             relations.append('X login %(login)s')
-        EUser.custom_login_edit = custom_login_edit
+        CWUser.custom_login_edit = custom_login_edit
         try:
             user = self.user
             eid = repr(user.eid)
             self.req.form = {
                 'eid': eid,
-                '__type:'+eid:  'EUser',
+                '__type:'+eid:  'CWUser',
                 'login:'+eid: u'foo',
                 'edits-login:'+eid:  unicode(user.login),
                 }
@@ -287,8 +287,8 @@
             rset = self.req.execute('Any L WHERE X eid %(x)s, X login L', {'x': user.eid}, 'x')
             self.assertEquals(rset[0][0], 'FOO')
         finally:
-            del EUser.custom_login_edit
-        
+            del CWUser.custom_login_edit
+
     def test_redirect_apply_button(self):
         redirectrql = rql_for_eid(4012) # whatever
         self.req.form = {
@@ -355,21 +355,21 @@
         path, params = self.expect_redirect_publish()
         self.assertEquals(path, 'view')
         self.assertEquals(params, {u'__message': u'entities deleted'})
-        
+
 
     def test_nonregr_multiple_empty_email_addr(self):
-        gueid = self.req.execute('EGroup G WHERE G name "users"')[0][0]
+        gueid = self.req.execute('CWGroup G WHERE G name "users"')[0][0]
         self.req.form = {'eid': ['X', 'Y'],
-                         
-                         '__type:X': 'EUser',
-                         'login:X': u'adim', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
-                         'in_group:X': gueid, 'edits-in_group:X': INTERNAL_FIELD_VALUE, 
-                         
+
+                         '__type:X': 'CWUser',
+                         'login:X': u'adim', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                         'in_group:X': gueid, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'', 'edits-address:Y': '',
                          'alias:Y': u'', 'edits-alias:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE, 
+                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
@@ -385,8 +385,8 @@
             self.req.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',
                          {'p' : p.eid, 'e' : e.eid})
             self.req.form = {'__cloned_eid:X': p.eid,
-                             'eid': 'X', '__type:X': 'EUser',
-                             'login': u'dodo', 'edits-login': u'dodo', 
+                             'eid': 'X', '__type:X': 'CWUser',
+                             'login': u'dodo', 'edits-login': u'dodo',
                              'surname:X': u'Boom', 'edits-surname:X': u'',
                              '__errorurl' : "whatever but required",
                              }
@@ -400,12 +400,12 @@
                 self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
                 self.req.form['vid'] = 'copy'
                 self.env.app.publish('view', self.req)
-            rset = self.req.execute('EUser P WHERE P surname "Boom"')
+            rset = self.req.execute('CWUser P WHERE P surname "Boom"')
             self.assertEquals(len(rset), 0)
         finally:
             p.__class__.skip_copy_for = old_skips
 
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/goa/test/unittest_metadata.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_metadata.py	Thu May 14 12:50:34 2009 +0200
@@ -7,7 +7,7 @@
 
 from google.appengine.api import datastore
 
-class Article(db.Model):        
+class Article(db.Model):
     content = db.TextProperty()
     synopsis = db.StringProperty(default='hello')
 
@@ -15,20 +15,20 @@
     diem = db.DateProperty(required=True, auto_now_add=True)
     title = db.StringProperty(required=True)
     content = db.TextProperty()
-    talks_about = db.ReferenceProperty(Article) 
-    cites = db.SelfReferenceProperty() 
+    talks_about = db.ReferenceProperty(Article)
+    cites = db.SelfReferenceProperty()
 
-  
+
 class MetaDataTC(GAEBasedTC):
     MODEL_CLASSES = (Article, Blog)
-    
+
     def setUp(self):
         GAEBasedTC.setUp(self)
         self.req = self.request()
         self.a = self.add_entity('Article')
-        self.p = self.add_entity('EProperty', pkey=u'ui.language', value=u'en')
+        self.p = self.add_entity('CWProperty', pkey=u'ui.language', value=u'en')
         self.session.commit()
-        
+
     def _test_timestamp(self, entity, attr, sleep=0.1):
         timestamp = getattr(entity, attr)
         self.failUnless(timestamp)
@@ -41,20 +41,20 @@
             entity.set_attributes(value=u'en')
         self.session.commit()
         return timestamp
-    
+
     def test_creation_date_dbmodel(self):
         cdate = self._test_timestamp(self.a, 'creation_date')
         self.assertEquals(cdate, self.a.creation_date)
-        
+
     def test_creation_date_yams(self):
         cdate = self._test_timestamp(self.p, 'creation_date')
         self.assertEquals(cdate, self.p.creation_date)
-        
+
     def test_modification_date_dbmodel(self):
         mdate = self._test_timestamp(self.a, 'modification_date', sleep=1)
         a = self.execute('Any X WHERE X eid %(x)s', {'x': self.a.eid}, 'x').get_entity(0, 0)
         self.failUnless(mdate < a.modification_date, (mdate, a.modification_date))
-        
+
     def test_modification_date_yams(self):
         mdate = self._test_timestamp(self.p, 'modification_date', sleep=1)
         p = self.execute('Any X WHERE X eid %(x)s', {'x': self.p.eid}, 'x').get_entity(0, 0)
@@ -67,10 +67,10 @@
         dbmodel = entity.to_gae_model()
         self.assertEquals(len(dbmodel['s_owned_by']), 1)
         self.assertIsInstance(dbmodel['s_owned_by'][0], datastore.Key)
-        
+
     def test_owned_by_dbmodel(self):
         self._test_owned_by(self.a)
-        
+
     def test_owned_by_yams(self):
         self._test_owned_by(self.p)
 
@@ -79,16 +79,16 @@
         creator = entity.created_by[0]
         self.assertIsInstance(creator, db.Model)
         self.assertIsInstance(entity.to_gae_model()['s_created_by'], datastore.Key)
-        
+
     def test_created_by_dbmodel(self):
         self._test_created_by(self.a)
-        
+
     def test_created_by_dbmodel(self):
         self._test_created_by(self.p)
-        
+
     def test_user_owns_dbmodel(self):
         self.failUnless(self.req.user.owns(self.a.eid))
-        
+
     def test_user_owns_yams(self):
         self.failUnless(self.req.user.owns(self.p.eid))
 
@@ -96,11 +96,11 @@
         en = self.execute('Any EN WHERE E name EN, X is E, X eid %(x)s', {'x': self.a.eid}, 'x')[0][0]
         self.assertEquals(en, 'Article')
         en = self.execute('Any EN WHERE E name EN, X is E, X eid %(x)s', {'x': self.p.eid}, 'x')[0][0]
-        self.assertEquals(en, 'EProperty') 
+        self.assertEquals(en, 'CWProperty')
         en = self.execute('Any EN WHERE E name EN, X is E, X eid %(x)s', {'x': self.req.user.eid}, 'x')[0][0]
-        self.assertEquals(en, 'EUser')
+        self.assertEquals(en, 'CWUser')
 
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/goa/test/unittest_rql.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_rql.py	Thu May 14 12:50:34 2009 +0200
@@ -32,7 +32,7 @@
 
 # end stored procedure definition #############################################
 
-class Article(db.Model):        
+class Article(db.Model):
     content = db.TextProperty()
     synopsis = db.StringProperty(default=u'hello')
 
@@ -40,14 +40,14 @@
     diem = db.DateProperty(required=True, auto_now_add=True)
     content = db.TextProperty()
     itemtype = db.StringProperty(required=True, choices=(u'personal', u'business'))
-    talks_about = db.ReferenceProperty(Article) 
-    cites = db.SelfReferenceProperty() 
+    talks_about = db.ReferenceProperty(Article)
+    cites = db.SelfReferenceProperty()
     data = db.BlobProperty()
 
-    
+
 class RQLTest(GAEBasedTC):
     MODEL_CLASSES = (Article, Blog)
-    
+
     def setUp(self):
         GAEBasedTC.setUp(self)
         # hack to make talks_about cardinality to ** instead of ?*
@@ -59,13 +59,13 @@
         self.execute('SET X talks_about Y WHERE X eid %(x)s, Y eid %(y)s',
                      {'x': self.blog.eid, 'y': self.article.eid})
         self.commit()
-        
+
     def _check_rset_size(self, rset, row, col):
         self.assertEquals(len(rset), row)
         self.assertEquals(len(rset[0]), col)
         self.assertEquals(len(rset.description), row)
         self.assertEquals(len(rset.description[0]), col)
-        
+
     def _check_blog_rset(self, rset):
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset.description[0][0], 'Blog')
@@ -121,20 +121,20 @@
         self.assertEquals(len(rset), 2)
         self.assertEquals(rset.description, [('Blog',), ('Blog',)])
 
-        
+
     def test_2_attribute_selection_1(self):
         rset = self.req.execute('Any X,D,C WHERE X is Blog, X diem D, X content C')
         self._check_rset_size(rset, 1, 3)
         self.assertEquals(rset[0], [self.blog.eid, today(), u'hop'])
         self.assertEquals(rset.description[0], ('Blog', 'Date', 'String'))
         self.assertIsInstance(rset[0][1], DateTimeType)
-        
+
     def test_2_attribute_selection_2(self):
         rset = self.req.execute('Any D,C WHERE X is Blog, X diem D, X content C')
         self._check_rset_size(rset, 1, 2)
         self.assertEquals(rset[0], [today(), u'hop'])
         self.assertEquals(rset.description[0], ('Date', 'String'))
-        
+
     def test_2_attribute_selection_binary(self):
         rset = self.req.execute('Any D WHERE X is Blog, X data D')
         self._check_rset_size(rset, 1, 1)
@@ -147,19 +147,19 @@
         self.assertIsInstance(rset[0][0], Binary)
         value = rset[0][0].getvalue()
         self.assertIsInstance(value, str)
-        self.failIf(isinstance(value, Blob)) 
+        self.failIf(isinstance(value, Blob))
         self.assertEquals(value, 'raw data')
         self.assertEquals(rset.description[0], ('Bytes',))
-        
+
     def test_2_attribute_selection_long_text(self):
         self.blog['content'] = text = 'a'*501
         self.blog.put()
         rset = self.req.execute('Any C WHERE X is Blog, X content C')
         self._check_rset_size(rset, 1, 1)
         self.assertIsInstance(rset[0][0], unicode)
-        self.failIf(isinstance(rset[0][0], Text)) 
+        self.failIf(isinstance(rset[0][0], Text))
         self.assertEquals(rset[0][0], text)
-        
+
     def test_2_attribute_selection_transformation(self):
         rset = self.req.execute('Any X,UPPER(C) WHERE X is Blog, X content C')
         self._check_rset_size(rset, 1, 2)
@@ -172,15 +172,15 @@
         self._check_blog_rset(rset)
         rset = self.req.execute('Any X WHERE X itemtype "business"')
         self.assertEquals(len(rset), 0)
-        
+
     def test_3_ambigous_attribute_restriction_1(self):
         rset = self.req.execute('Any X WHERE X content "hello"')
         self.assertEquals(len(rset), 0)
-        
+
     def test_3_ambigous_attribute_restriction_2(self):
         rset = self.req.execute('Any X WHERE X content "hop"')
         self._check_blog_rset(rset)
-        
+
     def test_3_ambigous_attribute_restriction_3(self):
         article = Article(content=u'hop')
         article.put()
@@ -193,11 +193,11 @@
         rset = self.req.execute('Any X WHERE X eid %(x)s, X content "hola"',
                                 {'x': self.blog.eid})
         self.assertEquals(len(rset), 0)
-        
+
     def test_3_multiple_attribute_restriction(self):
         rset = self.req.execute('Any X WHERE X content "hop", X itemtype "personal"')
         self._check_blog_rset(rset)
-        
+
     def test_3_incoherant_multiple_attribute_restriction(self):
         rset = self.req.execute('Any X WHERE X content "hip", X itemtype "personal"')
         self.assertEquals(len(rset), 0)
@@ -234,7 +234,7 @@
         repo = self.config.repository()
         versions = repo.get_versions()
         self.assertEquals(versions.keys(), ['cubicweb'])
-    
+
     def _setup_relation_description(self):
         self.article2 = self.add_entity('Article', content=u'hop')
         self.blog2 = self.add_entity('Blog', itemtype=u'personal', content=u'hip')
@@ -242,7 +242,7 @@
                      {'x': self.blog2.eid, 'y': self.article2.eid})
         self.blog3 = self.add_entity('Blog', itemtype=u'business', content=u'hep')
         self.commit()
-        
+
     def test_4_relation_restriction_1(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X WHERE X talks_about Y')
@@ -250,7 +250,7 @@
         self.assertUnorderedIterableEquals([r[0] for r in rset],
                              [self.blog.eid, self.blog2.eid])
         self.assertUnorderedIterableEquals([r[0] for r in rset.description], ['Blog', 'Blog'])
-        
+
     def test_4_relation_restriction_2(self):
         self._setup_relation_description()
         rset = self.req.execute('Any Y WHERE X talks_about Y')
@@ -259,7 +259,7 @@
                              [self.article.eid, self.article2.eid])
         self.assertUnorderedIterableEquals([r[0] for r in rset.description],
                              ['Article', 'Article'])
-        
+
     def test_4_relation_restriction_3(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,Y WHERE X talks_about Y')
@@ -270,7 +270,7 @@
         self.assertUnorderedIterableEquals([tuple(r) for r in rset.description],
                              [('Blog', 'Article'),
                               ('Blog', 'Article')])
-        
+
     def test_4_relation_restriction_4(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,Y WHERE X talks_about Y, X eid %(x)s',
@@ -278,7 +278,7 @@
         self._check_rset_size(rset, 1, 2)
         self.assertEquals(rset[0], [self.blog.eid, self.article.eid])
         self.assertUnorderedIterableEquals(rset.description[0], ['Blog', 'Article'])
-        
+
     def test_4_relation_restriction_5(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,Y WHERE X talks_about Y, Y eid %(x)s',
@@ -286,7 +286,7 @@
         self._check_rset_size(rset, 1, 2)
         self.assertEquals(rset[0], [self.blog.eid, self.article.eid])
         self.assertUnorderedIterableEquals(rset.description[0], ['Blog', 'Article'])
-        
+
     def test_4_relation_subject_restriction(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,Y WHERE X talks_about Y, X content %(c)s',
@@ -294,7 +294,7 @@
         self._check_rset_size(rset, 1, 2)
         self.assertEquals(rset[0], [self.blog.eid, self.article.eid])
         self.assertUnorderedIterableEquals(rset.description[0], ['Blog', 'Article'])
-        
+
     def test_4_relation_object_restriction(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X WHERE X is Blog, X talks_about Y, Y content %(c)s',
@@ -302,7 +302,7 @@
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset[0], [self.blog.eid])
         self.assertUnorderedIterableEquals(rset.description[0], ['Blog'])
-        
+
     def test_4_relation_subject_object_restriction(self):
         article2 = self.add_entity('Article', content=u'very interesting')
         rset = self.req.execute('Any X,XC WHERE X is Blog, X content XC, X content %(xc)s, '
@@ -311,20 +311,20 @@
         self._check_rset_size(rset, 1, 2)
         self.assertEquals(rset[0], [self.blog.eid, self.blog.content])
         self.assertUnorderedIterableEquals(rset.description[0], ['Blog', 'String'])
-        
+
     def test_4_relation_subject_object_restriction_no_res(self):
         article2 = self.add_entity('Article', content=u'very interesting')
         rset = self.req.execute('Any X,XC WHERE X is Blog, X content XC, X content %(xc)s, '
                                 'X talks_about Y, Y content %(c)s',
                                 {'xc': 'hip', 'c': 'very interesting'})
         self.assertEquals(len(rset), 0)
-        
+
     def test_4_relation_subject_object_restriction_no_res_2(self):
         rset = self.req.execute('Any X,XC WHERE X is Blog, X content XC, X content %(xc)s, '
                                 'X talks_about Y, Y content %(c)s',
                                 {'xc': 'hop', 'c': 'not interesting'})
         self.assertEquals(len(rset), 0)
-        
+
     def test_4_relation_restriction_7(self):
         self._setup_relation_description()
         rset = self.req.execute('Any XC,XD,YC WHERE X talks_about Y, Y eid %(x)s,'
@@ -333,7 +333,7 @@
         self._check_rset_size(rset, 1, 3)
         self.assertEquals(rset[0], [self.blog.content, self.blog.diem, self.article.content])
         self.assertUnorderedIterableEquals(rset.description[0], ['String', 'Date', 'String'])
-        
+
     def test_4_relation_restriction_8(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,Y WHERE X cites Y, Y eid %(x)s', {'x': self.blog.eid})
@@ -346,7 +346,7 @@
         rset = self.req.execute('Any X,Y WHERE X talks_about Y, X eid %(x)s, Y eid %(y)s',
                                 {'x': self.blog.eid, 'y': article2.eid})
         self._check_rset_size(rset, 1, 2)
-        
+
     def test_4_ambiguous_subject_relation(self):
         ye = self.add_entity('YamsEntity')
         self.req.execute('SET X ambiguous_relation Y WHERE X eid %(x)s, Y eid %(y)s',
@@ -365,7 +365,7 @@
         self._check_rset_size(rset, 2, 1)
         self.assertUnorderedIterableEquals([r[0] for r in rset], [self.blog.eid, self.article.eid])
         self.assertUnorderedIterableEquals([r[0] for r in rset.description], ['Blog', 'Article'])
-        
+
     def test_4_relation_selection(self):
         req = self.request()
         rset = req.execute('Any N WHERE G content N, U talks_about G, U eid %(u)s', {'u': self.blog.eid})
@@ -381,7 +381,7 @@
                           [[self.blog3.eid, 'hep'],
                            [self.blog2.eid, 'hip'],
                            [self.blog.eid, 'hop']])
-                           
+
     def test_5_orderby_desc(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,XC ORDERBY XC DESC WHERE X is Blog, X content XC')
@@ -417,7 +417,7 @@
                           [[self.blog.eid, 'hop', 'personal'],
                            [self.blog2.eid, 'hip', 'personal'],
                            [self.blog3.eid, 'hep', 'business']])
-        
+
     def test_5_orderby_several_terms_mixed_order(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X,XC,XI ORDERBY XI ASC,XC DESC WHERE X is Blog, X content XC, X itemtype XI')
@@ -449,25 +449,25 @@
                                 'WHERE X is Blog, X itemtype XIT')
         self._check_rset_size(rset, 2, 1)
         self.assertEquals(rset.rows, [[self.blog.eid], [blog2.eid]])
-                          
-        
+
+
     def test_6_limit(self):
         self._setup_relation_description()
         rset = self.req.execute('Any X LIMIT 2 WHERE X is Blog')
         self._check_rset_size(rset, 2, 1)
-        
+
     def test_6_offset(self):
         self._setup_relation_description()
         rset = self.req.execute('Any XC ORDERBY XC DESC OFFSET 1 WHERE X is Blog, X content XC')
         self._check_rset_size(rset, 2, 1)
         self.assertEquals(rset.rows, [['hip'], ['hep']])
-        
+
     def test_6_limit_and_orderby(self):
         self._setup_relation_description()
         rset = self.req.execute('Any XC ORDERBY XC LIMIT 2 WHERE X is Blog, X content XC')
         self._check_rset_size(rset, 2, 1)
         self.assertEquals(rset.rows, [['hep'], ['hip']])
-        
+
     def test_6_limit_offset_and_orderby(self):
         self._setup_relation_description()
         rset = self.req.execute('Any XC ORDERBY XC LIMIT 2 OFFSET 0 WHERE X is Blog, X content XC')
@@ -481,7 +481,7 @@
         self.assertEquals(rset.rows, [['hop']])
         rset = self.req.execute('Any XC ORDERBY XC LIMIT 2 OFFSET 3 WHERE X is Blog, X content XC')
         self.failIf(rset)
-        
+
 
     def test_7_simple_datetimecast(self):
         self._setup_relation_description()
@@ -496,7 +496,7 @@
         rset = self.req.execute('Any X WHERE X is Blog, X creation_date <= "%s"'
                                 % _tomorrow.strftime('%Y-%m-%d'))
         self._check_rset_size(rset, 3, 1)
-        
+
     def test_7_identity_relation(self):
         rset = self.req.execute('Any X WHERE X identity Y, X eid %(x)s, Y eid %(y)s',
                                 {'x': self.user.eid, 'y': self.user.eid})
@@ -509,13 +509,13 @@
         rset = self.req.execute('Any X WHERE X identity Y, X eid %(x)s, Y eid %(y)s',
                                 {'x': self.blog.eid, 'y': blog2.eid})
         self.failIf(rset)
-        
+
     def test_8_not_relation_1(self):
         rset = self.req.execute('Any X WHERE X identity U, NOT U in_group G, '
                                 'G name "guests", X eid %(x)s, U eid %(u)s',
                                 {'x': self.user.eid, 'u': self.user.eid})
         self._check_rset_size(rset, 1, 1)
-        self.assertEquals(rset.rows, [[self.user.eid]])        
+        self.assertEquals(rset.rows, [[self.user.eid]])
 
     def test_8_not_relation_linked_subject(self):
         rset = self.req.execute('Any X WHERE NOT X talks_about Y, Y eid %(y)s',
@@ -524,7 +524,7 @@
         blog2 = self.add_entity('Blog', content=u'hop', itemtype=u'personal')
         self.commit()
         rset = self.req.execute('Any X WHERE NOT X talks_about Y, Y eid %(y)s',
-                                {'y': self.article.eid})        
+                                {'y': self.article.eid})
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset.rows, [[blog2.eid]])
 
@@ -541,7 +541,7 @@
 
     def test_8_not_relation_linked_attr(self):
         self.skip('not yet implemented')
-        # TODO: this should generated 
+        # TODO: this should generated
         # Query(X)[s_talks_about] > "hop" || Query(X)[s_talks_about] < "hop"
         article2 = self.add_entity('Article', content=u'hop')
         self.req.execute('SET X talks_about Y WHERE X eid %(x)s, Y eid %(y)s',
@@ -564,15 +564,15 @@
         rset = self.req.execute('Any Y WHERE NOT X talks_about Y')
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset.rows, [[article2.eid]])
-        
+
     def test_8_not_relation_final_1(self):
-        rset = self.req.execute('Any G WHERE G is EGroup, NOT G name "guests"')
+        rset = self.req.execute('Any G WHERE G is CWGroup, NOT G name "guests"')
         self._check_rset_size(rset, 2, 1)
         self.assertUnorderedIterableEquals([g.name for g in rset.entities()],
-                                           ['users', 'managers'])        
-        
+                                           ['users', 'managers'])
+
     def test_8_not_relation_final_2(self):
-        rset = self.req.execute('Any GN WHERE G is EGroup, NOT G name "guests", G name GN')
+        rset = self.req.execute('Any GN WHERE G is CWGroup, NOT G name "guests", G name GN')
         self._check_rset_size(rset, 2, 1)
         self.assertUnorderedIterableEquals([gn for gn, in rset.rows],
                                            ['users', 'managers'])
@@ -587,8 +587,8 @@
         rset = self.req.execute('Any X WHERE X is Blog, EXISTS(X talks_about Y)')
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset.rows, [[self.blog.eid]])
-        
-        
+
+
     def test_error_unknown_eid(self):
         rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': '1234'})
         self.assertEquals(len(rset), 0)
@@ -603,6 +603,6 @@
         rset = self.execute('Any X WHERE Y inlined_relation X, Y eid %(y)s', {'y': eid})
         self._check_rset_size(rset, 1, 1)
         self.assertEquals(rset[0][0], self.blog.eid)
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/goa/test/unittest_schema.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_schema.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 from cubicweb.goa.testlib import *
 
-class Article(db.Model):        
+class Article(db.Model):
     content = db.TextProperty()
     synopsis = db.StringProperty(default='hello')
 
@@ -8,10 +8,10 @@
     diem = db.DateProperty(required=True, auto_now_add=True)
     title = db.StringProperty(required=True)
     content = db.TextProperty()
-    talks_about = db.ReferenceProperty(Article) 
-    cites = db.SelfReferenceProperty() 
+    talks_about = db.ReferenceProperty(Article)
+    cites = db.SelfReferenceProperty()
 
-  
+
 class SomeViewsTC(GAEBasedTC):
     MODEL_CLASSES = (Article, Blog)
 
@@ -21,8 +21,8 @@
                              set(('Boolean', 'Bytes', 'Date', 'Datetime', 'Float',
                               'Decimal',
                               'Int', 'Interval', 'Password', 'String', 'Time',
-                              'EEType', 'EGroup', 'EPermission', 'EProperty', 'ERType',
-                              'EUser', 'EmailAddress',
+                              'CWEType', 'CWGroup', 'CWPermission', 'CWProperty', 'CWRType',
+                              'CWUser', 'EmailAddress',
                               'RQLExpression', 'State', 'Transition', 'TrInfo',
                               'Article', 'Blog', 'YamsEntity')))
         self.assertSetEquals(set(str(e) for e in schema.relations()),
@@ -66,7 +66,7 @@
                              ('ambiguous_relation', 'talks_about', 'identity'))
 
     def test_yams_imported(self):
-        eschema = self.schema['EProperty']
+        eschema = self.schema['CWProperty']
         # only relations defined in the class are actually ordered
         orels = [str(e) for e in eschema.ordered_relations()]
         orels, others = orels[:3], orels[3:]
@@ -77,7 +77,7 @@
                            'is', 'is_instance_of', 'modification_date', 'owned_by'])
         self.assertUnorderedIterableEquals((str(e) for e in eschema.object_relations()),
                              ('identity',))
-    
+
     def test_yams_ambiguous_relation(self):
         rschema = self.schema['ambiguous_relation']
         # only relations defined in the class are actually ordered
@@ -87,7 +87,7 @@
                              ('Blog', 'Article'))
 
     def test_euser(self):
-        eschema = self.schema['EUser']
+        eschema = self.schema['CWUser']
         # XXX pretend to have some relations it has not
         self.assertEquals([str(e) for e in eschema.ordered_relations()],
                           ['login', 'firstname', 'surname', 'last_login_time',
@@ -103,7 +103,7 @@
         self.assertEquals(rschema.objects(), ('Bytes',))
         self.assertEquals(rschema.rproperty('Blog', 'Bytes', 'cardinality'), '?1')
 
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/goa/test/unittest_views.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/test/unittest_views.py	Thu May 14 12:50:34 2009 +0200
@@ -25,28 +25,28 @@
             return [mydate]
         return []
 
-  
+
 class SomeViewsTC(GAEBasedTC):
     MODEL_CLASSES = (Blog, )
     from cubicweb.web.views import basecontrollers, baseviews, navigation, boxes, calendar
     from data import views
     LOAD_APP_MODULES = (basecontrollers, baseviews, navigation, boxes, calendar, views)
-    
+
     def setUp(self):
         GAEBasedTC.setUp(self)
         self.req = self.request()
         self.blog = Blog(title=u'a blog', content=u'hop')
         self.blog.put(self.req)
-        
+
     def test_hcal(self):
         self.vreg.render('views', 'hcal', self.req, rset=self.blog.rset)
-        
+
     def test_django_index(self):
         self.vreg.render('views', 'index', self.req, rset=None)
 
 for vid in ('primary', 'secondary', 'oneline', 'incontext', 'outofcontext', 'text'):
     setattr(SomeViewsTC, 'test_%s'%vid, lambda self, vid=vid: self.blog.view(vid))
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/goa/testlib.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/testlib.py	Thu May 14 12:50:34 2009 +0200
@@ -1,4 +1,11 @@
-from logilab.common.testlib import TestCase, mock_object
+"""
+: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.common.testlib import TestCase
 
 import os, os.path as osp
 import time
@@ -15,10 +22,9 @@
     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
+    # XXX necessary ?
     class db:
         class Model:
             pass
@@ -32,10 +38,8 @@
         ReferenceProperty = DummyProperty
         SelfReferenceProperty = DummyProperty
     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
@@ -59,11 +63,11 @@
 
     def load_schema_hook(self, loader):
         loader.import_yams_cube_schema('data')
-    
+
     @property
     def DS_FILE(self):
         return self.DS_TEMPL_FILE.replace('-template', '')
-    
+
     @property
     def DS_TEMPL_FILE(self):
         return self._DS_TEMPL_FILE + '_'.join(sorted(cls.__name__ for cls in self.MODEL_CLASSES))
@@ -75,7 +79,7 @@
         stub = datastore_file_stub.DatastoreFileStub(self.APP_ID, dsfile,
                                                      dsfile+'.history')
         apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)
-        
+
     def setUp(self):
         if import_appengine_failed:
             self.skip(import_appengine_failed)
@@ -91,7 +95,7 @@
             self._set_ds_file(self.DS_TEMPL_FILE)
 #         from google.appengine.api import mail_stub
 #         from google3.apphosting.api import urlfetch_stub
-#         from google3.apphosting.api import user_service_stub        
+#         from google3.apphosting.api import user_service_stub
 #         # Use a fresh stub UserService.
 #         apiproxy_stub_map.apiproxy.RegisterStub(
 #             'user', user_service_stub.UserServiceStub())
@@ -152,15 +156,15 @@
                 req = FakeRequest(vreg=self.vreg)
                 self.session = self.session_manager.open_session(req)
             self.user = self.session.user()
-            
+
     def tearDown(self):
         self.session.close()
-        
+
     def request(self):
         req = FakeRequest(vreg=self.vreg)
         req.set_connection(self.session, self.user)
         return req
-    
+
     def add_entity(self, etype, **kwargs):
         cu = self.session.cursor()
         rql = 'INSERT %s X' % etype
@@ -177,10 +181,10 @@
 
     def rollback(self):
         self.session.rollback()
-        
+
     def create_user(self, login, groups=('users',), req=None):
         assert not self.config['use-google-auth']
-        user = self.add_entity('EUser', upassword=str(login), login=unicode(login))
+        user = self.add_entity('CWUser', upassword=str(login), login=unicode(login))
         cu = self.session.cursor()
         cu.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
                     % ','.join(repr(g) for g in groups),
@@ -193,4 +197,3 @@
         req.form['__login'] = login
         req.form['__password'] = password or login
         return self.session_manager.open_session(req)
-        
--- a/goa/tools/generate_schema_img.py	Thu May 14 12:50:14 2009 +0200
+++ b/goa/tools/generate_schema_img.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 import sys
 from os.path import dirname, abspath, join
-from yams import schema2dot 
+from yams import schema2dot
 
 APPLROOT = abspath(join(dirname(abspath(__file__)), '..'))
 
@@ -9,7 +9,7 @@
 except ImportError:
     sys.path.insert(0, APPLROOT)
     import custom
-    
+
 
 schema = custom.SCHEMA
 skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
--- a/goa/tools/i18n.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/goa/tools/laxctl.py	Thu May 14 12:50:34 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,18 +17,13 @@
 
 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
-    from cubicweb.goa import do_monkey_patch    
+    from cubicweb.goa import do_monkey_patch
     do_monkey_patch()
     from cubicweb.goa.goavreg import GAERegistry
     from cubicweb.goa.goaconfig import GAEConfiguration
@@ -37,48 +32,21 @@
     vreg = GAERegistry(config)
     vreg.set_schema(config.load_schema())
     return vreg
-        
+
 def alistdir(directory):
     return [osp.join(directory, f) for f in os.listdir(directory)]
 
 
 class LaxCommand(Command):
     """base command class for all lax commands
-    creates vreg, schema and calls 
+    creates vreg, schema and calls
     """
     min_args = max_args = 0
 
     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):
     """generates the schema's png file"""
@@ -86,7 +54,7 @@
 
     def _run(self, args):
         assert not args, 'no argument expected'
-        from yams import schema2dot        
+        from yams import schema2dot
         schema = self.vreg.schema
         skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
         path = osp.join(APPLROOT, 'data', 'schema.png')
@@ -139,7 +107,7 @@
 class GetSessionIdHandler(urllib2.HTTPRedirectHandler):
     def __init__(self, config):
         self.config = config
-        
+
     def http_error_303(self, req, fp, code, msg, headers):
         cookie = SimpleCookie(headers['Set-Cookie'])
         sessionid = cookie['__session'].value
@@ -147,7 +115,7 @@
         setattr(self.config, 'cookie', '__session=' + sessionid)
         return 1 # on exception should be raised
 
-    
+
 class URLCommand(LaxCommand):
     """abstract class for commands doing stuff by accessing the web application
     """
@@ -170,7 +138,7 @@
           'help': 'user password instead of giving raw cookie string (require '
           'lax based authentication).'}),
         )
-    
+
     def _run(self, args):
         baseurl = args[0]
         if not baseurl.startswith('http'):
@@ -186,9 +154,9 @@
             urllib2.install_opener(opener)
             data = urlencode(dict(__login=self.config.user,
                                   __password=self.config.password))
-            self.open_url(urllib2.Request(baseurl, data))            
+            self.open_url(urllib2.Request(baseurl, data))
         opener = urllib2.build_opener(NoRedirectHandler())
-        urllib2.install_opener(opener)        
+        urllib2.install_opener(opener)
         self.do_base_url(baseurl)
 
     def build_req(self, url):
@@ -196,7 +164,7 @@
         if self.config.cookie:
             req.headers['Cookie'] = self.config.cookie
         return req
-    
+
     def open_url(self, req):
         try:
             return urllib2.urlopen(req)
@@ -226,11 +194,11 @@
             msg = remove_html_tags(match.group(1))
             print msg
             return msg
-        
+
     def do_base_url(self, baseurl):
         raise NotImplementedError()
 
-        
+
 class DSInitCommand(URLCommand):
     """initialize the datastore"""
     name = 'db-init'
@@ -242,7 +210,7 @@
           'help': 'number of seconds to wait between each request to avoid '
           'going out of quota.'}),
         )
-        
+
     def do_base_url(self, baseurl):
         req = self.build_req(baseurl + '?vid=contentinit')
         while True:
@@ -272,11 +240,9 @@
         req = self.build_req(baseurl + '?vid=cleansessions')
         data = self.open_url(req)
         self.extract_message(data)
-            
-    
-register_commands([I18nUpdateCommand,
-                   I18nCompileCommand,
-                   GenerateSchemaCommand,
+
+
+register_commands([GenerateSchemaCommand,
                    PopulateDataDirCommand,
                    DSInitCommand,
                    CleanSessionsCommand,
@@ -284,6 +250,6 @@
 
 def run():
     main_run(sys.argv[1:])
-    
+
 if __name__ == '__main__':
     run()
--- a/hercule.py	Thu May 14 12:50:14 2009 +0200
+++ b/hercule.py	Thu May 14 12:50:34 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,13 +11,13 @@
 
 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 ############################################################
 
 PAGER = os.environ.get('PAGER', 'less')
-            
+
 def pager_format_results(writer, layout):
     """pipe results to a pager like more or less"""
     (r, w) = os.pipe()
@@ -37,13 +37,13 @@
         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)):
         yield list1[i] + tuple(list2[i])
-        
-def format_results(writer, layout, stream=sys.stdout): 
+
+def format_results(writer, layout, stream=sys.stdout):
     """format result as text into the given file like object"""
     writer.format(layout, stream)
 
@@ -60,7 +60,7 @@
     return str(value)
 
 # command line querier ########################################################
-    
+
 class RQLCli(CLIHelper):
     """Interactive command line client for CubicWeb, allowing user to execute
     arbitrary RQL queries and to fetch schema information
@@ -74,10 +74,10 @@
         'description'  : "CubicWeb",
         'commit' :       "CubicWeb",
         'rollback' :     "CubicWeb",
-        'autocommit'  :  "Others", 
+        'autocommit'  :  "Others",
         'debug' :        "Others",
         })
-    
+
     def __init__(self, application=None, user=None, password=None,
                  host=None, debug=0):
         CLIHelper.__init__(self, os.path.join(os.environ["HOME"], ".erqlhist"))
@@ -94,7 +94,7 @@
         if application is not None:
             self.do_connect(application, user, password, host)
         self.do_debug(debug)
-        
+
     def do_connect(self, application, user=None, password=None, host=None):
         """connect to an cubicweb application"""
         from cubicweb.dbapi import connect
@@ -113,7 +113,7 @@
         self._completer.list = (self.commands.keys() +
                                 self.schema.entities() + ['Any'])
         print _('You are now connected to %s') % application
-        
+
 
     help_do_connect = ('connect', "connect <application> [<user> [<password> [<host>]]]",
                        _(do_connect.__doc__))
@@ -127,19 +127,19 @@
             self._format = pager_format_results
         if self._debug:
             print _('Debug level set to %s'%debug)
-        
+
     help_do_debug = ('debug', "debug [debug_level]", _(do_debug.__doc__))
-    
+
     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__))
-    
+
     def do_schema(self, name=None):
         """display information about the application schema """
         if self.cnx is None:
@@ -159,28 +159,28 @@
                 done = 1
         if done is None:
             print _('Unable to find anything named "%s" in the schema !') % name
-            
+
     help_do_schema = ('schema', "schema [keyword]", _(do_schema.__doc__))
 
-    
+
     def do_commit(self):
         """commit the current transaction"""
         self.cnx.commit()
 
     help_do_commit = ('commit', "commit", _(do_commit.__doc__))
-    
+
     def do_rollback(self):
         """rollback the current transaction"""
         self.cnx.rollback()
 
     help_do_rollback = ('rollback', "rollback", _(do_rollback.__doc__))
-    
+
     def do_autocommit(self):
         """toggle autocommit mode"""
         self.autocommit = not self.autocommit
 
     help_do_autocommit = ('autocommit', "autocommit", _(do_autocommit.__doc__))
-    
+
 
     def handle_line(self, stripped_line):
         """handle non command line :
@@ -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"""
@@ -232,7 +232,7 @@
     """A command line querier for CubicWeb, using the Relation Query Language.
 
     <application>
-      identifier of the application to connect to 
+      identifier of the application to connect to
     """
     name = 'client'
     arguments = '<application>'
@@ -247,7 +247,7 @@
           'help': 'file containing a batch of RQL statements to execute.',
           }),
         )
-    
+
     def run(self, args):
         """run the command with its specific arguments"""
         appid = pop_arg(args, expected_size_after=None)
@@ -271,5 +271,5 @@
                 cli.handle_line(line)
         else:
             cli.run()
-        
+
 register_commands((CubicWebClientCommand,))
--- a/i18n/en.po	Thu May 14 12:50:14 2009 +0200
+++ b/i18n/en.po	Thu May 14 12:50:34 2009 +0200
@@ -196,11 +196,71 @@
 msgid "Bytes_plural"
 msgstr "Bytes"
 
-msgid "Card"
-msgstr "Card"
-
-msgid "Card_plural"
-msgstr "Cards"
+msgid "CWAttribute"
+msgstr "Attribute"
+
+msgid "CWAttribute_plural"
+msgstr "Attributes"
+
+msgid "CWCache"
+msgstr ""
+
+msgid "CWCache_plural"
+msgstr ""
+
+msgid "CWConstraint"
+msgstr "Constraint"
+
+msgid "CWConstraintType"
+msgstr "Constraint type"
+
+msgid "CWConstraintType_plural"
+msgstr "Constraint types"
+
+msgid "CWConstraint_plural"
+msgstr "Constraints"
+
+msgid "CWEType"
+msgstr "Entity type"
+
+msgid "CWEType_plural"
+msgstr "Entity types"
+
+msgid "CWGroup"
+msgstr "Group"
+
+msgid "CWGroup_plural"
+msgstr "Groups"
+
+msgid "CWPermission"
+msgstr "Permission"
+
+msgid "CWPermission_plural"
+msgstr "Permissions"
+
+msgid "CWProperty"
+msgstr "Property"
+
+msgid "CWProperty_plural"
+msgstr "Properties"
+
+msgid "CWRType"
+msgstr "Relation type"
+
+msgid "CWRType_plural"
+msgstr "Relation types"
+
+msgid "CWRelation"
+msgstr "Relation"
+
+msgid "CWRelation_plural"
+msgstr "Relations"
+
+msgid "CWUser"
+msgstr "User"
+
+msgid "CWUser_plural"
+msgstr "Users"
 
 msgid "Date"
 msgstr "Date"
@@ -227,75 +287,6 @@
 msgid "Do you want to delete the following element(s) ?"
 msgstr ""
 
-msgid "ECache"
-msgstr ""
-
-msgid "ECache_plural"
-msgstr ""
-
-msgid "EConstraint"
-msgstr "Constraint"
-
-msgid "EConstraintType"
-msgstr "Constraint type"
-
-msgid "EConstraintType_plural"
-msgstr "Constraint types"
-
-msgid "EConstraint_plural"
-msgstr "Constraints"
-
-msgid "EEType"
-msgstr "Entity type"
-
-msgid "EEType_plural"
-msgstr "Entity types"
-
-msgid "EFRDef"
-msgstr "Attribute"
-
-msgid "EFRDef_plural"
-msgstr "Attributes"
-
-msgid "EGroup"
-msgstr "Group"
-
-msgid "EGroup_plural"
-msgstr "Groups"
-
-msgid "ENFRDef"
-msgstr "Relation"
-
-msgid "ENFRDef_plural"
-msgstr "Relations"
-
-msgid "EPermission"
-msgstr "Permission"
-
-msgid "EPermission_plural"
-msgstr "Permissions"
-
-msgid "EProperty"
-msgstr "Property"
-
-msgid "EProperty_plural"
-msgstr "Properties"
-
-msgid "ERType"
-msgstr "Relation type"
-
-msgid "ERType_plural"
-msgstr "Relation types"
-
-msgid "EUser"
-msgstr "User"
-
-msgid "EUser_plural"
-msgstr "Users"
-
-msgid "Email body: "
-msgstr ""
-
 msgid "EmailAddress"
 msgstr "Email address"
 
@@ -314,7 +305,7 @@
 msgid "Float_plural"
 msgstr "Floats"
 
-msgid "From: "
+msgid "From:"
 msgstr ""
 
 msgid "Int"
@@ -332,40 +323,37 @@
 msgid "New Bookmark"
 msgstr "New bookmark"
 
-msgid "New Card"
-msgstr "New card"
-
-msgid "New ECache"
-msgstr ""
-
-msgid "New EConstraint"
-msgstr "New constraint"
-
-msgid "New EConstraintType"
-msgstr "New constraint type"
-
-msgid "New EEType"
-msgstr "New entity type"
-
-msgid "New EFRDef"
+msgid "New CWAttribute"
 msgstr "New attribute"
 
-msgid "New EGroup"
+msgid "New CWCache"
+msgstr ""
+
+msgid "New CWConstraint"
+msgstr "New constraint"
+
+msgid "New CWConstraintType"
+msgstr "New constraint type"
+
+msgid "New CWEType"
+msgstr "New entity type"
+
+msgid "New CWGroup"
 msgstr "New group"
 
-msgid "New ENFRDef"
-msgstr "New relation"
-
-msgid "New EPermission"
+msgid "New CWPermission"
 msgstr "New permission"
 
-msgid "New EProperty"
+msgid "New CWProperty"
 msgstr "New property"
 
-msgid "New ERType"
+msgid "New CWRType"
 msgstr "New relation type"
 
-msgid "New EUser"
+msgid "New CWRelation"
+msgstr "New relation"
+
+msgid "New CWUser"
 msgstr "New user"
 
 msgid "New EmailAddress"
@@ -401,16 +389,13 @@
 msgid "Please note that this is only a shallow copy"
 msgstr ""
 
-msgid "Problem occured"
-msgstr ""
-
 msgid "RQLExpression"
 msgstr "RQL expression"
 
 msgid "RQLExpression_plural"
 msgstr "RQL expressions"
 
-msgid "Recipients: "
+msgid "Recipients:"
 msgstr ""
 
 msgid "Relations"
@@ -444,7 +429,7 @@
 msgid "String_plural"
 msgstr "Strings"
 
-msgid "Subject: "
+msgid "Subject:"
 msgstr ""
 
 msgid "Submit bug report"
@@ -471,40 +456,37 @@
 msgid "This Bookmark"
 msgstr "This bookmark"
 
-msgid "This Card"
-msgstr "This card"
-
-msgid "This ECache"
-msgstr ""
-
-msgid "This EConstraint"
-msgstr "This constraint"
-
-msgid "This EConstraintType"
-msgstr "This constraint type"
-
-msgid "This EEType"
-msgstr "This entity type"
-
-msgid "This EFRDef"
+msgid "This CWAttribute"
 msgstr "This attribute"
 
-msgid "This EGroup"
+msgid "This CWCache"
+msgstr ""
+
+msgid "This CWConstraint"
+msgstr "This constraint"
+
+msgid "This CWConstraintType"
+msgstr "This constraint type"
+
+msgid "This CWEType"
+msgstr "This entity type"
+
+msgid "This CWGroup"
 msgstr "This group"
 
-msgid "This ENFRDef"
-msgstr "This relation"
-
-msgid "This EPermission"
+msgid "This CWPermission"
 msgstr "This permission"
 
-msgid "This EProperty"
+msgid "This CWProperty"
 msgstr "This property"
 
-msgid "This ERType"
+msgid "This CWRType"
 msgstr "This relation type"
 
-msgid "This EUser"
+msgid "This CWRelation"
+msgstr "This relation"
+
+msgid "This CWUser"
 msgstr "This user"
 
 msgid "This EmailAddress"
@@ -596,11 +578,6 @@
 msgstr ""
 
 msgid ""
-"a card is a textual content used as documentation, reference, procedure "
-"reminder"
-msgstr ""
-
-msgid ""
 "a simple cache entity characterized by a name and a validity date. The "
 "target application is responsible for updating timestamp when necessary to "
 "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
@@ -646,6 +623,12 @@
 msgid "actions_delete_description"
 msgstr ""
 
+msgid "actions_download_as_owl"
+msgstr ""
+
+msgid "actions_download_as_owl_description"
+msgstr ""
+
 msgid "actions_edit"
 msgstr "modify"
 
@@ -676,6 +659,12 @@
 msgid "actions_manage_description"
 msgstr ""
 
+msgid "actions_managepermission"
+msgstr ""
+
+msgid "actions_managepermission_description"
+msgstr ""
+
 msgid "actions_muledit"
 msgstr "modify all"
 
@@ -745,49 +734,49 @@
 msgid "add"
 msgstr ""
 
-msgid "add Bookmark bookmarked_by EUser object"
+msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "bookmark"
 
-msgid "add EEType add_permission RQLExpression subject"
-msgstr "rql expression for the add permission"
-
-msgid "add EEType delete_permission RQLExpression subject"
-msgstr "rql expression for the delete permission"
-
-msgid "add EEType read_permission RQLExpression subject"
-msgstr "rql expression for the read permission"
-
-msgid "add EEType update_permission RQLExpression subject"
-msgstr "rql expression for the update permission"
-
-msgid "add EFRDef constrained_by EConstraint subject"
+msgid "add CWAttribute constrained_by CWConstraint subject"
 msgstr "constraint"
 
-msgid "add EFRDef relation_type ERType object"
+msgid "add CWAttribute relation_type CWRType object"
 msgstr "attribute definition"
 
-msgid "add ENFRDef constrained_by EConstraint subject"
-msgstr "constraint"
-
-msgid "add ENFRDef relation_type ERType object"
-msgstr "relation definition"
-
-msgid "add EProperty for_user EUser object"
+msgid "add CWEType add_permission RQLExpression subject"
+msgstr "rql expression for the add permission"
+
+msgid "add CWEType delete_permission RQLExpression subject"
+msgstr "rql expression for the delete permission"
+
+msgid "add CWEType read_permission RQLExpression subject"
+msgstr "rql expression for the read permission"
+
+msgid "add CWEType update_permission RQLExpression subject"
+msgstr "rql expression for the update permission"
+
+msgid "add CWProperty for_user CWUser object"
 msgstr "property"
 
-msgid "add ERType add_permission RQLExpression subject"
+msgid "add CWRType add_permission RQLExpression subject"
 msgstr "rql expression for the add permission"
 
-msgid "add ERType delete_permission RQLExpression subject"
+msgid "add CWRType delete_permission RQLExpression subject"
 msgstr "rql expression for the delete permission"
 
-msgid "add ERType read_permission RQLExpression subject"
+msgid "add CWRType read_permission RQLExpression subject"
 msgstr "rql expression for the read permission"
 
-msgid "add EUser in_group EGroup object"
+msgid "add CWRelation constrained_by CWConstraint subject"
+msgstr "constraint"
+
+msgid "add CWRelation relation_type CWRType object"
+msgstr "relation definition"
+
+msgid "add CWUser in_group CWGroup object"
 msgstr "user"
 
-msgid "add EUser use_email EmailAddress subject"
+msgid "add CWUser use_email EmailAddress subject"
 msgstr "email address"
 
 msgid "add State allowed_transition Transition object"
@@ -796,7 +785,7 @@
 msgid "add State allowed_transition Transition subject"
 msgstr "allowed transition"
 
-msgid "add State state_of EEType object"
+msgid "add State state_of CWEType object"
 msgstr "state"
 
 msgid "add Transition condition RQLExpression subject"
@@ -808,46 +797,43 @@
 msgid "add Transition destination_state State subject"
 msgstr "destination state"
 
-msgid "add Transition transition_of EEType object"
+msgid "add Transition transition_of CWEType object"
 msgstr "transition"
 
 msgid "add a Bookmark"
 msgstr "add a bookmark"
 
-msgid "add a Card"
-msgstr "add a card"
-
-msgid "add a ECache"
-msgstr ""
-
-msgid "add a EConstraint"
-msgstr "add a constraint"
-
-msgid "add a EConstraintType"
-msgstr "add a constraint type"
-
-msgid "add a EEType"
-msgstr "add an entity type"
-
-msgid "add a EFRDef"
+msgid "add a CWAttribute"
 msgstr "add an attribute"
 
-msgid "add a EGroup"
+msgid "add a CWCache"
+msgstr ""
+
+msgid "add a CWConstraint"
+msgstr "add a constraint"
+
+msgid "add a CWConstraintType"
+msgstr "add a constraint type"
+
+msgid "add a CWEType"
+msgstr "add an entity type"
+
+msgid "add a CWGroup"
 msgstr "add a group"
 
-msgid "add a ENFRDef"
-msgstr "add a relation"
-
-msgid "add a EPermission"
+msgid "add a CWPermission"
 msgstr "add a permission"
 
-msgid "add a EProperty"
+msgid "add a CWProperty"
 msgstr "add a property"
 
-msgid "add a ERType"
+msgid "add a CWRType"
 msgstr "add a relation type"
 
-msgid "add a EUser"
+msgid "add a CWRelation"
+msgstr "add a relation"
+
+msgid "add a CWUser"
 msgstr "add a user"
 
 msgid "add a EmailAddress"
@@ -922,9 +908,6 @@
 msgid "am/pm calendar (year)"
 msgstr ""
 
-msgid "an abstract for this card"
-msgstr ""
-
 msgid "an electronic mail address associated to a short alias"
 msgstr ""
 
@@ -962,6 +945,9 @@
 msgid "attribute"
 msgstr ""
 
+msgid "attributes with modified permissions:"
+msgstr ""
+
 msgid "august"
 msgstr ""
 
@@ -1115,6 +1101,9 @@
 msgid "cardinality"
 msgstr ""
 
+msgid "category"
+msgstr ""
+
 #, python-format
 msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
 msgstr ""
@@ -1194,12 +1183,6 @@
 msgid "components_rqlinput_description"
 msgstr "the rql box in the page's header"
 
-msgid "components_rss_feed_url"
-msgstr ""
-
-msgid "components_rss_feed_url_description"
-msgstr ""
-
 msgid "composite"
 msgstr ""
 
@@ -1230,12 +1213,6 @@
 msgid "constraints applying on this relation"
 msgstr ""
 
-msgid "content"
-msgstr ""
-
-msgid "content_format"
-msgstr "content format"
-
 msgid "contentnavigation"
 msgstr "contextual components"
 
@@ -1342,54 +1319,58 @@
 msgid "created_by_object"
 msgstr "has created"
 
-msgid "creating Bookmark (Bookmark bookmarked_by EUser %(linkto)s)"
+msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
 msgstr "creating bookmark for %(linkto)s"
 
-msgid "creating EConstraint (EFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "creating constraint for attribute %(linkto)s"
-
-msgid "creating EConstraint (ENFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "creating constraint for relation %(linkto)s"
-
-msgid "creating EFRDef (EFRDef relation_type ERType %(linkto)s)"
+msgid "creating CWAttribute (CWAttribute relation_type CWRType %(linkto)s)"
 msgstr "creating attribute %(linkto)s"
 
-msgid "creating ENFRDef (ENFRDef relation_type ERType %(linkto)s)"
-msgstr "creating relation %(linkto)s"
-
-msgid "creating EProperty (EProperty for_user EUser %(linkto)s)"
+msgid ""
+"creating CWConstraint (CWAttribute %(linkto)s constrained_by CWConstraint)"
+msgstr "creating constraint for attribute %(linkto)s"
+
+msgid ""
+"creating CWConstraint (CWRelation %(linkto)s constrained_by CWConstraint)"
+msgstr "creating constraint for relation %(linkto)s"
+
+msgid "creating CWProperty (CWProperty for_user CWUser %(linkto)s)"
 msgstr "creating property for user %(linkto)s"
 
-msgid "creating EUser (EUser in_group EGroup %(linkto)s)"
+msgid "creating CWRelation (CWRelation relation_type CWRType %(linkto)s)"
+msgstr "creating relation %(linkto)s"
+
+msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)"
 msgstr "creating a new user in group %(linkto)s"
 
-msgid "creating EmailAddress (EUser %(linkto)s use_email EmailAddress)"
+msgid "creating EmailAddress (CWUser %(linkto)s use_email EmailAddress)"
 msgstr "creating email address for user %(linkto)s"
 
-msgid "creating RQLExpression (EEType %(linkto)s add_permission RQLExpression)"
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
 msgstr "creating rql expression for add permission on %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s delete_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s delete_permission RQLExpression)"
 msgstr "creating rql expression for delete permission on %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s read_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s read_permission RQLExpression)"
 msgstr "creating rql expression for read permission on %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s update_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s update_permission RQLExpression)"
 msgstr "creating rql expression for update permission on %(linkto)s"
 
-msgid "creating RQLExpression (ERType %(linkto)s add_permission RQLExpression)"
+msgid ""
+"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
 msgstr "creating rql expression for add permission on relations %(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s delete_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
 msgstr "creating rql expression for delete permission on relations %(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s read_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
 msgstr "creating rql expression for read permission on relations %(linkto)s"
 
 msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
@@ -1398,7 +1379,7 @@
 msgid "creating State (State allowed_transition Transition %(linkto)s)"
 msgstr "creating a state able to trigger transition %(linkto)s"
 
-msgid "creating State (State state_of EEType %(linkto)s)"
+msgid "creating State (State state_of CWEType %(linkto)s)"
 msgstr "creating state for the %(linkto)s entity type"
 
 msgid "creating State (Transition %(linkto)s destination_state State)"
@@ -1410,7 +1391,7 @@
 msgid "creating Transition (Transition destination_state State %(linkto)s)"
 msgstr "creating transition leading to state %(linkto)s"
 
-msgid "creating Transition (Transition transition_of EEType %(linkto)s)"
+msgid "creating Transition (Transition transition_of CWEType %(linkto)s)"
 msgstr "creating transition for the %(linkto)s entity type"
 
 msgid "creation"
@@ -1434,6 +1415,10 @@
 msgid "csv export"
 msgstr ""
 
+#, python-format
+msgid "currently attached file: %s"
+msgstr ""
+
 msgid "data directory url"
 msgstr ""
 
@@ -1549,6 +1534,10 @@
 msgid "detach attached file"
 msgstr ""
 
+#, python-format
+msgid "detach attached file %s"
+msgstr ""
+
 msgid "detailed schema view"
 msgstr ""
 
@@ -1581,6 +1570,9 @@
 msgid "download icon"
 msgstr ""
 
+msgid "download schema as owl"
+msgstr ""
+
 msgid "edit bookmarks"
 msgstr ""
 
@@ -1620,9 +1612,18 @@
 msgid "entities deleted"
 msgstr ""
 
+msgid "entity copied"
+msgstr ""
+
+msgid "entity created"
+msgstr ""
+
 msgid "entity deleted"
 msgstr ""
 
+msgid "entity edited"
+msgstr ""
+
 msgid "entity type"
 msgstr ""
 
@@ -1729,6 +1730,10 @@
 msgid "from"
 msgstr ""
 
+#, python-format
+msgid "from %(date)s"
+msgstr ""
+
 msgid "from_entity"
 msgstr "from entity"
 
@@ -1753,6 +1758,9 @@
 msgid "generic plot"
 msgstr ""
 
+msgid "generic relation to link one entity to another"
+msgstr ""
+
 msgid "go back to the index page"
 msgstr ""
 
@@ -1813,7 +1821,7 @@
 msgstr ""
 
 msgid "hide meta-data"
-msgstr ""
+msgstr "hide meta entities and relations"
 
 msgid "home"
 msgstr ""
@@ -1931,9 +1939,6 @@
 msgid "inlined"
 msgstr ""
 
-msgid "inlined view"
-msgstr ""
-
 msgid "internationalizable"
 msgstr ""
 
@@ -2009,6 +2014,11 @@
 msgstr ""
 
 msgid ""
+"link a permission to the entity. This permission should be used in the "
+"security definition of the entity's type to be useful."
+msgstr ""
+
+msgid ""
 "link a property to the user which want this property customization. Unless "
 "you're a site manager, this relation will be handled automatically."
 msgstr ""
@@ -2046,6 +2056,9 @@
 msgid "login"
 msgstr ""
 
+msgid "login or email"
+msgstr ""
+
 msgid "login_action"
 msgstr "log in"
 
@@ -2149,6 +2162,18 @@
 msgid "navigation"
 msgstr ""
 
+msgid "navigation.combobox-limit"
+msgstr "\"related\" combo-box"
+
+msgid "navigation.page-size"
+msgstr "number of results"
+
+msgid "navigation.related-limit"
+msgstr "number of entities in the primary view "
+
+msgid "navigation.short-line-size"
+msgstr "short description"
+
 msgid "navtop"
 msgstr "page top"
 
@@ -2161,7 +2186,7 @@
 msgid "no"
 msgstr ""
 
-msgid "no associated epermissions"
+msgid "no associated permissions"
 msgstr ""
 
 msgid "no possible transition"
@@ -2201,6 +2226,9 @@
 msgid "object"
 msgstr ""
 
+msgid "object_plural:"
+msgstr "objects:"
+
 msgid "october"
 msgstr ""
 
@@ -2216,6 +2244,9 @@
 msgid "only select queries are authorized"
 msgstr ""
 
+msgid "open all"
+msgstr ""
+
 msgid "order"
 msgstr ""
 
@@ -2259,6 +2290,12 @@
 msgid "permission"
 msgstr ""
 
+msgid "permissions for entities"
+msgstr ""
+
+msgid "permissions for relations"
+msgstr ""
+
 msgid "permissions for this entity"
 msgstr ""
 
@@ -2326,6 +2363,9 @@
 msgid "relation_type_object"
 msgstr "relation definitions"
 
+msgid "relations"
+msgstr ""
+
 msgid "relations deleted"
 msgstr ""
 
@@ -2335,40 +2375,37 @@
 msgid "remove this Bookmark"
 msgstr "remove this bookmark"
 
-msgid "remove this Card"
-msgstr "remove this card"
-
-msgid "remove this ECache"
-msgstr ""
-
-msgid "remove this EConstraint"
-msgstr "remove this constraint"
-
-msgid "remove this EConstraintType"
-msgstr "remove this constraint type"
-
-msgid "remove this EEType"
-msgstr "remove this entity type"
-
-msgid "remove this EFRDef"
+msgid "remove this CWAttribute"
 msgstr "remove this attribute"
 
-msgid "remove this EGroup"
+msgid "remove this CWCache"
+msgstr ""
+
+msgid "remove this CWConstraint"
+msgstr "remove this constraint"
+
+msgid "remove this CWConstraintType"
+msgstr "remove this constraint type"
+
+msgid "remove this CWEType"
+msgstr "remove this entity type"
+
+msgid "remove this CWGroup"
 msgstr "remove this group"
 
-msgid "remove this ENFRDef"
-msgstr "remove this relation"
-
-msgid "remove this EPermission"
+msgid "remove this CWPermission"
 msgstr "remove this permission"
 
-msgid "remove this EProperty"
+msgid "remove this CWProperty"
 msgstr "remove this property"
 
-msgid "remove this ERType"
+msgid "remove this CWRType"
 msgstr "remove this relation type"
 
-msgid "remove this EUser"
+msgid "remove this CWRelation"
+msgstr "remove this relation"
+
+msgid "remove this CWUser"
 msgstr "remove this user"
 
 msgid "remove this EmailAddress"
@@ -2392,6 +2429,12 @@
 msgid "require_group_object"
 msgstr "required by"
 
+msgid "require_permission"
+msgstr ""
+
+msgid "require_permission_object"
+msgstr ""
+
 msgid "required attribute"
 msgstr ""
 
@@ -2459,12 +2502,18 @@
 msgid "see them all"
 msgstr ""
 
+msgid "see_also"
+msgstr ""
+
 msgid "select"
 msgstr ""
 
 msgid "select a"
 msgstr ""
 
+msgid "select a key first"
+msgstr ""
+
 msgid "select a relation"
 msgstr ""
 
@@ -2521,6 +2570,9 @@
 msgstr ""
 
 msgid "show meta-data"
+msgstr "show the complete schema"
+
+msgid "sioc"
 msgstr ""
 
 msgid "site configuration"
@@ -2575,6 +2627,9 @@
 msgid "subject/object cardinality"
 msgstr ""
 
+msgid "subject_plural:"
+msgstr "subjects:"
+
 msgid "sunday"
 msgstr ""
 
@@ -2584,9 +2639,6 @@
 msgid "symetric"
 msgstr ""
 
-msgid "synopsis"
-msgstr ""
-
 msgid "system entities"
 msgstr ""
 
@@ -2648,6 +2700,10 @@
 msgid "to"
 msgstr ""
 
+#, python-format
+msgid "to %(date)s"
+msgstr ""
+
 msgid "to associate with"
 msgstr ""
 
@@ -2666,6 +2722,9 @@
 msgid "todo_by"
 msgstr "to do by"
 
+msgid "toggle check boxes"
+msgstr ""
+
 msgid "transition is not allowed"
 msgstr ""
 
@@ -2687,6 +2746,36 @@
 msgid "ui"
 msgstr ""
 
+msgid "ui.date-format"
+msgstr "date format"
+
+msgid "ui.datetime-format"
+msgstr "date and time format"
+
+msgid "ui.default-text-format"
+msgstr "text format"
+
+msgid "ui.encoding"
+msgstr "encoding"
+
+msgid "ui.fckeditor"
+msgstr "content editor"
+
+msgid "ui.float-format"
+msgstr "float format"
+
+msgid "ui.language"
+msgstr "language"
+
+msgid "ui.main-template"
+msgstr "main template"
+
+msgid "ui.site-title"
+msgstr "site title"
+
+msgid "ui.time-format"
+msgstr "time format"
+
 msgid "unaccessible"
 msgstr ""
 
@@ -2702,6 +2791,9 @@
 msgid "unknown property key"
 msgstr ""
 
+msgid "up"
+msgstr ""
+
 msgid "upassword"
 msgstr "password"
 
@@ -2828,9 +2920,6 @@
 "which is the preferred form."
 msgstr ""
 
-msgid "wikiid"
-msgstr "wiki identifier"
-
 #, python-format
 msgid "workflow for %s"
 msgstr ""
@@ -2849,3 +2938,33 @@
 
 msgid "you have been logged out"
 msgstr ""
+
+msgid "you should probably delete that property"
+msgstr ""
+
+#~ msgid "Card"
+#~ msgstr "Card"
+
+#~ msgid "Card_plural"
+#~ msgstr "Cards"
+
+#~ msgid "New Card"
+#~ msgstr "New card"
+
+#~ msgid "This Card"
+#~ msgstr "This card"
+
+#~ msgid "add a Card"
+#~ msgstr "add a card"
+
+#~ msgid "content_format"
+#~ msgstr "content format"
+
+#~ msgid "planned_delivery"
+#~ msgstr "planned delivery"
+
+#~ msgid "remove this Card"
+#~ msgstr "remove this card"
+
+#~ msgid "wikiid"
+#~ msgstr "wiki identifier"
--- a/i18n/es.po	Thu May 14 12:50:14 2009 +0200
+++ b/i18n/es.po	Thu May 14 12:50:34 2009 +0200
@@ -5,8 +5,8 @@
 msgstr ""
 "Project-Id-Version: cubicweb 2.46.0\n"
 "PO-Revision-Date: 2008-11-27 07:59+0100\n"
-"Last-Translator: Celso <contact@logilab.fr>\n"
-"Language-Team: fr <contact@logilab.fr>\n"
+"Last-Translator: Celso Flores<jcelsoflores@gmail.com>\n"
+"Language-Team: es <contact@logilab.fr>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -23,8 +23,8 @@
 "url: %(url)s\n"
 msgstr ""
 "\n"
-"%(user)s a cambiado su estado de <%(previous_state)s> hacia <%(current_state)"
-"s> por la entidad\n"
+"%(user)s ha cambiado su estado de <%(previous_state)s> hacia <%"
+"(current_state)s> por la entidad\n"
 "'%(title)s'\n"
 "\n"
 "%(comment)s\n"
@@ -33,7 +33,7 @@
 
 #, python-format
 msgid "  from state %(fromstate)s to state %(tostate)s\n"
-msgstr "  de el estado %(fromstate)s hacia el estado %(tostate)s\n"
+msgstr "  del estado %(fromstate)s hacia el estado %(tostate)s\n"
 
 #, python-format
 msgid "%(cstr)s constraint failed for value %(value)r"
@@ -104,6 +104,10 @@
 msgstr "%d&nbsp;semanas"
 
 #, python-format
+msgid "%d&nbsp;years"
+msgstr ""
+
+#, python-format
 msgid "%s error report"
 msgstr "%s reporte de errores"
 
@@ -117,7 +121,7 @@
 
 #, python-format
 msgid "%s software version of the database"
-msgstr "version sistema de la base para %s"
+msgstr "versión sistema de la base para %s"
 
 msgid "**"
 msgstr "0..n 0..n"
@@ -177,10 +181,10 @@
 msgstr "Aplicación"
 
 msgid "Bookmark"
-msgstr "Atajo"
+msgstr "Favorito"
 
 msgid "Bookmark_plural"
-msgstr "Atajos"
+msgstr "Favoritos"
 
 msgid "Boolean"
 msgstr "Booleano"
@@ -189,19 +193,79 @@
 msgstr "Booleanos"
 
 msgid "Browse by category"
-msgstr "Selecciona por categoría"
+msgstr "Busca por categoría"
 
 msgid "Bytes"
-msgstr "Datos binarios"
+msgstr "Bytes"
 
 msgid "Bytes_plural"
-msgstr "Datos binarios"
-
-msgid "Card"
-msgstr "Ficha"
-
-msgid "Card_plural"
-msgstr "Fichas"
+msgstr "Bytes"
+
+msgid "CWAttribute"
+msgstr "Atributo"
+
+msgid "CWAttribute_plural"
+msgstr "Atributos"
+
+msgid "CWCache"
+msgstr "Cache"
+
+msgid "CWCache_plural"
+msgstr "Caches"
+
+msgid "CWConstraint"
+msgstr "Restricción"
+
+msgid "CWConstraintType"
+msgstr "Tipo de Restricción"
+
+msgid "CWConstraintType_plural"
+msgstr "Tipos de Restricción"
+
+msgid "CWConstraint_plural"
+msgstr "Restricciones"
+
+msgid "CWEType"
+msgstr "Tipo de entidad"
+
+msgid "CWEType_plural"
+msgstr "Tipos de entidades"
+
+msgid "CWGroup"
+msgstr "Groupo"
+
+msgid "CWGroup_plural"
+msgstr "Groupos"
+
+msgid "CWPermission"
+msgstr "Autorización"
+
+msgid "CWPermission_plural"
+msgstr "Autorizaciones"
+
+msgid "CWProperty"
+msgstr "Propiedad"
+
+msgid "CWProperty_plural"
+msgstr "Propiedades"
+
+msgid "CWRType"
+msgstr "Tipo de relación"
+
+msgid "CWRType_plural"
+msgstr "Tipos de relación"
+
+msgid "CWRelation"
+msgstr "Relación"
+
+msgid "CWRelation_plural"
+msgstr "Relaciones"
+
+msgid "CWUser"
+msgstr "Usuario"
+
+msgid "CWUser_plural"
+msgstr "Usuarios"
 
 msgid "Date"
 msgstr "Fecha"
@@ -220,91 +284,22 @@
 msgstr "Nivel de debug puesto a %s"
 
 msgid "Decimal"
-msgstr "Número decimal"
+msgstr "Decimal"
 
 msgid "Decimal_plural"
-msgstr "Números decimales"
+msgstr "Decimales"
 
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Desea suprimir el(los) elemento(s) siguiente(s)"
 
-msgid "ECache"
-msgstr "Memoria Cache"
-
-msgid "ECache_plural"
-msgstr "Memorias Caches"
-
-msgid "EConstraint"
-msgstr "Condición"
-
-msgid "EConstraintType"
-msgstr "Tipo de condición"
-
-msgid "EConstraintType_plural"
-msgstr "Tipos de condición"
-
-msgid "EConstraint_plural"
-msgstr "Condiciones"
-
-msgid "EEType"
-msgstr "Tipo de entidades"
-
-msgid "EEType_plural"
-msgstr "Tipos de entidades"
-
-msgid "EFRDef"
-msgstr "Atributo"
-
-msgid "EFRDef_plural"
-msgstr "Atributos"
-
-msgid "EGroup"
-msgstr "Groupo"
-
-msgid "EGroup_plural"
-msgstr "Groupos"
-
-msgid "ENFRDef"
-msgstr "Relación"
-
-msgid "ENFRDef_plural"
-msgstr "Relaciones"
-
-msgid "EPermission"
-msgstr "Autorización"
-
-msgid "EPermission_plural"
-msgstr "Autorizaciones"
-
-msgid "EProperty"
-msgstr "Propiedad"
-
-msgid "EProperty_plural"
-msgstr "Propiedades"
-
-msgid "ERType"
-msgstr "Tipo de relación"
-
-msgid "ERType_plural"
-msgstr "Tipos de relación"
-
-msgid "EUser"
-msgstr "Usuario"
-
-msgid "EUser_plural"
-msgstr "Usuarios"
-
-msgid "Email body: "
-msgstr "Contenido del correo electrónico : "
-
 msgid "EmailAddress"
 msgstr "Correo Electrónico"
 
 msgid "EmailAddress_plural"
-msgstr "Direcciónes de Correo Electrónico"
+msgstr "Direcciones de Correo Electrónico"
 
 msgid "Entities"
-msgstr "entidades"
+msgstr "Entidades"
 
 msgid "Environment"
 msgstr "Ambiente"
@@ -315,8 +310,8 @@
 msgid "Float_plural"
 msgstr "Números flotantes"
 
-msgid "From: "
-msgstr "De : "
+msgid "From:"
+msgstr ""
 
 msgid "Int"
 msgstr "Número entero"
@@ -331,58 +326,55 @@
 msgstr "Duraciones"
 
 msgid "New Bookmark"
-msgstr "Nuevo Atajo"
-
-msgid "New Card"
-msgstr "Nueva ficha"
-
-msgid "New ECache"
-msgstr "Nueva memoria cache"
-
-msgid "New EConstraint"
-msgstr "Nueva condición"
-
-msgid "New EConstraintType"
-msgstr "Nuevo tipo de condición"
-
-msgid "New EEType"
-msgstr "Nuevo tipo de entidad"
-
-msgid "New EFRDef"
+msgstr "Agregar a Favoritos"
+
+msgid "New CWAttribute"
 msgstr "Nueva definición de relación final"
 
-msgid "New EGroup"
+msgid "New CWCache"
+msgstr "Agregar Cache"
+
+msgid "New CWConstraint"
+msgstr "Agregar Restricción"
+
+msgid "New CWConstraintType"
+msgstr "Agregar tipo de Restricción"
+
+msgid "New CWEType"
+msgstr "Agregar tipo de entidad"
+
+msgid "New CWGroup"
 msgstr "Nuevo grupo"
 
-msgid "New ENFRDef"
+msgid "New CWPermission"
+msgstr "Agregar autorización"
+
+msgid "New CWProperty"
+msgstr "Agregar Propiedad"
+
+msgid "New CWRType"
+msgstr "Agregar tipo de relación"
+
+msgid "New CWRelation"
 msgstr "Nueva definición de relación final"
 
-msgid "New EPermission"
-msgstr "Nueva autorización"
-
-msgid "New EProperty"
-msgstr "Nueva Propiedad"
-
-msgid "New ERType"
-msgstr "Nuevo tipo de relación"
-
-msgid "New EUser"
-msgstr "Nuevo usuario"
+msgid "New CWUser"
+msgstr "Agregar usuario"
 
 msgid "New EmailAddress"
-msgstr "Nuevo Email"
+msgstr "Agregar Email"
 
 msgid "New RQLExpression"
-msgstr "Nueva expresión rql"
+msgstr "Agregar expresión rql"
 
 msgid "New State"
-msgstr "Nuevo Estado"
+msgstr "Agregar Estado"
 
 msgid "New TrInfo"
-msgstr "Nueva Información de Transición"
+msgstr "Agregar Información de Transición"
 
 msgid "New Transition"
-msgstr "Nueva transición"
+msgstr "Agregar transición"
 
 msgid "No query has been executed"
 msgstr "Ninguna búsqueda ha sido ejecutada"
@@ -402,23 +394,20 @@
 msgid "Please note that this is only a shallow copy"
 msgstr "Recuerde que no es más que una copia superficial"
 
-msgid "Problem occured"
-msgstr "Ha ocurrido un error"
-
 msgid "RQLExpression"
 msgstr "Expresión RQL"
 
 msgid "RQLExpression_plural"
 msgstr "Expresiones RQL"
 
-msgid "Recipients: "
-msgstr "Destinatarios : "
+msgid "Recipients:"
+msgstr ""
 
 msgid "Relations"
 msgstr "Relaciones"
 
 msgid "Request"
-msgstr "Búsqueda"
+msgstr "Petición"
 
 #, python-format
 msgid "Schema %s"
@@ -431,7 +420,7 @@
 msgstr "Servidor"
 
 msgid "Startup views"
-msgstr "Vistas de inicio"
+msgstr "Vistas de Inicio"
 
 msgid "State"
 msgstr "Estado"
@@ -440,13 +429,13 @@
 msgstr "Estados"
 
 msgid "String"
-msgstr "Cadena de caractéres"
+msgstr "Cadena de caracteres"
 
 msgid "String_plural"
-msgstr "Cadenas de caractéres"
-
-msgid "Subject: "
-msgstr "Objeto : "
+msgstr "Cadenas de caracteres"
+
+msgid "Subject:"
+msgstr ""
 
 msgid "Submit bug report"
 msgstr "Enviar un reporte de error (bug)"
@@ -470,42 +459,39 @@
 msgstr "Este %s"
 
 msgid "This Bookmark"
-msgstr "Este atajo"
-
-msgid "This Card"
-msgstr "Esta Ficha"
-
-msgid "This ECache"
-msgstr "Esta Memoria Cache"
-
-msgid "This EConstraint"
-msgstr "Esta condición"
-
-msgid "This EConstraintType"
-msgstr "Este tipo de condición"
-
-msgid "This EEType"
+msgstr "Este favorito"
+
+msgid "This CWAttribute"
+msgstr "Esta definición de relación final"
+
+msgid "This CWCache"
+msgstr "Este Cache"
+
+msgid "This CWConstraint"
+msgstr "Esta Restricción"
+
+msgid "This CWConstraintType"
+msgstr "Este tipo de Restricción"
+
+msgid "This CWEType"
 msgstr "Este tipo de Entidad"
 
-msgid "This EFRDef"
-msgstr "Esta definición de relación final"
-
-msgid "This EGroup"
+msgid "This CWGroup"
 msgstr "Este grupo"
 
-msgid "This ENFRDef"
+msgid "This CWPermission"
+msgstr "Esta autorización"
+
+msgid "This CWProperty"
+msgstr "Esta propiedad"
+
+msgid "This CWRType"
+msgstr "Este tipo de relación"
+
+msgid "This CWRelation"
 msgstr "Esta definición de relación no final"
 
-msgid "This EPermission"
-msgstr "Esta autorización"
-
-msgid "This EProperty"
-msgstr "Esta propiedad"
-
-msgid "This ERType"
-msgstr "Este tipo de relación"
-
-msgid "This EUser"
+msgid "This CWUser"
 msgstr "Este usuario"
 
 msgid "This EmailAddress"
@@ -549,10 +535,10 @@
 msgstr "Utilizado por :"
 
 msgid "What's new?"
-msgstr "Ultimas Noticias"
+msgstr "Lo último en el sitio"
 
 msgid "Workflow history"
-msgstr "Registro de cambios de estado"
+msgstr "Histórico del Workflow"
 
 msgid "You are not connected to an application !"
 msgstr "Usted no esta conectado a una aplicación"
@@ -567,7 +553,7 @@
 "box, or edit file content online with the widget below."
 msgstr ""
 "Usted puede proponer un nuevo archivo utilizando el botón\n"
-"\"buscar\" aqui arriba, o eliminar el archivo ya elegido al\n"
+"\"buscar\" aquí arriba, o eliminar el archivo ya elegido al\n"
 "seleccionar el cuadro \"soltar archivo adjunto\", o editar el contenido\n"
 "del archivo en línea con el componente inferior."
 
@@ -581,7 +567,7 @@
 
 msgid "You can use any of the following substitutions in your text"
 msgstr ""
-"Puede realizar cualquiera de las siguietes sustituciones en el contenido de "
+"Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
 "su email."
 
 msgid "You have no access to this view or it's not applyable to current data"
@@ -591,8 +577,8 @@
 "You're not authorized to access this page. If you think you should, please "
 "contact the site administrator."
 msgstr ""
-"Usted no esta autorizado a acceder a esta página. Si Usted cree aue \n"
-"es un error, favor de contactar al administrador del sitio."
+"Usted no esta autorizado a acceder a esta página. Si Usted cree que \n"
+"hay un error, favor de contactar al administrador del sitio."
 
 #, python-format
 msgid "[%s supervision] changes summary"
@@ -606,16 +592,9 @@
 "be available. This query may use X and U variables that will respectivly "
 "represents the current entity and the current user"
 msgstr ""
-"una expresión RQL deviendo regresado resultado para que la transición pueda "
-"ser realizada. Esta expresión puede utilizar las variables X y U que "
-"representan respectivamente la entidad en transición y el usuarioactual. "
-
-msgid ""
-"a card is a textual content used as documentation, reference, procedure "
-"reminder"
-msgstr ""
-"una ficha es un texto utilizado como documentación, referencia, memoria de "
-"procedimiento..."
+"una expresión RQL que debe haber enviado resultados, para que la transición "
+"pueda ser realizada. Esta expresión puede utilizar las variables X y U que "
+"representan respectivamente la entidad en transición y el usuario actual. "
 
 msgid ""
 "a simple cache entity characterized by a name and a validity date. The "
@@ -623,12 +602,16 @@
 "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
 "get_cache() method."
 msgstr ""
+"una entidad cache simple caracterizada por un nombre y una fecha correcta. "
+"El sistema objetivo es responsable de actualizar timestamp cuand se "
+"necesario para invalidar el cache (usualmente en hooks).Recomendamos revisar "
+"el METODO  AppRsetObject.get_cache()."
 
 msgid "about this site"
-msgstr "Sobre este espacio"
+msgstr "Sobre este Espacio"
 
 msgid "access type"
-msgstr "tipo de acceso"
+msgstr "Tipo de Acceso"
 
 msgid "account state"
 msgstr "Estado de la Cuenta"
@@ -646,53 +629,65 @@
 msgstr ""
 
 msgid "actions_cancel"
-msgstr "Anular la selección"
+msgstr "Anular"
 
 msgid "actions_cancel_description"
 msgstr ""
 
 msgid "actions_copy"
-msgstr "copiar"
+msgstr "Copiar"
 
 msgid "actions_copy_description"
 msgstr ""
 
 msgid "actions_delete"
-msgstr "eliminar"
+msgstr "Eliminar"
 
 msgid "actions_delete_description"
 msgstr ""
 
+msgid "actions_download_as_owl"
+msgstr "Download como OWL"
+
+msgid "actions_download_as_owl_description"
+msgstr ""
+
 msgid "actions_edit"
-msgstr "modificar"
+msgstr "Modificar"
 
 msgid "actions_edit_description"
 msgstr ""
 
 msgid "actions_embed"
-msgstr "embarcar"
+msgstr "Embarcar"
 
 msgid "actions_embed_description"
 msgstr ""
 
 msgid "actions_follow"
-msgstr "seguir"
+msgstr "Seguir"
 
 msgid "actions_follow_description"
 msgstr ""
 
 msgid "actions_logout"
-msgstr "desconectarse"
+msgstr "Desconectarse"
 
 msgid "actions_logout_description"
 msgstr ""
 
 msgid "actions_manage"
-msgstr "administración del sitio"
+msgstr "Administración del sitio"
 
 msgid "actions_manage_description"
 msgstr ""
 
+msgid "actions_managepermission"
+msgstr ""
+
+msgid "actions_managepermission_description"
+msgstr ""
+
 msgid "actions_muledit"
 msgstr "Edición múltiple"
 
@@ -700,216 +695,213 @@
 msgstr ""
 
 msgid "actions_myinfos"
-msgstr "información personal"
+msgstr "Información personal"
 
 msgid "actions_myinfos_description"
 msgstr ""
 
 msgid "actions_myprefs"
-msgstr "preferencias del usuario"
+msgstr "Preferencias del usuario"
 
 msgid "actions_myprefs_description"
 msgstr ""
 
 msgid "actions_prefs"
-msgstr "preferencias"
+msgstr "Preferencias"
 
 msgid "actions_prefs_description"
 msgstr ""
 
 msgid "actions_schema"
-msgstr "ver el esquema"
+msgstr "Ver el esquema"
 
 msgid "actions_schema_description"
 msgstr ""
 
 msgid "actions_select"
-msgstr "seleccionar"
+msgstr "Seleccionar"
 
 msgid "actions_select_description"
 msgstr ""
 
 msgid "actions_sendemail"
-msgstr "enviar un email"
+msgstr "Enviar un email"
 
 msgid "actions_sendemail_description"
 msgstr ""
 
 msgid "actions_siteconfig"
-msgstr "configuración del sitio"
+msgstr "Configuración del sitio"
 
 msgid "actions_siteconfig_description"
 msgstr ""
 
 msgid "actions_view"
-msgstr "ver"
+msgstr "Ver"
 
 msgid "actions_view_description"
 msgstr ""
 
 msgid "actions_workflow"
-msgstr "ver el workflow"
+msgstr "Ver el workflow"
 
 msgid "actions_workflow_description"
 msgstr ""
 
 msgid "activate"
-msgstr "activar"
+msgstr "Activar"
 
 msgid "activated"
-msgstr "activado"
+msgstr "Activado"
 
 msgid "add"
-msgstr "agregar"
-
-msgid "add Bookmark bookmarked_by EUser object"
-msgstr ""
-
-msgid "add EEType add_permission RQLExpression subject"
-msgstr "Definir una expresión RQL de agregación"
-
-msgid "add EEType delete_permission RQLExpression subject"
-msgstr "Definir una expresión RQL de eliminación"
-
-msgid "add EEType read_permission RQLExpression subject"
+msgstr "Agregar"
+
+msgid "add Bookmark bookmarked_by CWUser object"
+msgstr "Agregar a los favoritos "
+
+msgid "add CWAttribute constrained_by CWConstraint subject"
+msgstr "Restricción"
+
+msgid "add CWAttribute relation_type CWRType object"
+msgstr "Definición de atributo"
+
+msgid "add CWEType add_permission RQLExpression subject"
+msgstr "Agregar una autorización"
+
+msgid "add CWEType delete_permission RQLExpression subject"
+msgstr "Eliminar una autorización"
+
+msgid "add CWEType read_permission RQLExpression subject"
 msgstr "Definir una expresión RQL de lectura"
 
-msgid "add EEType update_permission RQLExpression subject"
+msgid "add CWEType update_permission RQLExpression subject"
 msgstr "Definir una expresión RQL de actualización"
 
-msgid "add EFRDef constrained_by EConstraint subject"
-msgstr "condición"
-
-msgid "add EFRDef relation_type ERType object"
-msgstr "definición de atributo"
-
-msgid "add ENFRDef constrained_by EConstraint subject"
-msgstr "condición"
-
-msgid "add ENFRDef relation_type ERType object"
-msgstr "definición de relación"
-
-msgid "add EProperty for_user EUser object"
-msgstr "propiedad"
-
-msgid "add ERType add_permission RQLExpression subject"
-msgstr "expresión RQL de agregación"
-
-msgid "add ERType delete_permission RQLExpression subject"
-msgstr "expresión RQL de eliminación"
-
-msgid "add ERType read_permission RQLExpression subject"
-msgstr "expresión RQL de lectura"
-
-msgid "add EUser in_group EGroup object"
-msgstr "usuario"
-
-msgid "add EUser use_email EmailAddress subject"
-msgstr "agregar email"
+msgid "add CWProperty for_user CWUser object"
+msgstr "Agregar Propiedad"
+
+msgid "add CWRType add_permission RQLExpression subject"
+msgstr "Agregar expresión RQL de agregación"
+
+msgid "add CWRType delete_permission RQLExpression subject"
+msgstr "Agregar expresión RQL de eliminación"
+
+msgid "add CWRType read_permission RQLExpression subject"
+msgstr "Agregar expresión RQL de lectura"
+
+msgid "add CWRelation constrained_by CWConstraint subject"
+msgstr "Restricción"
+
+msgid "add CWRelation relation_type CWRType object"
+msgstr "Definición de relación"
+
+msgid "add CWUser in_group CWGroup object"
+msgstr "Agregar usuario"
+
+msgid "add CWUser use_email EmailAddress subject"
+msgstr "Agregar email"
 
 msgid "add State allowed_transition Transition object"
-msgstr "agregar un estado en entrada"
+msgstr "Agregar un estado en entrada"
 
 msgid "add State allowed_transition Transition subject"
-msgstr "agregar una transición en salida"
-
-msgid "add State state_of EEType object"
-msgstr "agregar un estado"
+msgstr "Agregar una transición en salida"
+
+msgid "add State state_of CWEType object"
+msgstr "Agregar un estado"
 
 msgid "add Transition condition RQLExpression subject"
-msgstr "agregar una condición"
+msgstr "Agregar una Restricción"
 
 msgid "add Transition destination_state State object"
-msgstr "agregar una transición de entrada"
+msgstr "Agregar una transición de entrada"
 
 msgid "add Transition destination_state State subject"
-msgstr "agregar el estado de salida"
-
-msgid "add Transition transition_of EEType object"
-msgstr "agregar una transición"
+msgstr "Agregar el estado de salida"
+
+msgid "add Transition transition_of CWEType object"
+msgstr "Agregar una transición"
 
 msgid "add a Bookmark"
-msgstr "agregar un atajo"
-
-msgid "add a Card"
-msgstr "agregar una ficha"
-
-msgid "add a ECache"
-msgstr "agregar una memoria cache"
-
-msgid "add a EConstraint"
-msgstr "agregar una condición"
-
-msgid "add a EConstraintType"
-msgstr "aun tipo de condición"
-
-msgid "add a EEType"
-msgstr "agregar un tipo de entidad"
-
-msgid "add a EFRDef"
-msgstr "agregar un tipo de relación"
-
-msgid "add a EGroup"
-msgstr "agregar un grupo de usuarios"
-
-msgid "add a ENFRDef"
-msgstr "agregar una relación"
-
-msgid "add a EPermission"
-msgstr "agregar una autorización"
-
-msgid "add a EProperty"
-msgstr "agregar una propiedad"
-
-msgid "add a ERType"
-msgstr "agregar un tipo de relación"
-
-msgid "add a EUser"
-msgstr "agregar un usuario"
+msgstr "Agregar un Favorito"
+
+msgid "add a CWAttribute"
+msgstr "Agregar un tipo de relación"
+
+msgid "add a CWCache"
+msgstr "Agregar un cache"
+
+msgid "add a CWConstraint"
+msgstr "Agregar una Restricción"
+
+msgid "add a CWConstraintType"
+msgstr "Agregar un tipo de Restricción"
+
+msgid "add a CWEType"
+msgstr "Agregar un tipo de entidad"
+
+msgid "add a CWGroup"
+msgstr "Agregar un grupo de usuarios"
+
+msgid "add a CWPermission"
+msgstr "Agregar una autorización"
+
+msgid "add a CWProperty"
+msgstr "Agregar una propiedad"
+
+msgid "add a CWRType"
+msgstr "Agregar un tipo de relación"
+
+msgid "add a CWRelation"
+msgstr "Agregar una relación"
+
+msgid "add a CWUser"
+msgstr "Agregar un usuario"
 
 msgid "add a EmailAddress"
-msgstr "agregar un email"
+msgstr "Agregar un email"
 
 msgid "add a RQLExpression"
-msgstr "agregar una expresión rql"
+msgstr "Agregar una expresión rql"
 
 msgid "add a State"
-msgstr "agregar un estado"
+msgstr "Agregar un estado"
 
 msgid "add a TrInfo"
-msgstr "agregar una información de transición"
+msgstr "Agregar una información de transición"
 
 msgid "add a Transition"
-msgstr "agregar una transición"
+msgstr "Agregar una transición"
 
 msgid "add a new permission"
-msgstr "agregar una autorización"
+msgstr "Agregar una autorización"
 
 msgid "add relation"
-msgstr "agregar una relación"
+msgstr "Agregar una relación"
 
 msgid "add_perm"
-msgstr "agregado"
+msgstr "Agregado"
 
 # subject and object forms for each relation type
 # (no object form for final relation types)
 msgid "add_permission"
-msgstr "autorización para agregar"
+msgstr "Autorización para agregar"
 
 msgid "add_permission_object"
 msgstr "tiene la autorización para agregar"
 
 #, python-format
 msgid "added %(etype)s #%(eid)s (%(title)s)"
-msgstr "agregado de la entidad %(etype)s #%(eid)s (%(title)s)"
+msgstr "Agregado %(etype)s #%(eid)s (%(title)s)"
 
 #, python-format
 msgid ""
 "added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
 "(toeid)s"
 msgstr ""
-"agregado de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
-"(toetype)s #%(toeid)s"
+"Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)s #"
+"%(toeid)s"
 
 msgid "address"
 msgstr "dirección"
@@ -941,14 +933,11 @@
 msgid "am/pm calendar (year)"
 msgstr "calendario am/pm (año)"
 
-msgid "an abstract for this card"
-msgstr "un resumen para esta ficha"
-
 msgid "an electronic mail address associated to a short alias"
 msgstr "una dirección electrónica asociada a este alias"
 
 msgid "an error occured"
-msgstr "a ocurrido un error"
+msgstr "ha ocurrido un error"
 
 msgid "an error occured while processing your request"
 msgstr "un error ocurrió al procesar su demanda"
@@ -963,16 +952,16 @@
 msgstr "y/o entre los diferentes valores"
 
 msgid "anonymous"
-msgstr "anónimo"
+msgstr "Anónimo"
 
 msgid "application entities"
-msgstr "entidades de la aplicación"
+msgstr "Entidades de la aplicación"
 
 msgid "application schema"
-msgstr "esquema de la aplicación"
+msgstr "Esquema de la aplicación"
 
 msgid "april"
-msgstr "abril"
+msgstr "Abril"
 
 #, python-format
 msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
@@ -981,117 +970,119 @@
 "otra via la relación %(rtype)s"
 
 msgid "attribute"
-msgstr "atributo"
+msgstr "Atributo"
+
+msgid "attributes with modified permissions:"
+msgstr ""
 
 msgid "august"
-msgstr "agosto"
+msgstr "Agosto"
 
 msgid "authentication failure"
 msgstr "Usuario o contraseña incorrecta"
 
 msgid "automatic"
-msgstr "automático"
+msgstr "Automático"
 
 msgid "bad value"
-msgstr "valor erróneo"
+msgstr "Valor erróneo"
 
 msgid "base url"
-msgstr "url de base"
+msgstr "Url de base"
 
 msgid "bookmark has been removed"
-msgstr "el atajo ha sido eliminado"
+msgstr "ha sido eliminado de sus favoritos"
 
 msgid "bookmark this page"
-msgstr "Agregue un atajo a esta página"
+msgstr "Agregar esta página a sus favoritos"
 
 msgid "bookmark this search"
-msgstr "Guarde esta búsqueda"
+msgstr "Guardar esta búsqueda"
 
 msgid "bookmarked_by"
-msgstr "utilizada por"
+msgstr "está en los favoritos de"
 
 msgid "bookmarked_by_object"
-msgstr "tiene como atajo"
+msgstr "selecciona en sus favoritos a"
 
 msgid "bookmarks"
-msgstr "atajos"
+msgstr "Favoritos"
 
 msgid "boxes"
-msgstr "cajas"
+msgstr "Cajas"
 
 msgid "boxes_bookmarks_box"
-msgstr "caja de atajos"
+msgstr "Caja de Favoritos"
 
 msgid "boxes_bookmarks_box_description"
-msgstr "Caja que contiene los atajos del usuario"
+msgstr "Caja que contiene los espacios favoritos del usuario"
 
 msgid "boxes_download_box"
-msgstr ""
+msgstr "Caja de download"
 
 msgid "boxes_download_box_description"
-msgstr ""
+msgstr "Caja que contiene los elementos bajados"
 
 msgid "boxes_edit_box"
-msgstr "caja de acciones"
+msgstr "Caja de acciones"
 
 msgid "boxes_edit_box_description"
 msgstr ""
-"caja aue muestra las diferentes acciones posibles sobre los datos presentes"
+"Caja que muestra las diferentes acciones posibles sobre los datos presentes"
 
 msgid "boxes_filter_box"
-msgstr "filtrar"
+msgstr "Filtros"
 
 msgid "boxes_filter_box_description"
-msgstr ""
-"caja permitiendo de realizar filtros sobre los resultados de una búsqueda"
+msgstr "Caja que permite realizar filtros sobre los resultados de una búsqueda"
 
 msgid "boxes_possible_views_box"
-msgstr "caja de vistas posibles"
+msgstr "Caja de Vistas Posibles"
 
 msgid "boxes_possible_views_box_description"
-msgstr "caja mostrando las vistas posibles para los datos actuales"
+msgstr "Caja mostrando las vistas posibles para los datos actuales"
 
 msgid "boxes_rss"
 msgstr "ícono RSS"
 
 msgid "boxes_rss_description"
-msgstr "el ícono RSS permite recuperar las vistas RSS de los datos presentes"
+msgstr "El ícono RSS permite recuperar las vistas RSS de los datos presentes"
 
 msgid "boxes_search_box"
-msgstr "caja de búsqueda"
+msgstr "Caja de búsqueda"
 
 msgid "boxes_search_box_description"
-msgstr "caja con un espacio de búsqueda simple"
+msgstr "Caja con un espacio de búsqueda simple"
 
 msgid "boxes_startup_views_box"
-msgstr "caja de las vistas de inicio"
+msgstr "Caja Vistas de inicio"
 
 msgid "boxes_startup_views_box_description"
-msgstr "caja mostrando las vistas de inicio de la aplicación"
+msgstr "Caja mostrando las vistas de inicio de la aplicación"
 
 msgid "bug report sent"
-msgstr "reporte de error enviado"
+msgstr "Reporte de error enviado"
 
 msgid "button_apply"
-msgstr "aplicar"
+msgstr "Aplicar"
 
 msgid "button_cancel"
-msgstr "anular"
+msgstr "Cancelar"
 
 msgid "button_delete"
-msgstr "eliminar"
+msgstr "Eliminar"
 
 msgid "button_ok"
-msgstr "validar"
+msgstr "Validar"
 
 msgid "button_reset"
-msgstr "anular los cambios"
+msgstr "Cancelar los cambios"
 
 msgid "by"
 msgstr "por"
 
 msgid "by relation"
-msgstr "por la relación"
+msgstr "por relación"
 
 msgid "calendar"
 msgstr "mostrar un calendario"
@@ -1129,10 +1120,10 @@
 "cardinalidad %(card)s"
 
 msgid "cancel select"
-msgstr "anular la selección"
+msgstr "Cancelar la selección"
 
 msgid "cancel this insert"
-msgstr "anular esta inserción"
+msgstr "Cancelar esta inserción"
 
 msgid "canonical"
 msgstr "canónico"
@@ -1140,93 +1131,90 @@
 msgid "cardinality"
 msgstr "cardinalidad"
 
+msgid "category"
+msgstr ""
+
 #, python-format
 msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
-msgstr "cambiar del estado de %(etype)s #%(eid)s (%(title)s)"
+msgstr "Cambiar del estado de %(etype)s #%(eid)s (%(title)s)"
 
 msgid "changes applied"
-msgstr "cambios realizados"
+msgstr "Cambios realizados"
 
 msgid "click on the box to cancel the deletion"
-msgstr "seleccione la zona de edición para anular la eliminación"
+msgstr "Seleccione la zona de edición para cancelar la eliminación"
 
 msgid "comment"
-msgstr "comentario"
+msgstr "Comentario"
 
 msgid "comment:"
-msgstr "comentario :"
+msgstr "Comentario:"
 
 msgid "comment_format"
-msgstr "formato"
+msgstr "Formato"
 
 msgid "components"
-msgstr "componentes"
+msgstr "Componentes"
 
 msgid "components_appliname"
-msgstr "título de la aplicación"
+msgstr "Título de la aplicación"
 
 msgid "components_appliname_description"
-msgstr "muestra el título de la aplicación en el encabezado de la página"
+msgstr "Muestra el título de la aplicación en el encabezado de la página"
 
 msgid "components_applmessages"
-msgstr "mensajes de la aplicación"
+msgstr "Mensajes de la aplicación"
 
 msgid "components_applmessages_description"
-msgstr "muestra los mensajes de la aplicación"
+msgstr "Muestra los mensajes de la aplicación"
 
 msgid "components_breadcrumbs"
-msgstr "hilo de Ariadna"
+msgstr "Ruta de Navegación"
 
 msgid "components_breadcrumbs_description"
 msgstr ""
-"muestra un camino que permite identificar el lugar donde se encuentra la "
+"Muestra un camino que permite identificar el lugar donde se encuentra la "
 "página en el sitio"
 
 msgid "components_etypenavigation"
-msgstr "filtro por tipo"
+msgstr "Filtro por tipo"
 
 msgid "components_etypenavigation_description"
-msgstr "permite filtrar por tipo de entidad los resultados de búsqueda"
+msgstr "Permite filtrar por tipo de entidad los resultados de búsqueda"
 
 msgid "components_help"
-msgstr "botón de ayuda"
+msgstr "Botón de ayuda"
 
 msgid "components_help_description"
-msgstr "el botón de ayuda, en el encabezado de página"
+msgstr "El botón de ayuda, en el encabezado de página"
 
 msgid "components_loggeduserlink"
-msgstr "liga usuario"
+msgstr "Liga usuario"
 
 msgid "components_loggeduserlink_description"
 msgstr ""
-"muestra un enlace hacia el formulario de conexión para los usuarios "
+"Muestra un enlace hacia el formulario de conexión para los usuarios "
 "anónimos, o una caja que contiene las ligas propias a el usuarioconectado. "
 
 msgid "components_logo"
-msgstr "logo"
+msgstr "Logo"
 
 msgid "components_logo_description"
-msgstr "el logo de la aplicación, en el encabezado de página"
+msgstr "El logo de la aplicación, en el encabezado de página"
 
 msgid "components_navigation"
-msgstr "navigación por página"
+msgstr "Navigación por página"
 
 msgid "components_navigation_description"
 msgstr ""
-"componente aue permite distribuir sobre varias páginas las búsquedas que "
-"arrojan mayores resultados a un número previamente elegido"
+"Componente que permite distribuir sobre varias páginas las búsquedas que "
+"arrojan mayores resultados que un número previamente elegido"
 
 msgid "components_rqlinput"
-msgstr "barra rql"
+msgstr "Barra rql"
 
 msgid "components_rqlinput_description"
-msgstr "la barre de demanda rql, en el encabezado de página"
-
-msgid "components_rss_feed_url"
-msgstr ""
-
-msgid "components_rss_feed_url_description"
-msgstr ""
+msgstr "La barra de demanda rql, en el encabezado de página"
 
 msgid "composite"
 msgstr "composite"
@@ -1235,82 +1223,76 @@
 msgstr "condición"
 
 msgid "condition:"
-msgstr "condición :"
+msgstr "condición:"
 
 msgid "condition_object"
 msgstr "condición de"
 
 msgid "confirm password"
-msgstr "confirmar contraseña"
+msgstr "Confirmar contraseña"
 
 msgid "constrained_by"
-msgstr "condicionado por"
+msgstr "Restricción hecha por"
 
 msgid "constrained_by_object"
-msgstr "condición de"
+msgstr "ha restringido"
 
 msgid "constraint factory"
-msgstr "fabrica de condiciones"
+msgstr "FAbrica de restricciones"
 
 msgid "constraints"
-msgstr "condiciones"
+msgstr "Restricciones"
 
 msgid "constraints applying on this relation"
-msgstr "condiciones que se aplican a esta relación"
-
-msgid "content"
-msgstr "contenido"
-
-msgid "content_format"
-msgstr "formato"
+msgstr "Restricciones que se aplican a esta relación"
 
 msgid "contentnavigation"
-msgstr "composantes contextuales"
+msgstr "Componentes contextuales"
 
 msgid "contentnavigation_breadcrumbs"
-msgstr "hilo de Ariadna"
+msgstr "Ruta de Navegación"
 
 msgid "contentnavigation_breadcrumbs_description"
-msgstr "muestra un camino que permite localizar la página actual en el sitio"
+msgstr "Muestra un camino que permite localizar la página actual en el sitio"
 
 msgid "contentnavigation_prevnext"
 msgstr "Elemento anterior / siguiente"
 
 msgid "contentnavigation_prevnext_description"
 msgstr ""
-"muestra las ligas que permiten pasar de una entidad a otra en lasentidades "
+"Muestra las ligas que permiten pasar de una entidad a otra en lasentidades "
 "que implementan la interface \"anterior/siguiente\"."
 
 msgid "contentnavigation_seealso"
-msgstr "vea también"
+msgstr "Vea también"
 
 msgid "contentnavigation_seealso_description"
 msgstr ""
-"sección aue muestra las entidades ligadas por la relación \"vea también\" , "
+"sección que muestra las entidades ligadas por la relación \"vea también\" , "
 "si la entidad soporta esta relación."
 
 msgid "contentnavigation_wfhistory"
-msgstr "histórico del workflow."
+msgstr "Histórico del workflow."
 
 msgid "contentnavigation_wfhistory_description"
 msgstr ""
-"sección que ofrece el reporte histórico del workflow para las entidades que "
+"Sección que ofrece el reporte histórico del workflow para las entidades que "
 "posean un workflow."
 
 msgid "context"
-msgstr "contexto"
+msgstr "Contexto"
 
 msgid "context where this box should be displayed"
-msgstr "contexto en el cual la caja debe aparecer en el sistema"
+msgstr "Contexto en el cual la caja debe aparecer en el sistema"
 
 msgid "context where this component should be displayed"
-msgstr "contexto en el cual el componente debe aparecer en el sistema"
+msgstr "Contexto en el cual el componente debe aparecer en el sistema"
 
 msgid "control subject entity's relations order"
-msgstr "controla el orden de relaciones de la entidad sujeto"
+msgstr "Controla el orden de relaciones de la entidad sujeto"
 
 msgid "copy"
-msgstr "copiar"
+msgstr "Copiar"
 
 msgid "copy edition"
 msgstr "Edición de una copia"
@@ -1319,66 +1301,66 @@
 "core relation giving to a group the permission to add an entity or relation "
 "type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de agregar unaentidad "
-"o una relación"
+"Relación sistema que otorga a un grupo la autorización de agregar una "
+"entidad o una relación"
 
 msgid ""
 "core relation giving to a group the permission to delete an entity or "
 "relation type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de eliminar una "
+"Relación sistema que otorga a un grupo la autorización de eliminar una "
 "entidad o relación"
 
 msgid ""
 "core relation giving to a group the permission to read an entity or relation "
 "type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de leer una entidad o "
+"Relación sistema que otorga a un grupo la autorización de leer una entidad o "
 "una relación "
 
 msgid "core relation giving to a group the permission to update an entity type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de actualizar una "
+"Relación sistema que otorga a un grupo la autorización de actualizar una "
 "entidad"
 
 msgid "core relation indicating a user's groups"
 msgstr ""
-"relación sistema que indica los grupos a los cuales pertenece un usuario"
+"Relación sistema que indica los grupos a los cuales pertenece un usuario"
 
 msgid ""
 "core relation indicating owners of an entity. This relation implicitly put "
 "the owner into the owners group for the entity"
 msgstr ""
-"relación sistema que indica el(los) propietario(s) de una entidad. Esta "
+"Relación sistema que indica el(los) propietario(s) de una entidad. Esta "
 "relación pone de manera implícita al propietario en el grupo de propietarios "
-"por una entidad"
+"de una entidad"
 
 msgid "core relation indicating the original creator of an entity"
-msgstr "relación sistema que indica el creador de una entidad."
+msgstr "Relación sistema que indica el creador de una entidad."
 
 msgid "core relation indicating the type of an entity"
-msgstr "relación sistema que indica el tipo de entidad"
+msgstr "Relación sistema que indica el tipo de entidad"
 
 msgid ""
 "core relation indicating the types (including specialized types) of an entity"
 msgstr ""
-"relación sistema indicando los tipos (incluídos los tipos padres) de una "
+"Relación sistema indicando los tipos (incluídos los tipos padres) de una "
 "entidad"
 
 msgid "cost"
-msgstr "costo"
+msgstr "Costo"
 
 msgid "could not connect to the SMTP server"
-msgstr "imposible de conectarse al servidor SMTP"
+msgstr "Imposible de conectarse al servidor SMTP"
 
 msgid "create an index for quick search on this attribute"
-msgstr "crear un índice para accelerar las búsquedas sobre este atributo"
+msgstr "Crear un índice para accelerar las búsquedas sobre este atributo"
 
 msgid "create an index page"
-msgstr "crear una página de inicio"
+msgstr "Crear una página de inicio"
 
 msgid "created on"
-msgstr "creado el"
+msgstr "Creado el"
 
 msgid "created_by"
 msgstr "creado por"
@@ -1386,142 +1368,149 @@
 msgid "created_by_object"
 msgstr "ha creado"
 
-msgid "creating Bookmark (Bookmark bookmarked_by EUser %(linkto)s)"
-msgstr ""
-
-msgid "creating EConstraint (EFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "creación condicionada por el atributo %(linkto)s"
-
-msgid "creating EConstraint (ENFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "creación condicionada por la relación %(linkto)s"
-
-msgid "creating EFRDef (EFRDef relation_type ERType %(linkto)s)"
-msgstr "creación atributo %(linkto)s"
-
-msgid "creating ENFRDef (ENFRDef relation_type ERType %(linkto)s)"
-msgstr "creación relación %(linkto)s"
-
-msgid "creating EProperty (EProperty for_user EUser %(linkto)s)"
-msgstr "creación de una propiedad por el usuario %(linkto)s"
-
-msgid "creating EUser (EUser in_group EGroup %(linkto)s)"
-msgstr "creación de un usuario para agregar al grupo %(linkto)s"
-
-msgid "creating EmailAddress (EUser %(linkto)s use_email EmailAddress)"
-msgstr "creación de una dirección electrónica para el usuario %(linkto)s"
-
-msgid "creating RQLExpression (EEType %(linkto)s add_permission RQLExpression)"
-msgstr ""
-"creación de una expresión RQL para la autorización de agregar %(linkto)s"
+msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
+msgstr "Creando Favorito"
+
+msgid "creating CWAttribute (CWAttribute relation_type CWRType %(linkto)s)"
+msgstr "Creación del atributo %(linkto)s"
+
+msgid ""
+"creating CWConstraint (CWAttribute %(linkto)s constrained_by CWConstraint)"
+msgstr "Creación condicionada por el atributo %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s delete_permission RQLExpression)"
-msgstr ""
-"creación de una expresión RQL para la autorización de eliminar %(linkto)s"
+"creating CWConstraint (CWRelation %(linkto)s constrained_by CWConstraint)"
+msgstr "Creación condicionada por la relación %(linkto)s"
+
+msgid "creating CWProperty (CWProperty for_user CWUser %(linkto)s)"
+msgstr "Creación de una propiedad por el usuario %(linkto)s"
+
+msgid "creating CWRelation (CWRelation relation_type CWRType %(linkto)s)"
+msgstr "Creación de la relación %(linkto)s"
+
+msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)"
+msgstr "Creación de un usuario para agregar al grupo %(linkto)s"
+
+msgid "creating EmailAddress (CWUser %(linkto)s use_email EmailAddress)"
+msgstr "Creación de una dirección electrónica para el usuario %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s read_permission RQLExpression)"
-msgstr "creación de una expresión RQL para la autorización de leer %(linkto)s"
+"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
+msgstr ""
+"Creación de una expresión RQL para la autorización de agregar %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s delete_permission RQLExpression)"
+msgstr ""
+"Creación de una expresión RQL para la autorización de eliminar %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s update_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s read_permission RQLExpression)"
+msgstr "Creación de una expresión RQL para la autorización de leer %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s update_permission RQLExpression)"
+msgstr "Creación de una expresión RQL para autorizar actualizar %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
 msgstr ""
-"creación de una expresión RQL para la autorización de actualizar %(linkto)s"
-
-msgid "creating RQLExpression (ERType %(linkto)s add_permission RQLExpression)"
-msgstr ""
-"creación de una expresión RQL para la autorización de agregar relaciones %"
+"Creación de una expresión RQL para la autorización de agregar relaciones %"
 "(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s delete_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
 msgstr ""
 "creación de una expresión RQL para autorizar la eliminación de relaciones %"
 "(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s read_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
 msgstr ""
-"creación de una expresión RQL para autorizar la lectura de relaciones %"
+"Creación de una expresión RQL para autorizar la lectura de relaciones %"
 "(linkto)s"
 
 msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
-msgstr "creación de una expresión RQL para la transición %(linkto)s"
+msgstr "Creación de una expresión RQL para la transición %(linkto)s"
 
 msgid "creating State (State allowed_transition Transition %(linkto)s)"
-msgstr "creación de un estado que pueda ir hacia la transición %(linkto)s"
-
-msgid "creating State (State state_of EEType %(linkto)s)"
-msgstr "creación de un estado por el tipo %(linkto)s"
+msgstr "Creación de un estado que pueda ir hacia la transición %(linkto)s"
+
+msgid "creating State (State state_of CWEType %(linkto)s)"
+msgstr "Creación de un estado por el tipo %(linkto)s"
 
 msgid "creating State (Transition %(linkto)s destination_state State)"
-msgstr "creación de un estado destinación de la transición %(linkto)s"
+msgstr "Creación de un estado destinación de la transición %(linkto)s"
 
 msgid "creating Transition (State %(linkto)s allowed_transition Transition)"
-msgstr "creación de una transición autorizada desde el estado %(linkto)s"
+msgstr "Creación de una transición autorizada desde el estado %(linkto)s"
 
 msgid "creating Transition (Transition destination_state State %(linkto)s)"
-msgstr "creación de un transición hacia el estado %(linkto)s"
-
-msgid "creating Transition (Transition transition_of EEType %(linkto)s)"
-msgstr "creación de una transición para el tipo %(linkto)s"
+msgstr "Creación de un transición hacia el estado %(linkto)s"
+
+msgid "creating Transition (Transition transition_of CWEType %(linkto)s)"
+msgstr "Creación de una transición para el tipo %(linkto)s"
 
 msgid "creation"
-msgstr "creación"
+msgstr "Creación"
 
 msgid "creation time of an entity"
-msgstr "fecha de creación de una entidad"
+msgstr "Fecha de creación de una entidad"
 
 msgid "creation_date"
 msgstr "fecha de creación"
 
 msgid "cstrtype"
-msgstr "tipo de condición"
+msgstr "Tipo de condición"
 
 msgid "cstrtype_object"
 msgstr "utilizado por"
 
 msgid "csv entities export"
-msgstr "exportar entidades en csv"
+msgstr "Exportar entidades en csv"
 
 msgid "csv export"
-msgstr "exportar CSV"
+msgstr "Exportar CSV"
+
+#, python-format
+msgid "currently attached file: %s"
+msgstr ""
 
 msgid "data directory url"
-msgstr "url del repertorio de datos"
+msgstr "Url del repertorio de datos"
 
 msgid "date"
-msgstr "fecha"
+msgstr "Fecha"
 
 msgid "deactivate"
-msgstr "desactivar"
+msgstr "Desactivar"
 
 msgid "deactivated"
-msgstr "desactivado"
+msgstr "Desactivado"
 
 msgid "december"
-msgstr "diciembre"
+msgstr "Diciembre"
 
 msgid "default"
-msgstr "valor por default"
+msgstr "Valor por defecto"
 
 msgid "default text format for rich text fields."
-msgstr "formato de texto como opción por defecto para los campos texto"
+msgstr "Formato de texto como opción por defecto para los campos texto"
 
 msgid "defaultval"
-msgstr "valor por defecto"
+msgstr "Valor por defecto"
 
 msgid "define a CubicWeb user"
-msgstr "define un usuario CubicWeb"
+msgstr "Define un usuario CubicWeb"
 
 msgid "define a CubicWeb users group"
-msgstr "define un grupo de usuarios CubicWeb"
+msgstr "Define un grupo de usuarios CubicWeb"
 
 msgid ""
 "define a final relation: link a final relation type from a non final entity "
 "to a final entity type. used to build the application schema"
 msgstr ""
-"define una relación no final: liga un tipo de relación no final desde una "
+"Define una relación no final: liga un tipo de relación no final desde una "
 "entidad hacia un tipo de entidad no final. Utilizada para construir el "
 "esquema de la aplicación"
 
@@ -1529,76 +1518,76 @@
 "define a non final relation: link a non final relation type from a non final "
 "entity to a non final entity type. used to build the application schema"
 msgstr ""
-"define una relación 'atributo', utilizada para construir el esquema dela "
+"Define una relación 'atributo', utilizada para construir el esquema dela "
 "aplicación"
 
 msgid "define a relation type, used to build the application schema"
 msgstr ""
-"define un tipo de relación, utilizada para construir el esquema de la "
+"Define un tipo de relación, utilizada para construir el esquema de la "
 "aplicación"
 
 msgid "define a rql expression used to define permissions"
 msgstr "Expresión RQL utilizada para definir los derechos de acceso"
 
 msgid "define a schema constraint"
-msgstr "define una condición de esquema"
+msgstr "Define una condición de esquema"
 
 msgid "define a schema constraint type"
-msgstr "define un tipo de condición de esquema"
+msgstr "Define un tipo de condición de esquema"
 
 msgid "define an entity type, used to build the application schema"
 msgstr ""
-"define un tipo de entidad, utilizada para construir el esquema de la "
+"Define un tipo de entidad, utilizada para construir el esquema de la "
 "aplicación"
 
 msgid ""
 "defines what's the property is applied for. You must select this first to be "
 "able to set value"
 msgstr ""
-"define a que se aplica la propiedad . Usted debe seleccionar esto antes de "
+"Define a que se aplica la propiedad . Usted debe seleccionar esto antes de "
 "poder fijar un valor"
 
 msgid "delete"
-msgstr "eliminar"
+msgstr "Eliminar"
 
 msgid "delete this bookmark"
-msgstr "eliminar este atajo"
+msgstr "Eliminar este favorito"
 
 msgid "delete this permission"
-msgstr "eliminar esta autorización"
+msgstr "Eliminar esta autorización"
 
 msgid "delete this relation"
-msgstr "eliminar estar relación"
+msgstr "Eliminar esta relación"
 
 msgid "delete_perm"
-msgstr "eliminar"
+msgstr "Eliminar"
 
 msgid "delete_permission"
-msgstr "autorización de eliminar"
+msgstr "Autorización de eliminar"
 
 msgid "delete_permission_object"
 msgstr "posee la autorización de eliminar"
 
 #, python-format
 msgid "deleted %(etype)s #%(eid)s (%(title)s)"
-msgstr "eliminación de la entidad %(etype)s #%(eid)s (%(title)s)"
+msgstr "Eliminación de la entidad %(etype)s #%(eid)s (%(title)s)"
 
 #, python-format
 msgid ""
 "deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
 "(toeid)s"
 msgstr ""
-"eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
+"Eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
 "(toetype)s #%(toeid)s"
 
 msgid "depends on the constraint type"
-msgstr "depende del tipo de condición"
+msgstr "Depende del tipo de condición"
 
 msgid "description"
-msgstr "descripción"
+msgstr "Descripción"
 
 msgid "description_format"
-msgstr "formato"
+msgstr "Formato"
 
 msgid "destination state for this transition"
 msgstr "Estado destino para esta transición"
@@ -1610,330 +1599,353 @@
 msgstr "Estado destino"
 
 msgid "destination_state_object"
-msgstr "destino de"
+msgstr "Destino de"
 
 msgid "detach attached file"
 msgstr "soltar el archivo existente"
 
+#, python-format
+msgid "detach attached file %s"
+msgstr "Quitar archivo adjunto %s"
+
 msgid "detailed schema view"
-msgstr "vista detallada del esquema"
+msgstr "Vista detallada del esquema"
 
 msgid "display order of the action"
-msgstr "orden de aparición de la acción"
+msgstr "Orden de aparición de la acción"
 
 msgid "display order of the box"
-msgstr "orden de aparición de la caja"
+msgstr "Orden de aparición de la caja"
 
 msgid "display order of the component"
-msgstr "orden de aparición del componente"
+msgstr "Orden de aparición del componente"
 
 msgid "display the action or not"
-msgstr "mostrar la acción o no"
+msgstr "Mostrar la acción o no"
 
 msgid "display the box or not"
-msgstr "mostrar la caja o no"
+msgstr "Mostrar la caja o no"
 
 msgid "display the component or not"
-msgstr "mostrar el componente o no"
+msgstr "Mostrar el componente o no"
 
 msgid ""
 "distinct label to distinguate between other permission entity of the same "
 "name"
 msgstr ""
-"etiqueta que permite distinguir esta autorización de otras que posean el "
+"Etiqueta que permite distinguir esta autorización de otras que posean el "
 "mismo nombre"
 
 msgid "download"
-msgstr "descargar"
+msgstr "Descargar"
 
 msgid "download icon"
 msgstr "ícono de descarga"
 
+msgid "download schema as owl"
+msgstr "Descargar esquema en OWL"
+
 msgid "edit bookmarks"
-msgstr "editar los atajos"
+msgstr "Editar favoritos"
 
 msgid "edit the index page"
-msgstr "editar la página de inicio"
+msgstr "Modificar la página de inicio"
 
 msgid "editable-table"
-msgstr "tabla modificable"
+msgstr "Tabla modificable"
 
 msgid "edition"
-msgstr "edición"
+msgstr "Edición"
 
 msgid "eid"
 msgstr "eid"
 
 msgid "element copied"
-msgstr "elemeto copiado"
+msgstr "Elemento copiado"
 
 msgid "element created"
-msgstr "elemento creado"
+msgstr "Elemento creado"
 
 msgid "element edited"
-msgstr "elemento editado"
+msgstr "Elemento editado"
 
 msgid "email address to use for notification"
-msgstr "dirección electrónica a utilizarse para notificar"
+msgstr "Dirección electrónica a utilizarse para notificar"
 
 msgid "emails successfully sent"
-msgstr "mensajes enviados con éxito"
+msgstr "Mensajes enviados con éxito"
 
 msgid "embed"
-msgstr "incrustrado"
+msgstr "Incrustrado"
 
 msgid "embedding this url is forbidden"
-msgstr "la inclusión de este url esta prohibida"
+msgstr "La inclusión de este url esta prohibida"
 
 msgid "entities deleted"
-msgstr "entidades eliminadas"
+msgstr "Entidades eliminadas"
+
+msgid "entity copied"
+msgstr ""
+
+msgid "entity created"
+msgstr ""
 
 msgid "entity deleted"
-msgstr "entidad eliminada"
+msgstr "Entidad eliminada"
+
+msgid "entity edited"
+msgstr ""
 
 msgid "entity type"
-msgstr "tipo de entidad"
+msgstr "Tipo de entidad"
 
 msgid ""
 "entity type that may be used to construct some advanced security "
 "configuration"
 msgstr ""
-"tipo de entidqd utilizada para definir una configuración de seguridad "
+"Tipo de entidad utilizada para definir una configuración de seguridad "
 "avanzada"
 
 msgid "entity types which may use this state"
-msgstr "tipo de entidades que pueden utilizar este estado"
+msgstr "Tipo de entidades que pueden utilizar este estado"
 
 msgid "entity types which may use this transition"
-msgstr "entidades que pueden utilizar esta transición"
+msgstr "Entidades que pueden utilizar esta transición"
 
 msgid "error while embedding page"
-msgstr "error durante la inclusión de la página"
+msgstr "Error durante la inclusión de la página"
 
 #, python-format
 msgid "error while handling __method: %s"
-msgstr "error ocurrido durante el tratamiento del formulario (%s)"
+msgstr "Error ocurrido durante el tratamiento del formulario (%s)"
 
 msgid "error while publishing ReST text"
 msgstr ""
-"se ha producido un error durante la interpretación del texto en formatoReST"
+"Se ha producido un error durante la interpretación del texto en formatoReST"
 
 #, python-format
 msgid "error while querying source %s, some data may be missing"
 msgstr ""
-"un error ha ocurrido al interrogar  %s, es posible que los \n"
+"Un error ha ocurrido al interrogar  %s, es posible que los \n"
 "datos visibles se encuentren incompletos"
 
 msgid "eta_date"
 msgstr "fecha de fin"
 
 msgid "expected:"
-msgstr "previsto :"
+msgstr "Previsto :"
 
 msgid "expression"
-msgstr "expresión"
+msgstr "Expresión"
 
 msgid "exprtype"
-msgstr "tipo de la expresión"
+msgstr "Tipo de la expresión"
 
 msgid "external page"
-msgstr "página externa"
+msgstr "Página externa"
 
 msgid "facetbox"
-msgstr "caja de facetas"
+msgstr "Caja de facetas"
 
 msgid "facets_created_by-facet"
 msgstr "faceta \"creada por\""
 
 msgid "facets_created_by-facet_description"
-msgstr ""
+msgstr "faceta creado por"
 
 msgid "facets_etype-facet"
 msgstr "faceta \"es de tipo\""
 
 msgid "facets_etype-facet_description"
-msgstr ""
+msgstr "faceta es de tipo"
 
 msgid "facets_has_text-facet"
 msgstr "faceta \"contiene el texto\""
 
 msgid "facets_has_text-facet_description"
-msgstr ""
+msgstr "faceta contiene el texto"
 
 msgid "facets_in_group-facet"
 msgstr "faceta \"forma parte del grupo\""
 
 msgid "facets_in_group-facet_description"
-msgstr ""
+msgstr "faceta en grupo"
 
 msgid "facets_in_state-facet"
 msgstr "faceta \"en el estado\""
 
 msgid "facets_in_state-facet_description"
-msgstr ""
+msgstr "faceta en el estado"
 
 msgid "february"
-msgstr "febrero"
+msgstr "Febrero"
 
 msgid "file tree view"
-msgstr ""
+msgstr "File Vista Arborescencia"
 
 msgid "final"
-msgstr "final"
+msgstr "Final"
 
 msgid "firstname"
-msgstr "nombre"
+msgstr "Nombre"
 
 msgid "foaf"
-msgstr ""
+msgstr "Amigo de un Amigo, FOAF"
 
 msgid "follow"
-msgstr "seguir la liga"
+msgstr "Seguir la liga"
 
 msgid "for_user"
-msgstr "para el usuario"
+msgstr "Para el usuario"
 
 msgid "for_user_object"
-msgstr "utiliza las propiedades"
+msgstr "Utiliza las propiedades"
 
 msgid "friday"
-msgstr "viernes"
+msgstr "Viernes"
 
 msgid "from"
-msgstr "de"
+msgstr "De"
+
+#, python-format
+msgid "from %(date)s"
+msgstr ""
 
 msgid "from_entity"
-msgstr "de la entidad"
+msgstr "De la entidad"
 
 msgid "from_entity_object"
-msgstr "relación sujeto"
+msgstr "Relación sujeto"
 
 msgid "from_state"
-msgstr "de el estado"
+msgstr "De el estado"
 
 msgid "from_state_object"
-msgstr "transiciones desde este estado"
+msgstr "Transiciones desde este estado"
 
 msgid "full text or RQL query"
-msgstr "texto de búsqueda o demanda RQL"
+msgstr "Texto de búsqueda o demanda RQL"
 
 msgid "fulltext_container"
-msgstr "contenedor de texto indexado"
+msgstr "Contenedor de texto indexado"
 
 msgid "fulltextindexed"
-msgstr "indexación de texto"
+msgstr "Indexación de texto"
 
 msgid "generic plot"
-msgstr "trazado de curbas estándares"
+msgstr "Trazado de curbas estándares"
+
+msgid "generic relation to link one entity to another"
+msgstr ""
 
 msgid "go back to the index page"
-msgstr "regresar a la página de inicio"
+msgstr "Regresar a la página de inicio"
 
 msgid "granted to groups"
-msgstr "otorgado a los grupos"
+msgstr "Otorgado a los grupos"
 
 msgid "graphical representation of the application'schema"
-msgstr "representación gráfica del esquema de la aplicación"
+msgstr "Representación gráfica del esquema de la aplicación"
 
 #, python-format
 msgid "graphical schema for %s"
-msgstr "gráfica del esquema por %s"
+msgstr "Gráfica del esquema por %s"
 
 #, python-format
 msgid "graphical workflow for %s"
-msgstr "gráfica del workflow por %s"
+msgstr "Gráfica del workflow por %s"
 
 msgid "group in which a user should be to be allowed to pass this transition"
-msgstr "grupo en el cual el usuario debe estar para poder pasar la transición"
+msgstr "Grupo en el cual el usuario debe estar para poder pasar la transición"
 
 msgid "groups"
-msgstr "grupos"
+msgstr "Grupos"
 
 msgid "groups allowed to add entities/relations of this type"
-msgstr "grupos autorizados a agregar entidades/relaciones de este tipo"
+msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo"
 
 msgid "groups allowed to delete entities/relations of this type"
-msgstr "grupos autorizados a eliminar entidades/relaciones de este tipo"
+msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo"
 
 msgid "groups allowed to read entities/relations of this type"
-msgstr "grupos autorizados a leer entidades/relaciones de este tipo"
+msgstr "Grupos autorizados a leer entidades/relaciones de este tipo"
 
 msgid "groups allowed to update entities of this type"
-msgstr "grupos autorizados a actualizar entidades de este tipo"
+msgstr "Grupos autorizados a actualizar entidades de este tipo"
 
 msgid "groups grant permissions to the user"
-msgstr "los grupos otorgan las autorizaciones al usuario"
+msgstr "Los grupos otorgan las autorizaciones al usuario"
 
 msgid "groups to which the permission is granted"
-msgstr "grupos quienes tienen otorgada esta autorización"
+msgstr "Grupos quienes tienen otorgada esta autorización"
 
 msgid "groups:"
-msgstr "grupos :"
+msgstr "Grupos :"
 
 msgid "guests"
-msgstr "invitados"
+msgstr "Invitados"
 
 msgid "hCalendar"
 msgstr "hCalendar"
 
 msgid "has_text"
-msgstr "contiene el texto"
+msgstr "Contiene el texto"
 
 msgid "help"
-msgstr "ayuda"
+msgstr "Ayuda"
 
 msgid "hide filter form"
-msgstr "esconder el filtro"
+msgstr "Esconder el filtro"
 
 msgid "hide meta-data"
-msgstr "esconder los meta-datos"
+msgstr "Esconder los meta-datos"
 
 msgid "home"
-msgstr "inicio"
+msgstr "Inicio"
 
 msgid ""
 "how to format date and time in the ui (\"man strftime\" for format "
 "description)"
 msgstr ""
-"como formatear la fecha en la interface (\"man strftime\" por la descripción "
+"Como formatear la fecha en la interface (\"man strftime\" por la descripción "
 "del formato)"
 
 msgid "how to format date in the ui (\"man strftime\" for format description)"
 msgstr ""
-"como formatear la fecha en la interface (\"man strftime\" por la descripción "
+"Como formatear la fecha en la interface (\"man strftime\" por la descripción "
 "del formato)"
 
 msgid "how to format float numbers in the ui"
-msgstr "como formatear los números flotantes en la interface"
+msgstr "Como formatear los números flotantes en la interface"
 
 msgid "how to format time in the ui (\"man strftime\" for format description)"
 msgstr ""
-"como formatear la hora en la interface (\"man strftime\" por la descripción "
+"Como formatear la hora en la interface (\"man strftime\" por la descripción "
 "del formato)"
 
 msgid "html class of the component"
-msgstr "clase HTML de este componente"
+msgstr "Clase HTML de este componente"
 
 msgid "htmlclass"
-msgstr "clase html"
+msgstr "Clase html"
 
 msgid "i18n_login_popup"
-msgstr "identificarse"
+msgstr "Identificarse"
 
 msgid "i18nprevnext_next"
-msgstr "siguiente"
+msgstr "Siguiente"
 
 msgid "i18nprevnext_previous"
-msgstr "anterior"
+msgstr "Anterior"
 
 msgid "i18nprevnext_up"
-msgstr "padre"
+msgstr "Padre"
 
 msgid "iCalendar"
 msgstr "iCalendar"
 
 msgid "id of main template used to render pages"
-msgstr "id del template principal"
+msgstr "ID del template principal"
 
 msgid "identical_to"
 msgstr "idéntico a"
@@ -1948,23 +1960,23 @@
 "if full text content of subject/object entity should be added to other side "
 "entity (the container)."
 msgstr ""
-"si el texto indexado de la entidad sujeto/objeto debe ser agregado a la "
+"Si el texto indexado de la entidad sujeto/objeto debe ser agregado a la "
 "entidad a el otro extremo de la relación (el contenedor)."
 
 msgid "image"
-msgstr "imagen"
+msgstr "Imagen"
 
 msgid "in memory entity schema"
-msgstr "esquema de la entidad en memoria"
+msgstr "Esquema de la entidad en memoria"
 
 msgid "in memory relation schema"
-msgstr "esquema de la relación en memoria"
+msgstr "Esquema de la relación en memoria"
 
 msgid "in_group"
-msgstr "en el grupo"
+msgstr "En el grupo"
 
 msgid "in_group_object"
-msgstr "miembros"
+msgstr "Miembros"
 
 msgid "in_state"
 msgstr "estado"
@@ -1973,67 +1985,64 @@
 msgstr "estado de"
 
 msgid "incontext"
-msgstr "en el contexto"
+msgstr "En el contexto"
 
 #, python-format
 msgid "incorrect value (%(value)s) for type \"%(type)s\""
 msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
 
 msgid "index"
-msgstr "índice"
+msgstr "Indice"
 
 msgid "index this attribute's value in the plain text index"
-msgstr "indexar el valor de este atributo en el índice de texto simple"
+msgstr "Indexar el valor de este atributo en el índice de texto simple"
 
 msgid "indexed"
-msgstr "indexado"
+msgstr "Indexado"
 
 msgid "indicate the current state of an entity"
-msgstr "indica el estado actual de una entidad"
+msgstr "Indica el estado actual de una entidad"
 
 msgid ""
 "indicate which state should be used by default when an entity using states "
 "is created"
 msgstr ""
-"indica cual estado deberá ser utilizado por defecto al crear una entidad"
+"Indica cual estado deberá ser utilizado por defecto al crear una entidad"
 
 #, python-format
 msgid "initial estimation %s"
-msgstr "estimación inicial %s"
+msgstr "Estimación inicial %s"
 
 msgid "initial state for entities of this type"
-msgstr "estado inicial para las entidades de este tipo"
+msgstr "Estado inicial para las entidades de este tipo"
 
 msgid "initial_state"
 msgstr "estado inicial"
 
 msgid "initial_state_object"
-msgstr "estado inicial de"
+msgstr "es el estado inicial de"
 
 msgid "inlined"
-msgstr "puesto en línea"
-
-msgid "inlined view"
-msgstr "vista incluída (en línea)"
+msgstr "Puesto en línea"
 
 msgid "internationalizable"
-msgstr "internacionalizable"
+msgstr "Internacionalizable"
 
 #, python-format
 msgid "invalid action %r"
-msgstr "acción %r invalida"
+msgstr "Acción %r invalida"
 
 msgid "invalid date"
-msgstr "esta fecha no es válida"
+msgstr "Esta fecha no es válida"
 
 msgid "is"
-msgstr "de tipo"
+msgstr "es"
 
 msgid "is it an application entity type or not ?"
-msgstr "es una entidad aplicativa o no ?"
+msgstr "Es un Tipo de entidad en la aplicación o no ?"
 
 msgid "is it an application relation type or not ?"
-msgstr "es una relación aplicativa o no ?"
+msgstr "Es una relación aplicativa o no ?"
 
 msgid ""
 "is the subject/object entity of the relation composed of the other ? This "
@@ -2043,62 +2052,67 @@
 "ser así, el destruir el composite destruirá de igual manera sus componentes "
 
 msgid "is this attribute's value translatable"
-msgstr "es el valor de este atributo traducible ?"
+msgstr "Es el valor de este atributo traducible ?"
 
 msgid "is this relation equivalent in both direction ?"
-msgstr "es esta relación equivalente en los ambos sentidos ?"
+msgstr "Es esta relación equivalente en los ambos sentidos ?"
 
 msgid ""
 "is this relation physically inlined? you should know what you're doing if "
 "you are changing this!"
 msgstr ""
-"es esta relación puesta en línea en la base de datos  ? Usted debe saber lo "
+"Es esta relación puesta en línea en la base de datos  ? Usted debe saber lo "
 "que hace si cambia esto !"
 
 msgid "is_instance_of"
 msgstr "es una instancia de"
 
 msgid "is_instance_of_object"
-msgstr "tipo de"
+msgstr "tiene como instancias"
 
 msgid "is_object"
 msgstr "tiene por instancia"
 
 msgid "january"
-msgstr "enero"
+msgstr "Enero"
 
 msgid "july"
-msgstr "julio"
+msgstr "Julio"
 
 msgid "june"
-msgstr "junio"
+msgstr "Junio"
 
 msgid "label"
-msgstr "etiqueta"
+msgstr "Etiqueta"
 
 msgid "language of the user interface"
-msgstr "idioma para la interface del usuario"
+msgstr "Idioma para la interface del usuario"
 
 msgid "last connection date"
-msgstr "última fecha de conexión"
+msgstr "Ultima fecha de conexión"
 
 msgid "last_login_time"
-msgstr "última fecha de conexión"
+msgstr "Ultima fecha de conexión"
 
 msgid "latest modification time of an entity"
-msgstr "fecha de la última modificación de una entidad "
+msgstr "Fecha de la última modificación de una entidad "
 
 msgid "latest update on"
-msgstr "última actualización"
+msgstr "actualizado el"
 
 msgid "left"
 msgstr "izquierda"
 
 msgid ""
+"link a permission to the entity. This permission should be used in the "
+"security definition of the entity's type to be useful."
+msgstr ""
+
+msgid ""
 "link a property to the user which want this property customization. Unless "
 "you're a site manager, this relation will be handled automatically."
 msgstr ""
-"liga una propiedad a el usuario que desea esta personalización. A menos que "
+"Liga una propiedad a el usuario que desea esta personalización. Salvo que "
 "usted sea un administrador del sistema, esta relación es gestionada "
 "automáticamente."
 
@@ -2109,209 +2123,230 @@
 msgstr "liga una definición de relación a su tipo de relación"
 
 msgid "link a relation definition to its subject entity type"
-msgstr "lie une dÈfinition de relation ‡ son type d'entitÈ sujet"
+msgstr "liga una definición de relación a su tipo de entidad"
 
 msgid "link a state to one or more entity type"
-msgstr "lier un Ètat ‡ une ou plusieurs entitÈs"
+msgstr "liga un estado a una o mas entidades"
 
 msgid "link a transition information to its object"
-msgstr "liÈ une enregistrement de transition vers l'objet associÈ"
+msgstr "liga una transcion de informacion a los objetos asociados"
 
 msgid "link a transition to one or more entity type"
-msgstr "lie une transition ‡ un ou plusieurs types d'entitÈs"
+msgstr "liga una transición a una o mas tipos de entidad"
 
 msgid "link to each item in"
-msgstr "lier vers chaque ÈlÈment dans"
+msgstr "ligar hacia cada elemento en"
 
 msgid "list"
-msgstr "liste"
+msgstr "Lista"
 
 msgid "loading"
-msgstr ""
+msgstr "Cargando"
 
 msgid "log in"
-msgstr "s'identifier"
+msgstr "Identificarse"
 
 msgid "login"
-msgstr "identifiant"
+msgstr "Clave de acesso"
+
+msgid "login or email"
+msgstr ""
 
 msgid "login_action"
-msgstr "identifiez vous"
+msgstr "Ingresa tus datos"
 
 msgid "logout"
-msgstr "se dÈconnecter"
+msgstr "Desconectarse"
 
 #, python-format
 msgid "loop in %(rel)s relation (%(eid)s)"
-msgstr "boucle dÈtectÈe en parcourant la relation %(rel)s de l'entitÈ #%(eid)s"
+msgstr "loop detectado en %(rel)s de la entidad #%(eid)s"
 
 msgid "main informations"
-msgstr "Informations gÈnÈrales"
+msgstr "Informaciones Generales"
 
 msgid "mainvars"
-msgstr "variables principales"
+msgstr "Principales variables"
 
 msgid "manage"
-msgstr "gestion du site"
+msgstr "Administracion del Sitio"
 
 msgid "manage bookmarks"
-msgstr "gÈrer les signets"
+msgstr "Administra tus favoritos"
 
 msgid "manage permissions"
-msgstr ""
+msgstr "Administración de Autorizaciones"
 
 msgid "manage security"
-msgstr "gestion de la sÈcuritÈ"
+msgstr "Administración de la Seguridad"
 
 msgid "managers"
-msgstr "administrateurs"
+msgstr "editores"
 
 msgid "march"
-msgstr "mars"
+msgstr "Marzo"
 
 msgid "maximum number of characters in short description"
-msgstr "nombre maximum de caractËres dans les descriptions courtes"
+msgstr "Numero maximo de caracteres en las descripciones cortas"
 
 msgid "maximum number of entities to display in related combo box"
-msgstr "nombre maximum d'entitÈs ‡ afficher dans les listes dÈroulantes"
+msgstr "Numero maximo de entidades a mostrar en las listas dinamicas"
 
 msgid "maximum number of objects displayed by page of results"
-msgstr "nombre maximum d'entitÈs affichÈes par pages"
+msgstr "Numero maximo de objetos mostrados por pagina de resultados"
 
 msgid "maximum number of related entities to display in the primary view"
-msgstr "nombre maximum d'entitÈs liÈes ‡ afficher dans la vue primaire"
+msgstr "Numero maximo de entidades ligadas a mostrar en la vista primaria"
 
 msgid "may"
-msgstr "mai"
+msgstr "Mayo"
 
 msgid "meta"
-msgstr "mÈta"
+msgstr "Meta"
 
 msgid "milestone"
-msgstr "jalon"
+msgstr "Milestone"
 
 #, python-format
 msgid "missing parameters for entity %s"
-msgstr "paramËtres manquants pour l'entitÈ %s"
+msgstr "Parametros faltantes a la entidad %s"
 
 msgid "modification_date"
-msgstr "date de modification"
+msgstr "Fecha de modificacion"
 
 msgid "modify"
-msgstr "modifier"
+msgstr "Modificar"
 
 msgid "monday"
-msgstr "lundi"
+msgstr "Lundi"
 
 msgid "more actions"
-msgstr "plus d'actions"
+msgstr "mas acciones"
 
 msgid "multiple edit"
-msgstr "Èdition multiple"
+msgstr "Edicion multiple"
 
 msgid "my custom search"
-msgstr "ma recherche personnalisÈe"
+msgstr "Mi busqueda personalizada"
 
 msgid "name"
-msgstr "nom"
+msgstr "Nombre"
 
 msgid "name of the cache"
-msgstr "nom du cache applicatif"
+msgstr "Nombre del Cache"
 
 msgid ""
 "name of the main variables which should be used in the selection if "
 "necessary (comma separated)"
 msgstr ""
-"nom des variables principaes qui devrait Ãtre utilisÈes dans la sÈlection si "
-"nÈcessaire (les sÈparer par des virgules)"
+"Nombre de las variables principales que deberian se utilizadas en la "
+"selecciónde ser necesario (separarlas con comas)"
 
 msgid "name or identifier of the permission"
-msgstr "nom (identifiant) de la permission"
+msgstr "Nombre o indentificador de la autorización"
 
 msgid "navbottom"
-msgstr "bas de page"
+msgstr "Pie de pagina"
 
 msgid "navcontentbottom"
-msgstr "bas de page du contenu principal"
+msgstr "Pie de pagina del contenido principal"
 
 msgid "navcontenttop"
-msgstr "haut de page"
+msgstr "Encabezado"
 
 msgid "navigation"
-msgstr "navigation"
+msgstr "Navegación"
+
+msgid "navigation.combobox-limit"
+msgstr ""
+
+msgid "navigation.page-size"
+msgstr ""
+
+msgid "navigation.related-limit"
+msgstr ""
+
+msgid "navigation.short-line-size"
+msgstr ""
 
 msgid "navtop"
-msgstr "haut de page du contenu principal"
+msgstr "Encabezado del contenido principal"
 
 msgid "new"
-msgstr "nouveau"
+msgstr "Nuevo"
 
 msgid "next_results"
-msgstr "rÈsultats suivants"
+msgstr "Siguientes resultados"
 
 msgid "no"
-msgstr "non"
-
-msgid "no associated epermissions"
-msgstr "aucune permission spÈcifique n'est dÈfinie"
+msgstr "no"
+
+msgid "no associated permissions"
+msgstr ""
 
 msgid "no possible transition"
-msgstr "aucune transition possible"
+msgstr "transición no posible"
 
 msgid "no related project"
-msgstr "pas de projet rattachÈ"
+msgstr "no hay proyecto relacionado"
 
 msgid "no selected entities"
-msgstr "pas d'entitÈ sÈlectionnÈe"
+msgstr "no hay entidades seleccionadas"
 
 #, python-format
 msgid "no such entity type %s"
-msgstr "le type d'entitÈ '%s' n'existe pas"
+msgstr "el tipo de entidad '%s' no existe"
 
 msgid "no version information"
-msgstr "pas d'information de version"
+msgstr "no información de version"
 
 msgid "not authorized"
-msgstr "non autorisÈ"
+msgstr "no autorizado"
 
 msgid "not selected"
-msgstr ""
+msgstr "no seleccionado"
 
 msgid "not specified"
-msgstr "non spÈcifiÈ"
+msgstr "no especificado"
 
 msgid "not the initial state for this entity"
-msgstr "n'est pas l'Ètat initial pour cette entitÈ"
+msgstr "no el estado inicial para esta entidad"
 
 msgid "nothing to edit"
-msgstr "rien ‡ Èditer"
+msgstr "nada que editar"
 
 msgid "november"
-msgstr "novembre"
+msgstr "noviembre"
 
 msgid "object"
-msgstr "objet"
+msgstr "objeto"
+
+msgid "object_plural:"
+msgstr ""
 
 msgid "october"
-msgstr "octobre"
+msgstr "octubre"
 
 msgid "one month"
-msgstr "un mois"
+msgstr "un mes"
 
 msgid "one week"
-msgstr "une semaine"
+msgstr "una semana"
 
 msgid "oneline"
-msgstr "une ligne"
+msgstr "una linea"
 
 msgid "only select queries are authorized"
-msgstr "seules les requÃtes de sÈlections sont autorisÈes"
+msgstr "solo estan permitidas consultas de lectura"
+
+msgid "open all"
+msgstr ""
 
 msgid "order"
-msgstr "ordre"
+msgstr "orden"
 
 msgid "ordernum"
-msgstr "ordre"
+msgstr "orden"
 
 msgid "owl"
 msgstr ""
@@ -2320,175 +2355,185 @@
 msgstr ""
 
 msgid "owned_by"
-msgstr "appartient ‡"
+msgstr "pertenece a"
 
 msgid "owned_by_object"
-msgstr "possËde"
+msgstr "pertenece al objeto"
 
 msgid "owners"
-msgstr "propriÈtaires"
+msgstr "proprietarios"
 
 msgid "ownership"
-msgstr "propriÈtÈ"
+msgstr "pertenencia"
 
 msgid "ownerships have been changed"
-msgstr "les droits de propriÈtÈ ont ÈtÈ modifiÈs"
+msgstr "la pertenencia ha sido modificada"
 
 msgid "pageid-not-found"
-msgstr ""
-"des donnÈes nÈcessaires semblent expirÈes, veuillez recharger la page et "
-"recommencer."
+msgstr "pagina no encontrada."
 
 msgid "password"
-msgstr "mot de passe"
+msgstr "Clave de acceso"
 
 msgid "password and confirmation don't match"
-msgstr "le mot de passe et la confirmation sont diffÈrents"
+msgstr "La clave de acceso y la confirmación no concuerdan"
 
 msgid "path"
-msgstr "chemin"
+msgstr "Ruta"
 
 msgid "permission"
-msgstr "permission"
+msgstr "Permiso"
+
+msgid "permissions for entities"
+msgstr ""
+
+msgid "permissions for relations"
+msgstr ""
 
 msgid "permissions for this entity"
-msgstr "permissions pour cette entitÈ"
+msgstr "Permisos para esta entidad"
 
 msgid "personnal informations"
-msgstr "informations personnelles"
+msgstr "Información personal"
 
 msgid "pick existing bookmarks"
-msgstr "rÈcupÈrer des signets existants"
+msgstr "Seleccione los favoritos existentes"
 
 msgid "pkey"
-msgstr "clÈ"
+msgstr ""
 
 msgid "please correct errors below"
-msgstr "veuillez corriger les erreurs ci-dessous"
+msgstr "Favor de corregir errores"
 
 msgid "please correct the following errors:"
-msgstr "veuillez corriger les erreurs suivantes :"
+msgstr "Favor de corregir los siguientes errores :"
 
 msgid "possible views"
-msgstr "vues possibles"
+msgstr "Vistas posibles"
 
 msgid "preferences"
-msgstr "prÈfÈrences"
+msgstr "Preferencias"
 
 msgid "previous_results"
-msgstr "rÈsultats prÈcÈdents"
+msgstr "Resultados anteriores"
 
 msgid "primary"
-msgstr "primaire"
+msgstr "Primaria"
 
 msgid "primary_email"
-msgstr "adresse email principale"
+msgstr "Dirección de email principal"
 
 msgid "primary_email_object"
-msgstr "adresse email principale (object)"
+msgstr "Dirección de email principal (objeto)"
 
 msgid "progress"
-msgstr "avancement"
+msgstr "Avance"
 
 msgid "progress bar"
-msgstr "barre d'avancement"
+msgstr "Barra de progreso de avance"
 
 msgid "project"
-msgstr "projet"
+msgstr "Proyecto"
 
 msgid "read"
-msgstr "lecture"
+msgstr "Lectura"
 
 msgid "read_perm"
-msgstr "lecture"
+msgstr "Lectura"
 
 msgid "read_permission"
-msgstr "permission de lire"
+msgstr "Permiso de lectura"
 
 msgid "read_permission_object"
-msgstr "a la permission de lire"
+msgstr "Objeto_permiso_lectura"
 
 #, python-format
 msgid "relation %(relname)s of %(ent)s"
-msgstr "relation %(relname)s de %(ent)s"
+msgstr "relación %(relname)s de %(ent)s"
 
 msgid "relation_type"
-msgstr "type de relation"
+msgstr "tipo de relación"
 
 msgid "relation_type_object"
-msgstr "dÈfinition"
+msgstr "Definición"
+
+msgid "relations"
+msgstr ""
 
 msgid "relations deleted"
-msgstr "relations supprimÈes"
+msgstr "Relaciones eliminadas"
 
 msgid "relative url of the bookmarked page"
-msgstr "url relative de la page"
+msgstr "Url relativa de la pagina"
 
 msgid "remove this Bookmark"
-msgstr "supprimer ce signet"
-
-msgid "remove this Card"
-msgstr "supprimer cette fiche"
-
-msgid "remove this ECache"
-msgstr "supprimer ce cache applicatif"
-
-msgid "remove this EConstraint"
-msgstr "supprimer cette contrainte"
-
-msgid "remove this EConstraintType"
-msgstr "supprimer ce type de contrainte"
-
-msgid "remove this EEType"
-msgstr "supprimer ce type d'entitÈ"
-
-msgid "remove this EFRDef"
-msgstr "supprimer cet attribut"
-
-msgid "remove this EGroup"
-msgstr "supprimer ce groupe"
-
-msgid "remove this ENFRDef"
-msgstr "supprimer cette relation"
-
-msgid "remove this EPermission"
-msgstr "supprimer cette permission"
-
-msgid "remove this EProperty"
-msgstr "supprimer cette propriÈtÈ"
-
-msgid "remove this ERType"
-msgstr "supprimer cette dÈfinition de relation"
-
-msgid "remove this EUser"
-msgstr "supprimer cet utilisateur"
+msgstr "Eliminar este Favorito"
+
+msgid "remove this CWAttribute"
+msgstr "Eliminar este atributo"
+
+msgid "remove this CWCache"
+msgstr "Eliminar esta cache de aplicación"
+
+msgid "remove this CWConstraint"
+msgstr "Eliminar esta restricción"
+
+msgid "remove this CWConstraintType"
+msgstr "Eliminar este tipo de restricción"
+
+msgid "remove this CWEType"
+msgstr "Eliminar este tipo de entidad"
+
+msgid "remove this CWGroup"
+msgstr "Eliminar este grupo"
+
+msgid "remove this CWPermission"
+msgstr "Eliminar este permiso"
+
+msgid "remove this CWProperty"
+msgstr "Eliminar esta propiedad"
+
+msgid "remove this CWRType"
+msgstr "Eliminar esta definición de relación"
+
+msgid "remove this CWRelation"
+msgstr "Eliminar esta relación"
+
+msgid "remove this CWUser"
+msgstr "Eliminar este usuario"
 
 msgid "remove this EmailAddress"
-msgstr "supprimer cette adresse email"
+msgstr "Eliminar este correo electronico"
 
 msgid "remove this RQLExpression"
-msgstr "supprimer cette expression rql"
+msgstr "Eliminar esta expresión RQL"
 
 msgid "remove this State"
-msgstr "supprimer cet Ètat"
+msgstr "Eliminar este estado"
 
 msgid "remove this TrInfo"
-msgstr "retirer cette information de transition"
+msgstr "Eliminar información de esta transición"
 
 msgid "remove this Transition"
-msgstr "supprimer cette transition"
+msgstr "Eliminar esta transición"
 
 msgid "require_group"
-msgstr "nÈcessite le groupe"
+msgstr "Requiere_grupo"
 
 msgid "require_group_object"
-msgstr "‡ les droits"
+msgstr "Objeto_grupo_requerido"
+
+msgid "require_permission"
+msgstr ""
+
+msgid "require_permission_object"
+msgstr ""
 
 msgid "required attribute"
-msgstr "attribut requis"
+msgstr "Atributo requerido"
 
 msgid "required field"
-msgstr "champ requis"
+msgstr "Campo requerido"
 
 msgid ""
 "restriction part of a rql query. For entity rql expression, X and U are "
@@ -2496,460 +2541,503 @@
 "relation rql expression, S, O and U are predefined respectivly to the "
 "current relation'subject, object and to the request user. "
 msgstr ""
-"partie restriction de la requÃte rql. Pour une expression s'appliquant ‡ une "
-"entitÈ, X et U sont respectivement prÈfÈfinis ‡ l'entitÈ et ‡ l'utilisateur "
-"courant. Pour une expression s'appliquant ‡ une relation, S, O et U sont "
-"respectivement prÈfÈfinis au sujet/objet de la relation et ‡ l'utilisateur "
-"courant."
+"restriction part of a rql query. For entity rql expression, X and U are "
+"predefined respectivly to the current object and to the request user. For "
+"relation rql expression, S, O and U are predefined respectivly to the "
+"current relation'subject, object and to the request user. "
 
 msgid "revert changes"
-msgstr "annuler les changements"
+msgstr "Revertir cambios"
 
 msgid "right"
-msgstr "droite"
+msgstr "Derecha"
 
 msgid "rql expression allowing to add entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit d'ajouter des entitÈs/relations de ce type"
+msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo"
 
 msgid "rql expression allowing to delete entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit de supprimer des entitÈs/relations de ce type"
+msgstr "expresion RQL permitiendo eliminar entidades/relaciones de este tipo"
 
 msgid "rql expression allowing to read entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit de lire des entitÈs/relations de ce type"
+msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo"
 
 msgid "rql expression allowing to update entities of this type"
-msgstr ""
-"expression RQL donnant le droit de modifier des entitÈs/relations de ce type"
+msgstr "expresion RQL permitiendo actualizar entidades de este tipo"
 
 msgid "rql expressions"
-msgstr "conditions rql"
+msgstr "expresiones rql"
 
 msgid "rss"
 msgstr "RSS"
 
 msgid "sample format"
-msgstr "exemple"
+msgstr "ejemplo"
 
 msgid "saturday"
-msgstr "samedi"
+msgstr "sabado"
 
 msgid "schema entities"
-msgstr "entitÈs dÈfinissant le schÈma"
+msgstr "entidades del esquema"
 
 msgid "schema's permissions definitions"
-msgstr "permissions dÈfinies dans le schÈma"
+msgstr "definiciones de permisos del esquema"
 
 msgid "search"
-msgstr "rechercher"
+msgstr "buscar"
 
 msgid "search for association"
-msgstr "rechercher pour associer"
+msgstr "buscar por asociación"
 
 msgid "searching for"
-msgstr "Recherche de"
+msgstr "buscando "
 
 msgid "secondary"
-msgstr "secondaire"
+msgstr "secundario"
 
 msgid "security"
-msgstr "sÈcuritÈ"
+msgstr "seguridad"
 
 msgid "see them all"
-msgstr "les voir toutes"
+msgstr "Ver todos"
+
+msgid "see_also"
+msgstr ""
 
 msgid "select"
-msgstr "sÈlectionner"
+msgstr "Seleccionar"
 
 msgid "select a"
-msgstr "sÈlectionner un"
+msgstr "seleccione un"
+
+msgid "select a key first"
+msgstr ""
 
 msgid "select a relation"
-msgstr "sÈlectionner une relation"
+msgstr "seleccione una relación"
 
 msgid "select this entity"
-msgstr "sÈlectionner cette entitÈ"
+msgstr "seleccionar esta entidad"
 
 msgid "selected"
-msgstr ""
+msgstr "seleccionado"
 
 msgid "semantic description of this attribute"
-msgstr "description sÈmantique de cet attribut"
+msgstr "descripción semantica de este atributo"
 
 msgid "semantic description of this entity type"
-msgstr "description sÈmantique de ce type d'entitÈ"
+msgstr "descripción semantica de este tipo de entidad"
 
 msgid "semantic description of this relation"
-msgstr "description sÈmantique de cette relation"
+msgstr "descripción semantica de esta relación"
 
 msgid "semantic description of this relation type"
-msgstr "description sÈmantique de ce type de relation"
+msgstr "descripción semantica de este tipo de relación"
 
 msgid "semantic description of this state"
-msgstr "description sÈmantique de cet Ètat"
+msgstr "descripción semantica de este estado"
 
 msgid "semantic description of this transition"
-msgstr "description sÈmantique de cette transition"
+msgstr "descripcion semantica de esta transición"
 
 msgid "send email"
-msgstr "envoyer un courriel"
+msgstr "enviar email"
 
 msgid "september"
-msgstr "septembre"
+msgstr "septiembre"
 
 msgid "server debug information"
-msgstr "informations de dÈboguage serveur"
+msgstr "server debug information"
 
 msgid "server information"
-msgstr "informations serveur"
+msgstr "server information"
 
 msgid ""
 "should html fields being edited using fckeditor (a HTML WYSIWYG editor).  "
 "You should also select text/html as default text format to actually get "
 "fckeditor."
 msgstr ""
-"indique si les champs HTML doivent Ãtre Èditer avec fckeditor (un\n"
-"Èditer HTML WYSIWYG). Il est Ègalement conseill'de choisir text/html\n"
-"comme format de texte par dÈfaut pour pouvoir utiliser fckeditor."
+"indique si los campos deberan ser editados usando fckeditor (un\n"
+"editor HTML WYSIWYG). Debera tambien elegir text/html\n"
+"como formato de texto por default para poder utilizar fckeditor."
 
 #, python-format
 msgid "show %s results"
-msgstr "montrer %s rÈsultats"
+msgstr "mostrar %s resultados"
 
 msgid "show advanced fields"
-msgstr "montrer les champs avancÈs"
+msgstr "mostrar campos avanzados"
 
 msgid "show filter form"
 msgstr "afficher le filtre"
 
 msgid "show meta-data"
-msgstr "afficher les mÈta-donnÈes"
+msgstr "mostrar meta-data"
+
+msgid "sioc"
+msgstr ""
 
 msgid "site configuration"
-msgstr "configuration du site"
+msgstr "configuracion del sitio"
 
 msgid "site documentation"
-msgstr "documentation du site"
+msgstr "documentacion del sitio"
 
 msgid "site schema"
-msgstr "schÈma du site"
+msgstr "esquema del sitio"
 
 msgid "site title"
-msgstr "titre du site"
+msgstr "titulo del sitio"
 
 msgid "site-wide property can't be set for user"
-msgstr "une propriÈtÈ spÈcifique au site ne peut Ãtre propre ‡ un utilisateur"
+msgstr ""
+"una propiedad especifica para el sitio no puede establecerse para el usuario"
 
 msgid "sorry, the server is unable to handle this query"
-msgstr "dÈsolÈ, le serveur ne peut traiter cette requÃte"
+msgstr "lo sentimos, el servidor no puede manejar esta consulta"
 
 msgid "specializes"
-msgstr "dÈrive de"
+msgstr "derivado de"
 
 msgid "specializes_object"
-msgstr "parent de"
+msgstr "objeto_derivado"
 
 msgid "startup views"
-msgstr "vues de dÈpart"
+msgstr "vistas de inicio"
 
 msgid "state"
-msgstr "Ètat"
+msgstr "estado"
 
 msgid "state_of"
-msgstr "Ètat de"
+msgstr "estado_de"
 
 msgid "state_of_object"
-msgstr "a pour Ètat"
+msgstr "objeto_estado_de"
 
 msgid "status change"
-msgstr "changer l'Ètat"
+msgstr "cambio de estatus"
 
 msgid "status changed"
-msgstr "changement d'Ètat"
+msgstr "estatus cambiado"
 
 #, python-format
 msgid "status will change from %(st1)s to %(st2)s"
-msgstr "l'entitÈ passera de l'Ètat %(st1)s ‡ l'Ètat %(st2)s"
+msgstr "estatus cambiara de %(st1)s a %(st2)s"
 
 msgid "subject"
-msgstr "sujet"
+msgstr "sujeto"
 
 msgid "subject/object cardinality"
-msgstr "cardinalitÈ sujet/objet"
+msgstr "cardinalidad sujeto/objeto"
+
+msgid "subject_plural:"
+msgstr ""
 
 msgid "sunday"
-msgstr "dimanche"
+msgstr "domingo"
 
 msgid "surname"
-msgstr "nom"
+msgstr "apellido"
 
 msgid "symetric"
-msgstr "symÈtrique"
-
-msgid "synopsis"
-msgstr "synopsis"
+msgstr "simetrico"
 
 msgid "system entities"
-msgstr "entitÈs systËmes"
+msgstr "entidades de sistema"
 
 msgid "table"
-msgstr "table"
+msgstr "tabla"
 
 msgid "tablefilter"
-msgstr "filtre de tableau"
+msgstr "filtro de tabla"
 
 msgid "task progression"
-msgstr "avancement de la t‚che"
+msgstr "progreso de la tarea"
 
 msgid "text"
 msgstr "text"
 
 msgid "text/cubicweb-page-template"
-msgstr "contenu dynamique"
+msgstr "text/cubicweb-page-template"
 
 msgid "text/html"
 msgstr "html"
 
 msgid "text/plain"
-msgstr "texte pur"
+msgstr "text/plain"
 
 msgid "text/rest"
-msgstr "ReST"
+msgstr "text/rest"
 
 msgid "the prefered email"
-msgstr "l'adresse Èlectronique principale"
+msgstr "dirección principal de email"
 
 #, python-format
 msgid "the value \"%s\" is already used, use another one"
-msgstr "la valeur \"%s\" est dÈj‡ utilisÈe, veuillez utiliser une autre valeur"
+msgstr "el valor \"%s\" ya esta en uso, favor de utilizar otro"
 
 msgid "this action is not reversible!"
-msgstr ""
-"Attention ! Cette opÈration va dÈtruire les donnÈes de faÃon irrÈversible."
+msgstr "esta acción es irreversible!."
 
 msgid "this entity is currently owned by"
-msgstr "cette entitÈ appartient ‡"
+msgstr "esta entidad es propiedad de"
 
 msgid "this resource does not exist"
-msgstr "cette ressource est introuvable"
+msgstr "este recurso no existe"
 
 msgid "thursday"
-msgstr "jeudi"
+msgstr "jueves"
 
 msgid "timestamp"
-msgstr "date"
+msgstr "fecha"
 
 msgid "timestamp of the latest source synchronization."
-msgstr "date de la derniËre synchronisation avec la source."
+msgstr "fecha de la ultima sincronización de la fuente."
 
 msgid "timetable"
-msgstr "emploi du temps"
+msgstr "tabla de tiempos"
 
 msgid "title"
-msgstr "titre"
+msgstr "titulo"
 
 msgid "to"
-msgstr "‡"
+msgstr "a"
+
+#, python-format
+msgid "to %(date)s"
+msgstr ""
 
 msgid "to associate with"
-msgstr "pour associer ‡"
+msgstr "a asociar con"
 
 msgid "to_entity"
-msgstr "vers l'entitÈ"
+msgstr "hacia entidad"
 
 msgid "to_entity_object"
-msgstr "relation objet"
+msgstr "hacia entidad objeto"
 
 msgid "to_state"
-msgstr "vers l'Ètat"
+msgstr "hacia el estado"
 
 msgid "to_state_object"
-msgstr "transitions vers cette Ètat"
+msgstr "hacia objeto estado"
 
 msgid "todo_by"
-msgstr "‡ faire par"
+msgstr "a hacer por"
+
+msgid "toggle check boxes"
+msgstr ""
 
 msgid "transition is not allowed"
-msgstr "transition non permise"
+msgstr "transition no permitida"
 
 msgid "transition_of"
-msgstr "transition de"
+msgstr "transicion de"
 
 msgid "transition_of_object"
-msgstr "a pour transition"
+msgstr "objeto de transición"
 
 msgid "tree view"
 msgstr ""
 
 msgid "tuesday"
-msgstr "mardi"
+msgstr "martes"
 
 msgid "type"
 msgstr "type"
 
 msgid "ui"
-msgstr "propriÈtÈs gÈnÈriques de l'interface"
+msgstr "interfaz de usuario"
+
+msgid "ui.date-format"
+msgstr ""
+
+msgid "ui.datetime-format"
+msgstr ""
+
+msgid "ui.default-text-format"
+msgstr ""
+
+msgid "ui.encoding"
+msgstr ""
+
+msgid "ui.fckeditor"
+msgstr ""
+
+msgid "ui.float-format"
+msgstr ""
+
+msgid "ui.language"
+msgstr ""
+
+msgid "ui.main-template"
+msgstr ""
+
+msgid "ui.site-title"
+msgstr ""
+
+msgid "ui.time-format"
+msgstr ""
 
 msgid "unaccessible"
-msgstr "inaccessible"
+msgstr "inaccesible"
 
 msgid "unauthorized value"
-msgstr "valeur non autorisÈe"
+msgstr "valor no permitido"
 
 msgid "unique identifier used to connect to the application"
-msgstr "identifiant unique utilisÈ pour se connecter ‡ l'application"
+msgstr "identificador unico utilizado para conectar a la aplicación"
 
 msgid "unknown external entity"
-msgstr "entitÈ (externe) introuvable"
+msgstr "entidad externa desconocida"
 
 msgid "unknown property key"
-msgstr "clÈ de propriÈtÈ inconnue"
+msgstr "propiedad desconocida"
+
+msgid "up"
+msgstr ""
 
 msgid "upassword"
-msgstr "mot de passe"
+msgstr "clave de acceso"
 
 msgid "update"
-msgstr "modification"
+msgstr "modificación"
 
 msgid "update_perm"
-msgstr "modification"
+msgstr "modificación"
 
 msgid "update_permission"
-msgstr "permission de modification"
+msgstr "Permiso de modificación"
 
 msgid "update_permission_object"
-msgstr "‡ la permission de modifier"
+msgstr "objeto de autorización de modificaciones"
 
 #, python-format
 msgid "updated %(etype)s #%(eid)s (%(title)s)"
-msgstr "modification de l'entitÈ %(etype)s #%(eid)s (%(title)s)"
+msgstr "actualización de la entidad %(etype)s #%(eid)s (%(title)s)"
 
 msgid "use template languages"
-msgstr "utiliser les langages de template"
+msgstr "utilizar plantillas de lenguaje"
 
 msgid ""
 "use to define a transition from one or multiple states to a destination "
 "states in workflow's definitions."
 msgstr ""
-"utiliser dans une dÈfinition de processus pour ajouter une transition depuis "
-"un ou plusieurs Ètats vers un Ètat de destination."
+"utilizado para definir una transición desde uno o multiples estados hacia "
+"uno o varios estados destino en las definiciones del workflow"
 
 msgid "use_email"
-msgstr "adresse Èlectronique"
+msgstr "correo electrónico"
 
 msgid "use_email_object"
-msgstr "adresse utilisÈe par"
+msgstr "objeto email utilizado"
 
 msgid "use_template_format"
-msgstr "utilisation du format 'cubicweb template'"
+msgstr "utilización del formato 'cubicweb template'"
 
 msgid ""
 "used for cubicweb configuration. Once a property has been created you can't "
 "change the key."
 msgstr ""
-"utilisÈ pour la configuration de l'application. Une fois qu'une propriÈtÈ a "
-"ÈtÈ crÈÈe, vous ne pouvez plus changez la clÈ associÈe"
+"utilizado para la configuración de cubicweb. Una vez que la propiedad ha "
+"sido creada no puede cambiar la llave"
 
 msgid ""
 "used to associate simple states to an entity type and/or to define workflows"
-msgstr "associe les Ètats ‡ un type d'entitÈ pour dÈfinir un workflow"
+msgstr ""
+"utilizado para asociar estados simples a un tipo de entidad y/o para definir "
+"workflows"
 
 msgid "used to grant a permission to a group"
-msgstr "utiliser pour donner une permission ‡ un groupe"
+msgstr "utilizado para otorgar permisos a un grupo"
 
 #, python-format
 msgid ""
 "user %s has made the following change(s):\n"
 "\n"
 msgstr ""
-"l'utilisateur %s a effectuÈ le(s) changement(s) suivant(s):\n"
+"el usuario %s ha efectuado los siguentes cambios:\n"
 "\n"
 
 msgid ""
 "user for which this property is applying. If this relation is not set, the "
 "property is considered as a global property"
 msgstr ""
-"utilisateur a qui s'applique cette propriÈtÈ. Si cette relation n'est pas "
-"spÈcifiÈe la propriÈtÈ est considÈrÈe comme globale."
+"usuario para el cual aplica esta propiedad. Si no se establece esta "
+"relación, la propiedad es considerada como una propiedad global."
 
 msgid "user interface encoding"
-msgstr "encodage utilisÈ dans l'interface utilisateur"
+msgstr "codificación de la interfaz de usuario"
 
 msgid "user preferences"
-msgstr "prÈfÈrences utilisateur"
+msgstr "preferencias del usuario"
 
 msgid "users"
-msgstr "utilisateurs"
+msgstr "usuarios"
 
 msgid "users using this bookmark"
-msgstr "utilisateurs utilisant ce signet"
+msgstr "usuarios en este favorito"
 
 msgid "validate modifications on selected items"
-msgstr "valider les modifications apportÈes aux ÈlÈments sÈlectionnÈs"
+msgstr "valida modificaciones sobre elementos seleccionados"
 
 msgid "validating..."
-msgstr "chargement en cours ..."
+msgstr "validando ..."
 
 msgid "value"
-msgstr "valeur"
+msgstr "valor"
 
 msgid "value associated to this key is not editable manually"
-msgstr "la valeur associÈe ‡ cette clÈ n'est pas Èditable manuellement"
+msgstr "el valor asociado a este elemento no es editable manualmente"
 
 msgid "vcard"
 msgstr "vcard"
 
 msgid "view"
-msgstr "voir"
+msgstr "ver"
 
 msgid "view all"
-msgstr "voir tous"
+msgstr "ver todos"
 
 msgid "view detail for this entity"
-msgstr "voir les dÈtails de cette entitÈ"
+msgstr "ver detalle de esta entidad"
 
 msgid "view workflow"
-msgstr "voir les Ètats possibles"
+msgstr "ver workflow"
 
 msgid "views"
-msgstr "vues"
+msgstr "vistas"
 
 msgid "visible"
 msgstr "visible"
 
 msgid "wednesday"
-msgstr "mercredi"
+msgstr "miercoles"
 
 msgid "week"
 msgstr "sem."
 
 #, python-format
 msgid "welcome %s !"
-msgstr "bienvenue %s !"
+msgstr "bienvenido %s !"
 
 msgid "wf_info_for"
-msgstr "historique de"
+msgstr "historial de"
 
 msgid "wf_info_for_object"
-msgstr "historique des transitions"
+msgstr "historial de transiciones"
 
 msgid ""
 "when multiple addresses are equivalent (such as python-projects@logilab.org "
 "and python-projects@lists.logilab.org), set this to true on one of them "
 "which is the preferred form."
 msgstr ""
-"quand plusieurs adresses sont Èquivalentes (comme python-projects@logilab."
-"org et python-projects@lists.logilab.org), mettez cette propriÈtÈ ‡ vrai sur "
-"l'une d'entre-elle qui sera la forme canonique"
-
-msgid "wikiid"
-msgstr "identifiant wiki"
+"cuando multiples direcciones de correo son equivalentes (como python-"
+"projects@logilab.org y python-projects@lists.logilab.org), establecer esto "
+"como verdadero en una de ellas es la forma preferida "
 
 #, python-format
 msgid "workflow for %s"
-msgstr "workflow pour %s"
+msgstr "workflow para %s"
 
 msgid "xbel"
 msgstr "xbel"
@@ -2961,10 +3049,13 @@
 msgstr ""
 
 msgid "yes"
-msgstr "oui"
+msgstr "si"
 
 msgid "you have been logged out"
-msgstr "vous avez ÈtÈ dÈconnectÈ"
+msgstr "ha terminado la sesion"
+
+msgid "you should probably delete that property"
+msgstr ""
 
 #~ msgid "%s constraint failed"
 #~ msgstr "La contrainte %s n'est pas satisfaite"
@@ -2975,12 +3066,52 @@
 #~ msgid "%s, or without time: %s"
 #~ msgstr "%s, ou bien sans prÈciser d'heure: %s"
 
+#~ msgid "Card"
+#~ msgstr "Ficha"
+
+#~ msgid "Card_plural"
+#~ msgstr "Fichas"
+
+#~ msgid "Email body: "
+#~ msgstr "Contenido del correo electrónico : "
+
+#~ msgid "From: "
+#~ msgstr "De : "
+
 #~ msgid "Loading"
 #~ msgstr "chargement"
 
+#~ msgid "New Card"
+#~ msgstr "Agregar Ficha"
+
+#~ msgid "Problem occured"
+#~ msgstr "Ha ocurrido un error"
+
 #~ msgid "Problem occured while setting new value"
 #~ msgstr "Un problËme est survenu lors de la mise ‡ jour"
 
+#~ msgid "Recipients: "
+#~ msgstr "Destinatarios : "
+
+#~ msgid "Subject: "
+#~ msgstr "Objeto : "
+
+#~ msgid "This Card"
+#~ msgstr "Esta Ficha"
+
+#~ msgid ""
+#~ "a card is a textual content used as documentation, reference, procedure "
+#~ "reminder"
+#~ msgstr ""
+#~ "una ficha es un texto utilizado como documentación, referencia, memoria "
+#~ "de algún procedimiento..."
+
+#~ msgid "add a Card"
+#~ msgstr "Agregar una ficha"
+
+#~ msgid "an abstract for this card"
+#~ msgstr "un resumen para esta ficha"
+
 #~ msgid "and"
 #~ msgstr "et"
 
@@ -2990,6 +3121,18 @@
 #~ msgid "cancel edition"
 #~ msgstr "annuler l'Èdition"
 
+#~ msgid "components_rss_feed_url"
+#~ msgstr "RSS FEED URL"
+
+#~ msgid "components_rss_feed_url_description"
+#~ msgstr "El espacio para administrar RSS"
+
+#~ msgid "content"
+#~ msgstr "Contenido"
+
+#~ msgid "content_format"
+#~ msgstr "Formato"
+
 #~ msgid ""
 #~ "default language (look at the i18n directory of the application to see "
 #~ "available languages)"
@@ -3012,6 +3155,15 @@
 #~ msgid "incorrect value for type \"%s\""
 #~ msgstr "valeur incorrecte pour le type \"%s\""
 
+#~ msgid "inlined view"
+#~ msgstr "Vista incluída (en línea)"
+
+#~ msgid ""
+#~ "link a transition to one or more rql expression allowing to go through "
+#~ "this transition"
+#~ msgstr ""
+#~ "liga una transición a una o mas expresiones RQL permitiendo que funcione"
+
 #~ msgid "linked"
 #~ msgstr "liÈ"
 
@@ -3023,14 +3175,29 @@
 #~ msgstr ""
 #~ "nombre maximum d'entitÈs liÈes ‡ afficher dans la vue de restriction"
 
+#~ msgid "no associated epermissions"
+#~ msgstr "permisos no asociados"
+
 #~ msgid "owned by"
 #~ msgstr "appartient ‡"
 
+#~ msgid "planned_delivery"
+#~ msgstr "entrega planeada"
+
+#~ msgid "remove this Card"
+#~ msgstr "Eliminar esta Ficha"
+
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
 #~ msgid "status will change from %s to %s"
 #~ msgstr "l'Ètat va passer de %s ‡ %s"
 
+#~ msgid "synopsis"
+#~ msgstr "sinopsis"
+
+#~ msgid "wikiid"
+#~ msgstr "identificador wiki"
+
 #~ msgid "workflow history"
 #~ msgstr "historique du workflow"
--- a/i18n/fr.po	Thu May 14 12:50:14 2009 +0200
+++ b/i18n/fr.po	Thu May 14 12:50:34 2009 +0200
@@ -201,11 +201,71 @@
 msgid "Bytes_plural"
 msgstr "Données binaires"
 
-msgid "Card"
-msgstr "Fiche"
-
-msgid "Card_plural"
-msgstr "Fiches"
+msgid "CWAttribute"
+msgstr "Attribut"
+
+msgid "CWAttribute_plural"
+msgstr "Attributs"
+
+msgid "CWCache"
+msgstr "Cache applicatif"
+
+msgid "CWCache_plural"
+msgstr "Caches applicatifs"
+
+msgid "CWConstraint"
+msgstr "Contrainte"
+
+msgid "CWConstraintType"
+msgstr "Type de contrainte"
+
+msgid "CWConstraintType_plural"
+msgstr "Types de contrainte"
+
+msgid "CWConstraint_plural"
+msgstr "Contraintes"
+
+msgid "CWEType"
+msgstr "Type d'entité"
+
+msgid "CWEType_plural"
+msgstr "Types d'entité"
+
+msgid "CWGroup"
+msgstr "Groupe"
+
+msgid "CWGroup_plural"
+msgstr "Groupes"
+
+msgid "CWPermission"
+msgstr "Permission"
+
+msgid "CWPermission_plural"
+msgstr "Permissions"
+
+msgid "CWProperty"
+msgstr "Propriété"
+
+msgid "CWProperty_plural"
+msgstr "Propriétés"
+
+msgid "CWRType"
+msgstr "Type de relation"
+
+msgid "CWRType_plural"
+msgstr "Types de relation"
+
+msgid "CWRelation"
+msgstr "Relation"
+
+msgid "CWRelation_plural"
+msgstr "Relations"
+
+msgid "CWUser"
+msgstr "Utilisateur"
+
+msgid "CWUser_plural"
+msgstr "Utilisateurs"
 
 msgid "Date"
 msgstr "Date"
@@ -232,75 +292,6 @@
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Voulez vous supprimer le(s) élément(s) suivant(s)"
 
-msgid "ECache"
-msgstr "Cache applicatif"
-
-msgid "ECache_plural"
-msgstr "Caches applicatifs"
-
-msgid "EConstraint"
-msgstr "Contrainte"
-
-msgid "EConstraintType"
-msgstr "Type de contrainte"
-
-msgid "EConstraintType_plural"
-msgstr "Types de contrainte"
-
-msgid "EConstraint_plural"
-msgstr "Contraintes"
-
-msgid "EEType"
-msgstr "Type d'entité"
-
-msgid "EEType_plural"
-msgstr "Types d'entité"
-
-msgid "EFRDef"
-msgstr "Attribut"
-
-msgid "EFRDef_plural"
-msgstr "Attributs"
-
-msgid "EGroup"
-msgstr "Groupe"
-
-msgid "EGroup_plural"
-msgstr "Groupes"
-
-msgid "ENFRDef"
-msgstr "Relation"
-
-msgid "ENFRDef_plural"
-msgstr "Relations"
-
-msgid "EPermission"
-msgstr "Permission"
-
-msgid "EPermission_plural"
-msgstr "Permissions"
-
-msgid "EProperty"
-msgstr "Propriété"
-
-msgid "EProperty_plural"
-msgstr "Propriétés"
-
-msgid "ERType"
-msgstr "Type de relation"
-
-msgid "ERType_plural"
-msgstr "Types de relation"
-
-msgid "EUser"
-msgstr "Utilisateur"
-
-msgid "EUser_plural"
-msgstr "Utilisateurs"
-
-msgid "Email body: "
-msgstr "Contenu du courriel : "
-
 msgid "EmailAddress"
 msgstr "Adresse électronique"
 
@@ -319,8 +310,8 @@
 msgid "Float_plural"
 msgstr "Nombres flottants"
 
-msgid "From: "
-msgstr "De : "
+msgid "From:"
+msgstr "De :"
 
 msgid "Int"
 msgstr "Nombre entier"
@@ -337,40 +328,37 @@
 msgid "New Bookmark"
 msgstr "Nouveau signet"
 
-msgid "New Card"
-msgstr "Nouvelle fiche"
-
-msgid "New ECache"
-msgstr "Nouveau cache applicatif"
-
-msgid "New EConstraint"
-msgstr "Nouvelle contrainte"
-
-msgid "New EConstraintType"
-msgstr "Nouveau type de contrainte"
-
-msgid "New EEType"
-msgstr "Nouveau type d'entité"
-
-msgid "New EFRDef"
+msgid "New CWAttribute"
 msgstr "Nouvelle définition de relation finale"
 
-msgid "New EGroup"
+msgid "New CWCache"
+msgstr "Nouveau cache applicatif"
+
+msgid "New CWConstraint"
+msgstr "Nouvelle contrainte"
+
+msgid "New CWConstraintType"
+msgstr "Nouveau type de contrainte"
+
+msgid "New CWEType"
+msgstr "Nouveau type d'entité"
+
+msgid "New CWGroup"
 msgstr "Nouveau groupe"
 
-msgid "New ENFRDef"
-msgstr "Nouvelle définition de relation non finale"
-
-msgid "New EPermission"
+msgid "New CWPermission"
 msgstr "Nouvelle permission"
 
-msgid "New EProperty"
+msgid "New CWProperty"
 msgstr "Nouvelle propriété"
 
-msgid "New ERType"
+msgid "New CWRType"
 msgstr "Nouveau type de relation"
 
-msgid "New EUser"
+msgid "New CWRelation"
+msgstr "Nouvelle définition de relation non finale"
+
+msgid "New CWUser"
 msgstr "Nouvel utilisateur"
 
 msgid "New EmailAddress"
@@ -406,17 +394,14 @@
 msgid "Please note that this is only a shallow copy"
 msgstr "Attention, cela n'effectue qu'une copie de surface"
 
-msgid "Problem occured"
-msgstr "Une erreur est survenue"
-
 msgid "RQLExpression"
 msgstr "Expression RQL"
 
 msgid "RQLExpression_plural"
 msgstr "Expressions RQL"
 
-msgid "Recipients: "
-msgstr "Destinataires : "
+msgid "Recipients:"
+msgstr "Destinataires :"
 
 msgid "Relations"
 msgstr "Relations"
@@ -449,8 +434,8 @@
 msgid "String_plural"
 msgstr "Chaînes de caractères"
 
-msgid "Subject: "
-msgstr "Sujet : "
+msgid "Subject:"
+msgstr "Sujet :"
 
 msgid "Submit bug report"
 msgstr "Soumettre un rapport de bug"
@@ -476,40 +461,37 @@
 msgid "This Bookmark"
 msgstr "Ce signet"
 
-msgid "This Card"
-msgstr "Cette fiche"
-
-msgid "This ECache"
-msgstr "Ce cache applicatif"
-
-msgid "This EConstraint"
-msgstr "Cette contrainte"
-
-msgid "This EConstraintType"
-msgstr "Ce type de contrainte"
-
-msgid "This EEType"
-msgstr "Ce type d'entité"
-
-msgid "This EFRDef"
+msgid "This CWAttribute"
 msgstr "Cette définition de relation finale"
 
-msgid "This EGroup"
+msgid "This CWCache"
+msgstr "Ce cache applicatif"
+
+msgid "This CWConstraint"
+msgstr "Cette contrainte"
+
+msgid "This CWConstraintType"
+msgstr "Ce type de contrainte"
+
+msgid "This CWEType"
+msgstr "Ce type d'entité"
+
+msgid "This CWGroup"
 msgstr "Ce groupe"
 
-msgid "This ENFRDef"
-msgstr "Cette définition de relation non finale"
-
-msgid "This EPermission"
+msgid "This CWPermission"
 msgstr "Cette permission"
 
-msgid "This EProperty"
+msgid "This CWProperty"
 msgstr "Cette propriété"
 
-msgid "This ERType"
+msgid "This CWRType"
 msgstr "Ce type de relation"
 
-msgid "This EUser"
+msgid "This CWRelation"
+msgstr "Cette définition de relation non finale"
+
+msgid "This CWUser"
 msgstr "Cet utilisateur"
 
 msgid "This EmailAddress"
@@ -617,13 +599,6 @@
 "transition et l'utilisateur courant."
 
 msgid ""
-"a card is a textual content used as documentation, reference, procedure "
-"reminder"
-msgstr ""
-"une fiche est un texte utilisé comme documentation, référence, rappel de "
-"procédure..."
-
-msgid ""
 "a simple cache entity characterized by a name and a validity date. The "
 "target application is responsible for updating timestamp when necessary to "
 "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
@@ -669,6 +644,12 @@
 msgid "actions_delete_description"
 msgstr ""
 
+msgid "actions_download_as_owl"
+msgstr ""
+
+msgid "actions_download_as_owl_description"
+msgstr ""
+
 msgid "actions_edit"
 msgstr "modifier"
 
@@ -699,6 +680,12 @@
 msgid "actions_manage_description"
 msgstr ""
 
+msgid "actions_managepermission"
+msgstr ""
+
+msgid "actions_managepermission_description"
+msgstr ""
+
 msgid "actions_muledit"
 msgstr "édition multiple"
 
@@ -768,49 +755,49 @@
 msgid "add"
 msgstr "ajouter"
 
-msgid "add Bookmark bookmarked_by EUser object"
+msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "signet"
 
-msgid "add EEType add_permission RQLExpression subject"
-msgstr "définir une expression RQL d'ajout"
-
-msgid "add EEType delete_permission RQLExpression subject"
-msgstr "définir une expression RQL de suppression"
-
-msgid "add EEType read_permission RQLExpression subject"
-msgstr "définir une expression RQL de lecture"
-
-msgid "add EEType update_permission RQLExpression subject"
-msgstr "définir une expression RQL de mise à jour"
-
-msgid "add EFRDef constrained_by EConstraint subject"
+msgid "add CWAttribute constrained_by CWConstraint subject"
 msgstr "contrainte"
 
-msgid "add EFRDef relation_type ERType object"
+msgid "add CWAttribute relation_type CWRType object"
 msgstr "définition d'attribut"
 
-msgid "add ENFRDef constrained_by EConstraint subject"
-msgstr "contrainte"
-
-msgid "add ENFRDef relation_type ERType object"
-msgstr "définition de relation"
-
-msgid "add EProperty for_user EUser object"
+msgid "add CWEType add_permission RQLExpression subject"
+msgstr "définir une expression RQL d'ajout"
+
+msgid "add CWEType delete_permission RQLExpression subject"
+msgstr "définir une expression RQL de suppression"
+
+msgid "add CWEType read_permission RQLExpression subject"
+msgstr "définir une expression RQL de lecture"
+
+msgid "add CWEType update_permission RQLExpression subject"
+msgstr "définir une expression RQL de mise à jour"
+
+msgid "add CWProperty for_user CWUser object"
 msgstr "propriété"
 
-msgid "add ERType add_permission RQLExpression subject"
+msgid "add CWRType add_permission RQLExpression subject"
 msgstr "expression RQL d'ajout"
 
-msgid "add ERType delete_permission RQLExpression subject"
+msgid "add CWRType delete_permission RQLExpression subject"
 msgstr "expression RQL de suppression"
 
-msgid "add ERType read_permission RQLExpression subject"
+msgid "add CWRType read_permission RQLExpression subject"
 msgstr "expression RQL de lecture"
 
-msgid "add EUser in_group EGroup object"
+msgid "add CWRelation constrained_by CWConstraint subject"
+msgstr "contrainte"
+
+msgid "add CWRelation relation_type CWRType object"
+msgstr "définition de relation"
+
+msgid "add CWUser in_group CWGroup object"
 msgstr "utilisateur"
 
-msgid "add EUser use_email EmailAddress subject"
+msgid "add CWUser use_email EmailAddress subject"
 msgstr "ajouter une addresse email"
 
 msgid "add State allowed_transition Transition object"
@@ -819,7 +806,7 @@
 msgid "add State allowed_transition Transition subject"
 msgstr "ajouter une transition en sortie"
 
-msgid "add State state_of EEType object"
+msgid "add State state_of CWEType object"
 msgstr "ajouter un état"
 
 msgid "add Transition condition RQLExpression subject"
@@ -831,46 +818,43 @@
 msgid "add Transition destination_state State subject"
 msgstr "ajouter l'état de sortie"
 
-msgid "add Transition transition_of EEType object"
+msgid "add Transition transition_of CWEType object"
 msgstr "ajouter une transition"
 
 msgid "add a Bookmark"
 msgstr "ajouter un signet"
 
-msgid "add a Card"
-msgstr "ajouter une fiche"
-
-msgid "add a ECache"
-msgstr "ajouter un cache applicatif"
-
-msgid "add a EConstraint"
-msgstr "ajouter une contrainte"
-
-msgid "add a EConstraintType"
-msgstr "ajouter un type de contrainte"
-
-msgid "add a EEType"
-msgstr "ajouter un type d'entité"
-
-msgid "add a EFRDef"
+msgid "add a CWAttribute"
 msgstr "ajouter un type de relation"
 
-msgid "add a EGroup"
+msgid "add a CWCache"
+msgstr "ajouter un cache applicatif"
+
+msgid "add a CWConstraint"
+msgstr "ajouter une contrainte"
+
+msgid "add a CWConstraintType"
+msgstr "ajouter un type de contrainte"
+
+msgid "add a CWEType"
+msgstr "ajouter un type d'entité"
+
+msgid "add a CWGroup"
 msgstr "ajouter un groupe d'utilisateurs"
 
-msgid "add a ENFRDef"
-msgstr "ajouter une relation"
-
-msgid "add a EPermission"
+msgid "add a CWPermission"
 msgstr "ajouter une permission"
 
-msgid "add a EProperty"
+msgid "add a CWProperty"
 msgstr "ajouter une propriété"
 
-msgid "add a ERType"
+msgid "add a CWRType"
 msgstr "ajouter un type de relation"
 
-msgid "add a EUser"
+msgid "add a CWRelation"
+msgstr "ajouter une relation"
+
+msgid "add a CWUser"
 msgstr "ajouter un utilisateur"
 
 msgid "add a EmailAddress"
@@ -918,7 +902,7 @@
 "s #%(toeid)s"
 
 msgid "address"
-msgstr "adresse"
+msgstr "adresse électronique"
 
 msgid "alias"
 msgstr "alias"
@@ -947,9 +931,6 @@
 msgid "am/pm calendar (year)"
 msgstr "calendrier am/pm (année)"
 
-msgid "an abstract for this card"
-msgstr "un résumé pour cette fiche"
-
 msgid "an electronic mail address associated to a short alias"
 msgstr "une addresse électronique associée à un alias"
 
@@ -989,6 +970,9 @@
 msgid "attribute"
 msgstr "attribut"
 
+msgid "attributes with modified permissions:"
+msgstr "attributs ayant des permissions modifiées :"
+
 msgid "august"
 msgstr "août"
 
@@ -1145,6 +1129,9 @@
 msgid "cardinality"
 msgstr "cardinalité"
 
+msgid "category"
+msgstr "categorie"
+
 #, python-format
 msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
 msgstr "changement de l'état de %(etype)s #%(eid)s (%(title)s)"
@@ -1227,12 +1214,6 @@
 msgid "components_rqlinput_description"
 msgstr "la barre de requête rql, dans l'en-tête de page"
 
-msgid "components_rss_feed_url"
-msgstr "syndication rss"
-
-msgid "components_rss_feed_url_description"
-msgstr ""
-
 msgid "composite"
 msgstr "composite"
 
@@ -1263,12 +1244,6 @@
 msgid "constraints applying on this relation"
 msgstr "contraintes s'appliquant à cette relation"
 
-msgid "content"
-msgstr "contenu"
-
-msgid "content_format"
-msgstr "format"
-
 msgid "contentnavigation"
 msgstr "composants contextuels"
 
@@ -1392,60 +1367,64 @@
 msgid "created_by_object"
 msgstr "a créé"
 
-msgid "creating Bookmark (Bookmark bookmarked_by EUser %(linkto)s)"
+msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
 msgstr "création d'un signet pour %(linkto)s"
 
-msgid "creating EConstraint (EFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "création d'une contrainte pour l'attribut %(linkto)s"
-
-msgid "creating EConstraint (ENFRDef %(linkto)s constrained_by EConstraint)"
-msgstr "création d'une contrainte pour la relation %(linkto)s"
-
-msgid "creating EFRDef (EFRDef relation_type ERType %(linkto)s)"
+msgid "creating CWAttribute (CWAttribute relation_type CWRType %(linkto)s)"
 msgstr "création d'un attribut %(linkto)s"
 
-msgid "creating ENFRDef (ENFRDef relation_type ERType %(linkto)s)"
-msgstr "création relation %(linkto)s"
-
-msgid "creating EProperty (EProperty for_user EUser %(linkto)s)"
+msgid ""
+"creating CWConstraint (CWAttribute %(linkto)s constrained_by CWConstraint)"
+msgstr "création d'une contrainte pour l'attribut %(linkto)s"
+
+msgid ""
+"creating CWConstraint (CWRelation %(linkto)s constrained_by CWConstraint)"
+msgstr "création d'une contrainte pour la relation %(linkto)s"
+
+msgid "creating CWProperty (CWProperty for_user CWUser %(linkto)s)"
 msgstr "création d'une propriété pour l'utilisateur %(linkto)s"
 
-msgid "creating EUser (EUser in_group EGroup %(linkto)s)"
+msgid "creating CWRelation (CWRelation relation_type CWRType %(linkto)s)"
+msgstr "création relation %(linkto)s"
+
+msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)"
 msgstr "création d'un utilisateur à rajouter au groupe %(linkto)s"
 
-msgid "creating EmailAddress (EUser %(linkto)s use_email EmailAddress)"
+msgid "creating EmailAddress (CWUser %(linkto)s use_email EmailAddress)"
 msgstr "création d'une adresse électronique pour l'utilisateur %(linkto)s"
 
-msgid "creating RQLExpression (EEType %(linkto)s add_permission RQLExpression)"
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
 msgstr "création d'une expression RQL pour la permission d'ajout de %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s delete_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s delete_permission RQLExpression)"
 msgstr ""
 "création d'une expression RQL pour la permission de suppression de %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s read_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s read_permission RQLExpression)"
 msgstr "création d'une expression RQL pour la permission de lire %(linkto)s"
 
 msgid ""
-"creating RQLExpression (EEType %(linkto)s update_permission RQLExpression)"
+"creating RQLExpression (CWEType %(linkto)s update_permission RQLExpression)"
 msgstr ""
 "création d'une expression RQL pour la permission de mise à jour de %(linkto)s"
 
-msgid "creating RQLExpression (ERType %(linkto)s add_permission RQLExpression)"
+msgid ""
+"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
 msgstr ""
 "création d'une expression RQL pour la permission d'ajout des relations %"
 "(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s delete_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
 msgstr ""
 "création d'une expression RQL pour la permission de suppression des "
 "relations %(linkto)s"
 
 msgid ""
-"creating RQLExpression (ERType %(linkto)s read_permission RQLExpression)"
+"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
 msgstr ""
 "création d'une expression RQL pour la permission de lire les relations %"
 "(linkto)s"
@@ -1456,7 +1435,7 @@
 msgid "creating State (State allowed_transition Transition %(linkto)s)"
 msgstr "création d'un état pouvant aller vers la transition %(linkto)s"
 
-msgid "creating State (State state_of EEType %(linkto)s)"
+msgid "creating State (State state_of CWEType %(linkto)s)"
 msgstr "création d'un état pour le type %(linkto)s"
 
 msgid "creating State (Transition %(linkto)s destination_state State)"
@@ -1468,7 +1447,7 @@
 msgid "creating Transition (Transition destination_state State %(linkto)s)"
 msgstr "création d'une transition vers l'état %(linkto)s"
 
-msgid "creating Transition (Transition transition_of EEType %(linkto)s)"
+msgid "creating Transition (Transition transition_of CWEType %(linkto)s)"
 msgstr "création d'une transition pour le type %(linkto)s"
 
 msgid "creation"
@@ -1492,6 +1471,10 @@
 msgid "csv export"
 msgstr "export CSV"
 
+#, python-format
+msgid "currently attached file: %s"
+msgstr "fichie actuellement attaché %s"
+
 msgid "data directory url"
 msgstr "url du répertoire de données"
 
@@ -1619,6 +1602,10 @@
 msgid "detach attached file"
 msgstr "détacher le fichier existant"
 
+#, python-format
+msgid "detach attached file %s"
+msgstr "détacher le fichier existant %s"
+
 msgid "detailed schema view"
 msgstr "vue détaillée du schéma"
 
@@ -1653,6 +1640,9 @@
 msgid "download icon"
 msgstr "icône de téléchargement"
 
+msgid "download schema as owl"
+msgstr "télécharger le schéma OWL"
+
 msgid "edit bookmarks"
 msgstr "éditer les signets"
 
@@ -1692,9 +1682,18 @@
 msgid "entities deleted"
 msgstr "entités supprimées"
 
+msgid "entity copied"
+msgstr "entité copiée"
+
+msgid "entity created"
+msgstr "entité créée"
+
 msgid "entity deleted"
 msgstr "entité supprimée"
 
+msgid "entity edited"
+msgstr "entité éditée"
+
 msgid "entity type"
 msgstr "type d'entité"
 
@@ -1805,6 +1804,10 @@
 msgid "from"
 msgstr "de"
 
+#, python-format
+msgid "from %(date)s"
+msgstr "du %(date)s"
+
 msgid "from_entity"
 msgstr "de l'entité"
 
@@ -1829,6 +1832,9 @@
 msgid "generic plot"
 msgstr "tracé de courbes standard"
 
+msgid "generic relation to link one entity to another"
+msgstr "relation générique pour lier une entité à une autre"
+
 msgid "go back to the index page"
 msgstr "retourner sur la page d'accueil"
 
@@ -1890,7 +1896,7 @@
 msgstr "cacher le filtre"
 
 msgid "hide meta-data"
-msgstr "cacher les méta-données"
+msgstr "cacher les entités et relations \"méta\""
 
 msgid "home"
 msgstr "maison"
@@ -2017,9 +2023,6 @@
 msgid "inlined"
 msgstr "mise en ligne"
 
-msgid "inlined view"
-msgstr "vue embarquée (en ligne)"
-
 msgid "internationalizable"
 msgstr "internationalisable"
 
@@ -2100,6 +2103,13 @@
 msgstr "gauche"
 
 msgid ""
+"link a permission to the entity. This permission should be used in the "
+"security definition of the entity's type to be useful."
+msgstr ""
+"lie une permission à une entité. Cette permission doit généralement être utilisée "
+"dans la définition de sécurité du type d'entité pour être utile."
+
+msgid ""
 "link a property to the user which want this property customization. Unless "
 "you're a site manager, this relation will be handled automatically."
 msgstr ""
@@ -2140,6 +2150,9 @@
 msgid "login"
 msgstr "identifiant"
 
+msgid "login or email"
+msgstr "identifiant ou email"
+
 msgid "login_action"
 msgstr "identifiez vous"
 
@@ -2245,6 +2258,18 @@
 msgid "navigation"
 msgstr "navigation"
 
+msgid "navigation.combobox-limit"
+msgstr "nombre d'entités dans les listes déroulantes"
+
+msgid "navigation.page-size"
+msgstr "nombre de résultats"
+
+msgid "navigation.related-limit"
+msgstr "nombre d'entités dans la vue primaire"
+
+msgid "navigation.short-line-size"
+msgstr "description courtes"
+
 msgid "navtop"
 msgstr "haut de page du contenu principal"
 
@@ -2257,8 +2282,8 @@
 msgid "no"
 msgstr "non"
 
-msgid "no associated epermissions"
-msgstr "aucune permission spécifique n'est définie"
+msgid "no associated permissions"
+msgstr "aucune permission associée"
 
 msgid "no possible transition"
 msgstr "aucune transition possible"
@@ -2297,6 +2322,9 @@
 msgid "object"
 msgstr "objet"
 
+msgid "object_plural:"
+msgstr "objets :"
+
 msgid "october"
 msgstr "octobre"
 
@@ -2312,6 +2340,9 @@
 msgid "only select queries are authorized"
 msgstr "seules les requêtes de sélections sont autorisées"
 
+msgid "open all"
+msgstr "tout ouvrir"
+
 msgid "order"
 msgstr "ordre"
 
@@ -2319,10 +2350,10 @@
 msgstr "ordre"
 
 msgid "owl"
-msgstr ""
+msgstr "owl"
 
 msgid "owlabox"
-msgstr ""
+msgstr "owl ABox"
 
 msgid "owned_by"
 msgstr "appartient à"
@@ -2356,6 +2387,12 @@
 msgid "permission"
 msgstr "permission"
 
+msgid "permissions for entities"
+msgstr "permissions pour les entités"
+
+msgid "permissions for relations"
+msgstr "permissions pour les relations"
+
 msgid "permissions for this entity"
 msgstr "permissions pour cette entité"
 
@@ -2423,6 +2460,9 @@
 msgid "relation_type_object"
 msgstr "définition"
 
+msgid "relations"
+msgstr "relations"
+
 msgid "relations deleted"
 msgstr "relations supprimées"
 
@@ -2432,40 +2472,37 @@
 msgid "remove this Bookmark"
 msgstr "supprimer ce signet"
 
-msgid "remove this Card"
-msgstr "supprimer cette fiche"
-
-msgid "remove this ECache"
-msgstr "supprimer ce cache applicatif"
-
-msgid "remove this EConstraint"
-msgstr "supprimer cette contrainte"
-
-msgid "remove this EConstraintType"
-msgstr "supprimer ce type de contrainte"
-
-msgid "remove this EEType"
-msgstr "supprimer ce type d'entité"
-
-msgid "remove this EFRDef"
+msgid "remove this CWAttribute"
 msgstr "supprimer cet attribut"
 
-msgid "remove this EGroup"
+msgid "remove this CWCache"
+msgstr "supprimer ce cache applicatif"
+
+msgid "remove this CWConstraint"
+msgstr "supprimer cette contrainte"
+
+msgid "remove this CWConstraintType"
+msgstr "supprimer ce type de contrainte"
+
+msgid "remove this CWEType"
+msgstr "supprimer ce type d'entité"
+
+msgid "remove this CWGroup"
 msgstr "supprimer ce groupe"
 
-msgid "remove this ENFRDef"
-msgstr "supprimer cette relation"
-
-msgid "remove this EPermission"
+msgid "remove this CWPermission"
 msgstr "supprimer cette permission"
 
-msgid "remove this EProperty"
+msgid "remove this CWProperty"
 msgstr "supprimer cette propriété"
 
-msgid "remove this ERType"
+msgid "remove this CWRType"
 msgstr "supprimer cette définition de relation"
 
-msgid "remove this EUser"
+msgid "remove this CWRelation"
+msgstr "supprimer cette relation"
+
+msgid "remove this CWUser"
 msgstr "supprimer cet utilisateur"
 
 msgid "remove this EmailAddress"
@@ -2489,6 +2526,12 @@
 msgid "require_group_object"
 msgstr "à les droits"
 
+msgid "require_permission"
+msgstr "require permission"
+
+msgid "require_permission_object"
+msgstr "permission of"
+
 msgid "required attribute"
 msgstr "attribut requis"
 
@@ -2565,12 +2608,18 @@
 msgid "see them all"
 msgstr "les voir toutes"
 
+msgid "see_also"
+msgstr "see also"
+
 msgid "select"
 msgstr "sélectionner"
 
 msgid "select a"
 msgstr "sélectionner un"
 
+msgid "select a key first"
+msgstr "sélectionnez d'abord une clé"
+
 msgid "select a relation"
 msgstr "sélectionner une relation"
 
@@ -2630,7 +2679,10 @@
 msgstr "afficher le filtre"
 
 msgid "show meta-data"
-msgstr "afficher les méta-données"
+msgstr "afficher le schéma complet"
+
+msgid "sioc"
+msgstr "sioc"
 
 msgid "site configuration"
 msgstr "configuration du site"
@@ -2684,6 +2736,9 @@
 msgid "subject/object cardinality"
 msgstr "cardinalité sujet/objet"
 
+msgid "subject_plural:"
+msgstr "sujets :"
+
 msgid "sunday"
 msgstr "dimanche"
 
@@ -2693,9 +2748,6 @@
 msgid "symetric"
 msgstr "symétrique"
 
-msgid "synopsis"
-msgstr "synopsis"
-
 msgid "system entities"
 msgstr "entités systèmes"
 
@@ -2758,6 +2810,10 @@
 msgid "to"
 msgstr "à"
 
+#, python-format
+msgid "to %(date)s"
+msgstr "au %(date)s"
+
 msgid "to associate with"
 msgstr "pour associer à"
 
@@ -2776,6 +2832,9 @@
 msgid "todo_by"
 msgstr "à faire par"
 
+msgid "toggle check boxes"
+msgstr "inverser les cases à cocher"
+
 msgid "transition is not allowed"
 msgstr "transition non permise"
 
@@ -2797,6 +2856,36 @@
 msgid "ui"
 msgstr "propriétés génériques de l'interface"
 
+msgid "ui.date-format"
+msgstr "format de date"
+
+msgid "ui.datetime-format"
+msgstr "format de date et de l'heure"
+
+msgid "ui.default-text-format"
+msgstr "format de texte"
+
+msgid "ui.encoding"
+msgstr "encodage"
+
+msgid "ui.fckeditor"
+msgstr "éditeur du contenu"
+
+msgid "ui.float-format"
+msgstr "format des flottants"
+
+msgid "ui.language"
+msgstr "langue"
+
+msgid "ui.main-template"
+msgstr "gabarit principal"
+
+msgid "ui.site-title"
+msgstr "titre du site"
+
+msgid "ui.time-format"
+msgstr "format de l'heure"
+
 msgid "unaccessible"
 msgstr "inaccessible"
 
@@ -2812,6 +2901,9 @@
 msgid "unknown property key"
 msgstr "clé de propriété inconnue"
 
+msgid "up"
+msgstr "haut"
+
 msgid "upassword"
 msgstr "mot de passe"
 
@@ -2949,9 +3041,6 @@
 "org et python-projects@lists.logilab.org), mettez cette propriété à vrai sur "
 "l'une d'entre-elle qui sera la forme canonique"
 
-msgid "wikiid"
-msgstr "identifiant wiki"
-
 #, python-format
 msgid "workflow for %s"
 msgstr "workflow pour %s"
@@ -2971,6 +3060,9 @@
 msgid "you have been logged out"
 msgstr "vous avez été déconnecté"
 
+msgid "you should probably delete that property"
+msgstr "vous devriez probablement supprimer cette propriété"
+
 #~ msgid "%s constraint failed"
 #~ msgstr "La contrainte %s n'est pas satisfaite"
 
@@ -2980,12 +3072,55 @@
 #~ msgid "%s, or without time: %s"
 #~ msgstr "%s, ou bien sans préciser d'heure: %s"
 
+#~ msgid "Card"
+#~ msgstr "Fiche"
+
+#~ msgid "Card_plural"
+#~ msgstr "Fiches"
+
+#~ msgid "Email body: "
+#~ msgstr "Contenu du courriel : "
+
+#~ msgid "From: "
+#~ msgstr "De : "
+
 #~ msgid "Loading"
 #~ msgstr "chargement"
 
+#~ msgid "New Card"
+#~ msgstr "Nouvelle fiche"
+
+#~ msgid "Problem occured"
+#~ msgstr "Une erreur est survenue"
+
 #~ msgid "Problem occured while setting new value"
 #~ msgstr "Un problème est survenu lors de la mise à jour"
 
+#~ msgid "Recipients: "
+#~ msgstr "Destinataires : "
+
+#~ msgid "Subject: "
+#~ msgstr "Sujet : "
+
+#~ msgid "This Card"
+#~ msgstr "Cette fiche"
+
+#~ msgid ""
+#~ "a card is a textual content used as documentation, reference, procedure "
+#~ "reminder"
+#~ msgstr ""
+#~ "une fiche est un texte utilisé comme documentation, référence, rappel de "
+#~ "procédure..."
+
+#~ msgid "actions_addpermission"
+#~ msgstr "ajouter une permission"
+
+#~ msgid "add a Card"
+#~ msgstr "ajouter une fiche"
+
+#~ msgid "an abstract for this card"
+#~ msgstr "un résumé pour cette fiche"
+
 #~ msgid "and"
 #~ msgstr "et"
 
@@ -2995,6 +3130,18 @@
 #~ msgid "cancel edition"
 #~ msgstr "annuler l'édition"
 
+#~ msgid "close all"
+#~ msgstr "tout fermer"
+
+#~ msgid "components_rss_feed_url"
+#~ msgstr "syndication rss"
+
+#~ msgid "content"
+#~ msgstr "contenu"
+
+#~ msgid "content_format"
+#~ msgstr "format"
+
 #~ msgid ""
 #~ "default language (look at the i18n directory of the application to see "
 #~ "available languages)"
@@ -3017,6 +3164,9 @@
 #~ msgid "incorrect value for type \"%s\""
 #~ msgstr "valeur incorrecte pour le type \"%s\""
 
+#~ msgid "inlined view"
+#~ msgstr "vue embarquée (en ligne)"
+
 #~ msgid "linked"
 #~ msgstr "lié"
 
@@ -3028,14 +3178,29 @@
 #~ msgstr ""
 #~ "nombre maximum d'entités liées à afficher dans la vue de restriction"
 
+#~ msgid "no associated epermissions"
+#~ msgstr "aucune permission spécifique n'est définie"
+
 #~ msgid "owned by"
 #~ msgstr "appartient à"
 
+#~ msgid "planned_delivery"
+#~ msgstr "livraison prévue"
+
+#~ msgid "remove this Card"
+#~ msgstr "supprimer cette fiche"
+
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
 #~ msgid "status will change from %s to %s"
 #~ msgstr "l'état va passer de %s à %s"
 
+#~ msgid "synopsis"
+#~ msgstr "synopsis"
+
+#~ msgid "wikiid"
+#~ msgstr "identifiant wiki"
+
 #~ msgid "workflow history"
 #~ msgstr "historique du workflow"
--- a/interfaces.py	Thu May 14 12:50:14 2009 +0200
+++ b/interfaces.py	Thu May 14 12:50:34 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
 """
 
@@ -11,7 +11,7 @@
 
 class IEmailable(Interface):
     """interface for emailable entities"""
-    
+
     def get_email(self):
         """return email address"""
 
@@ -28,7 +28,7 @@
     def as_email_context(self):
         """returns the dictionary as used by the sendmail controller to
         build email bodies.
-        
+
         NOTE: the dictionary keys should match the list returned by the
         `allowed_massmail_keys` method.
         """
@@ -45,12 +45,12 @@
         """change the entity's state according to a state defined in given
         parameters
         """
-    
+
     def can_pass_transition(self, trname):
         """return true if the current user can pass the transition with the
         given name
         """
-    
+
     def latest_trinfo(self):
         """return the latest transition information for this entity
         """
@@ -73,7 +73,7 @@
     @property
     def todo(self):
         """what remains to be done"""
-    
+
     def progress_info(self):
         """returns a dictionary describing progress/estimated cost of the
         version.
@@ -93,19 +93,19 @@
 
     def progress(self):
         """returns the % progress of the task item"""
-        
-    
+
+
 class IMileStone(IProgress):
     """represents an ITask's item"""
-    
+
     parent_type = None # specify main task's type
-    
+
     def get_main_task(self):
         """returns the main ITask entity"""
 
     def initial_prevision_date(self):
         """returns the initial expected end of the milestone"""
-        
+
     def eta_date(self):
         """returns expected date of completion based on what remains
         to be done
@@ -128,7 +128,7 @@
 
     def __iter__(self):
         """iterates over the item's children"""
-        
+
     def is_leaf(self):
         """returns true if this node as no child"""
 
@@ -146,7 +146,7 @@
     """interface for entities which can be linked to a previous and/or next
     entity
     """
-    
+
     def next_entity(self):
         """return the 'next' entity"""
     def previous_entity(self):
@@ -155,10 +155,10 @@
 
 class IBreadCrumbs(Interface):
     """interface for entities which can be "located" on some path"""
-    
+
     def breadcrumbs(self, view, recurs=False):
         """return a list containing some:
-        
+
         * tuple (url, label)
         * entity
         * simple label string
@@ -173,7 +173,7 @@
 
 class IDownloadable(Interface):
     """interface for downloadable entities"""
-    
+
     def download_url(self): # XXX not really part of this interface
         """return an url to download entity's content"""
     def download_content_type(self):
@@ -188,31 +188,31 @@
 
 class IEmbedable(Interface):
     """interface for embedable entities"""
-    
+
     def embeded_url(self):
         """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"""
     def matching_dates(self, begin, end):
         """
         :param begin: day considered as begin of the range (`DateTime`)
         :param end: day considered as end of the range (`DateTime`)
-        
+
         :return:
           a list of dates (`DateTime`) in the range [`begin`, `end`] on which
           this entity apply
         """
-        
+
 class ITimetableViews(Interface):
     """timetable views interface"""
     def timetable_date(self):
         """XXX explain
-        
+
         :return: date (`DateTime`)
         """
 
@@ -231,17 +231,18 @@
         """returns the icon that should be used as the marker
         (returns None for default)
         """
-        
+
 class IFeed(Interface):
     """interface for entities with rss flux"""
-    
+
     def rss_feed_url(self):
         """return an url which layout sub-entities item
         """
+
 class ISiocItem(Interface):
     """interface for entities (which are item
     in sioc specification) with sioc views"""
-    
+
     def isioc_content(self):
         """return content entity"""
 
@@ -252,11 +253,11 @@
         """return container type (post, BlogPost, MailMessage)"""
 
     def isioc_replies(self):
-        """return replies items"""       
+        """return replies items"""
 
     def isioc_topics(self):
         """return topics items"""
-            
+
 class ISiocContainer(Interface):
     """interface for entities (which are container
     in sioc specification) with sioc views"""
@@ -266,6 +267,3 @@
 
     def isioc_items(self):
         """return contained items"""
-
-   
-    
--- a/md5crypt.py	Thu May 14 12:50:14 2009 +0200
+++ b/md5crypt.py	Thu May 14 12:50:34 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/cwdesklets/rqlsensor/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/cwdesklets/rqlsensor/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -52,7 +52,7 @@
             output.set('resultbg[%s]' % index, 'black')
             webbrowser.open(self._urls[index])
         self._send_output(output)
-        
+
     def __get_connection(self):
         try:
             return self._v_cnx
@@ -73,8 +73,8 @@
             del self._v_cnx
             raise
         self._urls = []
-        output.set('layout', 'vertical, 14')        
-        output.set('length', rset.rowcount)        
+        output.set('layout', 'vertical, 14')
+        output.set('length', rset.rowcount)
         i = 0
         for line in rset:
             output.set('result[%s]' % i, ', '.join([str(v) for v in line[1:]]))
@@ -84,7 +84,7 @@
             except:
                 self._urls.append('')
             i += 1
-    
+
     def __update(self):
         output = self._new_output()
         try:
@@ -92,12 +92,12 @@
         except Exception, ex:
             import traceback
             traceback.print_exc()
-            output.set('layout', 'vertical, 10')        
-            output.set('length', 1)        
+            output.set('layout', 'vertical, 10')
+            output.set('length', 1)
             output.set('result[0]', str(ex))
         self._send_output(output)
         self._add_timer(int(self._get_config('delay'))*1000, self.__update)
 
-        
+
 def new_sensor(args):
     return RQLSensor(*args)
--- a/misc/cwfs/cwfs.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/cwfs/cwfs.py	Thu May 14 12:50:34 2009 +0200
@@ -24,7 +24,7 @@
                                ] ),
                  })
 
-    
+
 
 DATA = { 'societe': [ ('CETIAD', 'Dijon'),
                       ('EDF_R&D', 'Clamart'),
@@ -58,7 +58,7 @@
         self._attr = None
         self._rel = None
         self._restrictions = []
-        
+
     def parse(self) :
         self._entity = self._components.next()
         try:
@@ -97,7 +97,7 @@
             for nom, entity in self.schema.get_relations(self._entity) :
                 yield nom+'/'
                 yield entity+'/'
-    
+
 def ls(path) :
     p = PathParser(SCHEMA,path)
     p.parse()
@@ -113,7 +113,7 @@
         self._e_type = None
         self._restrictions = []
         self._alphabet = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
-        
+
     def parse(self):
         self._var = self._alphabet.pop(0)
         self._e_type = self._components.next()
@@ -124,7 +124,7 @@
         except StopIteration :
             pass
         return 'Any %s WHERE %s' % (self._var, ', '.join(self._restrictions))
-    
+
     def process_entity(self) :
         _next = self._components.next()
         if _next in self.schema.get_attrs(self._e_type) :
@@ -147,9 +147,9 @@
                 self._restrictions.append('%s is %s' % (r_var, _next.capitalize()))
             except StopIteration:
                 raise
-        self.process_entity()            
+        self.process_entity()
 
-        
+
 def to_rql(path) :
     p = SytPathParser(SCHEMA,path)
     return p.parse()
--- a/misc/migration/2.37.1_Any.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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()
-
--- a/misc/migration/2.42.1_Any.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/2.42.1_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -14,5 +14,5 @@
 
 if 'inline_view' in schema:
     # inline_view attribute should have been deleted for a while now....
-    drop_attribute('ENFRDef', 'inline_view')
-    
+    drop_attribute('CWRelation', 'inline_view')
+
--- a/misc/migration/2.44.0_Any.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/2.44.0_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -1,11 +1,11 @@
-change_relation_props('EFRDef', 'cardinality', 'String', internationalizable=True)
-change_relation_props('ENFRDef', 'cardinality', 'String', internationalizable=True)
+change_relation_props('CWAttribute', 'cardinality', 'String', internationalizable=True)
+change_relation_props('CWRelation', 'cardinality', 'String', internationalizable=True)
 
-drop_relation_definition('EPermission', 'require_state', 'State')
+drop_relation_definition('CWPermission', 'require_state', 'State')
 
 if confirm('cleanup require_permission relation'):
     try:
-        newrschema = newschema.rschema('require_permission')
+        newrschema = fsschema.rschema('require_permission')
     except KeyError:
         newrschema = None
     for rsubj, robj in schema.rschema('require_permission').rdefs():
--- a/misc/migration/2.48.8_Any.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/2.48.8_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -1,2 +1,2 @@
-for etype in ('ERType', 'EFRDef', 'ENFRDef', 'EConstraint', 'EConstraintType'):
+for etype in ('CWRType', 'CWAttribute', 'CWRelation', 'CWConstraint', 'CWConstraintType'):
     synchronize_permissions(etype)
--- a/misc/migration/2.99.0_Any.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/2.99.0_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 from cubicweb import CW_MIGRATION_MAP
 
-for pk, in rql('Any K WHERE X is EProperty, X pkey IN (%s), X pkey K'
+for pk, in rql('Any K WHERE X is CWProperty, X pkey IN (%s), X pkey K'
                % ','.join("'system.version.%s'" % cube for cube in CW_MIGRATION_MAP),
                ask_confirm=False):
     cube = pk.split('.')[-1]
@@ -9,4 +9,4 @@
         {'oldk': pk, 'newk': newk}, ask_confirm=False)
     print 'renamed', pk, 'to', newk
 
-add_entity_type('ECache')
+add_entity_type('CWCache')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.1.5_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+synchronize_permissions('condition')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.2.0_Any.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,4 @@
+rql('SET X value "main-template" WHERE X is CWProperty, '
+    'X pkey "ui.main-template", X value "main"')
+checkpoint()
+
--- a/misc/migration/bootstrapmigration_repository.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Thu May 14 12:50:34 2009 +0200
@@ -3,27 +3,30 @@
 it should only include low level schema 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
 """
 
+if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0):
+   add_cube('card', update_database=False)
+
 if applcubicwebversion < (2, 47, 0) and cubicwebversion >= (2, 47, 0):
     from cubicweb.server import schemaserial
     schemaserial.HAS_FULLTEXT_CONTAINER = False
-    cnx.set_shared_data('do-not-insert-is_instance_of', True)
-    add_attribute('ERType', 'fulltext_container')
+    session.set_shared_data('do-not-insert-is_instance_of', True)
+    add_attribute('CWRType', 'fulltext_container')
     schemaserial.HAS_FULLTEXT_CONTAINER = True
 
 
- 
+
 if applcubicwebversion < (2, 50, 0) and cubicwebversion >= (2, 50, 0):
-    cnx.set_shared_data('do-not-insert-is_instance_of', True)
+    session.set_shared_data('do-not-insert-is_instance_of', True)
     add_relation_type('is_instance_of')
     # fill the relation using an efficient sql query instead of using rql
     sql('INSERT INTO is_instance_of_relation '
 	'  SELECT * from is_relation')
     checkpoint()
-    cnx.set_shared_data('do-not-insert-is_instance_of', False)
+    session.set_shared_data('do-not-insert-is_instance_of', False)
 
 if applcubicwebversion < (2, 42, 0) and cubicwebversion >= (2, 42, 0):
     sql('ALTER TABLE entities ADD COLUMN mtime TIMESTAMP')
@@ -40,4 +43,4 @@
     sql('CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime)')
     sql('CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid)')
     checkpoint()
-   
+
--- a/misc/migration/postcreate.py	Thu May 14 12:50:14 2009 +0200
+++ b/misc/migration/postcreate.py	Thu May 14 12:50:34 2009 +0200
@@ -1,22 +1,22 @@
 """cubicweb post creation script, set user's workflow"""
 
-activatedeid = add_state(_('activated'), 'EUser', initial=True)
-deactivatedeid = add_state(_('deactivated'), 'EUser')
-add_transition(_('deactivate'), 'EUser',
+activatedeid = add_state(_('activated'), 'CWUser', initial=True)
+deactivatedeid = add_state(_('deactivated'), 'CWUser')
+add_transition(_('deactivate'), 'CWUser',
                (activatedeid,), deactivatedeid,
                requiredgroups=('managers',))
-add_transition(_('activate'), 'EUser',
+add_transition(_('activate'), 'CWUser',
                (deactivatedeid,), activatedeid,
                requiredgroups=('managers',))
 
 # need this since we already have at least one user in the database (the default admin)
-rql('SET X in_state S WHERE X is EUser, S eid %s' % activatedeid)
+rql('SET X in_state S WHERE X is CWUser, S eid %s' % activatedeid)
 
 # create anonymous user if all-in-one config and anonymous user has been specified
 if hasattr(config, 'anonymous_user'):
     anonlogin, anonpwd = config.anonymous_user()
     if anonlogin:
-        rql('INSERT EUser X: X login %(login)s, X upassword %(pwd)s,'
+        rql('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s,'
             'X in_state S, X in_group G WHERE G name "guests", S name "activated"',
             {'login': unicode(anonlogin), 'pwd': anonpwd})
 
@@ -30,11 +30,11 @@
         default = cfg.option_default(optname, optdict)
         # only record values differing from default
         if value != default:
-            rql('INSERT EProperty X: X pkey %(k)s, X value %(v)s', {'k': key, 'v': value})
+            rql('INSERT CWProperty X: X pkey %(k)s, X value %(v)s', {'k': key, 'v': value})
 
 # add PERM_USE_TEMPLATE_FORMAT permission
 from cubicweb.schema import PERM_USE_TEMPLATE_FORMAT
-eid = add_entity('EPermission', name=PERM_USE_TEMPLATE_FORMAT,
+eid = add_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT,
                  label=_('use template languages'))
 rql('SET X require_group G WHERE G name "managers", X eid %(x)s',
     {'x': eid}, 'x')    
--- a/rset.py	Thu May 14 12:50:14 2009 +0200
+++ b/rset.py	Thu May 14 12:50:34 2009 +0200
@@ -11,7 +11,7 @@
 from rql import nodes
 
 from cubicweb import NotAnEntity
-    
+
 
 class ResultSet(object):
     """a result set wrap a RQL query result. This object implements a partial
@@ -51,12 +51,14 @@
         # set by the cursor which returned this resultset
         self.vreg = None
         self.req = None
-   
+        # actions cache
+        self._rsetactions = None
+
     def __str__(self):
         if not self.rows:
             return '<empty resultset %s>' % self.rql
         return '<resultset %s (%s rows)>' % (self.rql, len(self.rows))
-    
+
     def __repr__(self):
         if not self.rows:
             return '<empty resultset for %r>' % self.rql
@@ -70,25 +72,35 @@
                                                  '\n'.join('%s (%s)' % (r, d)
                                                            for r, d in zip(rows, self.description)))
 
-    @cached
-    def possible_actions(self):
-        return self.vreg.possible_vobjects('actions', self.req, self)
-    
+    def possible_actions(self, **kwargs):
+        if self._rsetactions is None:
+            self._rsetactions = {}
+        if kwargs:
+            key = tuple(sorted(kwargs.iteritems()))
+        else:
+            key = None
+        try:
+            return self._rsetactions[key]
+        except KeyError:
+            actions = self.vreg.possible_vobjects('actions', self.req, self, **kwargs)
+            self._rsetactions[key] = actions
+            return actions
+
     def __len__(self):
         """returns the result set's size"""
         return self.rowcount
 
     def __nonzero__(self):
         return self.rowcount
-    
+
     def __getitem__(self, i):
         """returns the ith element of the result set"""
         return self.rows[i] #ResultSetRow(self.rows[i])
-    
+
     def __getslice__(self, i, j):
         """returns slice [i:j] of the result set"""
         return self.rows[i:j]
-        
+
     def __iter__(self):
         """Returns an iterator over rows"""
         return iter(self.rows)
@@ -114,7 +126,7 @@
         :param transformcb:
           a callable which should take a row and its type description as
           parameters, and return the transformed row and type description.
-          
+
 
         :type col: int
         :param col: the column index
@@ -179,8 +191,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)
@@ -188,7 +199,7 @@
 
     def split_rset(self, keyfunc=None, col=0, return_dict=False):
         """Splits the result set in multiple result set according to a given key
-    
+
         :type keyfunc: callable(entity or FinalType)
         :param keyfunc:
           a callable which should take a value of the rset in argument and
@@ -210,7 +221,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:
@@ -245,7 +256,7 @@
 
         :type offset: int
         :param offset: the offset index
-        
+
         :type inplace: bool
         :param inplace:
           if true, the result set is modified in place, else a new result set
@@ -278,7 +289,7 @@
                 copy_cache(rset, 'get_entity', self)
         rset.limited = (limit, offset)
         return rset
-    
+
     def printable_rql(self, encoded=False):
         """return the result set's origin rql as a string, with arguments
         substitued
@@ -290,11 +301,11 @@
             if isinstance(rqlstr, unicode):
                 return rqlstr
             return unicode(rqlstr, encoding)
-        else: 
+        else:
             if isinstance(rqlstr, unicode):
                 return rqlstr.encode(encoding)
             return rqlstr
-       
+
     # client helper methods ###################################################
 
     def entities(self, col=0):
@@ -309,7 +320,7 @@
     def get_entity(self, row, col=None):
         """special method for query retreiving a single entity, returns a
         partially initialized Entity instance.
-        
+
         WARNING: due to the cache wrapping this function, you should NEVER
                  give row as a named parameter (i.e. rset.get_entity(req, 0)
                  is OK but rset.get_entity(row=0, req=req) isn't
@@ -340,7 +351,7 @@
 
         partially means that only attributes selected in the RQL
         query will be directly assigned to the entity.
-        
+
         :type row,col: int, int
         :param row,col:
           row and col numbers localizing the entity among the result's table
@@ -369,6 +380,9 @@
             pass
         # build entity instance
         etype = self.description[row][col]
+        if etype == 'EUser':
+            import traceback
+            traceback.printstack()
         entity = self.vreg.etype_class(etype)(req, self, row, col)
         entity.set_eid(eid)
         # cache entity
@@ -413,7 +427,7 @@
 
     @cached
     def syntax_tree(self):
-        """get the syntax tree for the source query. 
+        """get the syntax tree for the source query.
 
         :rtype: rql.stmts.Statement
         :return: the RQL syntax tree of the originating query
@@ -427,12 +441,12 @@
         else:
             rqlst = self.vreg.parse(self.req, self.rql, self.args)
         return rqlst
-        
+
     @cached
     def column_types(self, col):
         """return the list of different types in the column with the given col
         index default to 0 (ie the first column)
-        
+
         :type col: int
         :param col: the index of the desired column
 
@@ -469,7 +483,7 @@
         etype = self.description[row][col]
         if self.vreg.schema.eschema(etype).is_final():
             # final type, find a better one to locate the correct subquery
-            # (ambiguous if possible) 
+            # (ambiguous if possible)
             for i in xrange(len(rqlst.children[0].selection)):
                 if i == col:
                     continue
@@ -508,7 +522,7 @@
                 __, rhs = rel.get_variable_parts()
                 return rhs.eval(self.args)
         return None
-        
+
 
 def attr_desc_iterator(rqlst, index=0):
     """return an iterator on a list of 2-uple (index, attr_relation)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rtags.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,143 @@
+"""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"
+
+import logging
+
+from logilab.common.logging_ext import set_log_methods
+
+RTAGS = []
+def register_rtag(rtag):
+    RTAGS.append(rtag)
+
+class RelationTags(object):
+    """a tag store for full relation definitions :
+
+         (subject type, relation type, object type, tagged)
+
+    allowing to set tags using wildcard (eg '*') as subject type / object type
+
+    This class associates a single tag to each key.
+    """
+    _allowed_values = None
+    def __init__(self, initfunc=None, allowed_values=None):
+        self._tagdefs = {}
+        if allowed_values is not None:
+            self._allowed_values = allowed_values
+        self._initfunc = initfunc
+        register_rtag(self)
+
+    def __repr__(self):
+        return repr(self._tagdefs)
+
+    # dict compat
+    def __getitem__(self, key):
+        return self.get(*key)
+    __contains__ = __getitem__
+
+    def _get_keys(self, stype, rtype, otype, tagged):
+        keys = [(rtype, tagged, '*', '*'),
+                (rtype, tagged, '*', otype),
+                (rtype, tagged, stype, '*'),
+                (rtype, tagged, stype, otype)]
+        if stype == '*' or otype == '*':
+            keys.remove((rtype, tagged, '*', '*'))
+            if stype == '*':
+                keys.remove((rtype, tagged, '*', otype))
+            if otype == '*':
+                keys.remove((rtype, tagged, stype, '*'))
+        return keys
+
+    def init(self, schema, check=True):
+        # XXX check existing keys against schema
+        if check:
+            for (rtype, tagged, stype, otype), value in self._tagdefs.items():
+                for ertype in (stype, rtype, otype):
+                    if ertype != '*' and not ertype in schema:
+                        self.warning('removing rtag %s: %s, %s undefined in schema',
+                                     (stype, rtype, otype, tagged), value, ertype)
+                        self.del_rtag(stype, rtype, otype, tagged)
+                        break
+        if self._initfunc is not None:
+            for eschema in schema.entities():
+                for rschema, tschemas, role in eschema.relation_definitions(True):
+                    for tschema in tschemas:
+                        if role == 'subject':
+                            sschema, oschema = eschema, tschema
+                        else:
+                            sschema, oschema = tschema, eschema
+                        self._initfunc(self, sschema, rschema, oschema, role)
+
+    # rtag declaration api ####################################################
+
+    def tag_attribute(self, key, tag):
+        key = list(key)
+        key.append('*')
+        self.tag_subject_of(key, tag)
+
+    def tag_subject_of(self, key, tag):
+        key = list(key)
+        key.append('subject')
+        self.tag_relation(key, tag)
+
+    def tag_object_of(self, key, tag):
+        key = list(key)
+        key.append('object')
+        self.tag_relation(key, tag)
+
+    def tag_relation(self, key, tag):
+        #if isinstance(key, basestring):
+        #    stype, rtype, otype = key.split()
+        #else:
+        stype, rtype, otype, tagged = [str(k) for k in key]
+        if self._allowed_values is not None:
+            assert tag in self._allowed_values, '%r is not an allowed tag' % tag
+        self._tagdefs[(rtype, tagged, stype, otype)] = tag
+
+    # rtag runtime api ########################################################
+
+    def del_rtag(self, stype, rtype, otype, tagged):
+        del self._tagdefs[(rtype, tagged, stype, otype)]
+
+    def get(self, stype, rtype, otype, tagged):
+        for key in reversed(self._get_keys(stype, rtype, otype, tagged)):
+            try:
+                return self._tagdefs[key]
+            except KeyError:
+                continue
+        return None
+
+    def etype_get(self, etype, rtype, role, ttype='*'):
+        if role == 'subject':
+            return self.get(etype, rtype, ttype, role)
+        return self.get(ttype, rtype, etype, role)
+
+
+
+class RelationTagsSet(RelationTags):
+    """This class associates a set of tags to each key."""
+
+    def tag_relation(self, key, tag):
+        stype, rtype, otype, tagged = [str(k) for k in key]
+        rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set())
+        rtags.add(tag)
+
+    def get(self, stype, rtype, otype, tagged):
+        rtags = set()
+        for key in self._get_keys(stype, rtype, otype, tagged):
+            try:
+                rtags.update(self._tagdefs[key])
+            except KeyError:
+                continue
+        return rtags
+
+
+class RelationTagsBool(RelationTags):
+    _allowed_values = frozenset((True, False))
+
+
+set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
--- a/schema.py	Thu May 14 12:50:14 2009 +0200
+++ b/schema.py	Thu May 14 12:50:34 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,9 +42,8 @@
 
 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])            
+            etype, ETYPE_NAME_MAP[etype])
         warn(msg, DeprecationWarning, stacklevel=4)
         etype = ETYPE_NAME_MAP[etype]
     return etype
@@ -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 following 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
@@ -219,19 +258,19 @@
             eid = getattr(edef, 'eid', None)
         self.eid = eid
         # take care: no _groups attribute when deep-copying
-        if getattr(self, '_groups', None): 
+        if getattr(self, '_groups', None):
             for groups in self._groups.itervalues():
                 for group_or_rqlexpr in groups:
                     if isinstance(group_or_rqlexpr, RRQLExpression):
                         msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)"
                         raise BadSchemaDefinition(msg % self.type)
-            
+
     def attribute_definitions(self):
         """return an iterator on attribute definitions
-        
+
         attribute relations are a subset of subject relations where the
         object's type is a final entity
-        
+
         an attribute definition is a 2-uple :
         * name of the relation
         * schema of the destination entity type
@@ -241,7 +280,7 @@
             if rschema.type == 'has_text':
                 continue
             yield rschema, attrschema
-            
+
     def add_subject_relation(self, rschema):
         """register the relation schema as possible subject relation"""
         super(CubicWebEntitySchema, self).add_subject_relation(rschema)
@@ -250,7 +289,7 @@
     def del_subject_relation(self, rtype):
         super(CubicWebEntitySchema, self).del_subject_relation(rtype)
         self._update_has_text(False)
-        
+
     def _update_has_text(self, need_has_text=None):
         may_need_has_text, has_has_text = False, False
         for rschema in self.subject_relations():
@@ -278,23 +317,11 @@
             self.schema.add_relation_def(rdef)
         elif not need_has_text and has_has_text:
             self.schema.del_relation_def(self.type, 'has_text', 'String')
-            
+
     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
@@ -310,17 +337,17 @@
         # else if there is some rql expressions, check them
         if any(rqlexpr.check(session, eid)
                for rqlexpr in self.get_rqlexprs(action)):
-            return        
+            return
         raise Unauthorized(action, str(self))
 
     def rql_expression(self, expression, mainvars=None, eid=None):
         """rql expression factory"""
         return ERQLExpression(expression, mainvars, eid)
-    
+
 class CubicWebRelationSchema(RelationSchema):
     RelationSchema._RPROPERTIES['eid'] = None
     _perms_checked = False
-    
+
     def __init__(self, schema=None, rdef=None, eid=None, **kwargs):
         if rdef is not None:
             # if this relation is inlined
@@ -329,8 +356,8 @@
         if eid is None and rdef is not None:
             eid = getattr(rdef, 'eid', None)
         self.eid = eid
-                    
-        
+
+
     def update(self, subjschema, objschema, rdef):
         super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef)
         if not self._perms_checked and self._groups:
@@ -350,7 +377,7 @@
                             newrqlexprs.append(ERQLExpression(rqlexpr.expression,
                                                               rqlexpr.mainvars,
                                                               rqlexpr.eid))
-                            self.set_rqlexprs(action, newrqlexprs) 
+                            self.set_rqlexprs(action, newrqlexprs)
                         else:
                             msg = "can't use RRQLExpression on a final relation "\
                                   "type (eg attribute relation), use an ERQLExpression (%s)"
@@ -361,16 +388,16 @@
                               "a RRQLExpression (%s)"
                         raise BadSchemaDefinition(msg % self.type)
             self._perms_checked = True
-            
+
     def cardinality(self, subjtype, objtype, target):
         card = self.rproperty(subjtype, objtype, 'cardinality')
         return (target == 'subject' and card[0]) or \
                (target == 'object' and card[1])
-    
+
     def schema_relation(self):
         return self.type in ('relation_type', 'from_entity', 'to_entity',
                              'constrained_by', 'cstrtype')
-    
+
     def physical_mode(self):
         """return an appropriate mode for physical storage of this relation type:
         * 'subjectinline' if every possible subject cardinalities are 1 or ?
@@ -386,7 +413,7 @@
         # in an allowed group, if so that's enough internal sessions should
         # always stop there
         if session.user.matching_groups(self.get_groups(action)):
-            return 
+            return
         # else if there is some rql expressions, check them
         if any(rqlexpr.check(session, *args, **kwargs)
                for rqlexpr in self.get_rqlexprs(action)):
@@ -399,7 +426,7 @@
             return ERQLExpression(expression, mainvars, eid)
         return RRQLExpression(expression, mainvars, eid)
 
-    
+
 class CubicWebSchema(Schema):
     """set of entities and relations schema defining the possible data sets
     used in an application
@@ -407,11 +434,11 @@
 
     :type name: str
     :ivar name: name of the schema, usually the application identifier
-    
+
     :type base: str
     :ivar base: path of the directory where the schema is defined
     """
-    reading_from_database = False    
+    reading_from_database = False
     entity_class = CubicWebEntitySchema
     relation_class = CubicWebRelationSchema
 
@@ -428,15 +455,15 @@
         rschema = self.add_relation_type(ybo.RelationType('identity', meta=True))
         rschema.final = False
         rschema.set_default_groups()
-        
+
     def schema_entity_types(self):
         """return the list of entity types used to build the schema"""
-        return frozenset(('EEType', 'ERType', 'EFRDef', 'ENFRDef',
-                          'EConstraint', 'EConstraintType', 'RQLExpression',
+        return frozenset(('CWEType', 'CWRType', 'CWAttribute', 'CWRelation',
+                          'CWConstraint', 'CWConstraintType', 'RQLExpression',
                           # XXX those are not really "schema" entity types
                           #     but we usually don't want them as @* targets
-                          'EProperty', 'EPermission', 'State', 'Transition'))
-        
+                          'CWProperty', 'CWPermission', 'State', 'Transition'))
+
     def add_entity_type(self, edef):
         edef.name = edef.name.encode()
         edef.name = bw_normalize_etype(edef.name)
@@ -451,13 +478,13 @@
             self.add_relation_def(rdef)
         self._eid_index[eschema.eid] = eschema
         return eschema
-        
+
     def add_relation_type(self, rdef):
         rdef.name = rdef.name.lower().encode()
         rschema = super(CubicWebSchema, self).add_relation_type(rdef)
         self._eid_index[rschema.eid] = rschema
         return rschema
-    
+
     def add_relation_def(self, rdef):
         """build a part of a relation schema
         (i.e. add a relation between two specific entity's types)
@@ -477,25 +504,25 @@
         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)
         self._eid_index.pop(rschema.eid, None)
         super(CubicWebSchema, self).del_relation_type(rtype)
-    
+
     def del_relation_def(self, subjtype, rtype, objtype):
         for k, v in self._eid_index.items():
             if v == (subjtype, rtype, objtype):
                 del self._eid_index[k]
         super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype)
-        
+
     def del_entity_type(self, etype):
         eschema = self.eschema(etype)
         self._eid_index.pop(eschema.eid, None)
@@ -504,7 +531,7 @@
         if 'has_text' in eschema.subject_relations():
             self.del_relation_def(etype, 'has_text', 'String')
         super(CubicWebSchema, self).del_entity_type(etype)
-        
+
     def schema_by_eid(self, eid):
         return self._eid_index[eid]
 
@@ -516,22 +543,22 @@
 
     limit the proposed values to a set of entities returned by a rql query,
     but this is not enforced at the repository level
-    
+
      restriction is additional rql restriction that will be added to
      a predefined query, where the S and O variables respectivly represent
      the subject and the object of the relation
     """
-    
+
     def __init__(self, restriction):
         self.restriction = restriction
 
     def serialize(self):
         return self.restriction
-    
+
     def deserialize(cls, value):
         return cls(value)
     deserialize = classmethod(deserialize)
-    
+
     def check(self, entity, rtype, value):
         """return true if the value satisfy the constraint, else false"""
         # implemented as a hook in the repository
@@ -541,7 +568,7 @@
         """raise ValidationError if the relation doesn't satisfy the constraint
         """
         pass # this is a vocabulary constraint, not enforce
-    
+
     def __str__(self):
         return self.restriction
 
@@ -559,7 +586,7 @@
                                       ('s', 'o'), build_descr=False)
     def error(self, eid, rtype, msg):
         raise ValidationError(eid, {rtype: msg})
-        
+
     def repo_check(self, session, eidfrom, rtype, eidto):
         """raise ValidationError if the relation doesn't satisfy the constraint
         """
@@ -581,12 +608,12 @@
             #     eidfrom or eidto (from user interface point of view)
             self.error(eidfrom, rtype, 'unique constraint %s failed' % self)
 
-    
+
 def split_expression(rqlstring):
     for expr in rqlstring.split(','):
         for word in expr.split():
             yield word
-            
+
 def normalize_expression(rqlstring):
     """normalize an rql expression to ease schema synchronization (avoid
     suppressing and reinserting an expression if only a space has been added/removed
@@ -610,19 +637,19 @@
             if len(self.rqlst.defined_vars[mainvar].references()) <= 2:
                 LOGGER.warn('You did not use the %s variable in your RQL expression %s',
                             mainvar, self)
-    
+
     def __str__(self):
         return self.full_rql
     def __repr__(self):
         return '%s(%s)' % (self.__class__.__name__, self.full_rql)
-        
+
     def __deepcopy__(self, memo):
         return self.__class__(self.expression, self.mainvars)
     def __getstate__(self):
         return (self.expression, self.mainvars)
     def __setstate__(self, state):
         self.__init__(*state)
-        
+
     @cached
     def transform_has_permission(self):
         found = None
@@ -666,7 +693,7 @@
             rqlst.recover()
             return rql, found, keyarg
         return rqlst.as_string(), None, None
-        
+
     def _check(self, session, **kwargs):
         """return True if the rql expression is matching the given relation
         between fromeid and toeid
@@ -726,7 +753,7 @@
         if self.eid is not None:
             session.local_perm_cache[key] = False
         return False
-    
+
     @property
     def minimal_rql(self):
         return 'Any %s WHERE %s' % (self.mainvars, self.expression)
@@ -751,16 +778,16 @@
         if 'U' in defined:
             rql += ', U eid %(u)s'
         return rql
-    
+
     def check(self, session, eid=None):
         if 'X' in self.rqlst.defined_vars:
             if eid is None:
                 return False
             return self._check(session, x=eid)
         return self._check(session)
-    
+
 PyFileReader.context['ERQLExpression'] = ERQLExpression
-        
+
 class RRQLExpression(RQLExpression):
     def __init__(self, expression, mainvars=None, eid=None):
         if mainvars is None:
@@ -790,7 +817,7 @@
         if 'U' in defined:
             rql += ', U eid %(u)s'
         return rql
-    
+
     def check(self, session, fromeid=None, toeid=None):
         kwargs = {}
         if 'S' in self.rqlst.defined_vars:
@@ -802,17 +829,44 @@
                 return False
             kwargs['o'] = toeid
         return self._check(session, **kwargs)
-        
+
 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):
     """cubicweb specific relation file reader, handling additional RQL
     constraints on a relation definition
     """
-    
+
     def handle_constraint(self, rdef, constraint_text):
         """arbitrary constraint is an rql expression for cubicweb"""
         if not rdef.constraints:
@@ -824,7 +878,7 @@
             rdef.inlined = True
         RelationFileReader.process_properties(self, rdef, relation_def)
 
-        
+
 CONSTRAINTS['RQLConstraint'] = RQLConstraint
 CONSTRAINTS['RQLUniqueConstraint'] = RQLUniqueConstraint
 CONSTRAINTS['RQLVocabularyConstraint'] = RQLVocabularyConstraint
@@ -839,20 +893,20 @@
     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
         for filepath in self.include_schema_files('bootstrap'):
             self.info('loading %s', filepath)
             self.handle_file(filepath)
-        
+
     def unhandled_file(self, filepath):
         """called when a file without handler associated has been found"""
         self.warning('ignoring file %r', filepath)
@@ -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,13 +926,13 @@
             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('Bookmark')
-                         + self.include_schema_files('Card')):
+                         + self.include_schema_files('workflow')
+                         + self.include_schema_files('Bookmark')):
             self.info('loading %s', filepath)
             self.handle_file(filepath)
         for cube in cubes:
@@ -892,14 +946,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
@@ -910,22 +965,22 @@
         a `cls` instance
         """
         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):
         return 'value in (%s)' % u', '.join(repr(unicode(word)) for word in self.vocabulary())
-    
-    
+
+
 format_constraint = FormatConstraint()
 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'))
@@ -936,7 +991,7 @@
 def bw_import_erschema(self, ertype, schemamod=None, instantiate=True):
     return orig_import_erschema(self, bw_normalize_etype(ertype), schemamod, instantiate)
 PyFileReader.import_erschema = bw_import_erschema
-    
+
 # XXX itou for some Statement methods
 from rql import stmts
 orig_get_etype = stmts.ScopeNode.get_etype
--- a/schemas/Bookmark.py	Thu May 14 12:50:14 2009 +0200
+++ b/schemas/Bookmark.py	Thu May 14 12:50:34 2009 +0200
@@ -4,10 +4,10 @@
     title = String(required=True, maxsize=128)
     path  = String(maxsize=512, required=True,
                    description=_("relative url of the bookmarked page"))
-    
-    bookmarked_by = SubjectRelation('EUser',
+
+    bookmarked_by = SubjectRelation('CWUser',
                                     description=_("users using this bookmark"))
-    
+
 
 class bookmarked_by(MetaUserRelationType):
     permissions = {'read':   ('managers', 'users', 'guests',),
--- a/schemas/Card.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-from cubicweb.schema import format_constraint
-
-class Card(EntityType):
-    """a card is a textual content used as documentation, reference, procedure reminder"""
-    permissions = {
-        'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', 'users'),
-        'delete': ('managers', 'owners'),
-        'update': ('managers', 'owners',),
-        }
-    
-    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)
-    wikiid = String(maxsize=64, indexed=True)
--- a/schemas/base.py	Thu May 14 12:50:14 2009 +0200
+++ b/schemas/base.py	Thu May 14 12:50:34 2009 +0200
@@ -1,16 +1,15 @@
 """core CubicWeb schema, but not necessary at bootstrap 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 cubicweb.schema import format_constraint
 
-
-class EUser(RestrictedEntityType):
+class CWUser(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',),
@@ -25,19 +24,14 @@
     surname   = String(maxsize=64)
     last_login_time  = Datetime(description=_('last connection date'))
     # allowing an email to be the primary email of multiple entities is necessary for
-    # test at least :-/    
+    # test at least :-/
     primary_email = SubjectRelation('EmailAddress', cardinality='??',
                                     description=_('email address to use for notification'))
     use_email     = SubjectRelation('EmailAddress', cardinality='*?', composite='subject')
 
-    in_group = SubjectRelation('EGroup', cardinality='+*',
+    in_group = SubjectRelation('CWGroup', 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):
@@ -48,9 +42,9 @@
         'delete': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
         'update': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
         }
-    
+
     alias   = String(fulltextindexed=True, maxsize=56)
-    address = String(required=True, fulltextindexed=True, 
+    address = String(required=True, fulltextindexed=True,
                      indexed=True, unique=True, maxsize=128)
     canonical = Boolean(default=False,
                         description=_('when multiple addresses are equivalent \
@@ -59,7 +53,7 @@
     identical_to = SubjectRelation('EmailAddress')
 
 class use_email(RelationType):
-    """"""
+    """ """
     permissions = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', RRQLExpression('U has_update_permission S'),),
@@ -70,7 +64,7 @@
 class primary_email(RelationType):
     """the prefered email"""
     permissions = use_email.permissions
-    
+
 class identical_to(RelationType):
     """identical_to"""
     symetric = True
@@ -89,22 +83,22 @@
 class in_group(MetaRelationType):
     """core relation indicating a user's groups"""
     meta = False
-    
+
 class owned_by(MetaRelationType):
     """core relation indicating owners of an entity. This relation
     implicitly put the owner into the owners group for the entity
     """
     permissions = {
         'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', RRQLExpression('S owned_by U'),), 
+        'add':    ('managers', RRQLExpression('S owned_by U'),),
         'delete': ('managers', RRQLExpression('S owned_by U'),),
         }
     # 0..n cardinality for entities created by internal session (no attached user)
     # and to support later deletion of a user which has created some entities
     cardinality = '**'
     subject = '**'
-    object = 'EUser'
-    
+    object = 'CWUser'
+
 class created_by(MetaRelationType):
     """core relation indicating the original creator of an entity"""
     permissions = {
@@ -114,11 +108,11 @@
         }
     # 0..1 cardinality for entities created by internal session (no attached user)
     # and to support later deletion of a user which has created some entities
-    cardinality = '?*' 
+    cardinality = '?*'
     subject = '**'
-    object = 'EUser'
+    object = 'CWUser'
 
-    
+
 class creation_date(MetaAttributeRelationType):
     """creation time of an entity"""
     cardinality = '11'
@@ -132,111 +126,7 @@
     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):
+class CWProperty(EntityType):
     """used for cubicweb configuration. Once a property has been created you
     can't change the key.
     """
@@ -253,8 +143,8 @@
                                 'You must select this first to be able to set '
                                 'value'))
     value = String(internationalizable=True, maxsize=256)
-    
-    for_user = SubjectRelation('EUser', cardinality='?*', composite='object',
+
+    for_user = SubjectRelation('CWUser', cardinality='?*', composite='object',
                                description=_('user for which this property is '
                                              'applying. If this relation is not '
                                              'set, the property is considered as'
@@ -273,17 +163,17 @@
     inlined = True
 
 
-class EPermission(MetaEntityType):
+class CWPermission(MetaEntityType):
     """entity type that may be used to construct some advanced security configuration
     """
     name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
                   description=_('name or identifier of the permission'))
     label = String(required=True, internationalizable=True, maxsize=100,
                    description=_('distinct label to distinguate between other permission entity of the same name'))
-    require_group = SubjectRelation('EGroup', 
+    require_group = SubjectRelation('CWGroup',
                                     description=_('groups to which the permission is granted'))
 
-# explicitly add X require_permission EPermission for each entity that should have
+# explicitly add X require_permission CWPermission for each entity that should have
 # configurable security
 class require_permission(RelationType):
     """link a permission to the entity. This permission should be used in the
@@ -294,7 +184,7 @@
         'add':    ('managers',),
         'delete': ('managers',),
         }
-    
+
 class require_group(MetaRelationType):
     """used to grant a permission to a group"""
     permissions = {
@@ -303,12 +193,13 @@
         'delete': ('managers',),
         }
 
-    
+
 class see_also(RelationType):
     """generic relation to link one entity to another"""
     symetric = True
 
-class ECache(MetaEntityType):
+
+class CWCache(MetaEntityType):
     """a simple cache entity characterized by a name and
     a validity date.
 
@@ -324,6 +215,6 @@
         'delete': ('managers',),
         }
 
-    name = String(required=True, unique=True, indexed=True, 
+    name = String(required=True, unique=True, indexed=True,  maxsize=128,
                   description=_('name of the cache'))
     timestamp = Datetime(default='NOW')
--- a/schemas/bootstrap.py	Thu May 14 12:50:14 2009 +0200
+++ b/schemas/bootstrap.py	Thu May 14 12:50:34 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
 """
 
@@ -10,20 +10,18 @@
 
 # not restricted since as "is" is handled as other relations, guests need
 # access to this
-class EEType(MetaEntityType):
+class CWEType(MetaEntityType):
     """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'))
 
 
-class ERType(MetaEntityType):
+class CWRType(MetaEntityType):
     """define a relation type, used to build the application schema"""
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
@@ -41,61 +39,61 @@
     final = Boolean(description=_('automatic'))
 
 
-class EFRDef(MetaEntityType):
+class CWAttribute(MetaEntityType):
     """define a final relation: link a final relation type from a non final
-    entity to a final entity type. 
+    entity to a final entity type.
 
     used to build the application schema
     """
-    relation_type = SubjectRelation('ERType', cardinality='1*',
+    relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final TRUE')],
                                     composite='object')
-    from_entity = SubjectRelation('EEType', cardinality='1*',
+    from_entity = SubjectRelation('CWEType', cardinality='1*',
                                   constraints=[RQLConstraint('O final FALSE')],
                                   composite='object')
-    to_entity = SubjectRelation('EEType', cardinality='1*',
+    to_entity = SubjectRelation('CWEType', cardinality='1*',
                                 constraints=[RQLConstraint('O final TRUE')],
                                 composite='object')
-    constrained_by = SubjectRelation('EConstraint', cardinality='*1', composite='subject')
-    
+    constrained_by = SubjectRelation('CWConstraint', cardinality='*1', composite='subject')
+
     cardinality = String(maxsize=2, internationalizable=True,
-                         vocabulary=[_('?1'), _('11'), _('??'), _('1?')], 
+                         vocabulary=[_('?1'), _('11'), _('??'), _('1?')],
                          description=_('subject/object cardinality'))
     ordernum = Int(description=('control subject entity\'s relations order'), default=0)
-    
+
     indexed = Boolean(description=_('create an index for quick search on this attribute'))
     fulltextindexed = Boolean(description=_('index this attribute\'s value in the plain text index'))
     internationalizable = Boolean(description=_('is this attribute\'s value translatable'))
     defaultval = String(maxsize=256)
-    
+
     description_format = String(meta=True, internationalizable=True, maxsize=50,
                                 default='text/plain', constraints=[format_constraint])
     description = String(internationalizable=True,
                          description=_('semantic description of this attribute'))
-    
+
 
-CARDINALITY_VOCAB = [_('?*'), _('1*'), _('+*'), _('**'), 
-                     _('?+'), _('1+'), _('++'), _('*+'), 
+CARDINALITY_VOCAB = [_('?*'), _('1*'), _('+*'), _('**'),
+                     _('?+'), _('1+'), _('++'), _('*+'),
                      _('?1'), _('11'), _('+1'), _('*1'),
                      _('??'), _('1?'), _('+?'), _('*?')]
 
-class ENFRDef(MetaEntityType):
+class CWRelation(MetaEntityType):
     """define a non final relation: link a non final relation type from a non
-    final entity to a non final entity type. 
+    final entity to a non final entity type.
 
     used to build the application schema
     """
-    relation_type = SubjectRelation('ERType', cardinality='1*',
+    relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final FALSE')],
                                     composite='object')
-    from_entity = SubjectRelation('EEType', cardinality='1*',
+    from_entity = SubjectRelation('CWEType', cardinality='1*',
                                   constraints=[RQLConstraint('O final FALSE')],
                                   composite='object')
-    to_entity = SubjectRelation('EEType', cardinality='1*',
+    to_entity = SubjectRelation('CWEType', cardinality='1*',
                                 constraints=[RQLConstraint('O final FALSE')],
                                 composite='object')
-    constrained_by = SubjectRelation('EConstraint', cardinality='*1', composite='subject')
-    
+    constrained_by = SubjectRelation('CWConstraint', cardinality='*1', composite='subject')
+
     cardinality = String(maxsize=2, internationalizable=True,
                          vocabulary=CARDINALITY_VOCAB,
                          description=_('subject/object cardinality'))
@@ -107,12 +105,12 @@
                                      'deleted.'),
                        vocabulary=('', _('subject'), _('object')),
                        maxsize=8, default=None)
-    
+
     description_format = String(meta=True, internationalizable=True, maxsize=50,
                                 default='text/plain', constraints=[format_constraint])
     description = String(internationalizable=True,
                          description=_('semantic description of this relation'))
-    
+
 
 # not restricted since it has to be read when checking allowed transitions
 class RQLExpression(MetaEntityType):
@@ -122,7 +120,7 @@
                       description=_('name of the main variables which should be '
                                     'used in the selection if necessary (comma '
                                     'separated)'))
-    expression = String(required=True, 
+    expression = String(required=True,
                         description=_('restriction part of a rql query. '
                                       'For entity rql expression, X and U are '
                                       'predefined respectivly to the current object and to '
@@ -131,45 +129,45 @@
                                       'relation\'subject, object and to '
                                       'the request user. '))
 
-    read_permission = ObjectRelation(('EEType', 'ERType'), cardinality='+?', composite='subject',
+    read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+?', composite='subject',
                                       description=_('rql expression allowing to read entities/relations of this type'))
-    add_permission = ObjectRelation(('EEType', 'ERType'), cardinality='*?', composite='subject',
+    add_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
                                      description=_('rql expression allowing to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('EEType', 'ERType'), cardinality='*?', composite='subject',
+    delete_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
                                         description=_('rql expression allowing to delete entities/relations of this type'))
-    update_permission = ObjectRelation('EEType', cardinality='*?', composite='subject',
+    update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject',
                                         description=_('rql expression allowing to update entities of this type'))
-    
+
 
-class EConstraint(MetaEntityType):
+class CWConstraint(MetaEntityType):
     """define a schema constraint"""
-    cstrtype = SubjectRelation('EConstraintType', cardinality='1*')
+    cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
     value = String(description=_('depends on the constraint type'))
 
 
-class EConstraintType(MetaEntityType):
+class CWConstraintType(MetaEntityType):
     """define a schema constraint type"""
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
 
 # not restricted since it has to be read when checking allowed transitions
-class EGroup(MetaEntityType):
+class CWGroup(MetaEntityType):
     """define a CubicWeb users group"""
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
-    read_permission = ObjectRelation(('EEType', 'ERType'), cardinality='+*',
+    read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+*',
                                       description=_('groups allowed to read entities/relations of this type'))
-    add_permission = ObjectRelation(('EEType', 'ERType'),
+    add_permission = ObjectRelation(('CWEType', 'CWRType'),
                                      description=_('groups allowed to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('EEType', 'ERType'),
+    delete_permission = ObjectRelation(('CWEType', 'CWRType'),
                                         description=_('groups allowed to delete entities/relations of this type'))
-    update_permission = ObjectRelation('EEType',
+    update_permission = ObjectRelation('CWEType',
                                         description=_('groups allowed to update entities of this type'))
-    
-    
-    
+
+
+
 class relation_type(MetaRelationType):
     """link a relation definition to its relation type"""
     inlined = True
@@ -181,7 +179,7 @@
     inlined = True
 class constrained_by(MetaRelationType):
     """constraints applying on this relation"""
-    
+
 class cstrtype(MetaRelationType):
     """constraint factory"""
     inlined = True
@@ -216,7 +214,7 @@
         }
     cardinality = '1*'
     subject = '**'
-    object = 'EEType'
+    object = 'CWEType'
 
 class is_instance_of(MetaRelationType):
     """core relation indicating the types (including specialized types)
@@ -231,7 +229,7 @@
         }
     cardinality = '+*'
     subject = '**'
-    object = 'EEType'
+    object = 'CWEType'
 
 class specializes(MetaRelationType):
     name = 'specializes'
@@ -241,5 +239,5 @@
         'delete': ('managers',),
         }
     cardinality = '?*'
-    subject = 'EEType'
-    object = 'EEType'
+    subject = 'CWEType'
+    object = 'CWEType'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/workflow.py	Thu May 14 12:50:34 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('CWEType', 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('CWEType', 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('CWGroup', cardinality='**',
+                                    description=_('group in which a user should be to be '
+                                                  'allowed to pass this transition'))
+    transition_of = SubjectRelation('CWEType', 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 CWUser table
+
+    # add/delete perms given to managers/users, after what most of the job
+    # is done by workflow enforcment
+
--- a/schemaviewer.py	Thu May 14 12:50:14 2009 +0200
+++ b/schemaviewer.py	Thu May 14 12:50:34 2009 +0200
@@ -1,13 +1,13 @@
 """an helper class to display CubicWeb schema using ureports
 
 :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.ureports import Section, Title, Table, Link, Span, Text
-from yams.schema2dot import CARD_MAP    
+from yams.schema2dot import CARD_MAP
 
 _ = unicode
 I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
@@ -24,7 +24,7 @@
         else:
             self._possible_views = lambda x: ()
         self.encoding = encoding
-        
+
     def format_acls(self, schema, access_types):
         """return a layout displaying access control lists"""
         data = [self.req._('access type'), self.req._('groups')]
@@ -36,7 +36,7 @@
         return Section(children=(Table(cols=2, cheaders=1, rheaders=1, children=data),),
                        klass='acl')
 
-        
+
     def visit_schema(self, schema, display_relations=0,
                      skiprels=(), skipmeta=True):
         """get a layout for a whole schema"""
@@ -46,17 +46,16 @@
         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(eschemas):
             esection.append(self.visit_entityschema(eschema, skiprels))
         if display_relations:
             title = Title(self.req._('Relations'), klass='titleUnderline')
-            rsection = Section(children=(title,)) 
+            rsection = Section(children=(title,))
             layout.append(rsection)
             relations = [rschema for rschema in schema.relations()
                          if not (rschema.is_final() or rschema.type in skiprels)]
@@ -77,7 +76,7 @@
                 continue
             aname = rschema.type
             if aname == 'eid':
-                continue            
+                continue
             data.append('%s (%s)' % (aname, _(aname)))
             data.append(_(aschema.type))
             defaultval = eschema.default(aname)
@@ -95,7 +94,7 @@
 
     def eschema_link_url(self, eschema):
         return self.req.build_url('eetype/%s?vid=eschema' % eschema)
-    
+
     def rschema_link_url(self, rschema):
         return self.req.build_url('ertype/%s?vid=eschema' % rschema)
 
@@ -106,7 +105,7 @@
 
     def stereotype(self, name):
         return Span((' <<%s>>' % name,), klass='stereotype')
-    
+
     def visit_entityschema(self, eschema, skiprels=()):
         """get a layout for an entity schema"""
         etype = eschema.type
@@ -118,7 +117,7 @@
             boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req), stereotype), klass='title')]
         else:
             boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req)), klass='title')]
-        table = Table(cols=4, rheaders=1, 
+        table = Table(cols=4, rheaders=1,
                       children=self._entity_attributes_data(eschema))
         boxchild.append(Section(children=(table,), klass='body'))
         data = []
@@ -158,14 +157,14 @@
             return layout
         _ = self.req._
         if self.req.user.matching_groups('managers'):
-            layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update')))
+            # layout.append(self.format_acls(eschema, ('read', 'add', 'delete', 'update')))
             # possible views for this entity type
             views = [_(view.title) for view in self.possible_views(etype)]
             layout.append(Section(children=(Table(cols=1, rheaders=1,
                                                   children=[_('views')]+views),),
                                   klass='views'))
         return layout
-    
+
     def visit_relationschema(self, rschema, title=True):
         """get a layout for a relation schema"""
         _ = self.req._
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/selectors.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1153 @@
+"""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, typed_eid)
+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
+
+
+def score_interface(cls_or_inst, cls, iface):
+    """Return true if the give object (maybe an instance or class) implements
+    the interface.
+    """
+    if getattr(iface, '__registry__', None) == 'etypes':
+        # adjust score if the interface is an entity class
+        parents = cls_or_inst.parent_classes()
+        if iface is cls:
+            return len(parents) + 4
+        if iface is parents[-1]: # Any
+            return 1
+        for index, basecls in enumerate(reversed(parents[:-1])):
+            if iface is basecls:
+                return index + 3
+        return 0
+    if implements_iface(cls_or_inst, iface):
+        # implenting an interface takes precedence other special Any interface
+        return 2
+    return 0
+
+
+# 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 ImplementsMixIn(object):
+    """mix-in class for selectors checking implemented interfaces of something
+    """
+    def __init__(self, *expected_ifaces):
+        super(ImplementsMixIn, 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_interfaces(self, cls_or_inst, cls):
+        score = 0
+        vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
+        for iface in self.expected_ifaces:
+            if isinstance(iface, basestring):
+                # entity type
+                try:
+                    iface = vreg.etype_class(iface)
+                except KeyError:
+                    continue # entity type not in the schema
+            score += score_interface(cls_or_inst, cls, iface)
+        return score
+
+
+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 'entity' find in kwargs, return the score returned by the score_entity
+      method for this entity
+    * 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 and not kwargs.get('entity'):
+            return 0
+        score = 0
+        if kwargs.get('entity'):
+            score = self.score_entity(kwargs['entity'])
+        elif 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 authenticated"""
+    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_transition(match_search_state):
+    @lltrace
+    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        try:
+            trname = req.execute('Any XN WHERE X is Transition, X eid %(x)s, X name XN',
+                                 {'x': typed_eid(req.form['treid'])})[0][0]
+        except (KeyError, IndexError):
+            return 0
+        # XXX check this is a transition that apply to the object?
+        if not trname in self.expected:
+            return 0
+        return 1
+
+
+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(ImplementsMixIn, EClassSelector):
+    """accept if entity classes 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 score_class(self, eclass, req):
+        return self.score_interfaces(eclass, eclass)
+
+
+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 entity_implements(ImplementsMixIn, EntitySelector):
+    """accept if entity instances found in the result set implements at least one
+    of the interfaces given as argument. Returned score is the number of
+    implemented interfaces.
+
+    See `EntitySelector` 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 score_entity(self, entity):
+        return self.score_interfaces(entity, entity.__class__)
+
+
+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 on %s'
+                 % cls,
+                 DeprecationWarning)
+            cls.__select__ &= implements(*cls.accepts)
+        return cls
+    return plug_selector
+
+@unbind_method
+def accepts_etype_compat(registered):
+    def plug_selector(cls, vreg):
+        cls = registered(cls, vreg)
+        if getattr(cls, 'accepts', None):
+            warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
+                 DeprecationWarning)
+            cls.__select__ &= specified_etype_implements(*cls.accepts)
+        return cls
+    return plug_selector
+
+@unbind_method
+def condition_compat(registered):
+    def plug_selector(cls, vreg):
+        cls = registered(cls, vreg)
+        if getattr(cls, 'condition', None):
+            warn('use "use rql_condition(expression)" instead of using condition',
+                 DeprecationWarning)
+            cls.__select__ &= rql_condition(cls.condition)
+        return cls
+    return plug_selector
+
+@unbind_method
+def has_relation_compat(registered):
+    def plug_selector(cls, vreg):
+        cls = registered(cls, vreg)
+        if getattr(cls, 'etype', None):
+            warn('use relation_possible selector instead of using etype_rtype',
+                 DeprecationWarning)
+            cls.__select__ &= relation_possible(cls.rtype, role(cls),
+                                                getattr(cls, 'etype', None),
+                                                action=getattr(cls, 'require_permission', 'read'))
+        return cls
+    return plug_selector
--- a/server/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -4,7 +4,7 @@
 This module contains functions to initialize a new repository.
 
 :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"
@@ -93,10 +93,10 @@
     print 'inserting default user and groups'
     needisfix = []
     for group in BASEGROUPS:
-        rset = session.execute('INSERT EGroup X: X name %(name)s',
+        rset = session.execute('INSERT CWGroup X: X name %(name)s',
                                {'name': unicode(group)})
         needisfix.append( (rset.rows[0][0], rset.description[0][0]) )
-    rset = session.execute('INSERT EUser X: X login %(login)s, X upassword %(pwd)s',
+    rset = session.execute('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s',
                            {'login': login, 'pwd': pwd})
     needisfix.append( (rset.rows[0][0], rset.description[0][0]) )
     session.execute('SET U in_group G WHERE G name "managers"')
@@ -116,10 +116,10 @@
         handler.session.unsafe_execute('SET X is_instance_of E WHERE X eid %(x)s, E name %(name)s',
                                        {'x': eid, 'name': etype}, 'x')
     # insert versions
-    handler.cmd_add_entity('EProperty', pkey=u'system.version.cubicweb',
+    handler.cmd_add_entity('CWProperty', pkey=u'system.version.cubicweb',
                            value=unicode(config.cubicweb_version()))
     for cube in config.cubes():
-        handler.cmd_add_entity('EProperty', 
+        handler.cmd_add_entity('CWProperty', 
                                pkey=u'system.version.%s' % cube.lower(),
                                value=unicode(config.cube_version(cube)))
     # yoo !
--- a/server/checkintegrity.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/checkintegrity.py	Thu May 14 12:50:34 2009 +0200
@@ -2,16 +2,18 @@
 is checked.
 
 :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 datetime import datetime
 
-from mx.DateTime import now
 from logilab.common.shellutils import ProgressBar
 
+from cubicweb.server.sqlutils import SQL_PREFIX
+
 def has_eid(sqlcursor, eid, eids):
     """return true if the eid is a valid eid"""
     if eids.has_key(eid):
@@ -26,7 +28,8 @@
         # XXX what to do...
         eids[eid] = True
         return True
-    sqlcursor.execute('SELECT * FROM %s WHERE eid=%s' % (etype, eid))
+    sqlcursor.execute('SELECT * FROM %s%s WHERE %seid=%s' % (SQL_PREFIX, etype,
+                                                             SQL_PREFIX, eid))
     result = sqlcursor.fetchall()
     if len(result) == 0:
         eids[eid] = False
@@ -58,7 +61,7 @@
                     yield container
     else:
         yield eschema
-    
+
 def reindex_entities(schema, session):
     """reindex all entities in the repository"""
     # deactivate modification_date hook since we don't want them
@@ -71,6 +74,7 @@
                             'before_update_entity', '')
     repo.hm.unregister_hook(uniquecstrcheck_before_modification,
                             'before_update_entity', '')
+    repo.do_fti = True  # ensure full-text indexation is activated
     etypes = set()
     for eschema in schema.entities():
         if eschema.is_final():
@@ -84,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
@@ -99,7 +103,7 @@
     # restore Entity.check
     Entity.check = _check
 
-    
+
 def check_schema(schema, session, eids, fix=1):
     """check serialized schema"""
     print 'Checking serialized schema'
@@ -107,7 +111,7 @@
                           'VocabularyConstraint', 'RQLConstraint',
                           'RQLVocabularyConstraint')
     rql = ('Any COUNT(X),RN,EN,ECTN GROUPBY RN,EN,ECTN ORDERBY 1 '
-           'WHERE X is EConstraint, R constrained_by X, '
+           'WHERE X is CWConstraint, R constrained_by X, '
            'R relation_type RT, R from_entity ET, RT name RN, '
            'ET name EN, X cstrtype ECT, ECT name ECTN')
     for count, rn, en, cstrname in session.execute(rql):
@@ -118,7 +122,7 @@
                 count, cstrname, en, rn)
 
 
-    
+
 def check_text_index(schema, session, eids, fix=1):
     """check all entities registered in the text index"""
     print 'Checking text index'
@@ -153,7 +157,9 @@
     for eschema in schema.entities():
         if eschema.is_final():
             continue
-        cursor = session.system_sql('SELECT eid FROM %s;' % eschema.type)
+        table = SQL_PREFIX + eschema.type
+        column = SQL_PREFIX +  'eid'
+        cursor = session.system_sql('SELECT %s FROM %s;' % (column, table))
         for row in cursor.fetchall():
             eid = row[0]
             # eids is full since we have fetched everyting from the entities table,
@@ -162,12 +168,12 @@
                 msg = '  Entity with eid %s exists in the %s table but not in the system table'
                 print >> sys.stderr, msg % (eid, eschema.type),
                 if fix:
-                    session.system_sql('DELETE FROM %s WHERE eid=%s;' % (eschema.type, eid))
+                    session.system_sql('DELETE FROM %s WHERE %s=%s;' % (table, column, eid))
                     print >> sys.stderr, ' [FIXED]'
                 else:
                     print >> sys.stderr
-                
-            
+
+
 def bad_related_msg(rtype, target, eid, fix):
     msg = '  A relation %s with %s eid %s exists but no such entity in sources'
     print >> sys.stderr, msg % (rtype, target, eid),
@@ -175,48 +181,49 @@
         print >> sys.stderr, ' [FIXED]'
     else:
         print >> sys.stderr
-    
-    
+
+
 def check_relations(schema, session, eids, fix=1):
     """check all relations registered in the repo system table"""
     print 'Checking relations'
     for rschema in schema.relations():
         if rschema.is_final():
             continue
-        rtype = rschema.type
-        if rtype == 'identity':
+        if rschema == 'identity':
             continue
         if rschema.inlined:
             for subjtype in rschema.subjects():
+                table = SQL_PREFIX + str(subjtype)
+                column = SQL_PREFIX +  str(rschema)
                 sql = 'SELECT %s FROM %s WHERE %s IS NOT NULL;' % (
-                    rtype, subjtype, rtype)
+                    column, table, column)
                 cursor = session.system_sql(sql)
                 for row in cursor.fetchall():
                     eid = row[0]
                     if not has_eid(cursor, eid, eids):
-                        bad_related_msg(rtype, 'object', eid, fix)
+                        bad_related_msg(rschema, 'object', eid, fix)
                         if fix:
-                            sql = 'UPDATE %s SET %s = NULL WHERE eid=%s;' % (
-                                subjtype, rtype, eid)
+                            sql = 'UPDATE %s SET %s = NULL WHERE %seid=%s;' % (
+                                table, column, SQL_PREFIX, eid)
                             session.system_sql(sql)
             continue
-        cursor = session.system_sql('SELECT eid_from FROM %s_relation;' % rtype)
+        cursor = session.system_sql('SELECT eid_from FROM %s_relation;' % rschema)
         for row in cursor.fetchall():
             eid = row[0]
             if not has_eid(cursor, eid, eids):
-                bad_related_msg(rtype, 'subject', eid, fix)
+                bad_related_msg(rschema, 'subject', eid, fix)
                 if fix:
                     sql = 'DELETE FROM %s_relation WHERE eid_from=%s;' % (
-                        rtype, eid)
+                        rschema, eid)
                     session.system_sql(sql)
-        cursor = session.system_sql('SELECT eid_to FROM %s_relation;' % rtype)
+        cursor = session.system_sql('SELECT eid_to FROM %s_relation;' % rschema)
         for row in cursor.fetchall():
             eid = row[0]
             if not has_eid(cursor, eid, eids):
-                bad_related_msg(rtype, 'object', eid, fix)
+                bad_related_msg(rschema, 'object', eid, fix)
                 if fix:
                     sql = 'DELETE FROM %s_relation WHERE eid_to=%s;' % (
-                        rtype, eid)
+                        rschema, eid)
                     session.system_sql(sql)
 
 
@@ -227,21 +234,26 @@
     """
     print 'Checking metadata'
     cursor = session.system_sql("SELECT DISTINCT type FROM entities;")
+    eidcolumn = SQL_PREFIX + 'eid'
     for etype, in cursor.fetchall():
-        for rel, default in ( ('creation_date', now()),
-                              ('modification_date', now()), ):
-            cursor = session.system_sql("SELECT eid FROM %s "
-                                        "WHERE %s is NULL" % (etype, rel))
+        table = SQL_PREFIX + etype
+        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))
             for eid, in cursor.fetchall():
                 msg = '  %s with eid %s has no %s'
                 print >> sys.stderr, msg % (etype, eid, rel),
                 if fix:
-                    session.system_sql("UPDATE %s SET %s=%(default)s WHERE eid=%s ;"
-                                       % (etype, rel, eid), {'default': default})
+                    session.system_sql("UPDATE %s SET %s=%%(v)s WHERE %s=%s ;"
+                                       % (table, column, eidcolumn, eid),
+                                       {'v': default})
                     print >> sys.stderr, ' [FIXED]'
                 else:
                     print >> sys.stderr
-    cursor = session.system_sql('SELECT MIN(eid) FROM euser;')
+    cursor = session.system_sql('SELECT MIN(%s) FROM %sCWUser;' % (eidcolumn,
+                                                                  SQL_PREFIX))
     default_user_eid = cursor.fetchone()[0]
     assert default_user_eid is not None, 'no user defined !'
     for rel, default in ( ('owned_by', default_user_eid), ):
--- a/server/hookhelper.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/hookhelper.py	Thu May 14 12:50:34 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):
@@ -42,7 +42,7 @@
     for session in repo._sessions.values():
         if ueid == session.user.eid:
             yield session
-        
+
 
 # mail related ################################################################
 
@@ -58,17 +58,17 @@
         else:
             assert recipients is None
             self.to_send = []
-        super(SendMailOp, self).__init__(session, **kwargs) 
-       
+        super(SendMailOp, self).__init__(session, **kwargs)
+
     def register(self, session):
         previous = super(SendMailOp, self).register(session)
         if previous:
             self.to_send = previous.to_send + self.to_send
-        
+
     def commit_event(self):
         self.repo.threaded_task(self.sendmails)
 
-    def sendmails(self):        
+    def sendmails(self):
         server, port = self.config['smtp-host'], self.config['smtp-port']
         SMTP_LOCK.acquire()
         try:
@@ -89,7 +89,7 @@
             smtp.close()
         finally:
             SMTP_LOCK.release()
-            
+
 
 # state related ###############################################################
 
--- a/server/hooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/hooks.py	Thu May 14 12:50:34 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)
@@ -20,27 +18,27 @@
 
 def relation_deleted(session, eidfrom, rtype, eidto):
     session.add_query_data('pendingrelations', (eidfrom, rtype, eidto))
-    
+
 
 # base meta-data handling #####################################################
 
 def setctime_before_add_entity(session, entity):
     """before create a new entity -> set creation and modification date
- 
+
     this is a conveniency hook, you shouldn't have to disable it
     """
     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):
-        
+
     def precommit_event(self):
         if self.eid in self.session.query_data('pendingeids', ()):
             # entity have been created and deleted in the same transaction
@@ -48,7 +46,7 @@
         ueid = self.session.user.eid
         execute = self.session.unsafe_execute
         if not execute('Any X WHERE X created_by U, X eid %(x)s',
-                       {'x': self.eid}, 'x'): 
+                       {'x': self.eid}, 'x'):
             execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s',
                     {'x': self.eid, 'u': ueid}, 'x')
 
@@ -62,6 +60,8 @@
 
 def setis_after_add_entity(session, entity):
     """create a new entity -> set is relation"""
+    if hasattr(entity, '_cw_recreating'):
+        return
     session.unsafe_execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
                            {'x': entity.eid, 'name': entity.id}, 'x')
     # XXX < 2.50 bw compat
@@ -86,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.
@@ -94,15 +93,15 @@
     if session.repo.schema.rschema(rtype).fulltext_container:
         FTIndexEntityOp(session, entity=session.entity(eidto))
         FTIndexEntityOp(session, entity=session.entity(eidfrom))
-    
+
 class SyncOwnersOp(PreCommitOperation):
-        
+
     def precommit_event(self):
         self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
                                     'NOT EXISTS(X owned_by U, X eid %(x)s)',
                                     {'c': self.compositeeid, 'x': self.composedeid},
                                     ('c', 'x'))
-        
+
 def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto):
     """when adding composite relation, the composed should have the same owners
     has the composite
@@ -115,7 +114,7 @@
         SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto)
     elif composite == 'object':
         SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom)
-    
+
 def _register_metadata_hooks(hm):
     """register meta-data related hooks on the hooks manager"""
     hm.register_hook(setctime_before_add_entity, 'before_add_entity', '')
@@ -126,16 +125,16 @@
     hm.register_hook(fti_update_after_delete_relation, 'after_delete_relation', '')
     if 'is' in hm.schema:
         hm.register_hook(setis_after_add_entity, 'after_add_entity', '')
-    if 'EUser' in hm.schema:
-        hm.register_hook(setowner_after_add_user, 'after_add_entity', 'EUser')
-            
+    if 'CWUser' in hm.schema:
+        hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
+
 # core hooks ##################################################################
-    
+
 class DelayedDeleteOp(PreCommitOperation):
     """delete the object of composite relation except if the relation
     has actually been redirected to another composite
     """
-        
+
     def precommit_event(self):
         session = self.session
         if not self.eid in session.query_data('pendingeids', ()):
@@ -143,7 +142,7 @@
             session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
                                    % (etype, self.relation),
                                    {'x': self.eid}, 'x')
-    
+
 def handle_composite_before_del_relation(session, eidfrom, rtype, eidto):
     """delete the object of composite relation"""
     composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
@@ -158,7 +157,7 @@
 
 
 # schema validation hooks #####################################################
-        
+
 class CheckConstraintsOperation(LateOperation):
     """check a new relation satisfy its constraints
     """
@@ -177,10 +176,10 @@
             except NotImplementedError:
                 self.critical('can\'t check constraint %s, not supported',
                               constraint)
-    
+
     def commit_event(self):
         pass
-    
+
 def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
     """check the relation satisfy its constraints
 
@@ -208,37 +207,13 @@
 
 
 
-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
     case the relation is being replaced
     """
     eid, rtype = None, None
-    
+
     def precommit_event(self):
         # recheck pending eids
         if self.eid in self.session.query_data('pendingeids', ()):
@@ -249,18 +224,18 @@
             raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype,
                                                                 'etype': etype,
                                                                 'eid': self.eid}})
-    
+
     def commit_event(self):
         pass
-        
+
     def _rql(self):
         raise NotImplementedError()
-    
+
 class CheckSRelationOp(CheckRequiredRelationOperation):
     """check required subject relation"""
     def _rql(self):
         return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-    
+
 class CheckORelationOp(CheckRequiredRelationOperation):
     """check required object relation"""
     def _rql(self):
@@ -273,7 +248,7 @@
             break
     else:
         opcls(session, rtype=rtype, eid=eid)
-    
+
 def cardinalitycheck_after_add_entity(session, entity):
     """check cardinalities are satisfied"""
     eid = entity.eid
@@ -307,26 +282,24 @@
 
 def _register_core_hooks(hm):
     hm.register_hook(handle_composite_before_del_relation, 'before_delete_relation', '')
-    hm.register_hook(before_del_group, 'before_delete_entity', 'EGroup')
-    
+    hm.register_hook(before_del_group, 'before_delete_entity', 'CWGroup')
+
     #hm.register_hook(cstrcheck_before_update_entity, 'before_update_entity', '')
     hm.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
     hm.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
     hm.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
     hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
     hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-    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 #################################################
-            
+
 class GroupOperation(Operation):
     """base class for group operation"""
     geid = None
     def __init__(self, session, *args, **kwargs):
         """override to get the group name before actual groups manipulation:
-        
+
         we may temporarily loose right access during a commit event, so
         no query should be emitted while comitting
         """
@@ -345,13 +318,13 @@
         except KeyError:
             self.error('user %s not in group %s',  self.cnxuser, self.group)
             return
-    
+
 def after_del_in_group(session, fromeid, rtype, toeid):
     """modify user permission, need to update users"""
     for session_ in get_user_sessions(session.repo, fromeid):
         DeleteGroupOp(session, cnxuser=session_.user, geid=toeid)
 
-        
+
 class AddGroupOp(GroupOperation):
     """synchronize user when a in_group relation has been added"""
     def commit_event(self):
@@ -374,7 +347,7 @@
     def __init__(self, session, cnxid):
         self.cnxid = cnxid
         Operation.__init__(self, session)
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -386,10 +359,10 @@
     """modify user permission, need to update users"""
     for session_ in get_user_sessions(session.repo, eid):
         DelUserOp(session, session_.id)
-    
+
 def _register_usergroup_hooks(hm):
     """register user/group related hooks on the hooks manager"""
-    hm.register_hook(after_del_user, 'after_delete_entity', 'EUser')
+    hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
     hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group')
     hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group')
 
@@ -457,7 +430,7 @@
 
 def set_initial_state_after_add(session, entity):
     SetInitialStateOp(session, entity=entity)
-    
+
 def _register_wf_hooks(hm):
     """register workflow related hooks on the hooks manager"""
     if 'in_state' in hm.schema:
@@ -469,12 +442,12 @@
                                  str(eschema))
 
 
-# EProperty hooks #############################################################
+# CWProperty hooks #############################################################
 
 
-class DelEPropertyOp(Operation):
+class DelCWPropertyOp(Operation):
     """a user's custom properties has been deleted"""
-    
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -482,22 +455,22 @@
         except KeyError:
             self.error('%s has no associated value', self.key)
 
-class ChangeEPropertyOp(Operation):
+class ChangeCWPropertyOp(Operation):
     """a user's custom properties has been added/changed"""
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         self.epropdict[self.key] = self.value
 
-class AddEPropertyOp(Operation):
+class AddCWPropertyOp(Operation):
     """a user's custom properties has been added/changed"""
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         eprop = self.eprop
         if not eprop.for_user:
             self.repo.vreg.eprop_values[eprop.pkey] = eprop.value
-        # if for_user is set, update is handled by a ChangeEPropertyOp operation
+        # if for_user is set, update is handled by a ChangeCWPropertyOp operation
 
 def after_add_eproperty(session, entity):
     key, value = entity.pkey, entity.value
@@ -511,8 +484,8 @@
         session.unsafe_execute('SET P for_user U WHERE P eid %(x)s,U eid %(u)s',
                                {'x': entity.eid, 'u': session.user.eid}, 'x')
     else:
-        AddEPropertyOp(session, eprop=entity)
-        
+        AddCWPropertyOp(session, eprop=entity)
+
 def after_update_eproperty(session, entity):
     key, value = entity.pkey, entity.value
     try:
@@ -523,13 +496,13 @@
         raise ValidationError(entity.eid, {'value': session._(str(ex))})
     if entity.for_user:
         for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
-            ChangeEPropertyOp(session, epropdict=session_.user.properties,
+            ChangeCWPropertyOp(session, epropdict=session_.user.properties,
                               key=key, value=value)
     else:
         # site wide properties
-        ChangeEPropertyOp(session, epropdict=session.vreg.eprop_values,
+        ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values,
                           key=key, value=value)
-        
+
 def before_del_eproperty(session, eid):
     for eidfrom, rtype, eidto in session.query_data('pendingrelations', ()):
         if rtype == 'for_user' and eidfrom == eid:
@@ -538,10 +511,10 @@
     else:
         key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
                               {'x': eid}, 'x')[0][0]
-        DelEPropertyOp(session, epropdict=session.vreg.eprop_values, key=key)
+        DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key)
 
 def after_add_for_user(session, fromeid, rtype, toeid):
-    if not session.describe(fromeid)[0] == 'EProperty':
+    if not session.describe(fromeid)[0] == 'CWProperty':
         return
     key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
                                  {'x': fromeid}, 'x')[0]
@@ -549,20 +522,20 @@
         raise ValidationError(fromeid,
                               {'for_user': session._("site-wide property can't be set for user")})
     for session_ in get_user_sessions(session.repo, toeid):
-        ChangeEPropertyOp(session, epropdict=session_.user.properties,
+        ChangeCWPropertyOp(session, epropdict=session_.user.properties,
                           key=key, value=value)
-        
+
 def before_del_for_user(session, fromeid, rtype, toeid):
     key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
                           {'x': fromeid}, 'x')[0][0]
     relation_deleted(session, fromeid, rtype, toeid)
     for session_ in get_user_sessions(session.repo, toeid):
-        DelEPropertyOp(session, epropdict=session_.user.properties, key=key)
+        DelCWPropertyOp(session, epropdict=session_.user.properties, key=key)
 
 def _register_eproperty_hooks(hm):
     """register workflow related hooks on the hooks manager"""
-    hm.register_hook(after_add_eproperty, 'after_add_entity', 'EProperty')
-    hm.register_hook(after_update_eproperty, 'after_update_entity', 'EProperty')
-    hm.register_hook(before_del_eproperty, 'before_delete_entity', 'EProperty')
+    hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty')
+    hm.register_hook(after_update_eproperty, 'after_update_entity', 'CWProperty')
+    hm.register_hook(before_del_eproperty, 'before_delete_entity', 'CWProperty')
     hm.register_hook(after_add_for_user, 'after_add_relation', 'for_user')
     hm.register_hook(before_del_for_user, 'before_delete_relation', 'for_user')
--- a/server/hooksmanager.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/hooksmanager.py	Thu May 14 12:50:34 2009 +0200
@@ -23,12 +23,12 @@
 
 
 :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"
 
-ENTITIES_HOOKS = ('before_add_entity',    'after_add_entity', 
+ENTITIES_HOOKS = ('before_add_entity',    'after_add_entity',
                   'before_update_entity', 'after_update_entity',
                   'before_delete_entity', 'after_delete_entity')
 RELATIONS_HOOKS = ('before_add_relation',   'after_add_relation' ,
@@ -42,7 +42,7 @@
     """handle hooks registration and calls
     """
     verification_hooks_activated = True
-    
+
     def __init__(self, schema):
         self.set_schema(schema)
 
@@ -50,20 +50,20 @@
         self._hooks = {}
         self.schema = schema
         self._init_hooks(schema)
-        
+
     def register_hooks(self, hooks):
         """register a dictionary of hooks :
-        
+
              {'event': {'entity or relation type': [callbacks list]}}
         """
         for event, subevents in hooks.items():
             for subevent, callbacks in subevents.items():
                 for callback in callbacks:
                     self.register_hook(callback, event, subevent)
-                    
+
     def register_hook(self, function, event, etype=''):
         """register a function to call when <event> occurs
-        
+
          <etype> is an entity/relation type or an empty string.
          If etype is the empty string, the function will be called at each
          event, else the function will be called only when event occurs on an
@@ -76,14 +76,14 @@
             self._hooks[event][etype].append(function)
             self.debug('registered hook %s on %s (%s)', event, etype or 'any',
                        function.func_name)
-            
+
         except KeyError:
             self.error('can\'t register hook %s on %s (%s)',
                        event, etype or 'any', function.func_name)
-            
+
     def unregister_hook(self, function, event, etype=''):
         """register a function to call when <event> occurs
-        
+
         <etype> is an entity/relation type or an empty string.
         If etype is the empty string, the function will be called at each
         event, else the function will be called only when event occurs on an
@@ -109,7 +109,7 @@
             for hook in self._hooks[__event][__type]:
                 #print '[%s]'%__type, hook.__name__
                 hook(*args, **kwargs)
-    
+
     def _init_hooks(self, schema):
         """initialize the hooks map"""
         for hook_event in ENTITIES_HOOKS:
@@ -164,7 +164,7 @@
         self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
 #         self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
 #         self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-        
+
     def reactivate_verification_hooks(self):
         from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
                                         cardinalitycheck_before_del_relation,
@@ -179,14 +179,13 @@
         self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
 #         self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
 #         self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-            
-from cubicweb.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,22 +194,21 @@
 class Hook(AppObject):
     __metaclass__ = autoid
     __registry__ = 'hooks'
-    __registerer__ = accepts_registerer
-    __selectors__ = (yes,)
+    __select__ = yes()
     # set this in derivated classes
     events = None
     accepts = None
     enabled = True
-    
+
     def __init__(self, event=None):
         super(Hook, self).__init__()
         self.event = event
-        
+
     @classmethod
     def registered(cls, vreg):
         super(Hook, cls).registered(vreg)
         return cls()
-    
+
     @classmethod
     def register_to(cls):
         if not cls.enabled:
@@ -234,7 +232,7 @@
                             continue
                         yield event, str(eetype)
                         done.add((event, eetype))
-                        
+
 
     def make_callback(self, event):
         if len(self.events) == 1:
@@ -243,9 +241,8 @@
 
     def call(self):
         raise NotImplementedError
-    
+
 class SystemHook(Hook):
-    __registerer__ = yes_registerer
     accepts = ('',)
 
 from logging import getLogger
--- a/server/migractions.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/migractions.py	Thu May 14 12:50:34 2009 +0200
@@ -19,25 +19,28 @@
 import sys
 import os
 from os.path import join, exists
+from datetime import datetime
 
-from mx.DateTime import now
+from logilab.common.deprecation import deprecated_function, obsolete
 from logilab.common.decorators import cached
 from logilab.common.adbh import get_adv_func_helper
 
 from yams.constraints import SizeConstraint
 from yams.schema2sql import eschema2sql, rschema2sql
 
-from cubicweb import AuthenticationError
+from cubicweb import AuthenticationError, ETYPE_NAME_MAP
+from cubicweb.schema import CubicWebRelationSchema
 from cubicweb.dbapi import get_repository, repo_connect
 from cubicweb.common.migration import MigrationHelper, yes
 
 try:
     from cubicweb.server import schemaserial as ss
     from cubicweb.server.utils import manager_userpasswd
-    from cubicweb.server.sqlutils import sqlexec
+    from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
 except ImportError: # LAX
     pass
 
+
 class ServerMigrationHelper(MigrationHelper):
     """specific migration helper for server side  migration scripts,
     providind actions related to schema/data migration
@@ -64,11 +67,11 @@
     def repo_connect(self):
         self.repo = get_repository(method='inmemory', config=self.config)
         return self.repo
-    
+
     def shutdown(self):
         if self.repo is not None:
             self.repo.shutdown()
-        
+
     def rewrite_vcconfiguration(self):
         """write current installed versions (of cubicweb software
         and of each used cube) into the database
@@ -78,12 +81,12 @@
             pkgversion = self.config.cube_version(pkg)
             self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
         self.commit()
-        
+
     def backup_database(self, backupfile=None, askconfirm=True):
         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))
@@ -106,9 +109,11 @@
                 if answer == 1: # 1: continue, 2: retry
                     break
             else:
+                from cubicweb.toolsutils import restrict_perms_to_user
                 print 'database backup:', backupfile
+                restrict_perms_to_user(backupfile, self.info)
                 break
-        
+
     def restore_database(self, backupfile, drop=True):
         config = self.config
         source = config.sources()['system']
@@ -134,7 +139,7 @@
                     else:
                         break
             print 'database restored'
-        
+
     def migrate(self, vcconf, toupgrade, options):
         if not options.fs_only:
             if options.backup_db is None:
@@ -142,7 +147,7 @@
             elif options.backup_db:
                 self.backup_database(askconfirm=False)
         super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
-    
+
     def process_script(self, migrscript, funcname=None, *args, **kwargs):
         """execute a migration script
         in interactive mode,  display the migration script path, ask for
@@ -154,7 +159,7 @@
         else:
             return super(ServerMigrationHelper, self).process_script(
                 migrscript, funcname, *args, **kwargs)
-        
+
     @property
     def cnx(self):
         """lazy connection"""
@@ -189,7 +194,7 @@
     @property
     def session(self):
         return self.repo._get_session(self.cnx.sessionid)
-    
+
     @property
     @cached
     def rqlcursor(self):
@@ -198,15 +203,15 @@
         # some query while no pool is set on the session (eg on entity attribute
         # access for instance)
         return self.cnx.cursor()
-    
+
     def commit(self):
         if hasattr(self, '_cnx'):
             self._cnx.commit()
-            
+
     def rollback(self):
         if hasattr(self, '_cnx'):
             self._cnx.rollback()
-                   
+
     def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True):
         for rql, kwargs in rqliter:
             self.rqlexec(rql, kwargs, cachekey, ask_confirm)
@@ -220,12 +225,12 @@
                         'rql': self.rqlexec,
                         'rqliter': self.rqliter,
                         'schema': self.repo.schema,
-                        # XXX deprecate
-                        'newschema': self.fs_schema,
                         'fsschema': self.fs_schema,
-                        'cnx': self.cnx,
                         'session' : self.session,
                         'repo' : self.repo,
+                        'synchronize_schema': deprecated_function(self.cmd_sync_schema_props_perms),
+                        'synchronize_eschema': deprecated_function(self.cmd_sync_schema_props_perms),
+                        'synchronize_rschema': deprecated_function(self.cmd_sync_schema_props_perms),
                         })
         return context
 
@@ -233,9 +238,9 @@
     def group_mapping(self):
         """cached group mapping"""
         return ss.group_mapping(self.rqlcursor)
-        
+
     def exec_event_script(self, event, cubepath=None, funcname=None,
-                          *args, **kwargs):            
+                          *args, **kwargs):
         if cubepath:
             apc = join(cubepath, 'migration', '%s.py' % event)
         else:
@@ -260,312 +265,10 @@
                     self.repo.hm.register_hook(setowner_after_add_entity,
                                                'after_add_entity', '')
                     self.reactivate_verification_hooks()
-    
-    # base actions ############################################################
-
-    def checkpoint(self):
-        """checkpoint action"""
-        if self.confirm('commit now ?', shell=False):
-            self.commit()
-
-    def cmd_add_cube(self, cube, update_database=True):
-        self.cmd_add_cubes( (cube,), update_database)
-    
-    def cmd_add_cubes(self, cubes, update_database=True):
-        """update_database is telling if the database schema should be updated
-        or if only the relevant eproperty should be inserted (for the case where
-        a cube has been extracted from an existing application, so the
-        cube schema is already in there)
-        """
-        newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
-        if not newcubes:
-            return
-        for pack in newcubes:
-            self.cmd_set_property('system.version.'+pack,
-                                  self.config.cube_version(pack))
-        if not update_database:
-            self.commit()
-            return
-        newcubes_schema = self.config.load_schema()
-        new = set()
-        # execute pre-create files
-        for pack in reversed(newcubes):
-            self.exec_event_script('precreate', self.config.cube_dir(pack))
-        # add new entity and relation types
-        for rschema in newcubes_schema.relations():
-            if not rschema in self.repo.schema:
-                self.cmd_add_relation_type(rschema.type)
-                new.add(rschema.type)
-        for eschema in newcubes_schema.entities():
-            if not eschema in self.repo.schema:
-                self.cmd_add_entity_type(eschema.type)
-                new.add(eschema.type)
-        # check if attributes has been added to existing entities
-        for rschema in newcubes_schema.relations():
-            existingschema = self.repo.schema.rschema(rschema.type)
-            for (fromtype, totype) in rschema.iter_rdefs():
-                if existingschema.has_rdef(fromtype, totype):
-                    continue
-                # check we should actually add the relation definition
-                if not (fromtype in new or totype in new or rschema in new):
-                    continue
-                self.cmd_add_relation_definition(str(fromtype), rschema.type, 
-                                                 str(totype))
-        # execute post-create files
-        for pack in reversed(newcubes):
-            self.exec_event_script('postcreate', self.config.cube_dir(pack))
-            self.commit()        
-                
-    def cmd_remove_cube(self, cube):
-        removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
-        if not removedcubes:
-            return
-        fsschema = self.fs_schema
-        removedcubes_schema = self.config.load_schema()
-        reposchema = self.repo.schema
-        # execute pre-remove files
-        for pack in reversed(removedcubes):
-            self.exec_event_script('preremove', self.config.cube_dir(pack))
-        # remove cubes'entity and relation types
-        for rschema in fsschema.relations():
-            if not rschema in removedcubes_schema and rschema in reposchema:
-                self.cmd_drop_relation_type(rschema.type)
-        for eschema in fsschema.entities():
-            if not eschema in removedcubes_schema and eschema in reposchema:
-                self.cmd_drop_entity_type(eschema.type)
-        for rschema in fsschema.relations():
-            if rschema in removedcubes_schema and rschema in reposchema: 
-                # check if attributes/relations has been added to entities from 
-                # other cubes
-                for fromtype, totype in rschema.iter_rdefs():
-                    if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
-                           reposchema[rschema.type].has_rdef(fromtype, totype):
-                        self.cmd_drop_relation_definition(
-                            str(fromtype), rschema.type, str(totype))
-        # execute post-remove files
-        for pack in reversed(removedcubes):
-            self.exec_event_script('postremove', self.config.cube_dir(pack))
-            self.rqlexec('DELETE EProperty X WHERE X pkey %(pk)s',
-                         {'pk': u'system.version.'+pack}, ask_confirm=False)
-            self.commit()
-            
-    # schema migration actions ################################################
-    
-    def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
-        """add a new attribute on the given entity type"""
-        if attrtype is None:
-            rschema = self.fs_schema.rschema(attrname)
-            attrtype = rschema.objects(etype)[0]
-        self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
-        
-    def cmd_drop_attribute(self, etype, attrname, commit=True):
-        """drop an existing attribute from the given entity type
-        
-        `attrname` is a string giving the name of the attribute to drop
-        """
-        rschema = self.repo.schema.rschema(attrname)
-        attrtype = rschema.objects(etype)[0]
-        self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
 
-    def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
-        """rename an existing attribute of the given entity type
-        
-        `oldname` is a string giving the name of the existing attribute
-        `newname` is a string giving the name of the renamed attribute
-        """
-        eschema = self.fs_schema.eschema(etype)
-        attrtype = eschema.destination(newname)
-        # have to commit this first step anyway to get the definition
-        # actually in the schema
-        self.cmd_add_attribute(etype, newname, attrtype, commit=True)
-        # skipp NULL values if the attribute is required
-        rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
-        card = eschema.rproperty(newname, 'cardinality')[0]
-        if card == '1':
-            rql += ', NOT X %s NULL' % oldname
-        self.rqlexec(rql, ask_confirm=self.verbosity>=2)
-        self.cmd_drop_attribute(etype, oldname, commit=commit)
-            
-    def cmd_add_entity_type(self, etype, auto=True, commit=True):
-        """register a new entity type
-        
-        in auto mode, automatically register entity's relation where the
-        targeted type is known
-        """
-        applschema = self.repo.schema
-        if etype in applschema:
-            eschema = applschema[etype]
-            if eschema.is_final():
-                applschema.del_entity_type(etype)
-        else:
-            eschema = self.fs_schema.eschema(etype)
-        confirm = self.verbosity >= 2
-        # register the entity into EEType
-        self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
-        # add specializes relation if needed
-        self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
-        # register groups / permissions for the entity
-        self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
-                        ask_confirm=confirm)
-        # register entity's attributes
-        for rschema, attrschema in eschema.attribute_definitions():
-            # ignore those meta relations, they will be automatically added
-            if rschema.type in ('eid', 'creation_date', 'modification_date'):
-                continue
-            if not rschema.type in applschema:
-                # need to add the relation type and to commit to get it
-                # actually in the schema
-                self.cmd_add_relation_type(rschema.type, False, commit=True)
-            # register relation definition
-            self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
-                            ask_confirm=confirm)
-        if auto:
-            # we have commit here to get relation types actually in the schema
-            self.commit()
-            added = []
-            for rschema in eschema.subject_relations():
-                # attribute relation have already been processed and
-                # 'owned_by'/'created_by' will be automatically added
-                if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): 
-                    continue
-                rtypeadded = rschema.type in applschema
-                for targetschema in rschema.objects(etype):
-                    # ignore relations where the targeted type is not in the
-                    # current application schema
-                    targettype = targetschema.type
-                    if not targettype in applschema and targettype != etype:
-                        continue
-                    if not rtypeadded:
-                        # need to add the relation type and to commit to get it
-                        # actually in the schema
-                        added.append(rschema.type)
-                        self.cmd_add_relation_type(rschema.type, False, commit=True)
-                        rtypeadded = True
-                    # register relation definition
-                    # remember this two avoid adding twice non symetric relation
-                    # such as "Emailthread forked_from Emailthread"
-                    added.append((etype, rschema.type, targettype))
-                    self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
-                                    ask_confirm=confirm)
-            for rschema in eschema.object_relations():
-                rtypeadded = rschema.type in applschema or rschema.type in added
-                for targetschema in rschema.subjects(etype):
-                    # ignore relations where the targeted type is not in the
-                    # current application schema
-                    targettype = targetschema.type
-                    # don't check targettype != etype since in this case the
-                    # relation has already been added as a subject relation
-                    if not targettype in applschema:
-                        continue
-                    if not rtypeadded:
-                        # need to add the relation type and to commit to get it
-                        # actually in the schema
-                        self.cmd_add_relation_type(rschema.type, False, commit=True)
-                        rtypeadded = True
-                    elif (targettype, rschema.type, etype) in added:
-                        continue
-                    # register relation definition
-                    self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
-                                    ask_confirm=confirm)
-        if commit:
-            self.commit()
-                
-    def cmd_drop_entity_type(self, etype, commit=True):
-        """unregister an existing entity type
-        
-        This will trigger deletion of necessary relation types and definitions
-        """
-        # XXX what if we delete an entity type which is specialized by other types
-        # unregister the entity from EEType
-        self.rqlexec('DELETE EEType X WHERE X name %(etype)s', {'etype': etype},
-                     ask_confirm=self.verbosity>=2)
-        if commit:
-            self.commit()
+    # schema synchronization internals ########################################
 
-    def cmd_rename_entity_type(self, oldname, newname, commit=True):
-        """rename an existing entity type in the persistent schema
-        
-        `oldname` is a string giving the name of the existing entity type
-        `newname` is a string giving the name of the renamed entity type
-        """
-        self.rqlexec('SET ET name %(newname)s WHERE ET is EEType, ET name %(oldname)s',
-                     {'newname' : unicode(newname), 'oldname' : oldname})
-        if commit:
-            self.commit()
-        
-    def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
-        """register a new relation type named `rtype`, as described in the
-        schema description file.
-
-        `addrdef` is a boolean value; when True, it will also add all relations
-        of the type just added found in the schema definition file. Note that it
-        implies an intermediate "commit" which commits the relation type
-        creation (but not the relation definitions themselves, for which
-        committing depends on the `commit` argument value).
-        
-        """
-        rschema = self.fs_schema.rschema(rtype)
-        # register the relation into ERType and insert necessary relation
-        # definitions
-        self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
-                        ask_confirm=self.verbosity>=2)
-        # register groups / permissions for the relation
-        self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
-                        ask_confirm=self.verbosity>=2)
-        if addrdef:
-            self.commit()
-            self.rqlexecall(ss.rdef2rql(rschema),
-                            ask_confirm=self.verbosity>=2)
-        if commit:
-            self.commit()
-        
-    def cmd_drop_relation_type(self, rtype, commit=True):
-        """unregister an existing relation type"""
-        # unregister the relation from ERType
-        self.rqlexec('DELETE ERType X WHERE X name %r' % rtype,
-                     ask_confirm=self.verbosity>=2)
-        if commit:
-            self.commit()
-        
-    def cmd_rename_relation(self, oldname, newname, commit=True):
-        """rename an existing relation
-        
-        `oldname` is a string giving the name of the existing relation
-        `newname` is a string giving the name of the renamed relation
-        """
-        self.cmd_add_relation_type(newname, commit=True)
-        self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
-                     ask_confirm=self.verbosity>=2)
-        self.cmd_drop_relation_type(oldname, commit=commit)
-
-    def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
-        """register a new relation definition, from its definition found in the
-        schema definition file
-        """
-        rschema = self.fs_schema.rschema(rtype)
-        if not rtype in self.repo.schema:
-            self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
-        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
-                        ask_confirm=self.verbosity>=2)
-        if commit:
-            self.commit()
-        
-    def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
-        """unregister an existing relation definition"""
-        rschema = self.repo.schema.rschema(rtype)
-        # unregister the definition from EFRDef or ENFRDef
-        if rschema.is_final():
-            etype = 'EFRDef'
-        else:
-            etype = 'ENFRDef'
-        rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
-               'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
-        self.rqlexec(rql % (etype, subjtype, rtype, objtype),
-                     ask_confirm=self.verbosity>=2)
-        if commit:
-            self.commit()
-        
-    def cmd_synchronize_permissions(self, ertype, commit=True):
+    def _synchronize_permissions(self, ertype):
         """permission synchronization for an entity or relation type"""
         if ertype in ('eid', 'has_text', 'identity'):
             return
@@ -629,20 +332,17 @@
                                  {'expr': expr, 'exprtype': exprtype,
                                   'vars': expression.mainvars, 'x': teid}, 'x',
                                  ask_confirm=False)
-        if commit:
-            self.commit()
-        
-    def cmd_synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True,
-                                commit=True):
+
+    def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True):
         """synchronize properties of the persistent relation schema against its
         current definition:
-        
+
         * description
         * symetric, meta
         * inlined
         * relation definitions if `syncrdefs`
         * permissions if `syncperms`
-        
+
         physical schema changes should be handled by repository's schema hooks
         """
         rtype = str(rtype)
@@ -657,17 +357,14 @@
             for subj, obj in rschema.iter_rdefs():
                 if not reporschema.has_rdef(subj, obj):
                     continue
-                self.cmd_synchronize_rdef_schema(subj, rschema, obj,
-                                                 commit=False)
+                self._synchronize_rdef_schema(subj, rschema, obj)
         if syncperms:
-            self.cmd_synchronize_permissions(rtype, commit=False)
-        if commit:
-            self.commit()
-                
-    def cmd_synchronize_eschema(self, etype, syncperms=True, commit=True):
+            self._synchronize_permissions(rtype)
+
+    def _synchronize_eschema(self, etype, syncperms=True):
         """synchronize properties of the persistent entity schema against
         its current definition:
-        
+
         * description
         * internationalizable, fulltextindexed, indexed, meta
         * relations from/to this entity
@@ -685,11 +382,11 @@
         repospschema = repoeschema.specializes()
         espschema = eschema.specializes()
         if repospschema and not espschema:
-            self.rqlexec('DELETE X specializes Y WHERE X is EEType, X name %(x)s',
+            self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
                          {'x': str(repoeschema)})
         elif not repospschema and espschema:
-            self.rqlexec('SET X specializes Y WHERE X is EEType, X name %(x)s, '
-                         'Y is EEType, Y name %(y)s',
+            self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
+                         'Y is CWEType, Y name %(y)s',
                          {'x': str(repoeschema), 'y': str(espschema)})
         self.rqlexecall(ss.updateeschema2rql(eschema),
                         ask_confirm=self.verbosity >= 2)
@@ -702,22 +399,18 @@
                 if not rschema in repoeschema.object_relations():
                     continue
                 subjtypes, objtypes = targettypes, [etype]
-            self.cmd_synchronize_rschema(rschema, syncperms=syncperms,
-                                         syncrdefs=False, commit=False)
+            self._synchronize_rschema(rschema, syncperms=syncperms,
+                                      syncrdefs=False)
             reporschema = self.repo.schema.rschema(rschema)
             for subj in subjtypes:
                 for obj in objtypes:
                     if not reporschema.has_rdef(subj, obj):
                         continue
-                    self.cmd_synchronize_rdef_schema(subj, rschema, obj,
-                                                     commit=False)
+                    self._synchronize_rdef_schema(subj, rschema, obj)
         if syncperms:
-            self.cmd_synchronize_permissions(etype, commit=False)
-        if commit:
-            self.commit()
+            self._synchronize_permissions(etype)
 
-    def cmd_synchronize_rdef_schema(self, subjtype, rtype, objtype,
-                                    commit=True):
+    def _synchronize_rdef_schema(self, subjtype, rtype, objtype):
         """synchronize properties of the persistent relation definition schema
         against its current definition:
         * order and other properties
@@ -751,7 +444,7 @@
                 self.rqlexec('DELETE X constrained_by C WHERE C eid %(x)s',
                              {'x': cstr.eid}, 'x',
                              ask_confirm=confirm)
-                self.rqlexec('DELETE EConstraint C WHERE C eid %(x)s',
+                self.rqlexec('DELETE CWConstraint C WHERE C eid %(x)s',
                              {'x': cstr.eid}, 'x',
                              ask_confirm=confirm)
             else:
@@ -765,24 +458,349 @@
             self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
                                               newcstr),
                             ask_confirm=confirm)
+
+    # base actions ############################################################
+
+    def checkpoint(self):
+        """checkpoint action"""
+        if self.confirm('commit now ?', shell=False):
+            self.commit()
+
+    def cmd_add_cube(self, cube, update_database=True):
+        self.cmd_add_cubes( (cube,), update_database)
+
+    def cmd_add_cubes(self, cubes, update_database=True):
+        """update_database is telling if the database schema should be updated
+        or if only the relevant eproperty should be inserted (for the case where
+        a cube has been extracted from an existing application, so the
+        cube schema is already in there)
+        """
+        newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
+        if not newcubes:
+            return
+        for pack in newcubes:
+            self.cmd_set_property('system.version.'+pack,
+                                  self.config.cube_version(pack))
+        if not update_database:
+            self.commit()
+            return
+        newcubes_schema = self.config.load_schema(construction_mode='non-strict')
+        new = set()
+        # execute pre-create files
+        for pack in reversed(newcubes):
+            self.exec_event_script('precreate', self.config.cube_dir(pack))
+        # add new entity and relation types
+        for rschema in newcubes_schema.relations():
+            if not rschema in self.repo.schema:
+                self.cmd_add_relation_type(rschema.type)
+                new.add(rschema.type)
+        for eschema in newcubes_schema.entities():
+            if not eschema in self.repo.schema:
+                self.cmd_add_entity_type(eschema.type)
+                new.add(eschema.type)
+        # check if attributes has been added to existing entities
+        for rschema in newcubes_schema.relations():
+            existingschema = self.repo.schema.rschema(rschema.type)
+            for (fromtype, totype) in rschema.iter_rdefs():
+                if existingschema.has_rdef(fromtype, totype):
+                    continue
+                # check we should actually add the relation definition
+                if not (fromtype in new or totype in new or rschema in new):
+                    continue
+                self.cmd_add_relation_definition(str(fromtype), rschema.type,
+                                                 str(totype))
+        # execute post-create files
+        for pack in reversed(newcubes):
+            self.exec_event_script('postcreate', self.config.cube_dir(pack))
+            self.commit()
+
+    def cmd_remove_cube(self, cube):
+        removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
+        if not removedcubes:
+            return
+        fsschema = self.fs_schema
+        removedcubes_schema = self.config.load_schema(construction_mode='non-strict')
+        reposchema = self.repo.schema
+        # execute pre-remove files
+        for pack in reversed(removedcubes):
+            self.exec_event_script('preremove', self.config.cube_dir(pack))
+        # remove cubes'entity and relation types
+        for rschema in fsschema.relations():
+            if not rschema in removedcubes_schema and rschema in reposchema:
+                self.cmd_drop_relation_type(rschema.type)
+        for eschema in fsschema.entities():
+            if not eschema in removedcubes_schema and eschema in reposchema:
+                self.cmd_drop_entity_type(eschema.type)
+        for rschema in fsschema.relations():
+            if rschema in removedcubes_schema and rschema in reposchema:
+                # check if attributes/relations has been added to entities from
+                # other cubes
+                for fromtype, totype in rschema.iter_rdefs():
+                    if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
+                           reposchema[rschema.type].has_rdef(fromtype, totype):
+                        self.cmd_drop_relation_definition(
+                            str(fromtype), rschema.type, str(totype))
+        # execute post-remove files
+        for pack in reversed(removedcubes):
+            self.exec_event_script('postremove', self.config.cube_dir(pack))
+            self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s',
+                         {'pk': u'system.version.'+pack}, ask_confirm=False)
+            self.commit()
+
+    # schema migration actions ################################################
+
+    def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True):
+        """add a new attribute on the given entity type"""
+        if attrtype is None:
+            rschema = self.fs_schema.rschema(attrname)
+            attrtype = rschema.objects(etype)[0]
+        self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit)
+
+    def cmd_drop_attribute(self, etype, attrname, commit=True):
+        """drop an existing attribute from the given entity type
+
+        `attrname` is a string giving the name of the attribute to drop
+        """
+        rschema = self.repo.schema.rschema(attrname)
+        attrtype = rschema.objects(etype)[0]
+        self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit)
+
+    def cmd_rename_attribute(self, etype, oldname, newname, commit=True):
+        """rename an existing attribute of the given entity type
+
+        `oldname` is a string giving the name of the existing attribute
+        `newname` is a string giving the name of the renamed attribute
+        """
+        eschema = self.fs_schema.eschema(etype)
+        attrtype = eschema.destination(newname)
+        # have to commit this first step anyway to get the definition
+        # actually in the schema
+        self.cmd_add_attribute(etype, newname, attrtype, commit=True)
+        # skipp NULL values if the attribute is required
+        rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
+        card = eschema.rproperty(newname, 'cardinality')[0]
+        if card == '1':
+            rql += ', NOT X %s NULL' % oldname
+        self.rqlexec(rql, ask_confirm=self.verbosity>=2)
+        self.cmd_drop_attribute(etype, oldname, commit=commit)
+
+    def cmd_add_entity_type(self, etype, auto=True, commit=True):
+        """register a new entity type
+
+        in auto mode, automatically register entity's relation where the
+        targeted type is known
+        """
+        applschema = self.repo.schema
+        if etype in applschema:
+            eschema = applschema[etype]
+            if eschema.is_final():
+                applschema.del_entity_type(etype)
+        else:
+            eschema = self.fs_schema.eschema(etype)
+        confirm = self.verbosity >= 2
+        # register the entity into CWEType
+        self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
+        # add specializes relation if needed
+        self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
+        # register groups / permissions for the entity
+        self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
+                        ask_confirm=confirm)
+        # register entity's attributes
+        for rschema, attrschema in eschema.attribute_definitions():
+            # ignore those meta relations, they will be automatically added
+            if rschema.type in ('eid', 'creation_date', 'modification_date'):
+                continue
+            if not rschema.type in applschema:
+                # need to add the relation type and to commit to get it
+                # actually in the schema
+                self.cmd_add_relation_type(rschema.type, False, commit=True)
+            # register relation definition
+            self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
+                            ask_confirm=confirm)
+        if auto:
+            # we have commit here to get relation types actually in the schema
+            self.commit()
+            added = []
+            for rschema in eschema.subject_relations():
+                # attribute relation have already been processed and
+                # 'owned_by'/'created_by' will be automatically added
+                if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'):
+                    continue
+                rtypeadded = rschema.type in applschema
+                for targetschema in rschema.objects(etype):
+                    # ignore relations where the targeted type is not in the
+                    # current application schema
+                    targettype = targetschema.type
+                    if not targettype in applschema and targettype != etype:
+                        continue
+                    if not rtypeadded:
+                        # need to add the relation type and to commit to get it
+                        # actually in the schema
+                        added.append(rschema.type)
+                        self.cmd_add_relation_type(rschema.type, False, commit=True)
+                        rtypeadded = True
+                    # register relation definition
+                    # remember this two avoid adding twice non symetric relation
+                    # such as "Emailthread forked_from Emailthread"
+                    added.append((etype, rschema.type, targettype))
+                    self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
+                                    ask_confirm=confirm)
+            for rschema in eschema.object_relations():
+                rtypeadded = rschema.type in applschema or rschema.type in added
+                for targetschema in rschema.subjects(etype):
+                    # ignore relations where the targeted type is not in the
+                    # current application schema
+                    targettype = targetschema.type
+                    # don't check targettype != etype since in this case the
+                    # relation has already been added as a subject relation
+                    if not targettype in applschema:
+                        continue
+                    if not rtypeadded:
+                        # need to add the relation type and to commit to get it
+                        # actually in the schema
+                        self.cmd_add_relation_type(rschema.type, False, commit=True)
+                        rtypeadded = True
+                    elif (targettype, rschema.type, etype) in added:
+                        continue
+                    # register relation definition
+                    self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
+                                    ask_confirm=confirm)
         if commit:
             self.commit()
-        
-    def cmd_synchronize_schema(self, syncperms=True, commit=True):
+
+    def cmd_drop_entity_type(self, etype, commit=True):
+        """unregister an existing entity type
+
+        This will trigger deletion of necessary relation types and definitions
+        """
+        # XXX what if we delete an entity type which is specialized by other types
+        # unregister the entity from CWEType
+        self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype},
+                     ask_confirm=self.verbosity>=2)
+        if commit:
+            self.commit()
+
+    def cmd_rename_entity_type(self, oldname, newname, commit=True):
+        """rename an existing entity type in the persistent schema
+
+        `oldname` is a string giving the name of the existing entity type
+        `newname` is a string giving the name of the renamed entity type
+        """
+        self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
+                     {'newname' : unicode(newname), 'oldname' : oldname})
+        if commit:
+            self.commit()
+
+    def cmd_add_relation_type(self, rtype, addrdef=True, commit=True):
+        """register a new relation type named `rtype`, as described in the
+        schema description file.
+
+        `addrdef` is a boolean value; when True, it will also add all relations
+        of the type just added found in the schema definition file. Note that it
+        implies an intermediate "commit" which commits the relation type
+        creation (but not the relation definitions themselves, for which
+        committing depends on the `commit` argument value).
+
+        """
+        rschema = self.fs_schema.rschema(rtype)
+        # register the relation into CWRType and insert necessary relation
+        # definitions
+        self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
+                        ask_confirm=self.verbosity>=2)
+        # register groups / permissions for the relation
+        self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
+                        ask_confirm=self.verbosity>=2)
+        if addrdef:
+            self.commit()
+            self.rqlexecall(ss.rdef2rql(rschema),
+                            ask_confirm=self.verbosity>=2)
+        if commit:
+            self.commit()
+
+    def cmd_drop_relation_type(self, rtype, commit=True):
+        """unregister an existing relation type"""
+        # unregister the relation from CWRType
+        self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
+                     ask_confirm=self.verbosity>=2)
+        if commit:
+            self.commit()
+
+    def cmd_rename_relation(self, oldname, newname, commit=True):
+        """rename an existing relation
+
+        `oldname` is a string giving the name of the existing relation
+        `newname` is a string giving the name of the renamed relation
+        """
+        self.cmd_add_relation_type(newname, commit=True)
+        self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
+                     ask_confirm=self.verbosity>=2)
+        self.cmd_drop_relation_type(oldname, commit=commit)
+
+    def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
+        """register a new relation definition, from its definition found in the
+        schema definition file
+        """
+        rschema = self.fs_schema.rschema(rtype)
+        if not rtype in self.repo.schema:
+            self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
+        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
+                        ask_confirm=self.verbosity>=2)
+        if commit:
+            self.commit()
+
+    def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
+        """unregister an existing relation definition"""
+        rschema = self.repo.schema.rschema(rtype)
+        # unregister the definition from CWAttribute or CWRelation
+        if rschema.is_final():
+            etype = 'CWAttribute'
+        else:
+            etype = 'CWRelation'
+        rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",'
+               'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"')
+        self.rqlexec(rql % (etype, subjtype, rtype, objtype),
+                     ask_confirm=self.verbosity>=2)
+        if commit:
+            self.commit()
+
+    def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True,
+                                    syncprops=True, syncrdefs=True, commit=True):
         """synchronize the persistent schema against the current definition
         schema.
-        
+
         It will synch common stuff between the definition schema and the
         actual persistent schema, it won't add/remove any entity or relation.
         """
-        for etype in self.repo.schema.entities():
-            self.cmd_synchronize_eschema(etype, syncperms=syncperms, commit=False)
+        assert syncperms or syncprops, 'nothing to do'
+        if ertype is not None:
+            if isinstance(ertype, (tuple, list)):
+                assert len(ertype) == 3, 'not a relation definition'
+                assert syncprops, 'can\'t update permission for a relation definition'
+                self._synchronize_rdef_schema(*ertype)
+            elif syncprops:
+                erschema = self.repo.schema[ertype]
+                if isinstance(erschema, CubicWebRelationSchema):
+                    self._synchronize_rschema(erschema, syncperms=syncperms,
+                                              syncrdefs=syncrdefs)
+                else:
+                    self._synchronize_eschema(erschema, syncperms=syncperms)
+            else:
+                self._synchronize_permissions(ertype)
+        else:
+            for etype in self.repo.schema.entities():
+                if syncprops:
+                    self._synchronize_eschema(etype, syncperms=syncperms)
+                else:
+                    self._synchronize_permissions(etype)
         if commit:
             self.commit()
-                
+
     def cmd_change_relation_props(self, subjtype, rtype, objtype,
                                   commit=True, **kwargs):
-        """change some properties of a relation definition"""
+        """change some properties of a relation definition
+
+        you usually want to use sync_schema_props_perms instead.
+        """
         assert kwargs
         restriction = []
         if subjtype and subjtype != 'Any':
@@ -805,7 +823,9 @@
     def cmd_set_size_constraint(self, etype, rtype, size, commit=True):
         """set change size constraint of a string attribute
 
-        if size is None any size constraint will be removed
+        if size is None any size constraint will be removed.
+
+        you usually want to use sync_schema_props_perms instead.
         """
         oldvalue = None
         for constr in self.repo.schema.eschema(etype).constraints(rtype):
@@ -814,7 +834,7 @@
         if oldvalue == size:
             return
         if oldvalue is None and not size is None:
-            ceid = self.rqlexec('INSERT EConstraint C: C value %(v)s, C cstrtype CT '
+            ceid = self.rqlexec('INSERT CWConstraint C: C value %(v)s, C cstrtype CT '
                                 'WHERE CT name "SizeConstraint"',
                                 {'v': SizeConstraint(size).serialize()},
                                 ask_confirm=self.verbosity>=2)[0][0]
@@ -834,12 +854,16 @@
                              'S name "%s", R name "%s"' % (etype, rtype),
                              ask_confirm=self.verbosity>=2)
                 # cleanup unused constraints
-                self.rqlexec('DELETE EConstraint C WHERE NOT X constrained_by C')
+                self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C')
         if commit:
             self.commit()
-    
+
+    @obsolete('use sync_schema_props_perms(ertype, syncprops=False)')
+    def cmd_synchronize_permissions(self, ertype, commit=True):
+        self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
+
     # Workflows handling ######################################################
-    
+
     def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
         """method to ease workflow definition: add a state for one or more
         entity type(s)
@@ -857,7 +881,7 @@
         if commit:
             self.commit()
         return stateeid
-    
+
     def cmd_add_transition(self, name, transitionof, fromstates, tostate,
                            requiredgroups=(), conditions=(), commit=False, **kwargs):
         """method to ease workflow definition: add a transition for one or more
@@ -914,27 +938,27 @@
         entity.change_state(entity.wf_state(statename).eid)
         if commit:
             self.commit()
-        
-    # EProperty handling ######################################################
+
+    # CWProperty handling ######################################################
 
     def cmd_property_value(self, pkey):
-        rql = 'Any V WHERE X is EProperty, X pkey %(k)s, X value V'
+        rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V'
         rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False)
         return rset[0][0]
 
     def cmd_set_property(self, pkey, value):
         value = unicode(value)
         try:
-            prop = self.rqlexec('EProperty X WHERE X pkey %(k)s', {'k': pkey},
+            prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey},
                                 ask_confirm=False).get_entity(0, 0)
         except:
-            self.cmd_add_entity('EProperty', pkey=unicode(pkey), value=value)
+            self.cmd_add_entity('CWProperty', pkey=unicode(pkey), value=value)
         else:
             self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s',
                          {'k': pkey, 'v': value}, ask_confirm=False)
 
     # other data migration commands ###########################################
-        
+
     def cmd_add_entity(self, etype, *args, **kwargs):
         """add a new entity of the given type"""
         rql = 'INSERT %s X' % etype
@@ -954,10 +978,10 @@
         if commit:
             self.commit()
         return eid
-    
+
     def sqlexec(self, sql, args=None, ask_confirm=True):
         """execute the given sql if confirmed
-        
+
         should only be used for low level stuff undoable with existing higher
         level actions
         """
@@ -975,7 +999,7 @@
             except:
                 # no result to fetch
                 return
-    
+
     def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
         """rql action"""
         if not isinstance(rql, (tuple, list)):
@@ -1002,7 +1026,7 @@
 
     def cmd_reactivate_verification_hooks(self):
         self.repo.hm.reactivate_verification_hooks()
-        
+
     # broken db commands ######################################################
 
     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
@@ -1015,8 +1039,8 @@
         rschema = self.repo.schema.rschema(attr)
         oldtype = rschema.objects(etype)[0]
         rdefeid = rschema.rproperty(etype, oldtype, 'eid')
-        sql = ("UPDATE EFRDef "
-               "SET to_entity=(SELECT eid FROM EEType WHERE name='%s')"
+        sql = ("UPDATE CWAttribute "
+               "SET to_entity=(SELECT eid FROM CWEType WHERE name='%s')"
                "WHERE eid=%s") % (newtype, rdefeid)
         self.sqlexec(sql, ask_confirm=False)
         dbhelper = self.repo.system_source.dbhelper
@@ -1025,20 +1049,21 @@
         self.sqlexec(sql, ask_confirm=False)
         if commit:
             self.commit()
-        
+
     def cmd_add_entity_type_table(self, etype, commit=True):
         """low level method to create the sql table for an existing entity.
         This may be useful on accidental desync between the repository schema
         and a sql database
         """
         dbhelper = self.repo.system_source.dbhelper
-        tablesql = eschema2sql(dbhelper, self.repo.schema.eschema(etype))
+        tablesql = eschema2sql(dbhelper, self.repo.schema.eschema(etype),
+                               prefix=SQL_PREFIX)
         for sql in tablesql.split(';'):
             if sql.strip():
                 self.sqlexec(sql)
         if commit:
             self.commit()
-            
+
     def cmd_add_relation_type_table(self, rtype, commit=True):
         """low level method to create the sql table for an existing relation.
         This may be useful on accidental desync between the repository schema
@@ -1051,7 +1076,7 @@
                 self.sqlexec(sql)
         if commit:
             self.commit()
-            
+
 
 class ForRqlIterator:
     """specific rql iterator to make the loop skipable"""
@@ -1061,10 +1086,10 @@
         self.kwargs = kwargs
         self.ask_confirm = ask_confirm
         self._rsetit = None
-        
+
     def __iter__(self):
         return self
-    
+
     def next(self):
         if self._rsetit is not None:
             return self._rsetit.next()
@@ -1077,7 +1102,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	Thu May 14 12:50:14 2009 +0200
+++ b/server/msplanner.py	Thu May 14 12:50:34 2009 +0200
@@ -1,35 +1,57 @@
 """plan execution of rql queries on multiple sources
 
-the best way to understand what are we trying to acheive here is to read
-the unit-tests in unittest_querier_planner.py
-
-
-
-Split and execution specifications
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-For a system source and a ldap user source (only EUser and its attributes
-is supported, no group or such):
+the best way to understand what are we trying to acheive here is to read the
+unit-tests in unittest_msplanner.py
 
 
-:EUser X:
-1. fetch EUser X from both sources and return concatenation of results
+What you need to know
+~~~~~~~~~~~~~~~~~~~~~
+1. The system source is expected  to support every entity and relation types
+
+2. Given "X relation Y":
+
+   * if relation, X and Y types are supported by the external source, we suppose
+     by default that X and Y should both come from the same source as the
+     relation. You can specify otherwise by adding relation into the
+     "cross_relations" set in the source's mapping file and it that case, we'll
+     consider that we can also find in the system source some relation between
+     X and Y coming from different sources.
+
+   * if "relation" isn't supported by the external source but X or Y
+     types (or both) are, we suppose by default that can find in the system
+     source some relation where X and/or Y come from the external source. You
+     can specify otherwise by adding relation into the "dont_cross_relations"
+     set in the source's mapping file and it that case, we'll consider that we
+     can only find in the system source some relation between X and Y coming
+     the system source.
 
 
-:EUser X WHERE X in_group G, G name 'users':
+Implementation
+~~~~~~~~~~~~~~
+XXX explain algorithm
+
+
+Exemples of multi-sources query execution
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+For a system source and a ldap user source (only CWUser and its attributes
+is supported, no group or such):
+
+:CWUser X:
+1. fetch CWUser X from both sources and return concatenation of results
+
+:CWUser X WHERE X in_group G, G name 'users':
 * catch 1
-  1. fetch EUser X from both sources, store concatenation of results
-     into a temporary table
-  2. return the result of TMP X WHERE X in_group G, G name 'users' from
-     the system source
-     
+  1. fetch CWUser X from both sources, store concatenation of results into a
+     temporary table
+  2. return the result of TMP X WHERE X in_group G, G name 'users' from the
+     system source
 * catch 2
-  1. return the result of EUser X WHERE X in_group G, G name 'users'
-     from system source, that's enough (optimization of the sql querier
-     will avoid join on EUser, so we will directly get local eids)
+  1. return the result of CWUser X WHERE X in_group G, G name 'users' from system
+     source, that's enough (optimization of the sql querier will avoid join on
+     CWUser, so we will directly get local eids)
 
-    
-:EUser X,L WHERE X in_group G, X login L, G name 'users':
-1. fetch Any X,L WHERE X is EUser, X login L from both sources, store
+:CWUser X,L WHERE X in_group G, X login L, G name 'users':
+1. fetch Any X,L WHERE X is CWUser, X login L from both sources, store
    concatenation of results into a temporary table
 2. return the result of Any X, L WHERE X is TMP, X login LX in_group G,
    G name 'users' from the system source
@@ -37,36 +59,35 @@
 
 :Any X WHERE X owned_by Y:
 * catch 1
-  1. fetch EUser X from both sources, store concatenation of results
-     into a temporary table
-  2. return the result of Any X WHERE X owned_by Y, Y is TMP from
-     the system source
-     
+  1. fetch CWUser X from both sources, store concatenation of results into a
+     temporary table
+  2. return the result of Any X WHERE X owned_by Y, Y is TMP from the system
+     source
 * catch 2
-  1. return the result of Any X WHERE X owned_by Y
-     from system source, that's enough (optimization of the sql querier
-     will avoid join on EUser, so we will directly get local eids)
+  1. return the result of Any X WHERE X owned_by Y from system source, that's
+     enough (optimization of the sql querier will avoid join on CWUser, so we
+     will directly get local eids)
 
 
 :organization: Logilab
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __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
+from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep,
+                                       add_types_restriction)
 from cubicweb.server.mssteps import *
 from cubicweb.server.sources import AbstractSource
 
@@ -78,14 +99,6 @@
 AbstractSource.dont_cross_relations = ()
 AbstractSource.cross_relations = ()
 
-def allequals(solutions):
-    """return true if all solutions are identical"""
-    sol = solutions.next()
-    for sol_ in solutions:
-        if sol_ != sol:
-            return False
-    return True
-    
 def need_aggr_step(select, sources, stepdefs=None):
     """return True if a temporary table is necessary to store some partial
     results to execute the given query
@@ -114,24 +127,6 @@
                 fstepsolindices.update(stepdef[2])
     return False
 
-def copy_node(newroot, node, subparts=()):
-    newnode = node.__class__(*node.initargs(newroot))
-    for part in subparts:
-        newnode.append(part)
-    return newnode
-        
-def same_scope(var):
-    """return true if the variable is always used in the same scope"""
-    try:
-        return var.stinfo['samescope']
-    except KeyError:
-        for rel in var.stinfo['relations']:
-            if not rel.scope is var.scope:
-                var.stinfo['samescope'] = False
-                return False
-        var.stinfo['samescope'] = True
-        return True
-
 def select_group_sort(select): # XXX something similar done in rql2sql
     # add variables used in groups and sort terms to the selection
     # if necessary
@@ -150,7 +145,17 @@
                 if select.groupby and not vref in select.groupby:
                     select.add_group_var(vref.copy(select))
 
-# XXX move to rql
+def allequals(solutions):
+    """return true if all solutions are identical"""
+    sol = solutions.next()
+    noconstsol = None
+    for sol_ in solutions:
+        if sol_ != sol:
+            return False
+    return True
+
+# XXX move functions below to rql ##############################################
+
 def is_ancestor(n1, n2):
     p = n1.parent
     while p is not None:
@@ -159,85 +164,143 @@
         p = p.parent
     return False
 
+def copy_node(newroot, node, subparts=()):
+    newnode = node.__class__(*node.initargs(newroot))
+    for part in subparts:
+        newnode.append(part)
+    return newnode
+
+def same_scope(var):
+    """return true if the variable is always used in the same scope"""
+    try:
+        return var.stinfo['samescope']
+    except KeyError:
+        for rel in var.stinfo['relations']:
+            if not rel.scope is var.scope:
+                var.stinfo['samescope'] = False
+                return False
+        var.stinfo['samescope'] = True
+        return True
+
+################################################################################
+
 class PartPlanInformation(object):
     """regroups necessary information to execute some part of a "global" rql
     query ("global" means as received by the querier, which may result in
-    several internal queries, e.g. parts, due to security insertions)
+    several internal queries, e.g. parts, due to security insertions). Actually
+    a PPI is created for each subquery and for each query in a union.
 
-    it exposes as well some methods helping in executing this part on a
+    It exposes as well some methods helping in executing this part on a
     multi-sources repository, modifying its internal structure during the
-    process
+    process.
+
+    :attr plan:
+      the execution plan
+    :attr rqlst:
+      the original rql syntax tree handled by this part
+
+    :attr needsplit:
+      bool telling if the query has to be split into multiple steps for
+      execution or if it can be executed at once
 
-    :attr solutions: a list of mappings (varname -> vartype)
-    :attr sourcesvars:
-      a dictionnary telling for each source which variable/solution are
-      supported, of the form {source : {varname: [solution index, ]}}
+    :attr temptable:
+      a SQL temporary table name or None, if necessary to handle aggregate /
+      sorting for this part of the query
+
+    :attr finaltable:
+      a SQL table name or None, if results for this part of the query should be
+      written into a temporary table (usually shared by multiple PPI)
+
+    :attr sourcesterms:
+      a dictionary {source : {term: set([solution index, ])}} telling for each
+      source which terms are supported for which solutions. A "term" may be
+      either a rql Variable, Constant or Relation node.
     """
     def __init__(self, plan, rqlst, rqlhelper=None):
+        self.plan = plan
+        self.rqlst = rqlst
         self.needsplit = False
         self.temptable = None
         self.finaltable = None
-        self.plan = plan
-        self.rqlst = rqlst
+        # shortcuts
+        self._schema = plan.schema
         self._session = plan.session
+        self._repo = self._session.repo
         self._solutions = rqlst.solutions
         self._solindices = range(len(self._solutions))
-        # source : {var: [solution index, ]}
-        self.sourcesvars = self._sourcesvars = {}
+        self.system_source = self._repo.system_source
+        # source : {term: [solution index, ]}
+        self.sourcesterms = self._sourcesterms = {}
         # source : {relation: set(child variable and constant)}
         self._crossrelations = {}
-        # dictionnary of variables which are linked to each other using a non
-        # final relation which is supported by multiple sources
-        self._linkedvars = {}
-        self._crosslinkedvars = {}
+        # dictionary of variables and constants which are linked to each other
+        # using a non final relation supported by multiple sources (crossed or
+        # not).
+        self._linkedterms = {}
         # processing
-        self._compute_sourcesvars()
-        self._remove_invalid_sources()
+        termssources = self._compute_sourcesterms()
+        self._remove_invalid_sources(termssources)
         self._compute_needsplit()
-        self.sourcesvars = {}
-        for k, v in self._sourcesvars.iteritems():
-            self.sourcesvars[k] = {}
+        # after initialisation, .sourcesterms contains the same thing as
+        # ._sourcesterms though during plan construction, ._sourcesterms will
+        # be modified while .sourcesterms will be kept unmodified
+        self.sourcesterms = {}
+        for k, v in self._sourcesterms.iteritems():
+            self.sourcesterms[k] = {}
             for k2, v2 in v.iteritems():
-                self.sourcesvars[k][k2] = v2.copy()
+                self.sourcesterms[k][k2] = v2.copy()
+        # cleanup linked var
+        for var, linkedrelsinfo in self._linkedterms.iteritems():
+            self._linkedterms[var] = frozenset(x[0] for x in linkedrelsinfo)
+        # map output of a step to input of a following step
         self._inputmaps = {}
+        # record input map conflicts to resolve them on final step generation
+        self._conflicts = []
         if rqlhelper is not None: # else test
             self._insert_identity_variable = rqlhelper._annotator.rewrite_shared_optional
-            
+        if server.DEBUG:
+            print 'sourcesterms:'
+            self._debug_sourcesterms()
+
+    def _debug_sourcesterms(self):
+        for source in self._sourcesterms:
+            print '-', source
+            for term, sols in self._sourcesterms[source].items():
+                print '  -', term, id(term), ':' ,sols
+
     def copy_solutions(self, solindices):
         return [self._solutions[solidx].copy() for solidx in solindices]
-    
+
     @property
     @cached
     def part_sources(self):
-        if self._sourcesvars:
-            return tuple(sorted(self._sourcesvars))
-        return (self._session.repo.system_source,)
-    
+        if self._sourcesterms:
+            return tuple(sorted(self._sourcesterms))
+        return (self.system_source,)
+
     @property
     @cached
     def _sys_source_set(self):
-        return frozenset((self._session.repo.system_source, solindex)
-                         for solindex in self._solindices)        
-       
+        return frozenset((self.system_source, solindex)
+                         for solindex in self._solindices)
+
     @cached
     def _norel_support_set(self, relation):
         """return a set of (source, solindex) where source doesn't support the
         relation
         """
-        return frozenset((source, solidx) for source in self._session.repo.sources
+        return frozenset((source, solidx) for source in self._repo.sources
                          for solidx in self._solindices
-                         if not ((source.support_relation(relation.r_type) and
-                                  not self.crossed_relation(source, relation))
+                         if not ((source.support_relation(relation.r_type))
                                  or relation.r_type in source.dont_cross_relations))
 
-
-    def _compute_sourcesvars(self):
-        """compute for each variable/solution in the rqlst which sources support
-        them
+    def _compute_sourcesterms(self):
+        """compute for each term (variable, rewritten constant, relation) and
+        for each solution in the rqlst which sources support them
         """
-        repo = self._session.repo
-        eschema = repo.schema.eschema
-        sourcesvars = self._sourcesvars
+        repo = self._repo
+        eschema = self._schema.eschema
+        sourcesterms = self._sourcesterms
         # find for each source which variable/solution are supported
         for varname, varobj in self.rqlst.defined_vars.items():
             # if variable has an eid specified, we can get its source directly
@@ -245,22 +308,22 @@
             if varobj.stinfo['uidrels']:
                 vrels = varobj.stinfo['relations'] - varobj.stinfo['uidrels']
                 for rel in varobj.stinfo['uidrels']:
-                    if  rel.neged(strict=True) or rel.operator() != '=':
+                    if rel.neged(strict=True) or rel.operator() != '=':
                         continue
                     for const in rel.children[1].get_nodes(Constant):
                         eid = const.eval(self.plan.args)
                         source = self._session.source_from_eid(eid)
                         if vrels and not any(source.support_relation(r.r_type)
                                              for r in vrels):
-                            self._set_source_for_var(repo.system_source, varobj)
+                            self._set_source_for_term(self.system_source, varobj)
                         else:
-                            self._set_source_for_var(source, varobj)
+                            self._set_source_for_term(source, varobj)
                 continue
             rels = varobj.stinfo['relations']
             if not rels and not varobj.stinfo['typerels']:
                 # (rare) case where the variable has no type specified nor
                 # relation accessed ex. "Any MAX(X)"
-                self._set_source_for_var(repo.system_source, varobj)
+                self._set_source_for_term(self.system_source, varobj)
                 continue
             for i, sol in enumerate(self._solutions):
                 vartype = sol[varname]
@@ -276,64 +339,36 @@
                         if not varobj._q_invariant or \
                                any(imap(source.support_relation,
                                         (r.r_type for r in rels if r.r_type != 'eid'))):
-                            sourcesvars.setdefault(source, {}).setdefault(varobj, set()).add(i)
+                            sourcesterms.setdefault(source, {}).setdefault(varobj, set()).add(i)
                         # if variable is not invariant and is used by a relation
                         # not supported by this source, we'll have to split the
                         # query
                         if not varobj._q_invariant and any(ifilterfalse(
                             source.support_relation, (r.r_type for r in rels))):
-                            self.needsplit = True               
-
-    def _handle_cross_relation(self, rel, relsources, vsources):
-        crossvars = None
-        for source in relsources:
-            if rel.r_type in source.cross_relations:
-                crossvars = set(x.variable for x in rel.get_nodes(VariableRef))
-                crossvars.update(frozenset(x for x in rel.get_nodes(Constant)))
-                assert len(crossvars) == 2
-                ssource = self._session.repo.system_source
-                needsplit = True
-                flag = 0
-                for v in crossvars:
-                    if isinstance(v, Constant):
-                        allsols = set(self._solindices)
-                        try:
-                            self._sourcesvars[ssource][v] = allsols
-                        except KeyError:
-                            self._sourcesvars[ssource] = {v: allsols}
-                    if len(vsources[v]) == 1:
-                        if iter(vsources[v]).next()[0].uri == 'system':
-                            flag = 1
-                            for ov in crossvars:
-                                if ov is not v and (isinstance(ov, Constant) or ov._q_invariant):
-                                    ssset = frozenset((ssource,))
-                                    self._remove_sources(ov, vsources[ov] - ssset)
-                        else:
-                            for ov in crossvars:
-                                if ov is not v and (isinstance(ov, Constant) or ov._q_invariant):
-                                    needsplit = False
-                                    break
-                            else:
-                                continue
-                        if not rel.neged(strict=True):
-                            break
-                else:
-                    self._crossrelations.setdefault(source, {})[rel] = crossvars
-                    if not flag:
-                        self._sourcesvars.setdefault(source, {})[rel] = set(self._solindices)
-                    self._sourcesvars.setdefault(ssource, {})[rel] = set(self._solindices)
-                    if needsplit:
-                        self.needsplit = True
-        return crossvars is None
-        
-    def _remove_invalid_sources(self):
-        """removes invalid sources from `sourcesvars` member according to
-        traversed relations and their properties (which sources support them,
-        can they cross sources, etc...)
-        """
-        repo = self._session.repo
-        rschema = repo.schema.rschema
-        vsources = {}
+                            self.needsplit = True
+        # add source for rewritten constants to sourcesterms
+        for vconsts in self.rqlst.stinfo['rewritten'].itervalues():
+            const = vconsts[0]
+            source = self._session.source_from_eid(const.eval(self.plan.args))
+            if source is self.system_source:
+                for const in vconsts:
+                    self._set_source_for_term(source, const)
+            elif source in self._sourcesterms:
+                source_scopes = frozenset(t.scope for t in self._sourcesterms[source])
+                for const in vconsts:
+                    if const.scope in source_scopes:
+                        self._set_source_for_term(source, const)
+                        # if system source is used, add every rewritten constant
+                        # to its supported terms even when associated entity
+                        # doesn't actually comes from it so we get a changes
+                        # that allequals will return True as expected when
+                        # computing needsplit
+                        # check const is used in a relation restriction
+                        if const.relation() and self.system_source in sourcesterms:
+                            self._set_source_for_term(self.system_source, const)
+        # add source for relations
+        rschema = self._schema.rschema
+        termssources = {}
         for rel in self.rqlst.iget_nodes(Relation):
             # process non final relations only
             # note: don't try to get schema for 'is' relation (not available
@@ -346,7 +381,6 @@
                 # XXX code below don't deal if some source allow relation
                 #     crossing but not another one
                 relsources = repo.rel_type_sources(rel.r_type)
-                crossvars = None
                 if len(relsources) < 2:
                     # filter out sources being there because they have this
                     # relation in their dont_cross_relations attribute
@@ -355,25 +389,54 @@
                     if relsources:
                         # this means the relation is using a variable inlined as
                         # a constant and another unsupported variable, in which
-                        # case we put the relation in sourcesvars
-                        self._sourcesvars.setdefault(relsources[0], {})[rel] = set(self._solindices)
+                        # case we put the relation in sourcesterms
+                        self._sourcesterms.setdefault(relsources[0], {})[rel] = set(self._solindices)
                     continue
                 lhs, rhs = rel.get_variable_parts()
                 lhsv, rhsv = getattr(lhs, 'variable', lhs), getattr(rhs, 'variable', rhs)
-                # update dictionnary of sources supporting lhs and rhs vars
-                if not lhsv in vsources:
-                    vsources[lhsv] = self._term_sources(lhs)
-                if not rhsv in vsources:
-                    vsources[rhsv] = self._term_sources(rhs)
-                if self._handle_cross_relation(rel, relsources, vsources):
-                    self._linkedvars.setdefault(lhsv, set()).add((rhsv, rel))
-                    self._linkedvars.setdefault(rhsv, set()).add((lhsv, rel))
+                # update dictionary of sources supporting lhs and rhs vars
+                if not lhsv in termssources:
+                    termssources[lhsv] = self._term_sources(lhs)
+                if not rhsv in termssources:
+                    termssources[rhsv] = self._term_sources(rhs)
+                self._handle_cross_relation(rel, relsources, termssources)
+                self._linkedterms.setdefault(lhsv, set()).add((rhsv, rel))
+                self._linkedterms.setdefault(rhsv, set()).add((lhsv, rel))
+        return termssources
+
+    def _handle_cross_relation(self, rel, relsources, termssources):
+        for source in relsources:
+            if rel.r_type in source.cross_relations:
+                ssource = self.system_source
+                crossvars = set(x.variable for x in rel.get_nodes(VariableRef))
+                for const in rel.get_nodes(Constant):
+                    if source.uri != 'system' and not const in self._sourcesterms.get(source, ()):
+                        continue
+                    crossvars.add(const)
+                self._crossrelations.setdefault(source, {})[rel] = crossvars
+                if len(crossvars) < 2:
+                    # this means there is a constant in the relation which is
+                    # not supported by the source, so we can stop here
+                    continue
+                self._sourcesterms.setdefault(ssource, {})[rel] = set(self._solindices)
+                for term in crossvars:
+                    if len(termssources[term]) == 1 and iter(termssources[term]).next()[0].uri == 'system':
+                        for ov in crossvars:
+                            if ov is not term and (isinstance(ov, Constant) or ov._q_invariant):
+                                ssset = frozenset((ssource,))
+                                self._remove_sources(ov, termssources[ov] - ssset)
+                        break
                 else:
-                    self._crosslinkedvars.setdefault(lhsv, set()).add((rhsv, rel))
-                    self._crosslinkedvars.setdefault(rhsv, set()).add((lhsv, rel))
-        for term in self._linkedvars:
-            self._remove_sources_until_stable(term, vsources)
-        if len(self._sourcesvars) > 1 and hasattr(self.plan.rqlst, 'main_relations'):
+                    self._sourcesterms.setdefault(source, {})[rel] = set(self._solindices)
+
+    def _remove_invalid_sources(self, termssources):
+        """removes invalid sources from `sourcesterms` member according to
+        traversed relations and their properties (which sources support them,
+        can they cross sources, etc...)
+        """
+        for term in self._linkedterms:
+            self._remove_sources_until_stable(term, termssources)
+        if len(self._sourcesterms) > 1 and hasattr(self.plan.rqlst, 'main_relations'):
             # the querier doesn't annotate write queries, need to do it here
             self.plan.annotate_rqlst()
             # insert/update/delete queries, we may get extra information from
@@ -383,6 +446,8 @@
                                 for etype, vref in self.plan.rqlst.main_variables)
             else:
                 inserted = {}
+            repo = self._repo
+            rschema = self._schema.rschema
             for rel in self.plan.rqlst.main_relations:
                 if not rschema(rel.r_type).is_final():
                     # nothing to do if relation is not supported by multiple sources
@@ -390,121 +455,138 @@
                         continue
                     lhs, rhs = rel.get_variable_parts()
                     try:
-                        lhsv = self._extern_term(lhs, vsources, inserted)
-                        rhsv = self._extern_term(rhs, vsources, inserted)
+                        lhsv = self._extern_term(lhs, termssources, inserted)
+                        rhsv = self._extern_term(rhs, termssources, inserted)
                     except KeyError, ex:
                         continue
-                    norelsup = self._norel_support_set(rel)
-                    self._remove_var_sources(lhsv, norelsup, rhsv, vsources)
-                    self._remove_var_sources(rhsv, norelsup, lhsv, vsources)
-        # cleanup linked var
-        for var, linkedrelsinfo in self._linkedvars.iteritems():
-            self._linkedvars[var] = frozenset(x[0] for x in linkedrelsinfo)
-        # if there are other sources than the system source, consider simplified
-        # variables'source
-        if self._sourcesvars and self._sourcesvars.keys() != [self._session.repo.system_source]:
-            # add source for rewritten constants to sourcesvars
-            for vconsts in self.rqlst.stinfo['rewritten'].itervalues():
-                const = vconsts[0]
-                eid = const.eval(self.plan.args)
-                source = self._session.source_from_eid(eid)
-                if source is self._session.repo.system_source:
-                    for const in vconsts:
-                        self._set_source_for_var(source, const)
-                elif source in self._sourcesvars:
-                    source_scopes = frozenset(v.scope for v in self._sourcesvars[source])
-                    for const in vconsts:
-                        if const.scope in source_scopes:
-                            self._set_source_for_var(source, const)
-                            
-    def _extern_term(self, term, vsources, inserted):
+                    self._remove_term_sources(lhsv, rel, rhsv, termssources)
+                    self._remove_term_sources(rhsv, rel, lhsv, termssources)
+
+    def _extern_term(self, term, termssources, inserted):
         var = term.variable
         if var.stinfo['constnode']:
             termv = var.stinfo['constnode']
-            vsources[termv] = self._term_sources(termv)
+            termssources[termv] = self._term_sources(termv)
         elif var in inserted:
             termv = var
-            source = self._session.repo.locate_etype_source(inserted[var])
-            vsources[termv] = set((source, solindex) for solindex in self._solindices)
+            source = self._repo.locate_etype_source(inserted[var])
+            termssources[termv] = set((source, solindex)
+                                      for solindex in self._solindices)
         else:
             termv = self.rqlst.defined_vars[var.name]
-            if not termv in vsources:
-                vsources[termv] = self._term_sources(termv)
+            if not termv in termssources:
+                termssources[termv] = self._term_sources(termv)
         return termv
-        
-    def _remove_sources_until_stable(self, var, vsources):
-        sourcesvars = self._sourcesvars
-        for ovar, rel in self._linkedvars.get(var, ()):
-            if not var.scope is ovar.scope and rel.scope.neged(strict=True):
+
+    def _remove_sources_until_stable(self, term, termssources):
+        sourcesterms = self._sourcesterms
+        for oterm, rel in self._linkedterms.get(term, ()):
+            if not term.scope is oterm.scope and rel.scope.neged(strict=True):
                 # can't get information from relation inside a NOT exists
-                # where variables don't belong to the same scope
+                # where terms don't belong to the same scope
                 continue
             need_ancestor_scope = False
-            if not (var.scope is rel.scope and ovar.scope is rel.scope):
+            if not (term.scope is rel.scope and oterm.scope is rel.scope):
                 if rel.ored():
                     continue
                 if rel.ored(traverse_scope=True):
                     # if relation has some OR as parent, constraints should only
                     # propagate from parent scope to child scope, nothing else
                     need_ancestor_scope = True
-            relsources = self._session.repo.rel_type_sources(rel.r_type)
+            relsources = self._repo.rel_type_sources(rel.r_type)
             if rel.neged(strict=True) and (
                 len(relsources) < 2
-                or not isinstance(ovar, Variable)
-                or ovar.valuable_references() != 1
-                or any(sourcesvars[source][var] != sourcesvars[source][ovar]
+                or not isinstance(oterm, Variable)
+                or oterm.valuable_references() != 1
+                or any(sourcesterms[source][term] != sourcesterms[source][oterm]
                        for source in relsources
-                       if var in sourcesvars.get(source, ())
-                       and ovar in sourcesvars.get(source, ()))):
-                # neged relation doesn't allow to infer variable sources unless we're
-                # on a multisource relation for a variable only used by this relation
-                # (eg "Any X WHERE NOT X multisource_rel Y" and over is Y), iif 
+                       if term in sourcesterms.get(source, ())
+                       and oterm in sourcesterms.get(source, ()))):
+                # neged relation doesn't allow to infer term sources unless
+                # we're on a multisource relation for a term only used by this
+                # relation (eg "Any X WHERE NOT X multisource_rel Y" and over is
+                # Y)
                 continue
-            norelsup = self._norel_support_set(rel)
-            # compute invalid sources for variables and remove them
-            if not need_ancestor_scope or is_ancestor(var.scope, ovar.scope):
-                self._remove_var_sources(var, norelsup, ovar, vsources)
-            if not need_ancestor_scope or is_ancestor(ovar.scope, var.scope):
-                self._remove_var_sources(ovar, norelsup, var, vsources)
-    
-    def _remove_var_sources(self, var, norelsup, ovar, vsources):
-        """remove invalid sources for var according to ovar's sources and the
-        relation between those two variables. 
+            # compute invalid sources for terms and remove them
+            if not need_ancestor_scope or is_ancestor(term.scope, oterm.scope):
+                self._remove_term_sources(term, rel, oterm, termssources)
+            if not need_ancestor_scope or is_ancestor(oterm.scope, term.scope):
+                self._remove_term_sources(oterm, rel, term, termssources)
+
+    def _remove_term_sources(self, term, rel, oterm, termssources):
+        """remove invalid sources for term according to oterm's sources and the
+        relation between those two terms.
         """
-        varsources = vsources[var]
-        invalid_sources = varsources - (vsources[ovar] | norelsup)
+        norelsup = self._norel_support_set(rel)
+        termsources = termssources[term]
+        invalid_sources = termsources - (termssources[oterm] | norelsup)
+        if invalid_sources and self._repo.can_cross_relation(rel.r_type):
+            invalid_sources -= self._sys_source_set
+            if invalid_sources and isinstance(term, Variable) \
+                   and self._need_ext_source_access(term, rel):
+                # if the term is a not invariant variable, we should filter out
+                # source where the relation is a cross relation from invalid
+                # sources
+                invalid_sources = frozenset((s, solidx) for s, solidx in invalid_sources
+                                            if not (s in self._crossrelations and
+                                                    rel in self._crossrelations[s]))
         if invalid_sources:
-            self._remove_sources(var, invalid_sources)
-            varsources -= invalid_sources
-            self._remove_sources_until_stable(var, vsources)
-        
+            self._remove_sources(term, invalid_sources)
+            termsources -= invalid_sources
+            self._remove_sources_until_stable(term, termssources)
+            if isinstance(oterm, Constant):
+                self._remove_sources(oterm, invalid_sources)
+
     def _compute_needsplit(self):
-        """tell according to sourcesvars if the rqlst has to be splitted for
+        """tell according to sourcesterms if the rqlst has to be splitted for
         execution among multiple sources
-        
+
         the execution has to be split if
         * a source support an entity (non invariant) but doesn't support a
           relation on it
         * a source support an entity which is accessed by an optional relation
-        * there is more than one source and either all sources'supported        
+        * there is more than one source and either all sources'supported
           variable/solutions are not equivalent or multiple variables have to
           be fetched from some source
         """
         # NOTE: < 2 since may be 0 on queries such as Any X WHERE X eid 2
-        if len(self._sourcesvars) < 2: 
+        if len(self._sourcesterms) < 2:
             self.needsplit = False
         elif not self.needsplit:
-            if not allequals(self._sourcesvars.itervalues()):
+            if not allequals(self._sourcesterms.itervalues()):
                 self.needsplit = True
             else:
-                sample = self._sourcesvars.itervalues().next()
-                if len(sample) > 1 and any(v for v in sample
-                                           if not v in self._linkedvars
-                                           and not v in self._crosslinkedvars):
-                    self.needsplit = True
-            
-    def _set_source_for_var(self, source, var):
-        self._sourcesvars.setdefault(source, {})[var] = set(self._solindices)
+                sample = self._sourcesterms.itervalues().next()
+                if len(sample) > 1:
+                    for term in sample:
+                        # need split if unlinked variable
+                        if isinstance(term, Variable) and not term in self._linkedterms:
+                            self.needsplit = True
+                            break
+                    else:
+                        # need split if there are some cross relation on non
+                        # invariant variable or if the variable is used in
+                        # multi-sources relation
+                        if self._crossrelations:
+                            for reldict in self._crossrelations.itervalues():
+                                for rel, terms in reldict.iteritems():
+                                    for term in terms:
+                                        if isinstance(term, Variable) \
+                                               and self._need_ext_source_access(term, rel):
+                                            self.needsplit = True
+                                            return
+
+    @cached
+    def _need_ext_source_access(self, var, rel):
+        if not var._q_invariant:
+            return True
+        if  any(r for x, r in self._linkedterms[var]
+                if not r is rel and self._repo.is_multi_sources_relation(r.r_type)):
+            return True
+        return False
+
+    def _set_source_for_term(self, source, term):
+        self._sourcesterms.setdefault(source, {})[term] = set(self._solindices)
 
     def _term_sources(self, term):
         """returns possible sources for terms `term`"""
@@ -513,31 +595,33 @@
             return set((source, solindex) for solindex in self._solindices)
         else:
             var = getattr(term, 'variable', term)
-            sources = [source for source, varobjs in self.sourcesvars.iteritems()
+            sources = [source for source, varobjs in self.sourcesterms.iteritems()
                        if var in varobjs]
             return set((source, solindex) for source in sources
-                       for solindex in self.sourcesvars[source][var])
+                       for solindex in self.sourcesterms[source][var])
 
-    def _remove_sources(self, var, sources):
-        """removes invalid sources (`sources`) from `sourcesvars`
+    def _remove_sources(self, term, sources):
+        """removes invalid sources (`sources`) from `sourcesterms`
 
         :param sources: the list of sources to remove
-        :param var: the analyzed variable
+        :param term: the analyzed term
         """
-        sourcesvars = self._sourcesvars
+        sourcesterms = self._sourcesterms
         for source, solindex in sources:
             try:
-                sourcesvars[source][var].remove(solindex)
+                sourcesterms[source][term].remove(solindex)
             except KeyError:
-                return # may occur with subquery column alias
-            if not sourcesvars[source][var]:
-                del sourcesvars[source][var]
-                if not sourcesvars[source]:
-                    del sourcesvars[source]
+                import rql.base as rqlb
+                assert isinstance(term, rqlb.BaseNode), repr(term)
+                continue # may occur with subquery column alias
+            if not sourcesterms[source][term]:
+                del sourcesterms[source][term]
+                if not sourcesterms[source]:
+                    del sourcesterms[source]
 
     def crossed_relation(self, source, relation):
         return relation in self._crossrelations.get(source, ())
-    
+
     def part_steps(self):
         """precompute necessary part steps before generating actual rql for
         each step. This is necessary to know if an aggregate step will be
@@ -545,45 +629,97 @@
         """
         steps = []
         select = self.rqlst
-        rschema = self.plan.schema.rschema
+        rschema = self._schema.rschema
         for source in self.part_sources:
-            sourcevars = self._sourcesvars[source]
-            while sourcevars:
-                # take a variable randomly, and all variables supporting the
+            try:
+                sourceterms = self._sourcesterms[source]
+            except KeyError:
+                continue # already proceed
+            while sourceterms:
+                # take a term randomly, and all terms supporting the
                 # same solutions
-                var, solindices = self._choose_var(sourcevars)
+                term, solindices = self._choose_term(sourceterms)
                 if source.uri == 'system':
                     # ensure all variables are available for the latest step
                     # (missing one will be available from temporary tables
                     # of previous steps)
                     scope = select
-                    variables = scope.defined_vars.values() + scope.aliases.values()
-                    sourcevars.clear()
+                    terms = scope.defined_vars.values() + scope.aliases.values()
+                    sourceterms.clear()
+                    sources = [source]
                 else:
-                    scope = var.scope
-                    variables = self._expand_vars(var, source, sourcevars, scope, solindices)
-                    if not sourcevars:
-                        del self._sourcesvars[source]
-                # find which sources support the same variables/solutions
-                sources = self._expand_sources(source, variables, solindices)
-                # suppose this is a final step until the contrary is proven
-                final = scope is select
-                # set of variables which should be additionaly selected when
+                    scope = term.scope
+                    # find which sources support the same term and solutions
+                    sources = self._expand_sources(source, term, solindices)
+                    # no try to get as much terms as possible
+                    terms = self._expand_terms(term, sources, sourceterms,
+                                               scope, solindices)
+                    if len(terms) == 1 and isinstance(terms[0], Constant):
+                        # we can't generate anything interesting with a single
+                        # constant term (will generate an empty "Any" query),
+                        # go to the next iteration directly!
+                        continue
+                    if not sourceterms:
+                         try:
+                             del self._sourcesterms[source]
+                         except KeyError:
+                             # XXX already cleaned
+                             pass
+                # set of terms which should be additionaly selected when
                 # possible
                 needsel = set()
-                # add attribute variables and mark variables which should be
-                # additionaly selected when possible
-                for var in select.defined_vars.itervalues():
-                    if not var in variables:
-                        stinfo = var.stinfo
-                        for ovar, rtype in stinfo['attrvars']:
-                            if ovar in variables:
+                if not self._sourcesterms:
+                    terms += scope.defined_vars.values() + scope.aliases.values()
+                    final = True
+                else:
+                    # suppose this is a final step until the contrary is proven
+                    final = scope is select
+                    # add attribute variables and mark variables which should be
+                    # additionaly selected when possible
+                    for var in select.defined_vars.itervalues():
+                        if not var in terms:
+                            stinfo = var.stinfo
+                            for ovar, rtype in stinfo['attrvars']:
+                                if ovar in terms:
+                                    needsel.add(var.name)
+                                    terms.append(var)
+                                    break
+                            else:
                                 needsel.add(var.name)
-                                variables.append(var)
+                                final = False
+                    # check where all relations are supported by the sources
+                    for rel in scope.iget_nodes(Relation):
+                        if rel.is_types_restriction():
+                            continue
+                        # take care not overwriting the existing "source" identifier
+                        for _source in sources:
+                            if not _source.support_relation(rel.r_type) or (
+                                self.crossed_relation(_source, rel) and not rel in terms):
+                                for vref in rel.iget_nodes(VariableRef):
+                                    needsel.add(vref.name)
+                                final = False
                                 break
                         else:
-                            needsel.add(var.name)
-                            final = False
+                            if not scope is select:
+                                self._exists_relation(rel, terms, needsel)
+                            # if relation is supported by all sources and some of
+                            # its lhs/rhs variable isn't in "terms", and the
+                            # other end *is* in "terms", mark it have to be
+                            # selected
+                            if source.uri != 'system' and not rschema(rel.r_type).is_final():
+                                lhs, rhs = rel.get_variable_parts()
+                                try:
+                                    lhsvar = lhs.variable
+                                except AttributeError:
+                                    lhsvar = lhs
+                                try:
+                                    rhsvar = rhs.variable
+                                except AttributeError:
+                                    rhsvar = rhs
+                                if lhsvar in terms and not rhsvar in terms:
+                                    needsel.add(lhsvar.name)
+                                elif rhsvar in terms and not lhsvar in terms:
+                                    needsel.add(rhsvar.name)
                 if final and source.uri != 'system':
                     # check rewritten constants
                     for vconsts in select.stinfo['rewritten'].itervalues():
@@ -591,62 +727,25 @@
                         eid = const.eval(self.plan.args)
                         _source = self._session.source_from_eid(eid)
                         if len(sources) > 1 or not _source in sources:
-                            # if there is some rewriten constant used by a
-                            # not neged relation while there are some source
-                            # not supporting the associated entity, this step
-                            # can't be final (unless the relation is explicitly
-                            # in `variables`, eg cross relations)
+                            # if there is some rewriten constant used by a not
+                            # neged relation while there are some source not
+                            # supporting the associated entity, this step can't
+                            # be final (unless the relation is explicitly in
+                            # `terms`, eg cross relations)
                             for c in vconsts:
                                 rel = c.relation()
-                                if rel is None or not (rel in variables or rel.neged(strict=True)):
-                                #if rel is not None and rel.r_type == 'identity' and not rel.neged(strict=True):
+                                if rel is None or not (rel in terms or rel.neged(strict=True)):
                                     final = False
                                     break
                             break
-                # check where all relations are supported by the sources
-                for rel in scope.iget_nodes(Relation):
-                    if rel.is_types_restriction():
-                        continue
-                    # take care not overwriting the existing "source" identifier
-                    for _source in sources:
-                        if not _source.support_relation(rel.r_type):
-                            for vref in rel.iget_nodes(VariableRef):
-                                needsel.add(vref.name)
-                            final = False
-                            break
-                        elif self.crossed_relation(_source, rel) and not rel in variables:
-                            final = False
-                            break
-                    else:
-                        if not scope is select:
-                            self._exists_relation(rel, variables, needsel)
-                        # if relation is supported by all sources and some of
-                        # its lhs/rhs variable isn't in "variables", and the
-                        # other end *is* in "variables", mark it have to be
-                        # selected
-                        if source.uri != 'system' and not rschema(rel.r_type).is_final():
-                            lhs, rhs = rel.get_variable_parts()
-                            try:
-                                lhsvar = lhs.variable
-                            except AttributeError:
-                                lhsvar = lhs
-                            try:
-                                rhsvar = rhs.variable
-                            except AttributeError:
-                                rhsvar = rhs
-                            if lhsvar in variables and not rhsvar in variables:
-                                needsel.add(lhsvar.name)
-                            elif rhsvar in variables and not lhsvar in variables:
-                                needsel.add(rhsvar.name)
                 if final:
-                    self._cleanup_sourcesvars(sources, solindices)
-                # XXX rename: variables may contain Relation and Constant nodes...
-                steps.append( (sources, variables, solindices, scope, needsel,
-                               final) )
+                    self._cleanup_sourcesterms(sources, solindices)
+                steps.append((sources, terms, solindices, scope, needsel, final)
+                             )
         return steps
 
-    def _exists_relation(self, rel, variables, needsel):
-        rschema = self.plan.schema.rschema(rel.r_type)
+    def _exists_relation(self, rel, terms, needsel):
+        rschema = self._schema.rschema(rel.r_type)
         lhs, rhs = rel.get_variable_parts()
         try:
             lhsvar, rhsvar = lhs.variable, rhs.variable
@@ -658,141 +757,171 @@
             # variable is refed by an outer scope and should be substituted
             # using an 'identity' relation (else we'll get a conflict of
             # temporary tables)
-            if rhsvar in variables and not lhsvar in variables:
-                self._identity_substitute(rel, lhsvar, variables, needsel)
-            elif lhsvar in variables and not rhsvar in variables:
-                self._identity_substitute(rel, rhsvar, variables, needsel)
+            if rhsvar in terms and not lhsvar in terms:
+                self._identity_substitute(rel, lhsvar, terms, needsel)
+            elif lhsvar in terms and not rhsvar in terms:
+                self._identity_substitute(rel, rhsvar, terms, needsel)
 
-    def _identity_substitute(self, relation, var, variables, needsel):
+    def _identity_substitute(self, relation, var, terms, needsel):
         newvar = self._insert_identity_variable(relation.scope, var)
         if newvar is not None:
             # ensure relation is using '=' operator, else we rely on a
             # sqlgenerator side effect (it won't insert an inequality operator
             # in this case)
-            relation.children[1].operator = '=' 
-            variables.append(newvar)
+            relation.children[1].operator = '='
+            terms.append(newvar)
             needsel.add(newvar.name)
-        
-    def _choose_var(self, sourcevars):
+
+    def _choose_term(self, sourceterms):
+        """pick one term among terms supported by a source, which will be used
+        as a base to generate an execution step
+        """
         secondchoice = None
-        if len(self._sourcesvars) > 1:
+        if len(self._sourcesterms) > 1:
             # priority to variable from subscopes
-            for var in sourcevars:
-                if not var.scope is self.rqlst:
-                    if isinstance(var, Variable):
-                        return var, sourcevars.pop(var)
-                    secondchoice = var
+            for term in sourceterms:
+                if not term.scope is self.rqlst:
+                    if isinstance(term, Variable):
+                        return term, sourceterms.pop(term)
+                    secondchoice = term
         else:
-            # priority to variable outer scope
-            for var in sourcevars:
-                if var.scope is self.rqlst:
-                    if isinstance(var, Variable):
-                        return var, sourcevars.pop(var)
-                    secondchoice = var
+            # priority to variable from outer scope
+            for term in sourceterms:
+                if term.scope is self.rqlst:
+                    if isinstance(term, Variable):
+                        return term, sourceterms.pop(term)
+                    secondchoice = term
         if secondchoice is not None:
-            return secondchoice, sourcevars.pop(secondchoice)
-        # priority to variable
-        for var in sourcevars:
-            if isinstance(var, Variable):
-                return var, sourcevars.pop(var)
-        # whatever
-        var = iter(sourcevars).next()
-        return var, sourcevars.pop(var)
-            
-            
-    def _expand_vars(self, var, source, sourcevars, scope, solindices):
-        variables = [var]
+            return secondchoice, sourceterms.pop(secondchoice)
+        # priority to variable with the less solutions supported and with the
+        # most valuable refs. Add variable name for test predictability
+        variables = sorted([(var, sols) for (var, sols) in sourceterms.items()
+                            if isinstance(var, Variable)],
+                           key=lambda (v, s): (len(s), -v.valuable_references(), v.name))
+        if variables:
+            var = variables[0][0]
+            return var, sourceterms.pop(var)
+        # priority to constant
+        for term in sourceterms:
+            if isinstance(term, Constant):
+                return term, sourceterms.pop(term)
+        # whatever (relation)
+        term = iter(sourceterms).next()
+        return term, sourceterms.pop(term)
+
+    def _expand_sources(self, selected_source, term, solindices):
+        """return all sources supporting given term / solindices"""
+        sources = [selected_source]
+        sourcesterms = self._sourcesterms
+        for source in sourcesterms.keys():
+            if source is selected_source:
+                continue
+            if not (term in sourcesterms[source] and
+                    solindices.issubset(sourcesterms[source][term])):
+                continue
+            sources.append(source)
+            if source.uri != 'system' or not (isinstance(term, Variable) and not term in self._linkedterms):
+                termsolindices = sourcesterms[source][term]
+                termsolindices -= solindices
+                if not termsolindices:
+                    del sourcesterms[source][term]
+                    if not sourcesterms[source]:
+                        del sourcesterms[source]
+        return sources
+
+    def _expand_terms(self, term, sources, sourceterms, scope, solindices):
+        terms = [term]
+        sources = sorted(sources)
+        sourcesterms = self._sourcesterms
         nbunlinked = 1
-        linkedvars = self._linkedvars
-        # variable has to belong to the same scope if there is more
+        linkedterms = self._linkedterms
+        # term has to belong to the same scope if there is more
         # than the system source remaining
-        if len(self._sourcesvars) > 1 and not scope is self.rqlst:
-            candidates = (v for v in sourcevars.keys() if scope is v.scope)
+        if len(sourcesterms) > 1 and not scope is self.rqlst:
+            candidates = (t for t in sourceterms.keys() if scope is t.scope)
         else:
-            candidates = sourcevars #.iterkeys()
-        # we only want one unlinked variable in each generated query
-        candidates = [v for v in candidates
-                      if isinstance(v, Constant) or
-                      (solindices.issubset(sourcevars[v]) and v in linkedvars)]
-        accept_var = lambda x: (isinstance(x, Constant) or any(v for v in variables if v in linkedvars.get(x, ())))
-        source_cross_rels = self._crossrelations.get(source, ())
-        if isinstance(var, Relation) and var in source_cross_rels:
-            cross_vars = source_cross_rels.pop(var)
-            base_accept_var = accept_var
-            accept_var = lambda x: (base_accept_var(x) or x in cross_vars)
-            for refed in cross_vars:
+            candidates = sourceterms #.iterkeys()
+        # we only want one unlinked term in each generated query
+        candidates = [t for t in candidates
+                      if isinstance(t, (Constant, Relation)) or
+                      (solindices.issubset(sourceterms[t]) and t in linkedterms)]
+        cross_rels = {}
+        for source in sources:
+            cross_rels.update(self._crossrelations.get(source, {}))
+        exclude = {}
+        for rel, crossvars in cross_rels.iteritems():
+            vars = [t for t in crossvars if isinstance(t, Variable)]
+            try:
+                exclude[vars[0]] = vars[1]
+                exclude[vars[1]] = vars[0]
+            except IndexError:
+                pass
+        accept_term = lambda x: (not any(s for s in sources if not x in sourcesterms.get(s, ()))
+                                 and any(t for t in terms if t in linkedterms.get(x, ()))
+                                 and not exclude.get(x) in terms)
+        if isinstance(term, Relation) and term in cross_rels:
+            cross_terms = cross_rels.pop(term)
+            base_accept_term = accept_term
+            accept_term = lambda x: (base_accept_term(x) or x in cross_terms)
+            for refed in cross_terms:
                 if not refed in candidates:
-                    candidates.append(refed)
-        else:
-            cross_vars = ()
-        # repeat until no variable can't be added, since addition of a new
-        # variable may permit to another one to be added
+                    terms.append(refed)
+        # repeat until no term can't be added, since addition of a new
+        # term may permit to another one to be added
         modified = True
         while modified and candidates:
             modified = False
-            for var in candidates[:]:
-                if accept_var(var):
-                    variables.append(var)
-                    try:
-                        # constant nodes should be systematically deleted
-                        if isinstance(var, Constant):
-                            del sourcevars[var]
-                        else:
-                            # variable nodes should be deleted once all possible
-                            # solutions indices have been consumed
-                            sourcevars[var] -= solindices
-                            if not sourcevars[var]:
-                                del sourcevars[var]
-                    except KeyError:
-                        assert var in cross_vars
-                    candidates.remove(var)
+            for term in candidates[:]:
+                if isinstance(term, Constant):
+                    relation = term.relation()
+                    if sorted(set(x[0] for x in self._term_sources(term))) != sources:
+                        continue
+                    terms.append(term)
+                    candidates.remove(term)
                     modified = True
-        return variables
-    
-    def _expand_sources(self, selected_source, vars, solindices):
-        sources = [selected_source]
-        sourcesvars = self._sourcesvars
-        for source in sourcesvars:
-            if source is selected_source:
-                continue
-            for var in vars:
-                if not (var in sourcesvars[source] and 
-                        solindices.issubset(sourcesvars[source][var])):
-                    break
-            else:
-                sources.append(source)
-                if source.uri != 'system':
-                    for var in vars:
-                        varsolindices = sourcesvars[source][var]
-                        varsolindices -= solindices
-                        if not varsolindices:
-                            del sourcesvars[source][var]                
-        return sources
-    
-    def _cleanup_sourcesvars(self, sources, solindices):
-        """on final parts, remove solutions so we know they are already processed"""
+                    del sourceterms[term]
+                elif accept_term(term):
+                    terms.append(term)
+                    candidates.remove(term)
+                    modified = True
+                    self._cleanup_sourcesterms(sources, solindices, term)
+        return terms
+
+    def _cleanup_sourcesterms(self, sources, solindices, term=None):
+        """remove solutions so we know they are already processed"""
         for source in sources:
             try:
-                sourcevars = self._sourcesvars[source]
+                sourceterms = self._sourcesterms[source]
             except KeyError:
                 continue
-            for var, varsolindices in sourcevars.items():
-                if isinstance(var, Relation) and self.crossed_relation(source, var):
-                    continue
-                varsolindices -= solindices
-                if not varsolindices:
-                    del sourcevars[var]
-                    
+            if term is None:
+                for term, termsolindices in sourceterms.items():
+                    if isinstance(term, Relation) and self.crossed_relation(source, term):
+                        continue
+                    termsolindices -= solindices
+                    if not termsolindices:
+                        del sourceterms[term]
+            else:
+                try:
+                    sourceterms[term] -= solindices
+                    if not sourceterms[term]:
+                        del sourceterms[term]
+                except KeyError:
+                    pass
+                    #assert term in cross_terms
+            if not sourceterms:
+                del self._sourcesterms[source]
+
     def merge_input_maps(self, allsolindices):
-        """inputmaps is a dictionary with tuple of solution indices as key with an
-        associateed input map as value. This function compute for each solution 
-        its necessary input map and return them grouped
+        """inputmaps is a dictionary with tuple of solution indices as key with
+        an associated input map as value. This function compute for each
+        solution its necessary input map and return them grouped
 
         ex:
         inputmaps = {(0, 1, 2): {'A': 't1.login1', 'U': 't1.C0', 'U.login': 't1.login1'},
                      (1,): {'X': 't2.C0', 'T': 't2.C1'}}
         return : [([1],  {'A': 't1.login1', 'U': 't1.C0', 'U.login': 't1.login1',
-                           'X': 't2.C0', 'T': 't2.C1'}),                   
+                           'X': 't2.C0', 'T': 't2.C1'}),
                   ([0,2], {'A': 't1.login1', 'U': 't1.C0', 'U.login': 't1.login1'})]
         """
         if not self._inputmaps:
@@ -822,26 +951,38 @@
 
     def build_final_part(self, select, solindices, inputmap,  sources,
                          insertedvars):
-        plan = self.plan
-        rqlst = plan.finalize(select, [self._solutions[i] for i in solindices],
-                              insertedvars)
+        solutions = [self._solutions[i] for i in solindices]
+        if self._conflicts:
+            for varname, mappedto in self._conflicts:
+                var = select.defined_vars[varname]
+                newvar = select.make_variable()
+                # XXX should use var.scope but scope hasn't been computed yet
+                select.add_relation(var, 'identity', newvar)
+                for sol in solutions:
+                    sol[newvar.name] = sol[varname]
+                inputmap[newvar.name] = mappedto
+        rqlst = self.plan.finalize(select, solutions, insertedvars)
         if self.temptable is None and self.finaltable is None:
-            return OneFetchStep(plan, rqlst, sources, inputmap=inputmap)
+            return OneFetchStep(self.plan, rqlst, sources, inputmap=inputmap)
         table = self.temptable or self.finaltable
-        return FetchStep(plan, rqlst, sources, table, True, inputmap)
+        return FetchStep(self.plan, rqlst, sources, table, True, inputmap)
 
     def build_non_final_part(self, select, solindices, sources, insertedvars,
                              table):
         """non final step, will have to store results in a temporary table"""
-        plan = self.plan
-        rqlst = plan.finalize(select, [self._solutions[i] for i in solindices],
-                              insertedvars)
-        step = FetchStep(plan, rqlst, sources, table, False)
+        solutions = [self._solutions[i] for i in solindices]
+        rqlst = self.plan.finalize(select, solutions, insertedvars)
+        step = FetchStep(self.plan, rqlst, sources, table, False)
         # update input map for following steps, according to processed solutions
         inputmapkey = tuple(sorted(solindices))
         inputmap = self._inputmaps.setdefault(inputmapkey, {})
+        for varname, mapping in step.outputmap.iteritems():
+            if varname in inputmap and \
+                   not (mapping == inputmap[varname] or
+                        self._schema.eschema(solutions[0][varname]).is_final()):
+                self._conflicts.append((varname, inputmap[varname]))
         inputmap.update(step.outputmap)
-        plan.add_step(step)
+        self.plan.add_step(step)
 
 
 class MSPlanner(SSPlanner):
@@ -849,10 +990,10 @@
 
     decompose the RQL query according to sources'schema
     """
-        
+
     def build_select_plan(self, plan, rqlst):
         """build execution plan for a SELECT RQL query
-               
+
         the rqlst should not be tagged at this point
         """
         if server.DEBUG:
@@ -899,7 +1040,7 @@
                     inputmap[colalias.name] = '%s.C%s' % (temptable, i)
                 ppi.plan.add_step(sstep)
         return inputmap
-    
+
     def _union_plan(self, plan, union, ppis, temptable=None):
         tosplit, cango, allsources = [], {}, set()
         for planinfo in ppis:
@@ -915,7 +1056,7 @@
             byinputmap = {}
             for ppi in cppis:
                 select = ppi.rqlst
-                if sources != (plan.session.repo.system_source,):
+                if sources != (ppi.system_source,):
                     add_types_restriction(self.schema, select)
                 # part plan info for subqueries
                 inputmap = self._ppi_subqueries(ppi)
@@ -957,7 +1098,7 @@
         return steps
 
     # internal methods for multisources decomposition #########################
-    
+
     def split_part(self, ppi, temptable):
         ppi.finaltable = temptable
         plan = ppi.plan
@@ -972,10 +1113,10 @@
             atemptable = None
             selection = select.selection
         ppi.temptable = atemptable
-        vfilter = VariablesFiltererVisitor(self.schema, ppi)
+        vfilter = TermsFiltererVisitor(self.schema, ppi)
         steps = []
-        for sources, variables, solindices, scope, needsel, final in stepdefs:
-            # extract an executable query using only the specified variables
+        for sources, terms, solindices, scope, needsel, final in stepdefs:
+            # extract an executable query using only the specified terms
             if sources[0].uri == 'system':
                 # in this case we have to merge input maps before call to
                 # filter so already processed restriction are correctly
@@ -983,7 +1124,7 @@
                 solsinputmaps = ppi.merge_input_maps(solindices)
                 for solindices, inputmap in solsinputmaps:
                     minrqlst, insertedvars = vfilter.filter(
-                        sources, variables, scope, set(solindices), needsel, final)
+                        sources, terms, scope, set(solindices), needsel, final)
                     if inputmap is None:
                         inputmap = subinputmap
                     else:
@@ -992,10 +1133,10 @@
                                                       sources, insertedvars))
             else:
                 # this is a final part (i.e. retreiving results for the
-                # original query part) if all variable / sources have been
+                # original query part) if all term / sources have been
                 # treated or if this is the last shot for used solutions
                 minrqlst, insertedvars = vfilter.filter(
-                    sources, variables, scope, solindices, needsel, final)
+                    sources, terms, scope, solindices, needsel, final)
                 if final:
                     solsinputmaps = ppi.merge_input_maps(solindices)
                     for solindices, inputmap in solsinputmaps:
@@ -1003,10 +1144,17 @@
                             inputmap = subinputmap
                         else:
                             inputmap.update(subinputmap)
-                        steps.append(ppi.build_final_part(minrqlst, solindices, inputmap,
-                                                  sources, insertedvars))
+                        if inputmap and len(sources) > 1:
+                            sources.remove(ppi.system_source)
+                            steps.append(ppi.build_final_part(minrqlst, solindices, None,
+                                                              sources, insertedvars))
+                            steps.append(ppi.build_final_part(minrqlst, solindices, inputmap,
+                                                              [ppi.system_source], insertedvars))
+                        else:
+                            steps.append(ppi.build_final_part(minrqlst, solindices, inputmap,
+                                                              sources, insertedvars))
                 else:
-                    table = '_T%s%s' % (''.join(sorted(v._ms_table_key() for v in variables)),
+                    table = '_T%s%s' % (''.join(sorted(v._ms_table_key() for v in terms)),
                                         ''.join(sorted(str(i) for i in solindices)))
                     ppi.build_non_final_part(minrqlst, solindices, sources,
                                              insertedvars, table)
@@ -1034,12 +1182,12 @@
             step.set_limit_offset(select.limit, select.offset)
         return step
 
-    
+
 class UnsupportedBranch(Exception):
     pass
 
 
-class VariablesFiltererVisitor(object):
+class TermsFiltererVisitor(object):
     def __init__(self, schema, ppi):
         self.schema = schema
         self.ppi = ppi
@@ -1047,10 +1195,10 @@
         self.hasaggrstep = self.ppi.temptable
         self.extneedsel = frozenset(vref.name for sortterm in ppi.rqlst.orderby
                                     for vref in sortterm.iget_nodes(VariableRef))
-        
-    def _rqlst_accept(self, rqlst, node, newroot, variables, setfunc=None):
+
+    def _rqlst_accept(self, rqlst, node, newroot, terms, setfunc=None):
         try:
-            newrestr, node_ = node.accept(self, newroot, variables[:])
+            newrestr, node_ = node.accept(self, newroot, terms[:])
         except UnsupportedBranch:
             return rqlst
         if setfunc is not None and newrestr is not None:
@@ -1059,18 +1207,18 @@
             rqlst = node.parent
         return rqlst
 
-    def filter(self, sources, variables, rqlst, solindices, needsel, final):
+    def filter(self, sources, terms, rqlst, solindices, needsel, final):
         if server.DEBUG:
-            print 'filter', final and 'final' or '', sources, variables, rqlst, solindices, needsel
+            print 'filter', final and 'final' or '', sources, terms, rqlst, solindices, needsel
         newroot = Select()
         self.sources = sorted(sources)
-        self.variables = variables
+        self.terms = terms
         self.solindices = solindices
         self.final = final
-        # variables which appear in unsupported branches
+        # terms which appear in unsupported branches
         needsel |= self.extneedsel
         self.needsel = needsel
-        # variables which appear in supported branches
+        # terms which appear in supported branches
         self.mayneedsel = set()
         # new inserted variables
         self.insertedvars = []
@@ -1079,36 +1227,36 @@
         self.use_only_defined = False
         self.scopes = {rqlst: newroot}
         if rqlst.where:
-            rqlst = self._rqlst_accept(rqlst, rqlst.where, newroot, variables,
+            rqlst = self._rqlst_accept(rqlst, rqlst.where, newroot, terms,
                                        newroot.set_where)
         if isinstance(rqlst, Select):
             self.use_only_defined = True
             if rqlst.groupby:
                 groupby = []
                 for node in rqlst.groupby:
-                    rqlst = self._rqlst_accept(rqlst, node, newroot, variables,
+                    rqlst = self._rqlst_accept(rqlst, node, newroot, terms,
                                                groupby.append)
                 if groupby:
                     newroot.set_groupby(groupby)
             if rqlst.having:
                 having = []
                 for node in rqlst.having:
-                    rqlst = self._rqlst_accept(rqlst, node, newroot, variables,
+                    rqlst = self._rqlst_accept(rqlst, node, newroot, terms,
                                                having.append)
                 if having:
                     newroot.set_having(having)
             if final and rqlst.orderby and not self.hasaggrstep:
                 orderby = []
                 for node in rqlst.orderby:
-                    rqlst = self._rqlst_accept(rqlst, node, newroot, variables,
+                    rqlst = self._rqlst_accept(rqlst, node, newroot, terms,
                                                orderby.append)
                 if orderby:
                     newroot.set_orderby(orderby)
-            self.process_selection(newroot, variables, rqlst)
+            self.process_selection(newroot, terms, rqlst)
         elif not newroot.where:
-            # no restrictions have been copied, just select variables and add
+            # no restrictions have been copied, just select terms and add
             # type restriction (done later by add_types_restriction)
-            for v in variables:
+            for v in terms:
                 if not isinstance(v, Variable):
                     continue
                 newroot.append_selected(VariableRef(newroot.get_variable(v.name)))
@@ -1155,13 +1303,13 @@
         if server.DEBUG:
             print '--->', newroot
         return newroot, self.insertedvars
-        
-    def visit_and(self, node, newroot, variables):
+
+    def visit_and(self, node, newroot, terms):
         subparts = []
         for i in xrange(len(node.children)):
             child = node.children[i]
             try:
-                newchild, child_ = child.accept(self, newroot, variables)
+                newchild, child_ = child.accept(self, newroot, terms)
                 if not child_ is child:
                     node = child_.parent
                 if newchild is None:
@@ -1180,10 +1328,10 @@
     def _relation_supported(self, relation):
         rtype = relation.r_type
         for source in self.sources:
-            if not source.support_relation(rtype) \
-                   or (rtype in source.cross_relations and not relation in self.variables):#self.ppi.crossed_relation(source, relation):
+            if not source.support_relation(rtype) or (
+                rtype in source.cross_relations and not relation in self.terms):
                 return False
-        if not self.final:
+        if not self.final and not relation in self.terms:
             rschema = self.schema.rschema(relation.r_type)
             if not rschema.is_final():
                 for term in relation.get_nodes((VariableRef, Constant)):
@@ -1192,8 +1340,8 @@
                     if termsources and termsources != self.sources:
                         return False
         return True
-        
-    def visit_relation(self, node, newroot, variables):
+
+    def visit_relation(self, node, newroot, terms):
         if not node.is_types_restriction():
             if node in self.skip and self.solindices.issubset(self.skip[node]):
                 if not self.schema.rschema(node.r_type).is_final():
@@ -1214,12 +1362,15 @@
         # copy a type restriction while the variable is not actually used)
         elif not any(self._relation_supported(rel)
                      for rel in node.children[0].variable.stinfo['relations']):
-            rel, node = self.visit_default(node, newroot, variables)
+            rel, node = self.visit_default(node, newroot, terms)
             return rel, node
         else:
             raise UnsupportedBranch()
         rschema = self.schema.rschema(node.r_type)
-        res = self.visit_default(node, newroot, variables)[0]
+        try:
+            res = self.visit_default(node, newroot, terms)[0]
+        except Exception, ex:
+            raise
         ored = node.ored()
         if rschema.is_final() or rschema.inlined:
             vrefs = node.children[1].get_nodes(VariableRef)
@@ -1227,19 +1378,19 @@
                 if not ored:
                     self.skip.setdefault(node, set()).update(self.solindices)
                 else:
-                    self.mayneedvar.setdefault((node.children[0].name, rschema), []).append( (res, ored) )                    
+                    self.mayneedvar.setdefault((node.children[0].name, rschema), []).append( (res, ored) )
             else:
                 assert len(vrefs) == 1
                 vref = vrefs[0]
                 # XXX check operator ?
                 self.hasvar[(node.children[0].name, rschema)] = vref
-                if self._may_skip_attr_rel(rschema, node, vref, ored, variables, res):
+                if self._may_skip_attr_rel(rschema, node, vref, ored, terms, res):
                     self.skip.setdefault(node, set()).update(self.solindices)
         elif not ored:
             self.skip.setdefault(node, set()).update(self.solindices)
         return res, node
 
-    def _may_skip_attr_rel(self, rschema, rel, vref, ored, variables, res):
+    def _may_skip_attr_rel(self, rschema, rel, vref, ored, terms, res):
         var = vref.variable
         if ored:
             return False
@@ -1247,35 +1398,35 @@
             return False
         if not same_scope(var):
             return False
-        if any(v for v,_ in var.stinfo['attrvars'] if not v.name in variables):
+        if any(v for v, _ in var.stinfo['attrvars'] if not v in terms):
             return False
         return True
-        
-    def visit_exists(self, node, newroot, variables):
+
+    def visit_exists(self, node, newroot, terms):
         newexists = node.__class__()
         self.scopes = {node: newexists}
-        subparts, node = self._visit_children(node, newroot, variables)
+        subparts, node = self._visit_children(node, newroot, terms)
         if not subparts:
             return None, node
         newexists.set_where(subparts[0])
         return newexists, node
-    
-    def visit_not(self, node, newroot, variables):
-        subparts, node = self._visit_children(node, newroot, variables)
+
+    def visit_not(self, node, newroot, terms):
+        subparts, node = self._visit_children(node, newroot, terms)
         if not subparts:
             return None, node
         return copy_node(newroot, node, subparts), node
-    
-    def visit_group(self, node, newroot, variables):
+
+    def visit_group(self, node, newroot, terms):
         if not self.final:
             return None, node
-        return self.visit_default(node, newroot, variables)
-            
-    def visit_variableref(self, node, newroot, variables):
+        return self.visit_default(node, newroot, terms)
+
+    def visit_variableref(self, node, newroot, terms):
         if self.use_only_defined:
             if not node.variable.name in newroot.defined_vars:
                 raise UnsupportedBranch(node.name)
-        elif not node.variable in variables:
+        elif not node.variable in terms:
             raise UnsupportedBranch(node.name)
         self.mayneedsel.add(node.name)
         # set scope so we can insert types restriction properly
@@ -1283,41 +1434,41 @@
         newvar.stinfo['scope'] = self.scopes.get(node.variable.scope, newroot)
         return VariableRef(newvar), node
 
-    def visit_constant(self, node, newroot, variables):
+    def visit_constant(self, node, newroot, terms):
         return copy_node(newroot, node), node
-    
-    def visit_default(self, node, newroot, variables):
-        subparts, node = self._visit_children(node, newroot, variables)
+
+    def visit_default(self, node, newroot, terms):
+        subparts, node = self._visit_children(node, newroot, terms)
         return copy_node(newroot, node, subparts), node
-        
+
     visit_comparison = visit_mathexpression = visit_constant = visit_function = visit_default
     visit_sort = visit_sortterm = visit_default
-    
-    def _visit_children(self, node, newroot, variables):
+
+    def _visit_children(self, node, newroot, terms):
         subparts = []
         for i in xrange(len(node.children)):
             child = node.children[i]
-            newchild, child_ = child.accept(self, newroot, variables)
+            newchild, child_ = child.accept(self, newroot, terms)
             if not child is child_:
                 node = child_.parent
             if newchild is not None:
                 subparts.append(newchild)
         return subparts, node
-    
-    def process_selection(self, newroot, variables, rqlst):
+
+    def process_selection(self, newroot, terms, rqlst):
         if self.final:
             for term in rqlst.selection:
                 newroot.append_selected(term.copy(newroot))
                 for vref in term.get_nodes(VariableRef):
                     self.needsel.add(vref.name)
-            return 
+            return
         for term in rqlst.selection:
             vrefs = term.get_nodes(VariableRef)
             if vrefs:
                 supportedvars = []
                 for vref in vrefs:
                     var = vref.variable
-                    if var in variables:
+                    if var in terms:
                         supportedvars.append(vref)
                         continue
                     else:
@@ -1330,10 +1481,10 @@
                 for vref in supportedvars:
                     if not vref in newroot.get_selected_variables():
                         newroot.append_selected(VariableRef(newroot.get_variable(vref.name)))
-            
-    def add_necessary_selection(self, newroot, variables):
+
+    def add_necessary_selection(self, newroot, terms):
         selected = tuple(newroot.get_selected_variables())
-        for varname in variables:
+        for varname in terms:
             var = newroot.defined_vars[varname]
             for vref in var.references():
                 rel = vref.relation()
--- a/server/mssteps.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/mssteps.py	Thu May 14 12:50:34 2009 +0200
@@ -16,7 +16,7 @@
 from cubicweb.server.ssplanner import (LimitOffsetMixIn, Step, OneFetchStep,
                                     varmap_test_repr, offset_result)
 
-AGGR_TRANSFORMS = {'COUNT':'SUM', 'MIN':'MIN', 'MAX':'MAX', 'SUM': 'SUM'} 
+AGGR_TRANSFORMS = {'COUNT':'SUM', 'MIN':'MIN', 'MAX':'MAX', 'SUM': 'SUM'}
 
 def remove_clauses(union, keepgroup):
     clauses = []
@@ -73,7 +73,7 @@
                         if lhsvar.name in srqlst.defined_vars:
                             key = '%s.%s' % (lhsvar.name, rel.r_type)
                             self.outputmap[key] = self.outputmap[var.name]
-                
+
     def execute(self):
         """execute this step"""
         self.execute_children()
@@ -86,7 +86,7 @@
             source.flying_insert(self.table, plan.session, union, plan.args,
                                  self.inputmap)
         restore_clauses(union, self.keepgroup, clauses)
-            
+
     def mytest_repr(self):
         """return a representation of this step suitable for test"""
         clauses = remove_clauses(self.union, self.keepgroup)
@@ -104,7 +104,7 @@
         finally:
             restore_clauses(self.union, self.keepgroup, clauses)
 
-    
+
 class AggrStep(LimitOffsetMixIn, Step):
     """step consisting in making aggregat from temporary data in the system
     source
@@ -123,7 +123,7 @@
             plan.init_temp_table(outputtable, selection, select.solutions[0])
 
         #self.inputmap = inputmap
-        
+
     def mytest_repr(self):
         """return a representation of this step suitable for test"""
         sel = self.select.selection
@@ -167,7 +167,7 @@
                     clause.append(term.accept(self))
                     # restaure the tree XXX necessary?
                     term.name = orig_name
-                    term.children = orig_children                
+                    term.children = orig_children
                 except KeyError:
                     clause.append(var_name)
             else:
@@ -215,28 +215,28 @@
             self.plan.create_temp_table(self.outputtable)
             sql = 'INSERT INTO %s %s' % (self.outputtable, sql)
         return self.plan.sqlexec(sql, self.plan.args)
-    
+
     def visit_function(self, function):
         """generate SQL name for a function"""
         return '%s(%s)' % (function.name,
                            ','.join(c.accept(self) for c in function.children))
-        
+
     def visit_variableref(self, variableref):
         """get the sql name for a variable reference"""
         try:
             return self.inputmap[variableref.name]
         except KeyError: # XXX duh? explain
             return variableref.variable.name
-        
+
     def visit_constant(self, constant):
         """generate SQL name for a constant"""
         assert constant.type == 'Int'
         return str(constant.value)
-    
+
 
 class UnionStep(LimitOffsetMixIn, Step):
     """union results of child in-memory steps (e.g. OneFetchStep / AggrStep)"""
-        
+
     def execute(self):
         """execute this step"""
         result = []
@@ -258,7 +258,7 @@
                 if len(result) >= olimit:
                     return result[:olimit]
         return result
-        
+
     def mytest_repr(self):
         """return a representation of this step suitable for test"""
         return (self.__class__.__name__, self.limit, self.offset)
@@ -266,7 +266,7 @@
 
 class IntersectStep(UnionStep):
     """return intersection of results of child in-memory steps (e.g. OneFetchStep / AggrStep)"""
-        
+
     def execute(self):
         """execute this step"""
         result = set()
--- a/server/pool.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/pool.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 
 * the rql repository has a limited number of connections pools, each of them
   dealing with a set of connections on each source used by the repository
-  
+
 * operation may be registered by hooks during a transaction, which will  be
   fired when the pool is commited or rollbacked
 
@@ -11,13 +11,13 @@
 
 
 :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
-    
+
 class ConnectionsPool(object):
     """handle connections on a set of sources, at some point associated to a
     user session
@@ -40,7 +40,7 @@
         for source, cnx in self.source_cnxs.values():
             # let exception propagates
             cnx.commit()
-        
+
     def rollback(self):
         """rollback the current transaction for this user"""
         for source, cnx in self.source_cnxs.values():
@@ -64,7 +64,7 @@
                 cnx.close()
             except:
                 continue
-            
+
     # internals ###############################################################
 
     def pool_set(self, session):
@@ -75,7 +75,7 @@
         """pool is being reseted"""
         for source, cnx in self.source_cnxs.values():
             source.pool_reset(cnx)
-        
+
     def __getitem__(self, uri):
         """subscription notation provide access to sources'cursors"""
         try:
@@ -86,7 +86,7 @@
                 # None possible on sources without cursor support such as ldap
                 self._cursors[uri] = cursor
         return cursor
-    
+
     def sources(self):
         """return the source objects handled by this pool"""
         # implementation details of flying insert requires the system source
@@ -97,11 +97,11 @@
                 continue
             yield source
         #return [source_cnx[0] for source_cnx in self.source_cnxs.values()]
-    
+
     def source(self, uid):
         """return the source object with the given uri"""
         return self.source_cnxs[uid][0]
-    
+
     def connection(self, uid):
         """return the connection on the source object with the given uri"""
         return self.source_cnxs[uid][1]
@@ -109,7 +109,7 @@
     def reconnect(self, source):
         """reopen a connection for this source"""
         source.info('trying to reconnect')
-        self.source_cnxs[source.uri] = (source, source.get_connection())        
+        self.source_cnxs[source.uri] = (source, source.get_connection())
         del self._cursors[source.uri]
 
     def check_connections(self):
@@ -133,11 +133,11 @@
       do any heavy computation or raise an exception if the commit can't go.
       You can add some new operation during this phase but their precommit
       event won't be triggered
-      
+
     commit:
       the pool is preparing to commit. You should avoid to do to expensive
       stuff or something that may cause an exception in this event
-      
+
     revertcommit:
       if an operation failed while commited, this event is triggered for
       all operations which had their commit event already to let them
@@ -153,7 +153,7 @@
     order of operations may be important, and is controlled according to:
     * operation's class
     """
-    
+
     def __init__(self, session, **kwargs):
         self.session = session
         self.user = session.user
@@ -165,10 +165,10 @@
         # execution information
         self.processed = None # 'precommit', 'commit'
         self.failed = False
-        
+
     def register(self, session):
         session.add_operation(self, self.insert_index())
-        
+
     def insert_index(self):
         """return the index of  the lastest instance which is not a
         LateOperation instance
@@ -177,17 +177,17 @@
             if isinstance(op, (LateOperation, SingleLastOperation)):
                 return i
         return None
-    
+
     def handle_event(self, event):
         """delegate event handling to the opertaion"""
         getattr(self, event)()
 
     def precommit_event(self):
         """the observed connections pool is preparing a commit"""
-    
+
     def revertprecommit_event(self):
         """an error went when pre-commiting this operation or a later one
-        
+
         should revert pre-commit's changes but take care, they may have not
         been all considered if it's this operation which failed
         """
@@ -195,17 +195,17 @@
     def commit_event(self):
         """the observed connections pool is commiting"""
         raise NotImplementedError()
-    
+
     def revertcommit_event(self):
         """an error went when commiting this operation or a later one
-        
+
         should revert commit's changes but take care, they may have not
         been all considered if it's this operation which failed
         """
-    
+
     def rollback_event(self):
         """the observed connections pool has been rollbacked
-        
+
         do nothing by default, the operation will just be removed from the pool
         operation list
         """
@@ -226,7 +226,7 @@
 class LateOperation(Operation):
     """special operation which should be called after all possible (ie non late)
     operations
-    """    
+    """
     def insert_index(self):
         """return the index of  the lastest instance which is not a
         SingleLastOperation instance
@@ -238,7 +238,7 @@
 
 
 class SingleOperation(Operation):
-    """special operation which should be called once"""    
+    """special operation which should be called once"""
     def register(self, session):
         """override register to handle cases where this operation has already
         been added
@@ -251,7 +251,7 @@
             equivalent = None
         session.add_operation(self, self.insert_index())
         return equivalent
-    
+
     def equivalent_index(self, operations):
         """return the index of the equivalent operation if any"""
         equivalents = [i for i, op in enumerate(operations)
@@ -264,7 +264,7 @@
 class SingleLastOperation(SingleOperation):
     """special operation which should be called once and after all other
     operations
-    """    
+    """
     def insert_index(self):
         return None
 
--- a/server/querier.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/querier.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 security checking and data aggregation.
 
 :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,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
@@ -85,7 +84,7 @@
             #assert len(erqlexprs) == 1
             localchecks[varname] = tuple(erqlexprs)
     return localchecks
-                    
+
 def noinvariant_vars(restricted, select, nbtrees):
     # a variable can actually be invariant if it has not been restricted for
     # security reason or if security assertion hasn't modified the possible
@@ -115,12 +114,12 @@
                 colalias = newselect.get_variable(vref.name, len(aliases))
                 aliases.append(VariableRef(colalias))
                 selected.add(vref.name)
-                
+
 # Plans #######################################################################
 
 class ExecutionPlan(object):
     """the execution model of a rql query, composed of querier steps"""
-    
+
     def __init__(self, querier, rqlst, args, session):
         # original rql syntax tree
         self.rqlst = rqlst
@@ -138,11 +137,11 @@
         self.schema = querier.schema
         self.rqlhelper = querier._rqlhelper
         self.sqlannotate = querier.sqlgen_annotate
-        
+
     def annotate_rqlst(self):
         if not self.rqlst.annotated:
             self.rqlhelper.annotate(self.rqlst)
-            
+
     def add_step(self, step):
         """add a step to the plan"""
         self.steps.append(step)
@@ -150,10 +149,10 @@
     def clean(self):
         """remove temporary tables"""
         self.syssource.clean_temp_data(self.session, self.temp_tables)
-        
+
     def sqlexec(self, sql, args=None):
         return self.syssource.sqlexec(self.session, sql, args)
-            
+
     def execute(self):
         """execute a plan and return resulting rows"""
         try:
@@ -163,7 +162,7 @@
             return result
         finally:
             self.clean()
-            
+
     def init_temp_table(self, table, selected, sol):
         """initialize sql schema and variable map for a temporary table which
         will be used to store result for the given rqlst
@@ -176,17 +175,17 @@
                                                                  table)
             self.temp_tables[table] = [outputmap, sqlschema, False]
         return outputmap
-        
+
     def create_temp_table(self, table):
         """create a temporary table to store result for the given rqlst"""
         if not self.temp_tables[table][-1]:
             sqlschema = self.temp_tables[table][1]
             self.syssource.create_temp_table(self.session, table, sqlschema)
             self.temp_tables[table][-1] = True
-        
+
     def preprocess(self, union, security=True):
         """insert security when necessary then annotate rql st for sql generation
-        
+
         return rqlst to actually execute
         """
         #if server.DEBUG:
@@ -280,7 +279,7 @@
         are removed, else if the user may read it (eg if an rql expression is
         defined for the "read" permission of the related type), the local checks
         dict for the solution is updated
-        
+
         return a dict with entries for each different local check necessary,
         with associated solutions as value. A local check is defined by a list
         of 2-uple, with variable name as first item and the necessary rql
@@ -347,11 +346,11 @@
         self.rqlhelper.annotate(rqlst)
         self.preprocess(rqlst, security=False)
         return rqlst
-       
+
 class InsertPlan(ExecutionPlan):
     """an execution model specific to the INSERT rql query
     """
-    
+
     def __init__(self, querier, rqlst, args, session):
         ExecutionPlan.__init__(self, querier, rqlst, args, session)
         # save originaly selected variable, we may modify this
@@ -388,7 +387,7 @@
                     value = rhs.eval(self.args)
                     eschema = edef.e_schema
                     attrtype = eschema.subject_relation(rtype).objects(eschema)[0]
-                    if attrtype == 'Password' and isinstance(value, unicode): 
+                    if attrtype == 'Password' and isinstance(value, unicode):
                         value = value.encode('UTF8')
                     edef[rtype] = value
                 elif to_build.has_key(str(rhs)):
@@ -398,12 +397,12 @@
                     to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
         return to_select
 
-        
+
     def add_entity_def(self, edef):
         """add an entity definition to build"""
         edef.querier_pending_relations = {}
         self.e_defs[-1].append(edef)
-        
+
     def add_relation_def(self, rdef):
         """add an relation definition to build"""
         self.r_defs.append(rdef)
@@ -411,11 +410,11 @@
             self._r_subj_index.setdefault(rdef[0], []).append(rdef)
         if not isinstance(rdef[2], int):
             self._r_obj_index.setdefault(rdef[2], []).append(rdef)
-        
+
     def substitute_entity_def(self, edef, edefs):
         """substitute an incomplete entity definition by a list of complete
         equivalents
-        
+
         e.g. on queries such as ::
           INSERT Personne X, Societe Y: X nom N, Y nom 'toto', X travaille Y
           WHERE U login 'admin', U login N
@@ -456,7 +455,7 @@
                     for edef in edefs:
                         result.append( (exp_rdef[0], exp_rdef[1], edef) )
                 self._expanded_r_defs[rdef] = result
-        
+
     def _expanded(self, rdef):
         """return expanded value for the given relation definition"""
         try:
@@ -464,7 +463,7 @@
         except KeyError:
             self.r_defs.remove(rdef)
             return [rdef]
-        
+
     def relation_defs(self):
         """return the list for relation definitions to insert"""
         for rdefs in self._expanded_r_defs.values():
@@ -472,11 +471,11 @@
                 yield rdef
         for rdef in self.r_defs:
             yield rdef
-            
+
     def insert_entity_defs(self):
         """return eids of inserted entities in a suitable form for the resulting
         result set, e.g.:
-        
+
         e.g. on queries such as ::
           INSERT Personne X, Societe Y: X nom N, Y nom 'toto', X travaille Y
           WHERE U login 'admin', U login N
@@ -491,7 +490,7 @@
             results.append([repo.glob_add_entity(session, edef)
                             for edef in row])
         return results
-        
+
     def insert_relation_defs(self):
         session = self.session
         repo = session.repo
@@ -515,18 +514,18 @@
 
 class QuerierHelper(object):
     """helper class to execute rql queries, putting all things together"""
-    
+
     def __init__(self, repo, schema):
         # system info helper
         self._repo = repo
         # application schema
         self.set_schema(schema)
-        
+
     def set_schema(self, schema):
         self.schema = schema
         # rql parsing / analysing helper
         self._rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
-                                                               'has_text': 'fti'})        
+                                                               'has_text': 'fti'})
         self._rql_cache = Cache(self._repo.config['rql-cache-size'])
         self.cache_hit, self.cache_miss = 0, 0
         # rql planner
@@ -536,11 +535,11 @@
             from cubicweb.server.ssplanner import SSPlanner
             self._planner = SSPlanner(schema, self._rqlhelper)
         else:
-            from cubicweb.server.msplanner import MSPlanner            
+            from cubicweb.server.msplanner import MSPlanner
             self._planner = MSPlanner(schema, self._rqlhelper)
         # sql generation annotator
         self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
-        
+
     def parse(self, rql, annotate=False):
         """return a rql syntax tree for the given rql"""
         try:
@@ -560,7 +559,7 @@
         if rqlst.TYPE == 'insert':
             return InsertPlan(self, rqlst, args, session)
         return ExecutionPlan(self, rqlst, args, session)
-        
+
     def execute(self, session, rql, args=None, eid_key=None, build_descr=True):
         """execute a rql query, return resulting rows and their description in
         a `ResultSet` object
@@ -579,7 +578,7 @@
 
         on INSERT queries, there will be on row with the eid of each inserted
         entity
-        
+
         result for DELETE and SET queries is undefined yet
 
         to maximize the rql parsing/analyzing cache performance, you should
--- a/server/repository.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/repository.py	Thu May 14 12:50:34 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
@@ -61,7 +60,7 @@
         remove inserted eid from repository type/source cache
         """
         self.repo.clear_caches(self.session.query_data('pendingeids', ()))
-        
+
     def rollback_event(self):
         """the observed connections pool has been rollbacked,
         remove inserted eid from repository type/source cache
@@ -85,7 +84,7 @@
         session.repo.system_source.fti_unindex_entity(session, entity.eid)
         for container in entity.fti_containers():
             session.repo.index_entity(session, container)
-            
+
     def commit_event(self):
         pass
 
@@ -113,20 +112,22 @@
     if card[0] in '1?':
         rschema = session.repo.schema.rschema(rtype)
         if not rschema.inlined:
-            session.unsafe_execute('DELETE X %s Y WHERE X eid %%(x)s, NOT Y eid %%(y)s' % rtype,
-                                   {'x': eidfrom, 'y': eidto}, 'x')
+            session.unsafe_execute(
+                'DELETE X %s Y WHERE X eid %%(x)s, NOT Y eid %%(y)s' % rtype,
+                {'x': eidfrom, 'y': eidto}, 'x')
     if card[1] in '1?':
-        session.unsafe_execute('DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype,
-                               {'x': eidfrom, 'y': eidto}, 'y')
+        session.unsafe_execute(
+            'DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype,
+            {'x': eidfrom, 'y': eidto}, 'y')
 
-    
+
 class Repository(object):
     """a repository provides access to a set of persistent storages for
     entities and relations
 
     XXX protect pyro access
     """
-    
+
     def __init__(self, config, vreg=None, debug=False):
         self.config = config
         if vreg is None:
@@ -144,6 +145,8 @@
         self.schema = CubicWebSchema(config.appid)
         # querier helper, need to be created after sources initialization
         self.querier = QuerierHelper(self, self.schema)
+        # should we reindex in changes?
+        self.do_fti = not config['delay-full-text-indexation']
         # sources
         self.sources = []
         self.sources_by_uri = {}
@@ -152,7 +155,7 @@
         for uri, source_config in config.sources().items():
             if uri == 'admin':
                 # not an actual source
-                continue 
+                continue
             source = self.get_source(uri, source_config)
             self.sources_by_uri[uri] = source
             self.sources.append(source)
@@ -177,18 +180,21 @@
             self.warning("set fs application'schema as bootstrap schema")
             config.bootstrap_cubes()
             self.set_bootstrap_schema(self.config.load_schema())
-            # need to load the Any and EUser entity types
+            # need to load the Any and CWUser entity types
             self.vreg.schema = self.schema
             etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
-            self.vreg.load_file(etdirectory, '__init__.py')
-            self.vreg.load_file(etdirectory, 'authobjs.py')
+            self.vreg.init_registration([etdirectory])
+            self.vreg.load_file(join(etdirectory, '__init__.py'),
+                                'cubicweb.entities.__init__')
+            self.vreg.load_file(join(etdirectory, 'authobjs.py'),
+                                'cubicweb.entities.authobjs')
         else:
             # test start: use the file system schema (quicker)
             self.warning("set fs application'schema")
             config.bootstrap_cubes()
             self.set_schema(self.config.load_schema())
         if not config.creating:
-            if 'EProperty' in self.schema:
+            if 'CWProperty' in self.schema:
                 self.vreg.init_properties(self.properties())
             # call source's init method to complete their initialisation if
             # needed (for instance looking for persistent configuration using an
@@ -208,16 +214,16 @@
                 source.init_creating()
         # close initialization pool and reopen fresh ones for proper
         # initialization now that we know cubes
-        self._get_pool().close(True) 
+        self._get_pool().close(True)
         for i in xrange(config['connections-pool-size']):
             self._available_pools.put_nowait(ConnectionsPool(self.sources))
-     
+
     # internals ###############################################################
 
     def get_source(self, uri, source_config):
         source_config['uri'] = uri
         return get_source(source_config, self.schema, self)
-        
+
     def set_schema(self, schema, resetvreg=True):
         schema.rebuild_infered_relations()
         self.info('set schema %s %#x', schema.name, id(schema))
@@ -251,11 +257,13 @@
             except BadSchemaDefinition:
                 raise
             except Exception, ex:
-                raise Exception('Is the database initialised ? (cause: %s)' % 
+                import traceback
+                traceback.print_exc()
+                raise Exception('Is the database initialised ? (cause: %s)' %
                                 (ex.args and ex.args[0].strip() or 'unknown')), \
                                 None, sys.exc_info()[-1]
             self.info('set the actual schema')
-            # XXX have to do this since EProperty isn't in the bootstrap schema
+            # XXX have to do this since CWProperty isn't in the bootstrap schema
             #     it'll be redone in set_schema
             self.set_bootstrap_schema(appschema)
             # 2.49 migration
@@ -263,13 +271,13 @@
                 session.set_pool()
                 if not 'template' in file(join(self.config.apphome, 'vc.conf')).read():
                     # remaning from cubicweb < 2.38...
-                    session.execute('DELETE EProperty X WHERE X pkey "system.version.template"')
+                    session.execute('DELETE CWProperty X WHERE X pkey "system.version.template"')
                     session.commit()
         finally:
             session.close()
         self.config.init_cubes(self.get_cubes())
         self.set_schema(appschema)
-        
+
     def set_bootstrap_schema(self, schema):
         """disable hooks when setting a bootstrap schema, but restore
         the configuration for the next time
@@ -287,7 +295,7 @@
         config.schema_hooks = True
         config.notification_hooks = True
         config.application_hooks = True
-            
+
     def start_looping_tasks(self):
         assert isinstance(self._looping_tasks, list), 'already started'
         for i, (interval, func) in enumerate(self._looping_tasks):
@@ -300,7 +308,7 @@
 
     def looping_task(self, interval, func):
         """register a function to be called every `interval` seconds.
-        
+
         looping tasks can only be registered during repository initialization,
         once done this method will fail.
         """
@@ -313,7 +321,7 @@
         """start function in a separated thread"""
         t = RepoThread(func, self._running_threads)
         t.start()
-        
+
     #@locked
     def _get_pool(self):
         try:
@@ -324,7 +332,7 @@
                             'connections) or to much load on the server (in '
                             'which case you can try to set a bigger '
                             'connections pools size)')
-        
+
     def _free_pool(self, pool):
         pool.rollback()
         self._available_pools.put_nowait(pool)
@@ -374,13 +382,13 @@
                       ((hits + misses) * 100) / (hits + misses + nocache))
         except ZeroDivisionError:
             pass
-        
+
     def authenticate_user(self, session, login, password):
         """validate login / password, raise AuthenticationError on failure
-        return associated EUser instance on success
+        return associated CWUser instance on success
         """
         for source in self.sources:
-            if source.support_entity('EUser'):
+            if source.support_entity('CWUser'):
                 try:
                     eid = source.authenticate(session, login, password)
                     break
@@ -395,19 +403,21 @@
         return euser
 
     def _build_user(self, session, eid):
-        cls = self.vreg.etype_class('EUser')
+        """return a CWUser entity for user with the given eid"""
+        cls = self.vreg.etype_class('CWUser')
         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
         euser.properties
         return euser
-        
+
     # public (dbapi) interface ################################################
-            
+
     def get_schema(self):
         """return the application schema. This is a public method, not
         requiring a session id
@@ -439,7 +449,7 @@
         session = self.internal_session()
         try:
             for pk, version in session.execute(
-                'Any K,V WHERE P is EProperty, P value V, P pkey K, '
+                'Any K,V WHERE P is CWProperty, P value V, P pkey K, '
                 'P pkey ~="system.version.%"', build_descr=False):
                 cube = pk.split('.')[-1]
                 # XXX cubicweb migration
@@ -459,7 +469,7 @@
         finally:
             session.close()
         return vcconf
-    
+
     @cached
     def source_defs(self):
         sources = self.config.sources().copy()
@@ -476,23 +486,28 @@
         """return a result set containing system wide properties"""
         session = self.internal_session()
         try:
-            return session.execute('Any K,V WHERE P is EProperty,'
+            return session.execute('Any K,V WHERE P is CWProperty,'
                                    'P pkey K, P value V, NOT P for_user U',
                                    build_descr=False)
         finally:
             session.close()
 
-    def register_user(self, login, password, **kwargs):
+    def register_user(self, login, password, email=None, **kwargs):
         """check a user with the given login exists, if not create it with the
         given password. This method is designed to be used for anonymous
         registration on public web site.
         """
+        # XXX should not be called from web interface
         session = self.internal_session()
+        # for consistency, keep same error as unique check hook (although not required)
+        errmsg = session._('the value "%s" is already used, use another one')
         try:
-            if session.execute('EUser X WHERE X login %(login)s', {'login': login}):
-                return False
+            if (session.execute('CWUser X WHERE X login %(login)s', {'login': login})
+                or session.execute('CWUser X WHERE X use_email C, C address %(login)s',
+                                   {'login': login})):
+                raise ValidationError(None, {'login': errmsg % login})
             # we have to create the user
-            user = self.vreg.etype_class('EUser')(session, None)
+            user = self.vreg.etype_class('CWUser')(session, None)
             if isinstance(password, unicode):
                 # password should *always* be utf8 encoded
                 password = password.encode('UTF8')
@@ -502,17 +517,23 @@
             self.glob_add_entity(session, user)
             session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"',
                             {'x': user.eid})
+            if email or '@' in login:
+                d = {'login': login, 'email': email or login}
+                if session.execute('EmailAddress X WHERE X address %(email)s', d):
+                    raise ValidationError(None, {'address': errmsg % d['email']})
+                session.execute('INSERT EmailAddress X: X address %(email)s, '
+                                'U primary_email X, U use_email X WHERE U login %(login)s', d)
             session.commit()
         finally:
             session.close()
         return True
-        
+
     def connect(self, login, password, cnxprops=None):
         """open a connection for a given user
 
         base_url may be needed to send mails
         cnxtype indicate if this is a pyro connection or a in-memory connection
-        
+
         raise `AuthenticationError` if the authentication failed
         raise `ConnectionError` if we can't open a connection
         """
@@ -564,7 +585,7 @@
                 raise
         finally:
             session.reset_pool()
-    
+
     def describe(self, sessionid, eid):
         """return a tuple (type, source, extid) for the entity with id <eid>"""
         session = self._get_session(sessionid, setpool=True)
@@ -598,12 +619,12 @@
         self.debug('begin commit for session %s', sessionid)
         try:
             self._get_session(sessionid, setpool=True).commit()
-        except (ValidationError, Unauthorized): 
+        except (ValidationError, Unauthorized):
             raise
         except:
             self.exception('unexpected error')
             raise
-        
+
     def rollback(self, sessionid):
         """commit transaction for the session with the given id"""
         self.debug('begin rollback for session %s', sessionid)
@@ -625,7 +646,7 @@
         session.close()
         del self._sessions[sessionid]
         self.info('closed session %s for user %s', sessionid, session.user.login)
-    
+
     def user_info(self, sessionid, props=None):
         """this method should be used by client to:
         * check session id validity
@@ -639,9 +660,9 @@
                 session.change_property(prop, value)
         user = session.user
         return user.eid, user.login, user.groups, user.properties
-            
+
     # public (inter-repository) interface #####################################
-    
+
     def entities_modified_since(self, etypes, mtime):
         """function designed to be called from an external repository which
         is using this one as a rql source for synchronization, and return a
@@ -654,7 +675,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)
@@ -663,7 +684,7 @@
             session.close()
 
     # session handling ########################################################
-        
+
     def close_sessions(self):
         """close every opened sessions"""
         for sessionid in self._sessions.keys():
@@ -685,7 +706,7 @@
                 self.close(session.id)
                 nbclosed += 1
         return nbclosed
-    
+
     def internal_session(self, cnxprops=None):
         """return a dbapi like connection/cursor using internal user which
         have every rights on the repository. You'll *have to* commit/rollback
@@ -696,7 +717,7 @@
         session = InternalSession(self, cnxprops)
         session.set_pool()
         return session
-            
+
     def _get_session(self, sessionid, setpool=False):
         """return the user associated to the given session identifier"""
         try:
@@ -711,7 +732,7 @@
     # * correspondance between eid and (type, source)
     # * correspondance between eid and local id (i.e. specific to a given source)
     # * searchable text indexes
-    
+
     def type_and_source_from_eid(self, eid, session=None):
         """return a tuple (type, source, extid) for the entity with id <eid>"""
         try:
@@ -751,15 +772,15 @@
             rqlcache.pop('Any X WHERE X eid %s' % eid, None)
             for source in self.sources:
                 source.clear_eid_cache(eid, etype)
-                
+
     def type_from_eid(self, eid, session=None):
         """return the type of the entity with id <eid>"""
         return self.type_and_source_from_eid(eid, session)[0]
-    
+
     def source_from_eid(self, eid, session=None):
         """return the source for the given entity's eid"""
         return self.sources_by_uri[self.type_and_source_from_eid(eid, session)[1]]
-        
+
     def eid2extid(self, source, eid, session=None):
         """get local id from an eid"""
         etype, uri, extid = self.type_and_source_from_eid(eid, session)
@@ -768,7 +789,8 @@
             raise UnknownEid(eid)
         return extid
 
-    def extid2eid(self, source, lid, etype, session=None, insert=True):
+    def extid2eid(self, source, lid, etype, session=None, insert=True,
+                  recreate=False):
         """get eid from a local id. An eid is attributed if no record is found"""
         cachekey = (str(lid), source.uri)
         try:
@@ -783,6 +805,15 @@
         if eid is not None:
             self._extid_cache[cachekey] = eid
             self._type_source_cache[eid] = (etype, source.uri, lid)
+            if recreate:
+                entity = source.before_entity_insertion(session, lid, etype, eid)
+                entity._cw_recreating = True
+                if source.should_call_hooks:
+                    self.hm.call_hooks('before_add_entity', etype, session, entity)
+                # XXX add fti op ?
+                source.after_entity_insertion(session, lid, entity)
+                if source.should_call_hooks:
+                    self.hm.call_hooks('after_add_entity', etype, session, entity)
             if reset_pool:
                 session.reset_pool()
             return eid
@@ -818,7 +849,7 @@
         except:
             session.rollback(reset_pool)
             raise
-        
+
     def add_info(self, session, entity, source, extid=None, complete=True):
         """add type and source info for an eid into the system table,
         and index the entity with the full text index
@@ -829,13 +860,14 @@
             entity.complete(entity.e_schema.indexable_attributes())
         session.add_query_data('neweids', entity.eid)
         # now we can update the full text index
-        FTIndexEntityOp(session, entity=entity)
+        if self.do_fti:
+            FTIndexEntityOp(session, entity=entity)
         CleanupEidTypeCacheOp(session)
-        
+
     def delete_info(self, session, eid):
         self._prepare_delete_info(session, eid)
         self._delete_info(session, eid)
-        
+
     def _prepare_delete_info(self, session, eid):
         """prepare the repository for deletion of an entity:
         * update the fti
@@ -846,7 +878,7 @@
         pending = session.query_data('pendingeids', set(), setdefault=True)
         pending.add(eid)
         CleanupEidTypeCacheOp(session)
-        
+
     def _delete_info(self, session, eid):
         """delete system information on deletion of an entity:
         * delete all relations on this entity
@@ -855,7 +887,7 @@
         etype, uri, extid = self.type_and_source_from_eid(eid, session)
         self._clear_eid_relations(session, etype, eid)
         self.system_source.delete_info(session, eid, etype, uri, extid)
-        
+
     def _clear_eid_relations(self, session, etype, eid):
         """when a entity is deleted, build and execute rql query to delete all
         its relations
@@ -886,7 +918,7 @@
             return
         alreadydone.add(entity.eid)
         self.system_source.fti_index_entity(session, entity)
-        
+
     def locate_relation_source(self, session, subject, rtype, object):
         subjsource = self.source_from_eid(subject, session)
         objsource = self.source_from_eid(object, session)
@@ -897,22 +929,17 @@
         else:
             source = subjsource
         return source
-    
-    @cached
-    def rel_type_sources(self, rtype):
-        return [source for source in self.sources
-                if source.support_relation(rtype) or rtype in source.dont_cross_relations]
-    
+
     def locate_etype_source(self, etype):
         for source in self.sources:
             if source.support_entity(etype, 1):
                 return source
         else:
             raise ETypeNotSupportedBySources(etype)
-        
+
     def glob_add_entity(self, session, entity):
         """add an entity to the repository
-        
+
         the entity eid should originaly be None and a unique eid is assigned to
         the entity instance
         """
@@ -955,7 +982,7 @@
                 self.hm.call_hooks('after_add_relation', attr, session,
                                     entity.eid, attr, value)
         return entity.eid
-        
+
     def glob_update_entity(self, session, entity):
         """replace an entity in the repository
         the type and the eid of an entity must not be changed
@@ -995,7 +1022,7 @@
                                     entity)
         source.update_entity(session, entity)
         if not only_inline_rels:
-            if need_fti_update:
+            if need_fti_update and self.do_fti:
                 # reindex the entity only if this query is updating at least
                 # one indexable attribute
                 FTIndexEntityOp(session, entity=entity)
@@ -1025,7 +1052,7 @@
         if source.should_call_hooks:
             self.hm.call_hooks('after_delete_entity', etype, session, eid)
         # don't clear cache here this is done in a hook on commit
-        
+
     def glob_add_relation(self, session, subject, rtype, object):
         """add a relation to the repository"""
         assert subject is not None
@@ -1063,7 +1090,7 @@
 
 
     # pyro handling ###########################################################
-    
+
     def pyro_register(self, host=''):
         """register the repository as a pyro object"""
         from Pyro import core
@@ -1082,7 +1109,7 @@
         self.info(msg, nsgroup, nsid)
         self.pyro_registered = True
         return daemon
-    
+
     def pyro_nameserver(self, host=None, group=None):
         """locate and bind the the name server to the daemon"""
         from Pyro import naming, errors
@@ -1096,6 +1123,26 @@
                 pass
         return nameserver
 
+    # multi-sources planner helpers ###########################################
+
+    @cached
+    def rel_type_sources(self, rtype):
+        return [source for source in self.sources
+                if source.support_relation(rtype)
+                or rtype in source.dont_cross_relations]
+
+    @cached
+    def can_cross_relation(self, rtype):
+        return [source for source in self.sources
+                if source.support_relation(rtype)
+                and rtype in source.cross_relations]
+
+    @cached
+    def is_multi_sources_relation(self, rtype):
+        return any(source for source in self.sources
+                   if not source is self.system_source
+                   and source.support_relation(rtype))
+
 
 def pyro_unregister(config):
     """unregister the repository from the pyro name server"""
--- a/server/rqlannotation.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/rqlannotation.py	Thu May 14 12:50:34 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)
@@ -71,7 +69,7 @@
             # "Any X", "Any X, Y WHERE X attr Y"
             stinfo['invariant'] = False
             continue
-        joins = set()            
+        joins = set()
         invariant = False
         for ref in var.references():
             rel = ref.relation()
@@ -80,7 +78,7 @@
             lhs, rhs = rel.get_parts()
             onlhs = ref is lhs
             if rel.r_type == 'eid':
-                if not (onlhs and len(stinfo['relations']) > 1): 
+                if not (onlhs and len(stinfo['relations']) > 1):
                     break
                 if not stinfo['constnode']:
                     joins.add(rel)
@@ -112,9 +110,9 @@
                 continue
             if not stinfo['constnode']:
                 if rschema.inlined and rel.neged(strict=True):
-                    # if relation is inlined, can't be invariant if that 
+                    # if relation is inlined, can't be invariant if that
                     # variable is used anywhere else.
-                    # see 'Any P WHERE NOT N ecrit_par P, N eid 512':                    
+                    # see 'Any P WHERE NOT N ecrit_par P, N eid 512':
                     # sql for 'NOT N ecrit_par P' is 'N.ecrit_par is NULL' so P
                     # can use N.ecrit_par as principal
                     if (stinfo['selected'] or len(stinfo['relations']) > 1):
@@ -186,7 +184,7 @@
         return iter(_sort(diffscope_rels)).next()
     # XXX  could use a relation for a different scope if it can't generate
     # duplicates, so we would have to check cardinality
-    raise CantSelectPrincipal()    
+    raise CantSelectPrincipal()
 
 def _select_main_var(relations):
     """given a list of rqlst relations, select one which will be used as main
@@ -267,12 +265,12 @@
             return False
         try:
             data = var.stmt._deamb_data
-        except AttributeError: 
+        except AttributeError:
             data = var.stmt._deamb_data = IsAmbData(self.schema, self.nfdomain)
             data.compute(var.stmt)
         return data.is_ambiguous(var)
 
-        
+
 class IsAmbData(object):
     def __init__(self, schema, nfdomain):
         self.schema = schema
@@ -290,7 +288,7 @@
         self.deambification_map = {}
         # not invariant variables (access to final.inlined relation)
         self.not_invariants = set()
-        
+
     def is_ambiguous(self, var):
         return var in self.ambiguousvars
 
@@ -298,7 +296,7 @@
         self.varsols[var] &= restricted_domain
         if var in self.ambiguousvars and self.varsols[var] == var.stinfo['possibletypes']:
             self.ambiguousvars.remove(var)
-    
+
     def compute(self, rqlst):
         # set domains for each variable
         for varname, var in rqlst.defined_vars.iteritems():
@@ -336,7 +334,7 @@
                 except KeyError:
                     # no relation to deambiguify
                     continue
-        
+
     def _debug_print(self):
         print 'varsols', dict((x, sorted(str(v) for v in values))
                                for x, values in self.varsols.iteritems())
@@ -352,7 +350,7 @@
                     self.maydeambrels[var].add(rel)
                 except KeyError:
                     self.maydeambrels[var] = set((rel,))
-        
+
     def deambiguifying_relation(self, var, rel):
         lhs, rhs = rel.get_variable_parts()
         onlhs = var is getattr(lhs, 'variable', None)
--- a/server/rqlrewrite.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/rqlrewrite.py	Thu May 14 12:50:34 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:
@@ -34,7 +34,7 @@
     return newsolutions
 
 class Unsupported(Exception): pass
-        
+
 class RQLRewriter(object):
     """insert some rql snippets into another rql syntax tree"""
     def __init__(self, querier, session):
@@ -51,7 +51,7 @@
             raise Unsupported()
         if len(self.select.solutions) < len(self.solutions):
             raise Unsupported()
-        
+
     def rewrite(self, select, snippets, solutions, kwargs):
         if server.DEBUG:
             print '---- rewrite', select, snippets, solutions
@@ -112,7 +112,7 @@
         add_types_restriction(self.schema, select)
         if server.DEBUG:
             print '---- rewriten', select
-            
+
     def build_variantes(self, newsolutions):
         variantes = set()
         for sol in newsolutions:
@@ -133,7 +133,7 @@
                 for variante in variantes:
                     del variante[(erqlexpr, mainvar, oldvar)]
         return variantes
-    
+
     def insert_snippets(self, snippets, varexistsmap=None):
         self.rewritten = {}
         for varname, erqlexprs in snippets:
@@ -175,7 +175,7 @@
             if varexistsmap is None and not inserted:
                 # no rql expression found matching rql solutions. User has no access right
                 raise Unauthorized()
-            
+
     def insert_snippet(self, varname, snippetrqlst, parent=None):
         new = snippetrqlst.where.accept(self)
         if new is not None:
@@ -240,7 +240,7 @@
                     else:
                         parent.parent.replace(or_, or_.children[0])
                         self._cleanup_inserted(new)
-                    raise 
+                    raise
             return new
 
     def _cleanup_inserted(self, node):
@@ -250,7 +250,7 @@
             if not vref.variable.stinfo['references']:
                 # no more references, undefine the variable
                 del self.select.defined_vars[vref.name]
-        
+
     def _visit_binary(self, node, cls):
         newnode = cls()
         for c in node.children:
@@ -270,20 +270,20 @@
             return None
         newnode = cls()
         newnode.append(newc)
-        return newnode 
-        
+        return newnode
+
     def visit_and(self, et):
         return self._visit_binary(et, nodes.And)
 
     def visit_or(self, ou):
         return self._visit_binary(ou, nodes.Or)
-        
+
     def visit_not(self, node):
         return self._visit_unary(node, nodes.Not)
 
     def visit_exists(self, node):
         return self._visit_unary(node, nodes.Exists)
-   
+
     def visit_relation(self, relation):
         lhs, rhs = relation.get_variable_parts()
         if lhs.name == 'X':
@@ -301,7 +301,7 @@
             if relation.r_type in self.rhs_rels and self._may_be_shared(relation, 'subject'):
                 # ok, can share variable
                 term = self.rhs_rels[relation.r_type].children[0]
-                self._use_outer_term(lhs.name, term)            
+                self._use_outer_term(lhs.name, term)
                 return
         rel = nodes.Relation(relation.r_type, relation.optional)
         for c in relation.children:
@@ -315,11 +315,11 @@
         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_
-        
+
     def visit_function(self, function):
         """generate filter name for a function"""
         function_ = nodes.Function(function.name)
@@ -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 '+*':
@@ -371,7 +371,7 @@
             for inserted_vref in insertedvar.references():
                 inserted_vref.parent.replace(inserted_vref, term.copy(self.select))
         self.rewritten[key] = term
-        
+
     def _get_varname_or_term(self, vname):
         if vname == 'U':
             if self.u_varname is None:
--- a/server/schemahooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/schemahooks.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
 checking for schema consistency is done in hooks.py
 
 :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,13 +17,14 @@
 
 from cubicweb import ValidationError, RepositoryError
 from cubicweb.server import schemaserial as ss
+from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
 from cubicweb.server.hookhelper import (entity_attr, entity_name,
                                      check_internal_entity)
-    
+
 # core entity and relation types which can't be removed
-CORE_ETYPES = list(BASE_TYPES) + ['EEType', 'ERType', 'EUser', 'EGroup',
-                                  'EConstraint', 'EFRDef', 'ENFRDef']
+CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
+                                  'CWConstraint', 'CWAttribute', 'CWRelation']
 CORE_RTYPES = ['eid', 'creation_date', 'modification_date',
                'login', 'upassword', 'name',
                'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
@@ -44,19 +45,22 @@
 
 def add_inline_relation_column(session, etype, rtype):
     """add necessary column and index for an inlined relation"""
+    table = SQL_PREFIX + etype
+    column = SQL_PREFIX + rtype
     try:
         session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
-                               % (etype, rtype)))
-        session.info('added column %s to table %s', rtype, etype)
+                               % (table, column)))
+        session.info('added column %s to table %s', column, table)
     except:
-        # silent exception here, if this error has not been raised because the 
+        # silent exception here, if this error has not been raised because the
         # column already exists, index creation will fail anyway
-        session.exception('error while adding column %s to table %s', etype, rtype)
+        session.exception('error while adding column %s to table %s',
+                          table, column)
     # create index before alter table which may expectingly fail during test
     # (sqlite) while index creation should never fail (test for index existence
     # is done by the dbhelper)
-    session.pool.source('system').create_index(session, etype, rtype)
-    session.info('added index on %s(%s)', etype, rtype)
+    session.pool.source('system').create_index(session, table, column)
+    session.info('added index on %s(%s)', table, column)
     session.add_query_data('createdattrs', '%s.%s' % (etype, rtype))
 
 
@@ -70,7 +74,7 @@
         Operation.__init__(self, session, **kwargs)
         # every schema operation is triggering a schema update
         UpdateSchemaOp(session)
-        
+
 class EarlySchemaOperation(SchemaOperation):
     def insert_index(self):
         """schema operation which are inserted at the begining of the queue
@@ -81,7 +85,7 @@
             if not isinstance(op, EarlySchemaOperation):
                 return i
         return i + 1
-    
+
 class UpdateSchemaOp(SingleLastOperation):
     """the update schema operation:
 
@@ -89,17 +93,18 @@
     operations. It will trigger internal structures rebuilding to consider
     schema changes
     """
-    
+
     def __init__(self, session):
         self.repo = session.repo
         SingleLastOperation.__init__(self, session)
-        
+
     def commit_event(self):
         self.repo.set_schema(self.repo.schema)
 
-        
+
 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)
@@ -108,11 +113,12 @@
         dropped.add(self.table)
         self.session.system_sql('DROP TABLE %s' % self.table)
         self.info('dropped table %s', self.table)
-        
+
 class DropColumnOp(PreCommitOperation):
     """actually remove the attribut's column from entity table in the system
     database
     """
+    table = column = None # make pylint happy
     def precommit_event(self):
         session, table, column = self.session, self.table, self.column
         # drop index if any
@@ -124,12 +130,12 @@
         except Exception, ex:
             # not supported by sqlite for instance
             self.error('error while altering table %s: %s', table, ex)
-            
+
 
 # deletion ####################################################################
 
-class DeleteEETypeOp(SchemaOperation):
-    """actually remove the entity type from the application's schema"""    
+class DeleteCWETypeOp(SchemaOperation):
+    """actually remove the entity type from the application's schema"""
     def commit_event(self):
         try:
             # del_entity_type also removes entity's relations
@@ -139,26 +145,26 @@
             pass
 
 def before_del_eetype(session, eid):
-    """before deleting a EEType entity:
+    """before deleting a CWEType entity:
     * check that we don't remove a core entity type
-    * cascade to delete related EFRDef and ENFRDef entities
+    * cascade to delete related CWAttribute and CWRelation entities
     * instantiate an operation to delete the entity type on commit
     """
     # final entities can't be deleted, don't care about that
     name = check_internal_entity(session, eid, CORE_ETYPES)
     # delete every entities of this type
     session.unsafe_execute('DELETE %s X' % name)
-    DropTableOp(session, table=name)
-    DeleteEETypeOp(session, name)
+    DropTableOp(session, table=SQL_PREFIX + name)
+    DeleteCWETypeOp(session, name)
 
 def after_del_eetype(session, eid):
     # workflow cleanup
     session.execute('DELETE State X WHERE NOT X state_of Y')
     session.execute('DELETE Transition X WHERE NOT X transition_of Y')
 
-        
-class DeleteERTypeOp(SchemaOperation):
-    """actually remove the relation type from the application's schema"""    
+
+class DeleteCWRTypeOp(SchemaOperation):
+    """actually remove the relation type from the application's schema"""
     def commit_event(self):
         try:
             self.schema.del_relation_type(self.kobj)
@@ -167,20 +173,20 @@
             pass
 
 def before_del_ertype(session, eid):
-    """before deleting a ERType entity:
+    """before deleting a CWRType entity:
     * check that we don't remove a core relation type
-    * cascade to delete related EFRDef and ENFRDef entities
+    * cascade to delete related CWAttribute and CWRelation entities
     * instantiate an operation to delete the relation type on commit
     """
     name = check_internal_entity(session, eid, CORE_RTYPES)
     # delete relation definitions using this relation type
-    session.execute('DELETE EFRDef X WHERE X relation_type Y, Y eid %(x)s',
+    session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
                     {'x': eid})
-    session.execute('DELETE ENFRDef X WHERE X relation_type Y, Y eid %(x)s',
+    session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
                     {'x': eid})
-    DeleteERTypeOp(session, name)
+    DeleteCWRTypeOp(session, name)
 
-    
+
 class DelErdefOp(SchemaOperation):
     """actually remove the relation definition from the application's schema"""
     def commit_event(self):
@@ -190,9 +196,9 @@
         except KeyError:
             # relation type may have been already deleted
             pass
-        
+
 def after_del_relation_type(session, rdefeid, rtype, rteid):
-    """before deleting a EFRDef or ENFRDef entity:
+    """before deleting a CWAttribute or CWRelation entity:
     * if this is a final or inlined relation definition, instantiate an
       operation to drop necessary column, else if this is the last instance
       of a non final relation, instantiate an operation to drop necessary
@@ -204,9 +210,9 @@
     pendings = session.query_data('pendingeids', ())
     # first delete existing relation if necessary
     if rschema.is_final():
-        rdeftype = 'EFRDef'
+        rdeftype = 'CWAttribute'
     else:
-        rdeftype = 'ENFRDef'
+        rdeftype = 'CWRelation'
         if not (subjschema.eid in pendings or objschema.eid in pendings):
             session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
                             % (rschema, subjschema, objschema))
@@ -217,31 +223,33 @@
     # we have to update physical schema systematically for final and inlined
     # relations, but only if it's the last instance for this relation type
     # for other relations
-    
+
     if (rschema.is_final() or rschema.inlined):
         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
                        'R eid %%(x)s, X from_entity E, E name %%(name)s'
                        % rdeftype, {'x': rteid, 'name': str(subjschema)})
         if rset[0][0] == 0 and not subjschema.eid in pendings:
-            DropColumnOp(session, table=subjschema.type, column=rschema.type)
+            DropColumnOp(session, table=SQL_PREFIX + subjschema.type,
+                         column=SQL_PREFIX + rschema.type)
     elif lastrel:
         DropTableOp(session, table='%s_relation' % rschema.type)
     # if this is the last instance, drop associated relation type
     if lastrel and not rteid in pendings:
-        execute('DELETE ERType X WHERE X eid %(x)s', {'x': rteid}, 'x')
+        execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
     DelErdefOp(session, (subjschema, rschema, objschema))
 
-        
+
 # addition ####################################################################
 
-class AddEETypeOp(EarlySchemaOperation):
-    """actually add the entity type to the application's schema"""    
+class AddCWETypeOp(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
-        
+
 def before_add_eetype(session, entity):
-    """before adding a EEType entity:
+    """before adding a CWEType entity:
     * check that we are not using an existing entity type,
     """
     name = entity['name']
@@ -250,11 +258,11 @@
         raise RepositoryError('an entity type %s already exists' % name)
 
 def after_add_eetype(session, entity):
-    """after adding a EEType entity:
+    """after adding a CWEType entity:
     * create the necessary table
     * set creation_date and modification_date by creating the necessary
-      EFRDef entities
-    * add owned_by relation by creating the necessary ENFRDef entity
+      CWAttribute entities
+    * add owned_by relation by creating the necessary CWRelation entity
     * register an operation to add the entity type to the application's
       schema on commit
     """
@@ -270,7 +278,8 @@
     eschema = schema.add_entity_type(etype)
     eschema.set_default_groups()
     # generate table sql and rql to add metadata
-    tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema)
+    tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
+                           prefix=SQL_PREFIX)
     relrqls = []
     for rtype in ('is', 'is_instance_of', 'creation_date', 'modification_date',
                   'created_by', 'owned_by'):
@@ -288,38 +297,39 @@
     # register operation to modify the schema on commit
     # this have to be done before adding other relations definitions
     # or permission settings
-    AddEETypeOp(session, etype, eid=entity.eid)
+    AddCWETypeOp(session, etype, eid=entity.eid)
     # add meta creation_date, modification_date and owned_by relations
     for rql, kwargs in relrqls:
         session.execute(rql, kwargs)
 
 
-class AddERTypeOp(EarlySchemaOperation):
-    """actually add the relation type to the application's schema"""    
+class AddCWRTypeOp(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()
         rschema.eid = self.eid
-        
+
 def before_add_ertype(session, entity):
-    """before adding a ERType entity:
+    """before adding a CWRType entity:
     * check that we are not using an existing relation type,
     * register an operation to add the relation type to the application's
       schema on commit
-      
+
     We don't know yeat this point if a table is necessary
     """
     name = entity['name']
     if name in session.repo.schema.relations():
         raise RepositoryError('a relation type %s already exists' % name)
-    
+
 def after_add_ertype(session, entity):
-    """after a ERType entity has been added:
+    """after a CWRType entity has been added:
     * register an operation to add the relation type to the application's
       schema on commit
     We don't know yeat this point if a table is necessary
     """
-    AddERTypeOp(session, RelationType(name=entity['name'],
+    AddCWRTypeOp(session, RelationType(name=entity['name'],
                                       description=entity.get('description'),
                                       meta=entity.get('meta', False),
                                       inlined=entity.get('inlined', False),
@@ -330,7 +340,7 @@
 class AddErdefOp(EarlySchemaOperation):
     """actually add the attribute relation definition to the application's
     schema
-    """    
+    """
     def commit_event(self):
         self.schema.add_relation_def(self.kobj)
 
@@ -340,21 +350,22 @@
     'Float': float,
     'Password': str,
     'String': unicode,
-    'Date' : unicode, 
+    'Date' : unicode,
     'Datetime' : unicode,
     'Time' : unicode,
     }
 
 
-class AddEFRDefPreCommitOp(PreCommitOperation):
-    """an attribute relation (EFRDef) has been added:
+class AddCWAttributePreCommitOp(PreCommitOperation):
+    """an attribute relation (CWAttribute) has been added:
     * add the necessary column
     * set default on this column if any and possible
     * register an operation to add the relation definition to the
       application's schema on commit
-      
+
     constraints are handled by specific hooks
     """
+    entity = None # make pylint happy
     def precommit_event(self):
         session = self.session
         entity = self.entity
@@ -393,22 +404,24 @@
             extra_unique_index = False
         # added some str() wrapping query since some backend (eg psycopg) don't
         # allow unicode queries
+        table = SQL_PREFIX + subj
+        column = SQL_PREFIX + rtype
         try:
             session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
-                                   % (subj, rtype, attrtype)))
-            self.info('added column %s to table %s', rtype, subj)
+                                   % (table, column, attrtype)))
+            self.info('added column %s to table %s', table, column)
         except Exception, ex:
             # the column probably already exists. this occurs when
             # the entity's type has just been added or if the column
             # has not been previously dropped
-            self.error('error while altering table %s: %s', subj, ex)
+            self.error('error while altering table %s: %s', table, ex)
         if extra_unique_index or entity.indexed:
             try:
-                sysource.create_index(session, subj, rtype,
+                sysource.create_index(session, table, column,
                                       unique=extra_unique_index)
             except Exception, ex:
                 self.error('error while creating index for %s.%s: %s',
-                           subj, rtype, ex)
+                           table, column, ex)
         # postgres doesn't implement, so do it in two times
         # ALTER TABLE %s ADD COLUMN %s %s SET DEFAULT %s
         if default is not None:
@@ -416,20 +429,20 @@
                 default = default.encode(sysource.encoding)
             try:
                 session.system_sql('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT '
-                                   '%%(default)s' % (subj, rtype),
+                                   '%%(default)s' % (table, column),
                                    {'default': default})
             except Exception, ex:
                 # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', subj, ex)
-            session.system_sql('UPDATE %s SET %s=%%(default)s' % (subj, rtype),
+                self.error('error while altering table %s: %s', table, ex)
+            session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
                                {'default': default})
         AddErdefOp(session, rdef)
 
 def after_add_efrdef(session, entity):
-    AddEFRDefPreCommitOp(session, entity=entity)
+    AddCWAttributePreCommitOp(session, entity=entity)
 
 
-class AddENFRDefPreCommitOp(PreCommitOperation):
+class AddCWRelationPreCommitOp(PreCommitOperation):
     """an actual relation has been added:
     * if this is an inlined relation, add the necessary column
       else if it's the first instance of this relation type, add the
@@ -439,11 +452,12 @@
 
     constraints are handled by specific hooks
     """
+    entity = None # make pylint happy
     def precommit_event(self):
         session = self.session
         entity = self.entity
         fromentity = entity.from_entity[0]
-        relationtype = entity.relation_type[0] 
+        relationtype = entity.relation_type[0]
         session.execute('SET X ordernum Y+1 WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, X ordernum >= %(order)s, NOT X eid %(x)s',
                         {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0})
         subj, rtype = str(fromentity.name), str(relationtype.name)
@@ -494,9 +508,9 @@
                     if sql.strip():
                         self.session.system_sql(sql)
                 session.add_query_data('createdtables', rtype)
-                
+
 def after_add_enfrdef(session, entity):
-    AddENFRDefPreCommitOp(session, entity=entity)
+    AddCWRelationPreCommitOp(session, entity=entity)
 
 
 # update ######################################################################
@@ -530,38 +544,44 @@
 
 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
         sqlexec = self.session.system_sql
-        sqlexec('ALTER TABLE %s RENAME TO %s' % (self.oldname, self.newname))
+        sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname,
+                                                     SQL_PREFIX, self.newname))
         self.info('renamed table %s to %s', self.oldname, self.newname)
         sqlexec('UPDATE entities SET type=%s WHERE type=%s',
                 (self.newname, self.oldname))
         sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
                 (self.newname, self.oldname))
-        
+
     def commit_event(self):
         self.session.repo.schema.rename_entity_type(self.oldname, self.newname)
 
 
 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:
             sysource = self.session.pool.source('system')
-            table, column = self.kobj[0], self.rschema.type
+            etype, rtype = self.kobj[0], self.rschema.type
+            table = SQL_PREFIX + etype
+            column = SQL_PREFIX + rtype
             if self.values['indexed']:
                 sysource.create_index(self.session, table, column)
             else:
                 sysource.drop_index(self.session, table, column)
-                
+
     def commit_event(self):
         # structure should be clean, not need to remove entity's relations
         # at this point
         self.rschema._rproperties[self.kobj].update(self.values)
-    
+
+
 def after_update_erdef(session, entity):
     desttype = entity.to_entity[0].name
     rschema = session.repo.schema[entity.relation_type[0].name]
@@ -580,7 +600,9 @@
 
 
 class UpdateRtypeOp(SchemaOperation):
-    """actually update some properties of a relation definition"""    
+    """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
@@ -593,6 +615,7 @@
         # inlined changed, make necessary physical changes!
         sqlexec = self.session.system_sql
         rtype = rschema.type
+        eidcolumn = SQL_PREFIX + 'eid'
         if not inlined:
             # need to create the relation if it has not been already done by another
             # event of the same transaction
@@ -604,34 +627,39 @@
                         sqlexec(sql)
                 session.add_query_data('createdtables', rschema.type)
             # copy existant data
+            column = SQL_PREFIX + rtype
             for etype in rschema.subjects():
-                sqlexec('INSERT INTO %s_relation SELECT eid, %s FROM %s WHERE NOT %s IS NULL'
-                        % (rtype, rtype, etype, rtype))
+                table = SQL_PREFIX + str(etype)
+                sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
+                        % (rtype, eidcolumn, column, table, column))
             # drop existant columns
             for etype in rschema.subjects():
-                DropColumnOp(session, table=str(etype), column=rtype)
+                DropColumnOp(session, table=SQL_PREFIX + str(etype),
+                             column=SQL_PREFIX + rtype)
         else:
             for etype in rschema.subjects():
                 try:
-                    add_inline_relation_column(session, str(etype), rtype)                    
+                    add_inline_relation_column(session, str(etype), rtype)
                 except Exception, ex:
                     # the column probably already exists. this occurs when
                     # the entity's type has just been added or if the column
                     # has not been previously dropped
                     self.error('error while altering table %s: %s', etype, ex)
-                # copy existant data. 
+                # copy existant data.
                 # XXX don't use, it's not supported by sqlite (at least at when i tried it)
                 #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
                 #        'FROM %(rtype)s_relation '
                 #        'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
                 #        % locals())
-                cursor = sqlexec('SELECT eid_from, eid_to FROM %(etype)s, '
-                                 '%(rtype)s_relation WHERE %(etype)s.eid='
+                table = SQL_PREFIX + str(etype)
+                cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
+                                 '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
                                  '%(rtype)s_relation.eid_from' % locals())
                 args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
                 if args:
-                    cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE eid=%%(x)s'
-                                       % (etype, rtype), args)
+                    column = SQL_PREFIX + rtype
+                    cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
+                                       % (table, column, eidcolumn), args)
                 # drop existant table
                 DropTableOp(session, table='%s_relation' % rtype)
 
@@ -639,7 +667,7 @@
         # structure should be clean, not need to remove entity's relations
         # at this point
         self.rschema.__dict__.update(self.values)
-    
+
 def after_update_ertype(session, entity):
     rschema = session.repo.schema.rschema(entity.name)
     newvalues = {}
@@ -655,12 +683,14 @@
 
 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)
         rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
         return self.constraints
-    
+
     def precommit_event(self):
         rdef = self.entity.reverse_constrained_by[0]
         session = self.session
@@ -668,7 +698,7 @@
         # is created by AddEN?FRDefPreCommitOp, there is nothing to do here
         if rdef.eid in session.query_data('neweids', ()):
             self.cancelled = True
-            return 
+            return
         self.cancelled = False
         schema = session.repo.schema
         subjtype, rtype, objtype = schema.schema_by_eid(rdef.eid)
@@ -677,21 +707,23 @@
         self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
         self._cstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
         self._cstr.eid = self.entity.eid
+        table = SQL_PREFIX + str(subjtype)
+        column = SQL_PREFIX + str(rtype)
         # alter the physical schema on size constraint changes
         if self._cstr.type() == 'SizeConstraint' and (
             self.cstr is None or self.cstr.max != self._cstr.max):
             try:
                 session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE VARCHAR(%s)'
-                                   % (subjtype, rtype, self._cstr.max))
+                                   % (table, column, self._cstr.max))
                 self.info('altered column %s of table %s: now VARCHAR(%s)',
-                          rtype, subjtype, self._cstr.max)
+                          column, table, self._cstr.max)
             except Exception, ex:
                 # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', subjtype, ex)
+                self.error('error while altering table %s: %s', table, ex)
         elif cstrtype == 'UniqueConstraint':
             session.pool.source('system').create_index(
-                self.session, str(subjtype), str(rtype), unique=True)
-        
+                self.session, table, column, unique=True)
+
     def commit_event(self):
         if self.cancelled:
             return
@@ -700,33 +732,37 @@
             self.constraints.remove(self.cstr)
         self.constraints.append(self._cstr)
 
+
 def after_add_econstraint(session, entity):
     ConstraintOp(session, entity=entity)
 
 def after_update_econstraint(session, entity):
     ConstraintOp(session, entity=entity)
 
+
 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)
         cstrtype = self.cstr.type()
+        table = SQL_PREFIX + str(self.subjtype)
+        column = SQL_PREFIX + str(self.rtype)
         # alter the physical schema on size/unique constraint changes
         if cstrtype == 'SizeConstraint':
             try:
                 self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
-                                        % (self.subjtype, self.rtype))
-                self.info('altered column %s of table %s: now TEXT', 
-                          self.rtype,  self.subjtype)
+                                        % (table, column))
+                self.info('altered column %s of table %s: now TEXT',
+                          column, table)
             except Exception, ex:
                 # not supported by sqlite for instance
-                self.error('error while altering table %s: %s', 
-                           self.subjtype, ex)
+                self.error('error while altering table %s: %s', table, ex)
         elif cstrtype == 'UniqueConstraint':
             self.session.pool.source('system').drop_index(
-                self.session, str(self.subjtype), str(self.rtype), unique=True)
-                
+                self.session, table, column, unique=True)
+
     def commit_event(self):
         self.constraints.remove(self.cstr)
 
@@ -748,7 +784,7 @@
     if fromeid in session.query_data('neweids', ()):
         session.add_query_data(fromeid, toeid)
 
-    
+
 # schema permissions synchronization ##########################################
 
 class PermissionOp(Operation):
@@ -769,7 +805,7 @@
     def __init__(self, session, perm, etype_eid, group_eid):
         self.group = entity_name(session, group_eid)
         PermissionOp.__init__(self, session, perm, etype_eid)
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -779,7 +815,7 @@
             self.error('no schema for %s', self.name)
             return
         groups = list(erschema.get_groups(self.perm))
-        try:            
+        try:
             groups.index(self.group)
             self.warning('group %s already have permission %s on %s',
                          self.group, self.perm, erschema.type)
@@ -794,7 +830,7 @@
     def __init__(self, session, perm, etype_eid, expression):
         self.expr = expression
         PermissionOp.__init__(self, session, perm, etype_eid)
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -810,18 +846,18 @@
 def after_add_permission(session, subject, rtype, object):
     """added entity/relation *_permission, need to update schema"""
     perm = rtype.split('_', 1)[0]
-    if session.describe(object)[0] == 'EGroup':
+    if session.describe(object)[0] == 'CWGroup':
         AddGroupPermissionOp(session, perm, subject, object)
     else: # RQLExpression
         expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
                                {'x': object}, 'x')[0][0]
         AddRQLExpressionPermissionOp(session, perm, subject, expr)
-    
+
 
-        
+
 class DelGroupPermissionOp(AddGroupPermissionOp):
     """synchronize schema when a *_permission relation has been deleted from a group"""
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -831,17 +867,17 @@
             self.error('no schema for %s', self.name)
             return
         groups = list(erschema.get_groups(self.perm))
-        try:            
+        try:
             groups.remove(self.group)
             erschema.set_groups(self.perm, groups)
         except ValueError:
             self.error('can\'t remove permission %s on %s to group %s',
                 self.perm, erschema.type, self.group)
 
-        
+
 class DelRQLExpressionPermissionOp(AddRQLExpressionPermissionOp):
     """synchronize schema when a *_permission relation has been deleted from an rql expression"""
-        
+
     def commit_event(self):
         """the observed connections pool has been commited"""
         try:
@@ -861,7 +897,7 @@
             return
         erschema.set_rqlexprs(self.perm, rqlexprs)
 
-                
+
 def before_del_permission(session, subject, rtype, object):
     """delete entity/relation *_permission, need to update schema
 
@@ -870,7 +906,7 @@
     if subject in session.query_data('pendingeids', ()):
         return
     perm = rtype.split('_', 1)[0]
-    if session.describe(object)[0] == 'EGroup':
+    if session.describe(object)[0] == 'CWGroup':
         DelGroupPermissionOp(session, perm, subject, object)
     else: # RQLExpression
         expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
@@ -889,28 +925,28 @@
     """register schema related hooks on the hooks manager"""
     # schema synchronisation #####################
     # before/after add
-    hm.register_hook(before_add_eetype, 'before_add_entity', 'EEType')
-    hm.register_hook(before_add_ertype, 'before_add_entity', 'ERType')
-    hm.register_hook(after_add_eetype, 'after_add_entity', 'EEType')
-    hm.register_hook(after_add_ertype, 'after_add_entity', 'ERType')
-    hm.register_hook(after_add_efrdef, 'after_add_entity', 'EFRDef')
-    hm.register_hook(after_add_enfrdef, 'after_add_entity', 'ENFRDef')
+    hm.register_hook(before_add_eetype, 'before_add_entity', 'CWEType')
+    hm.register_hook(before_add_ertype, 'before_add_entity', 'CWRType')
+    hm.register_hook(after_add_eetype, 'after_add_entity', 'CWEType')
+    hm.register_hook(after_add_ertype, 'after_add_entity', 'CWRType')
+    hm.register_hook(after_add_efrdef, 'after_add_entity', 'CWAttribute')
+    hm.register_hook(after_add_enfrdef, 'after_add_entity', 'CWRelation')
     # before/after update
-    hm.register_hook(before_update_eetype, 'before_update_entity', 'EEType')
-    hm.register_hook(before_update_ertype, 'before_update_entity', 'ERType')
-    hm.register_hook(after_update_ertype, 'after_update_entity', 'ERType')
-    hm.register_hook(after_update_erdef, 'after_update_entity', 'EFRDef')
-    hm.register_hook(after_update_erdef, 'after_update_entity', 'ENFRDef')
+    hm.register_hook(before_update_eetype, 'before_update_entity', 'CWEType')
+    hm.register_hook(before_update_ertype, 'before_update_entity', 'CWRType')
+    hm.register_hook(after_update_ertype, 'after_update_entity', 'CWRType')
+    hm.register_hook(after_update_erdef, 'after_update_entity', 'CWAttribute')
+    hm.register_hook(after_update_erdef, 'after_update_entity', 'CWRelation')
     # before/after delete
-    hm.register_hook(before_del_eetype, 'before_delete_entity', 'EEType')
-    hm.register_hook(after_del_eetype, 'after_delete_entity', 'EEType')
-    hm.register_hook(before_del_ertype, 'before_delete_entity', 'ERType')
+    hm.register_hook(before_del_eetype, 'before_delete_entity', 'CWEType')
+    hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
+    hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
     hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
     hm.register_hook(rebuild_infered_relations, 'after_add_relation', 'specializes')
-    hm.register_hook(rebuild_infered_relations, 'after_delete_relation', 'specializes')    
+    hm.register_hook(rebuild_infered_relations, 'after_delete_relation', 'specializes')
     # constraints synchronization hooks
-    hm.register_hook(after_add_econstraint, 'after_add_entity', 'EConstraint')
-    hm.register_hook(after_update_econstraint, 'after_update_entity', 'EConstraint')
+    hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
+    hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
     hm.register_hook(before_delete_constrained_by, 'before_delete_relation', 'constrained_by')
     hm.register_hook(after_add_constrained_by, 'after_add_relation', 'constrained_by')
     # permissions synchronisation ################
--- a/server/schemaserial.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/schemaserial.py	Thu May 14 12:50:34 2009 +0200
@@ -1,11 +1,12 @@
 """functions for schema / permissions (de)serialization using RQL
 
 :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 itertools import chain
 
 from logilab.common.shellutils import ProgressBar
@@ -18,12 +19,12 @@
     """create a group mapping from an rql cursor
 
     A group mapping has standard group names as key (managers, owners at least)
-    and the actual EGroup entity's eid as associated value.
+    and the actual CWGroup entity's eid as associated value.
     In interactive mode (the default), missing groups'eid will be prompted
     from the user.
     """
     res = {}
-    for eid, name in cursor.execute('Any G, N WHERE G is EGroup, G name N'):
+    for eid, name in cursor.execute('Any G, N WHERE G is CWGroup, G name N'):
         res[name] = eid
     if not interactive:
         return res
@@ -31,7 +32,7 @@
     if missing:
         print 'some native groups are missing but the following groups have been found:'
         print '\n'.join('* %s (%s)' % (n, eid) for n, eid in res.items())
-        print 
+        print
         print 'enter the eid of a to group to map to each missing native group'
         print 'or just type enter to skip permissions granted to a group'
         for group in missing:
@@ -46,18 +47,71 @@
                     continue
     return res
 
+def _set_sql_prefix(prefix):
+    """3.2.0 migration function: allow to unset/reset SQL_PREFIX"""
+    for module in ('checkintegrity', 'migractions', 'schemahooks',
+                   'sources.rql2sql', 'sources.native'):
+        try:
+            sys.modules['cubicweb.server.%s' % module].SQL_PREFIX = prefix
+            print 'changed SQL_PREFIX for %s' % module
+        except KeyError:
+            pass
+
+def _update_database(schema, sqlcu):
+    """3.2.0 migration function: update database schema by adding SQL_PREFIX to
+    entity type tables and columns
+    """
+    for etype in schema.entities():
+        if etype.is_final():
+            continue
+        try:
+            sql = 'ALTER TABLE %s RENAME TO cw_%s' % (
+                etype, ETYPE_NAME_MAP.get(etype, etype))
+            print sql
+            sqlcu.execute(sql)
+        except:
+            pass
+        for rschema in etype.subject_relations():
+            if rschema == 'has_text':
+                continue
+            if rschema.is_final() or rschema.inlined:
+                sql = 'ALTER TABLE cw_%s RENAME %s TO cw_%s' % (
+                    etype, rschema, rschema)
+                print sql
+                sqlcu.execute(sql)
+
 # schema / perms deserialization ##############################################
 
 def deserialize_schema(schema, session):
     """return a schema according to information stored in an rql database
-    as ERType and EEType entities
+    as CWRType and CWEType entities
     """
+    #
+    repo = session.repo
+    sqlcu = session.pool['system']
+    _3_2_migration = False
+    tables = set(t.lower() for t in repo.system_source.dbhelper.list_tables(sqlcu))
+    if 'eetype' in tables:
+        _3_2_migration = True
+        # 3.2 migration
+        _set_sql_prefix('')
+        # first rename entity types whose name changed in 3.2 without adding the
+        # cw_ prefix
+        for etype in ('EFRDef', 'ENFRDef', 'ERType', 'EEType',
+                      'EConstraintType', 'EConstraint', 'EGroup', 'EUser',
+                      'ECache', 'EPermission', 'EProperty'):
+            if etype.lower() in tables:
+                sql = 'ALTER TABLE %s RENAME TO %s' % (etype,
+                                                       ETYPE_NAME_MAP[etype])
+                print sql
+                sqlcu.execute(sql)
+        # other table renaming done once schema has been readen
     # print 'reading schema from the database...'
     index = {}
     permsdict = deserialize_ertype_permissions(session)
     schema.reading_from_database = True
     for eid, etype, desc, meta in session.execute('Any X, N, D, M WHERE '
-                                                  'X is EEType, X name N, '
+                                                  'X is CWEType, X name N, '
                                                   'X description D, X meta M',
                                                   build_descr=False):
         # base types are already in the schema, skip them
@@ -70,7 +124,7 @@
         if etype in ETYPE_NAME_MAP: # XXX <2.45 bw compat
             print 'fixing etype name from %s to %s' % (etype, ETYPE_NAME_MAP[etype])
             # can't use write rql queries at this point, use raw sql
-            session.system_sql('UPDATE EEType SET name=%(n)s WHERE eid=%(x)s',
+            session.system_sql('UPDATE CWEType SET name=%(n)s WHERE eid=%(x)s',
                                {'x': eid, 'n': ETYPE_NAME_MAP[etype]})
             session.system_sql('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
                                {'x': etype, 'n': ETYPE_NAME_MAP[etype]})
@@ -81,9 +135,9 @@
             except:
                 pass
             tocleanup = [eid]
-            tocleanup += (eid for eid, (eidetype, uri, extid) in session.repo._type_source_cache.items()
+            tocleanup += (eid for eid, (eidetype, uri, extid) in repo._type_source_cache.items()
                           if etype == eidetype)
-            session.repo.clear_caches(tocleanup)
+            repo.clear_caches(tocleanup)
             session.commit(False)
             etype = ETYPE_NAME_MAP[etype]
         etype = ybo.EntityType(name=etype, description=desc, meta=meta, eid=eid)
@@ -91,7 +145,7 @@
         index[eid] = eschema
         set_perms(eschema, permsdict.get(eid, {}))
     try:
-        rset = session.execute('Any XN, ETN WHERE X is EEType, X name XN, '
+        rset = session.execute('Any XN, ETN WHERE X is CWEType, X name XN, '
                                'X specializes ET, ET name ETN')
     except: # `specializes` relation not available for versions prior to 2.50
         session.rollback(False)
@@ -102,7 +156,7 @@
             eschema._specialized_type = stype
             seschema._specialized_by.append(etype)
     for eid, rtype, desc, meta, sym, il in session.execute(
-        'Any X,N,D,M,S,I WHERE X is ERType, X name N, X description D, '
+        'Any X,N,D,M,S,I WHERE X is CWRType, X name N, X description D, '
         'X meta M, X symetric S, X inlined I', build_descr=False):
         try:
             # bw compat: fulltext_container added in 2.47
@@ -116,10 +170,10 @@
                                  fulltext_container=ft_container, eid=eid)
         rschema = schema.add_relation_type(rtype)
         index[eid] = rschema
-        set_perms(rschema, permsdict.get(eid, {}))        
+        set_perms(rschema, permsdict.get(eid, {}))
     cstrsdict = deserialize_rdef_constraints(session)
     for values in session.execute(
-        'Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT WHERE X is EFRDef,'
+        'Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT WHERE X is CWAttribute,'
         'X relation_type RT, X cardinality CARD, X ordernum ORD, X indexed IDX,'
         'X description DESC, X internationalizable I18N, X defaultval DFLT,'
         'X fulltextindexed FTIDX, X from_entity SE, X to_entity OE',
@@ -130,14 +184,14 @@
         rtype = index[reid].type
         toetype = index[teid].type
         rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card,
-                                  order=ord, description=desc, 
+                                  order=ord, description=desc,
                                   constraints=constraints,
                                   indexed=idx, fulltextindexed=ftidx,
                                   internationalizable=i18n,
                                   default=default, eid=rdefeid)
         schema.add_relation_def(rdef)
     for values in session.execute(
-        'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is ENFRDef, X relation_type RT,'
+        'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is CWRelation, X relation_type RT,'
         'X cardinality CARD, X ordernum ORD, X description DESC, '
         'X from_entity SE, X to_entity OE, X composite C', build_descr=False):
         rdefeid, seid, reid, teid, card, ord, desc, c = values
@@ -146,11 +200,14 @@
         toetype = index[teid].type
         constraints = cstrsdict.get(rdefeid, ())
         rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card,
-                                  order=ord, description=desc, 
+                                  order=ord, description=desc,
                                   composite=c, constraints=constraints,
                                   eid=rdefeid)
         schema.add_relation_def(rdef)
     schema.infer_specialization_rules()
+    if _3_2_migration:
+        _update_database(schema, sqlcu)
+        _set_sql_prefix('cw_')
     session.commit()
     schema.reading_from_database = False
 
@@ -159,11 +216,11 @@
     """return sect action:groups associations for the given
     entity or relation schema with its eid, according to schema's
     permissions stored in the database as [read|add|delete|update]_permission
-    relations between EEType/ERType and EGroup entities
+    relations between CWEType/CWRType and CWGroup entities
     """
     res = {}
     for action in ('read', 'add', 'update', 'delete'):
-        rql = 'Any E,N WHERE G is EGroup, G name N, E %s_permission G' % action
+        rql = 'Any E,N WHERE G is CWGroup, G name N, E %s_permission G' % action
         for eid, gname in session.execute(rql, build_descr=False):
             res.setdefault(eid, {}).setdefault(action, []).append(gname)
         rql = ('Any E,X,EXPR,V WHERE X is RQLExpression, X expression EXPR, '
@@ -187,21 +244,21 @@
                 actperms.append(erschema.rql_expression(*something))
             else: # group name
                 actperms.append(something)
-        erschema.set_permissions(action, actperms)            
+        erschema.set_permissions(action, actperms)
 
 
 def deserialize_rdef_constraints(session):
     """return the list of relation definition's constraints as instances"""
     res = {}
     for rdefeid, ceid, ct, val in session.execute(
-        'Any E, X,TN,V WHERE E constrained_by X, X is EConstraint, '
+        'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, '
         'X cstrtype T, T name TN, X value V', build_descr=False):
         cstr = CONSTRAINTS[ct].deserialize(val)
         cstr.eid = ceid
         res.setdefault(rdefeid, []).append(cstr)
     return res
-        
-        
+
+
 # schema / perms serialization ################################################
 
 def serialize_schema(cursor, schema, verbose=False):
@@ -215,7 +272,7 @@
         pb_size = len(aller) + len(CONSTRAINTS) + len([x for x in eschemas if x.specializes()])
         pb = ProgressBar(pb_size)
     for cstrtype in CONSTRAINTS:
-        rql = 'INSERT EConstraintType X: X name "%s"' % cstrtype
+        rql = 'INSERT CWConstraintType X: X name "%s"' % cstrtype
         if verbose:
             print rql
         cursor.execute(rql)
@@ -298,12 +355,12 @@
             value = unicode(value)
         values[amap.get(prop, prop)] = value
     return values
-    
+
 def nfrdef_relations_values(rschema, objtype, props):
     values = _rdef_values(rschema, objtype, props)
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
-    
+
 def frdef_relations_values(rschema, objtype, props):
     values = _rdef_values(rschema, objtype, props)
     default = values['default']
@@ -317,7 +374,7 @@
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-    
+
 def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None):
     if subjtype is None:
         assert objtype is None
@@ -341,7 +398,7 @@
 
 def schema2rql(schema, skip=None, allow=None):
     """return a list of rql insert statements to enter the schema in the
-    database as ERType and EEType entities
+    database as CWRType and CWEType entities
     """
     assert not (skip is not None and allow is not None), \
            'can\'t use both skip and allow'
@@ -351,7 +408,7 @@
     elif allow is not None:
         return chain(*[erschema2rql(schema[t]) for t in all if t in allow])
     return chain(*[erschema2rql(schema[t]) for t in all])
-        
+
 def erschema2rql(erschema):
     if isinstance(erschema, schemamod.EntitySchema):
         return eschema2rql(erschema)
@@ -359,12 +416,12 @@
 
 def eschema2rql(eschema):
     """return a list of rql insert statements to enter an entity schema
-    in the database as an EEType entity
+    in the database as an CWEType entity
     """
     relations, values = eschema_relations_values(eschema)
     # NOTE: 'specializes' relation can't be inserted here since there's no
     # way to make sure the parent type is inserted before the child type
-    yield 'INSERT EEType X: %s' % ','.join(relations) , values
+    yield 'INSERT CWEType X: %s' % ','.join(relations) , values
 
 def specialize2rql(schema):
     for eschema in schema.entities():
@@ -379,16 +436,16 @@
 
 def rschema2rql(rschema, addrdef=True):
     """return a list of rql insert statements to enter a relation schema
-    in the database as an ERType entity
+    in the database as an CWRType entity
     """
     if rschema.type == 'has_text':
         return
     relations, values = rschema_relations_values(rschema)
-    yield 'INSERT ERType X: %s' % ','.join(relations), values
+    yield 'INSERT CWRType X: %s' % ','.join(relations), values
     if addrdef:
         for rql, values in rdef2rql(rschema):
             yield rql, values
-            
+
 def rdef2rql(rschema, subjtype=None, objtype=None, props=None):
     genmap = {True: frdef2rql, False: nfrdef2rql}
     return __rdef2rql(genmap, rschema, subjtype, objtype, props)
@@ -401,18 +458,18 @@
     relations, values = frdef_relations_values(rschema, objtype, props)
     relations.append(_LOCATE_RDEF_RQL0)
     values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT EFRDef X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
+    yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
     for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is EFRDef', values
-            
+        yield rql + ', EDEF is CWAttribute', values
+
 def nfrdef2rql(rschema, subjtype, objtype, props):
     relations, values = nfrdef_relations_values(rschema, objtype, props)
     relations.append(_LOCATE_RDEF_RQL0)
     values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT ENFRDef X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
+    yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
     for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is ENFRDef', values
-                
+        yield rql + ', EDEF is CWRelation', values
+
 def rdefrelations2rql(rschema, subjtype, objtype, props):
     iterators = []
     for constraint in props['constraints']:
@@ -423,14 +480,14 @@
     values = {'ctname': unicode(constraint.type()),
               'value': unicode(constraint.serialize()),
               'rt': str(rschema), 'se': str(subjtype), 'oe': str(objtype)}
-    yield 'INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
+    yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
 CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, \
 ER name %(rt)s, SE name %(se)s, OE name %(oe)s', values
 
 def perms2rql(schema, groupmapping):
     """return rql insert statements to enter the schema's permissions in
     the database as [read|add|delete|update]_permission relations between
-    EEType/ERType and EGroup entities
+    CWEType/CWRType and CWGroup entities
 
     groupmapping is a dictionnary mapping standard group names to
     eids
@@ -443,10 +500,10 @@
 def erperms2rql(erschema, groupmapping):
     """return rql insert statements to enter the entity or relation
     schema's permissions in the database as
-    [read|add|delete|update]_permission relations between EEType/ERType
-    and EGroup entities
+    [read|add|delete|update]_permission relations between CWEType/CWRType
+    and CWGroup entities
     """
-    etype = isinstance(erschema, schemamod.EntitySchema) and 'EEType' or 'ERType'
+    etype = isinstance(erschema, schemamod.EntitySchema) and 'CWEType' or 'CWRType'
     for action in erschema.ACTIONS:
         for group in sorted(erschema.get_groups(action)):
             try:
@@ -465,13 +522,13 @@
 def updateeschema2rql(eschema):
     relations, values = eschema_relations_values(eschema)
     values['et'] = eschema.type
-    yield 'SET %s WHERE X is EEType, X name %%(et)s' % ','.join(relations), values
+    yield 'SET %s WHERE X is CWEType, X name %%(et)s' % ','.join(relations), values
 
 def updaterschema2rql(rschema):
     relations, values = rschema_relations_values(rschema)
     values['rt'] = rschema.type
-    yield 'SET %s WHERE X is ERType, X name %%(rt)s' % ','.join(relations), values
-            
+    yield 'SET %s WHERE X is CWRType, X name %%(rt)s' % ','.join(relations), values
+
 def updaterdef2rql(rschema, subjtype=None, objtype=None, props=None):
     genmap = {True: updatefrdef2rql, False: updatenfrdef2rql}
     return __rdef2rql(genmap, rschema, subjtype, objtype, props)
@@ -479,13 +536,13 @@
 def updatefrdef2rql(rschema, subjtype, objtype, props):
     relations, values = frdef_relations_values(rschema, objtype, props)
     values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is EFRDef' % (','.join(relations),
+    yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations),
                                                  _LOCATE_RDEF_RQL0,
                                                  _LOCATE_RDEF_RQL1), values
-            
+
 def updatenfrdef2rql(rschema, subjtype, objtype, props):
     relations, values = nfrdef_relations_values(rschema, objtype, props)
     values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is ENFRDef' % (','.join(relations),
+    yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations),
                                                  _LOCATE_RDEF_RQL0,
                                                  _LOCATE_RDEF_RQL1), values
--- a/server/securityhooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/securityhooks.py	Thu May 14 12:50:34 2009 +0200
@@ -24,25 +24,25 @@
         if rschema.is_final(): # non final relation are checked by other hooks
             # add/delete should be equivalent (XXX: unify them into 'update' ?)
             rschema.check_perm(session, 'add', eid)
-            
-    
+
+
 class CheckEntityPermissionOp(LateOperation):
     def precommit_event(self):
         #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
         self.entity.check_perm(self.action)
         check_entity_attributes(self.session, self.entity)
-        
+
     def commit_event(self):
         pass
-            
-    
+
+
 class CheckRelationPermissionOp(LateOperation):
     def precommit_event(self):
         self.rschema.check_perm(self.session, self.action, self.fromeid, self.toeid)
-        
+
     def commit_event(self):
         pass
-    
+
 def after_add_entity(session, entity):
     if not session.is_super_session:
         CheckEntityPermissionOp(session, entity=entity, action='add')
@@ -56,7 +56,7 @@
         except Unauthorized:
             entity.clear_local_perm_cache('update')
             CheckEntityPermissionOp(session, entity=entity, action='update')
-        
+
 def before_del_entity(session, eid):
     if not session.is_super_session:
         eschema = session.repo.schema[session.describe(eid)[0]]
@@ -67,7 +67,7 @@
     if rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
         rschema = session.repo.schema[rtype]
         rschema.check_perm(session, 'add', fromeid, toeid)
-        
+
 def after_add_relation(session, fromeid, rtype, toeid):
     if not rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
         rschema = session.repo.schema[rtype]
@@ -89,4 +89,4 @@
     hm.register_hook(before_add_relation, 'before_add_relation', '')
     hm.register_hook(after_add_relation, 'after_add_relation', '')
     hm.register_hook(before_del_relation, 'before_delete_relation', '')
-    
+
--- a/server/server.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/server.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Pyro RQL 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"
@@ -22,7 +22,7 @@
     """base event"""
     # timefunc = staticmethod(localtime)
     timefunc = localtime
-    
+
     def __init__(self, absolute=None, period=None):
         # local time tuple
         if absolute is None:
@@ -57,10 +57,10 @@
     def fire(self, server):
         server.repo.shutdown()
         server.quiting = True
-        
+
 
 class RepositoryServer(object):
-    
+
     def __init__(self, config, debug=False):
         """make the repository available as a PyRO object"""
         self.config = config
@@ -86,7 +86,7 @@
                     event.update()
                 except Finished:
                     self.events.remove(event)
-            
+
     def run(self, req_timeout=5.0):
         """enter the service loop"""
         while self.quiting is None:
@@ -95,7 +95,7 @@
             except select.error:
                 continue
             self.trigger_events()
-    
+
     def quit(self):
         """stop the server"""
         self.add_event(QuitEvent())
@@ -105,16 +105,16 @@
         necessary
         """
         self.daemon = self.repo.pyro_register(host)
-            
+
     # server utilitities ######################################################
-    
+
     def install_sig_handlers(self):
         """install signal handlers"""
         import signal
         self.info('installing signal handlers')
         signal.signal(signal.SIGINT, lambda x, y, s=self: s.quit())
         signal.signal(signal.SIGTERM, lambda x, y, s=self: s.quit())
-        
+
     def daemonize(self, pid_file=None):
         """daemonize the process"""
         # fork so the parent can exist
--- a/server/serverconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/serverconfig.py	Thu May 14 12:50:34 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"
@@ -73,6 +73,15 @@
           'help': 'size of the parsed rql cache size.',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('delay-full-text-indexation',
+         {'type' : 'yn', 'default': False,
+          'help': 'When full text indexation of entity has a too important cost'
+          ' to be done when entity are added/modified by users, activate this '
+          'option and setup a job using cubicweb-ctl db-rebuild-fti on your '
+          'system (using cron for instance).',
+          'group': 'main', 'inputlevel': 1,
+          }),
+
         # email configuration
         ('default-recipients-mode',
          {'type' : 'choice',
@@ -114,14 +123,14 @@
           'group': 'pyro-server', 'inputlevel': 2,
           }),
         ) + CubicWebConfiguration.options)
-        
+
     # read the schema from the database
     read_application_schema = True
     bootstrap_schema = True
-    
+
     # check user's state at login time
     consider_user_state = True
-    
+
     # hooks registration configuration
     # all hooks should be activated during normal execution
     core_hooks = True
@@ -133,7 +142,7 @@
 
     # should some hooks be deactivated during [pre|post]create script execution
     free_wheel = False
-    
+
     # list of enables sources when sources restriction is necessary
     # (eg repository initialization at least)
     _enabled_sources = None
@@ -141,7 +150,7 @@
     def enabled_sources(self, sourceuris=None):
         self._enabled_sources = sourceuris
         clear_cache(self, 'sources')
-        
+
     @classmethod
     def schemas_lib_dir(cls):
         """application schema directory"""
@@ -163,17 +172,17 @@
         else:
             # no cubes
             self.init_cubes(())
-        
+
     def write_bootstrap_cubes_file(self, cubes):
         stream = file(join(self.apphome, 'bootstrap_cubes'), 'w')
         stream.write('# this is a generated file only used for bootstraping\n')
         stream.write('# you should not have to edit this\n')
         stream.write('%s\n' % ','.join(cubes))
         stream.close()
-        
+
     def sources_file(self):
         return join(self.apphome, 'sources')
-    
+
     # this method has to be cached since when the server is running using a
     # restricted user, this user usually don't have access to the sources
     # configuration file (#16102)
@@ -187,11 +196,11 @@
             return allsources
         return dict((uri, config) for uri, config in allsources.items()
                     if uri in self._enabled_sources or uri == 'admin')
-    
+
     def pyro_enabled(self):
         """pyro is always enabled in standalone repository configuration"""
         return True
-        
+
     def load_hooks(self, vreg):
         hooks = {}
         for path in reversed([self.apphome] + self.cubes_path()):
@@ -200,8 +209,8 @@
                 self.warning('application_hooks.py is deprecated, use dynamic '
                              'objects to register hooks (%s)', hooksfile)
                 context = {}
-                # Use execfile rather than `load_module_from_name` because 
-                # the latter gets fooled by the `sys.modules` cache when 
+                # Use execfile rather than `load_module_from_name` because
+                # the latter gets fooled by the `sys.modules` cache when
                 # loading different configurations one after the other
                 # (another fix would have been to do :
                 #    sys.modules.pop('applications_hooks')
@@ -222,8 +231,8 @@
                 cb = hookdef.make_callback(event)
                 hooks.setdefault(event, {}).setdefault(ertype, []).append(cb)
         return hooks
-    
-    def load_schema(self, expand_cubes=False):
+
+    def load_schema(self, expand_cubes=False, **kwargs):
         from cubicweb.schema import CubicWebSchemaLoader
         if expand_cubes:
             # in case some new dependencies have been introduced, we have to
@@ -231,18 +240,18 @@
             origcubes = self.cubes()
             self._cubes = None
             self.init_cubes(self.expand_cubes(origcubes))
-        schema = CubicWebSchemaLoader().load(self)
+        schema = CubicWebSchemaLoader().load(self, **kwargs)
         if expand_cubes:
             # restaure original value
             self._cubes = origcubes
         return schema
-    
+
     def load_bootstrap_schema(self):
         from cubicweb.schema import BootstrapSchemaLoader
         schema = BootstrapSchemaLoader().load(self)
         schema.name = 'bootstrap'
         return schema
-    
+
     def set_sources_mode(self, sources):
         if 'migration' in sources:
             from cubicweb.server.sources import source_adapter
@@ -257,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:
@@ -265,7 +274,7 @@
             enabled_sources = sources
         self._enabled_sources = enabled_sources
         clear_cache(self, 'sources')
-        
+
     def migration_handler(self, schema=None, interactive=True,
                           cnx=None, repo=None, connect=True):
         """return a migration handler instance"""
--- a/server/serverctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/serverctl.py	Thu May 14 12:50:34 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
 
 
@@ -88,7 +89,7 @@
         # set_isolation_level() is psycopg specific
         pass
     return cnx
-    
+
 def generate_sources_file(sourcesfile, sourcescfg, keys=None):
     """serialize repository'sources configuration into a INI like file
 
@@ -108,7 +109,7 @@
             # get a Configuration object
             _sconfig = Configuration(options=SOURCE_TYPES[sconfig['adapter']].options)
             for attr, val in sconfig.items():
-                if attr == 'uri': 
+                if attr == 'uri':
                     continue
                 if attr == 'adapter':
                     _sconfig.adapter = val
@@ -138,8 +139,10 @@
             return in_memory_cnx(config, login, pwd)
         except AuthenticationError:
             print 'wrong user/password'
+            # reset cubes else we'll have an assertion error on next retry
+            config._cubes = None
         login, pwd = manager_userpasswd()
-    
+
 # repository specific command handlers ########################################
 
 class RepositoryCreateHandler(CommandHandler):
@@ -186,14 +189,14 @@
         restrict_perms_to_user(sourcesfile)
         # remember selected cubes for later initialization of the database
         config.write_bootstrap_cubes_file(cubes)
-        
+
     def postcreate(self):
         if confirm('do you want to create repository\'s system database?'):
             verbosity = (self.config.mode == 'installed') and 'y' or 'n'
             cmd_run('db-create', self.config.appid, '--verbose=%s' % verbosity)
         else:
             print 'nevermind, you can do it later using the db-create command'
-            
+
 USER_OPTIONS =  (
     ('login', {'type' : 'string',
                'default': REQUIRED,
@@ -235,7 +238,7 @@
                 cnx.rollback()
                 raise
 
-    
+
 class RepositoryStartHandler(CommandHandler):
     cmdname = 'start'
     cfgname = 'repository'
@@ -246,7 +249,7 @@
             command.append('--debug')
         command.append(self.config.appid)
         return ' '.join(command)
-        
+
 
 class RepositoryStopHandler(CommandHandler):
     cmdname = 'stop'
@@ -259,12 +262,12 @@
         if self.config.pyro_enabled():
             from cubicweb.server.repository import pyro_unregister
             pyro_unregister(self.config)
-    
+
 
 # repository specific commands ################################################
 class CreateApplicationDBCommand(Command):
     """Create the system database of an application (run after 'create').
-    
+
     You will be prompted for a login / password to use to connect to
     the system database.  The given user should have almost all rights
     on the database (ie a super user on the dbms allowed to create
@@ -275,7 +278,7 @@
     """
     name = 'db-create'
     arguments = '<application>'
-    
+
     options = (
         ("create-db",
          {'short': 'c', 'type': "yn", 'metavar': '<y or n>',
@@ -327,11 +330,11 @@
             except:
                 dbcnx.rollback()
                 raise
-        cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=verbose) 
+        cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=verbose)
         cursor = cnx.cursor()
         indexer = get_indexer(driver)
         indexer.init_extensions(cursor)
-        # postgres specific stuff        
+        # postgres specific stuff
         if driver == 'postgres':
             # install plpythonu/plpgsql language if not installed by the cube
             for extlang in ('plpythonu', 'plpgsql'):
@@ -345,10 +348,10 @@
         else:
             print 'nevermind, you can do it later using the db-init command'
 
-    
+
 class InitApplicationCommand(Command):
     """Initialize the system database of an application (run after 'db-create').
-    
+
     You will be prompted for a login / password to use to connect to
     the system database.  The given user should have the create tables,
     and grant permissions.
@@ -358,7 +361,7 @@
     """
     name = 'db-init'
     arguments = '<application>'
-    
+
     options = (
         ("drop",
          {'short': 'd', 'action': 'store_true',
@@ -376,7 +379,7 @@
 
 class GrantUserOnApplicationCommand(Command):
     """Grant a database user on a repository system database.
-    
+
     <application>
       the identifier of the application
     <user>
@@ -387,7 +390,7 @@
 
     options = (
         ("set-owner",
-         {'short': 'o', 'type' : "yn", 'metavar' : '<yes or no>', 
+         {'short': 'o', 'type' : "yn", 'metavar' : '<yes or no>',
           'default' : False,
           'help': 'Set the user as tables owner if yes (no by default).'}
          ),
@@ -416,10 +419,10 @@
             print 'grants given to %s on application %s' % (appid, user)
 
 
-    
+
 class StartRepositoryCommand(Command):
     """Start an CubicWeb RQL server for a given application.
-    
+
     The server will be accessible through pyro
 
     <application>
@@ -427,7 +430,7 @@
     """
     name = 'start-repository'
     arguments = '<application>'
-    
+
     options = (
         ("debug",
          {'short': 'D', 'action' : 'store_true',
@@ -466,8 +469,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
@@ -475,7 +478,8 @@
         raise ExecutionError('Error while retrieving the dump')
     rmcmd = 'ssh -t %s "rm -f /tmp/%s.dump"' % (host, appid)
     print rmcmd
-    if os.system(rmcmd) and not confirm('an error occured while deleting remote dump. Continue anyway?'):
+    if os.system(rmcmd) and not confirm(
+        'an error occured while deleting remote dump. Continue anyway?'):
         raise ExecutionError('Error while deleting remote dump')
 
 def _local_dump(appid, output):
@@ -532,7 +536,7 @@
             applversion = vcconf[cube]
         except KeyError:
             print "no cube version information for %s in version configuration" % cube
-            continue            
+            continue
         if softversion == applversion:
             continue
         if softversion > applversion:
@@ -540,11 +544,11 @@
         elif softversion < applversion:
             return 'needapplupgrade'
     return None
-    
+
 
 class DBDumpCommand(Command):
     """Backup the system database of an application.
-    
+
     <application>
       the identifier of the application to backup
       format [[user@]host:]appname
@@ -554,7 +558,7 @@
 
     options = (
         ("output",
-         {'short': 'o', 'type' : "string", 'metavar' : '<file>', 
+         {'short': 'o', 'type' : "string", 'metavar' : '<file>',
           'default' : None,
           'help': 'Specify the backup file where the backup will be stored.'}
          ),
@@ -576,7 +580,7 @@
 
 class DBRestoreCommand(Command):
     """Restore the system database of an application.
-    
+
     <application>
       the identifier of the application to restore
     """
@@ -585,7 +589,7 @@
 
     options = (
         ("no-drop",
-         {'short': 'n', 'action' : 'store_true', 
+         {'short': 'n', 'action' : 'store_true',
           'default' : False,
           'help': 'for some reason the database doesn\'t exist and so '
           'should not be dropped.'}
@@ -600,7 +604,7 @@
 
 class DBCopyCommand(Command):
     """Copy the system database of an application (backup and restore).
-    
+
     <src-application>
       the identifier of the application to backup
       format [[user@]host:]appname
@@ -613,7 +617,7 @@
 
     options = (
         ("no-drop",
-         {'short': 'n', 'action' : 'store_true', 
+         {'short': 'n', 'action' : 'store_true',
           'default' : False,
           'help': 'For some reason the database doesn\'t exist and so '
           'should not be dropped.'}
@@ -646,10 +650,10 @@
         else:
             os.remove(output)
 
-        
+
 class CheckRepositoryCommand(Command):
     """Check integrity of the system database of an application.
-    
+
     <application>
       the identifier of the application to check
     """
@@ -658,25 +662,25 @@
 
     options = (
         ("checks",
-         {'short': 'c', 'type' : "csv", 'metavar' : '<check list>', 
+         {'short': 'c', 'type' : "csv", 'metavar' : '<check list>',
           'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
           'help': 'Comma separated list of check to run. By default run all \
 checks, i.e. entities, relations, text_index and metadata.'}
          ),
-        
+
         ("autofix",
-         {'short': 'a', 'type' : "yn", 'metavar' : '<yes or no>', 
+         {'short': 'a', 'type' : "yn", 'metavar' : '<yes or no>',
           'default' : False,
           'help': 'Automatically correct integrity problems if this option \
 is set to "y" or "yes", else only display them'}
          ),
         ("reindex",
-         {'short': 'r', 'type' : "yn", 'metavar' : '<yes or no>', 
+         {'short': 'r', 'type' : "yn", 'metavar' : '<yes or no>',
           'default' : False,
           'help': 're-indexes the database for full text search if this \
 option is set to "y" or "yes" (may be long for large database).'}
          ),
-        
+
         )
 
     def run(self, args):
@@ -690,7 +694,7 @@
 
 class RebuildFTICommand(Command):
     """Rebuild the full-text index of the system database of an application.
-    
+
     <application>
       the identifier of the application to rebuild
     """
@@ -708,10 +712,10 @@
         reindex_entities(repo.schema, session)
         cnx.commit()
 
-    
+
 class SynchronizeApplicationSchemaCommand(Command):
     """Synchronize persistent schema with cube schema.
-        
+
     Will synchronize common stuff between the cube schema and the
     actual persistent schema, but will not add/remove any entity or relation.
 
@@ -728,7 +732,7 @@
         mih.cmd_synchronize_schema()
 
 
-register_commands( (CreateApplicationDBCommand,                   
+register_commands( (CreateApplicationDBCommand,
                     InitApplicationCommand,
                     GrantUserOnApplicationCommand,
                     StartRepositoryCommand,
--- a/server/session.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/session.py	Thu May 14 12:50:34 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 import RequestSessionMixIn, Binary, UnknownEid
 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
@@ -51,7 +29,7 @@
                 continue
             if etype in BASE_TYPES:
                 return True
-            return False   
+            return False
 
 def _make_description(selected, args, solution):
     """return a description for a result set"""
@@ -67,7 +45,7 @@
     """tie session id, user, connections pool and other session data all
     together
     """
-    
+
     def __init__(self, user, repo, cnxprops=None, _id=None):
         super(Session, self).__init__(repo.vreg)
         self.id = _id or make_uid(user.login.encode('UTF8'))
@@ -87,7 +65,7 @@
         # i18n initialization
         self.set_language(cnxprops.lang)
         self._threaddata = threading.local()
-        
+
     def get_mode(self):
         return getattr(self._threaddata, 'mode', 'read')
     def set_mode(self, value):
@@ -100,12 +78,12 @@
     def set_commit_state(self, value):
         self._threaddata.commit_state = value
     commit_state = property(get_commit_state, set_commit_state)
-    
+
     # set according to transaction mode for each query
     @property
     def pool(self):
         return getattr(self._threaddata, 'pool', None)
-    
+
     # pending transaction operations
     @property
     def pending_operations(self):
@@ -114,7 +92,7 @@
         except AttributeError:
             self._threaddata.pending_operations = []
             return self._threaddata.pending_operations
-    
+
     # rql rewriter
     @property
     def rql_rewriter(self):
@@ -123,7 +101,7 @@
         except AttributeError:
             self._threaddata._rewriter = RQLRewriter(self.repo.querier, self)
             return self._threaddata._rewriter
-    
+
     # transaction queries data
     @property
     def _query_data(self):
@@ -132,7 +110,7 @@
         except AttributeError:
             self._threaddata._query_data = {}
             return self._threaddata._query_data
-    
+
     def set_language(self, language):
         """i18n configuration for translation"""
         vreg = self.vreg
@@ -146,42 +124,42 @@
             except KeyError:
                 self._ = self.__ = unicode
         self.lang = language
-        
+
     def change_property(self, prop, value):
         assert prop == 'lang' # this is the only one changeable property for now
         self.set_language(value)
 
     def __str__(self):
-        return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login, 
+        return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
                                              self.id, id(self))
 
     def etype_class(self, etype):
         """return an entity class for the given entity type"""
         return self.vreg.etype_class(etype)
-    
+
     def entity(self, eid):
         """return a result set for the given eid"""
         return self.eid_rset(eid).get_entity(0, 0)
-        
+
     def _touch(self):
         """update latest session usage timestamp and reset mode to read
         """
         self.timestamp = time()
         self.local_perm_cache.clear()
         self._threaddata.mode = 'read'
-        
+
     def set_pool(self):
         """the session need a pool to execute some queries"""
         if self.pool is None:
             self._threaddata.pool = self.repo._get_pool()
-            try:                
+            try:
                 self._threaddata.pool.pool_set(self)
             except:
                 self.repo._free_pool(self.pool)
                 self._threaddata.pool = None
                 raise
         return self._threaddata.pool
-            
+
     def reset_pool(self):
         """the session has no longer using its pool, at least for some time
         """
@@ -192,7 +170,7 @@
             self.repo._free_pool(self.pool)
             self.pool.pool_reset(self)
             self._threaddata.pool = None
-            
+
     def system_sql(self, sql, args=None):
         """return a sql cursor on the system database"""
         if not sql.split(None, 1)[0].upper() == 'SELECT':
@@ -203,26 +181,26 @@
 
     def actual_session(self):
         """return the original parent session if any, else self"""
-        return self        
+        return self
 
     # shared data handling ###################################################
-    
+
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         if pop:
             return self.data.pop(key, default)
         else:
             return self.data.get(key, default)
-        
+
     def set_shared_data(self, key, value, querydata=False):
         """set value associated to `key` in session data"""
         if querydata:
             self.set_query_data(key, value)
         else:
             self.data[key] = value
-        
+
     # request interface #######################################################
-    
+
     def set_entity_cache(self, entity):
         # no entity cache in the server, too high risk of inconsistency
         # between pre/post hooks
@@ -233,20 +211,20 @@
 
     def base_url(self):
         return self.repo.config['base-url'] or u''
-        
+
     def from_controller(self):
         """return the id (string) of the controller issuing the request (no
         sense here, always return 'view')
         """
         return 'view'
-    
+
     def source_defs(self):
         return self.repo.source_defs()
 
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         return self.repo.type_and_source_from_eid(eid, self)
-    
+
     # db-api like interface ###################################################
 
     def source_from_eid(self, eid):
@@ -271,7 +249,7 @@
         # need shared pool set
         self.set_pool()
         return csession
-        
+
     def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=False,
                        propagate=False):
         """like .execute but with security checking disabled (this method is
@@ -288,7 +266,7 @@
     def cursor(self):
         """return a rql cursor"""
         return self
-    
+
     def execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
                 propagate=False):
         """db-api like method directly linked to the querier execute method
@@ -298,7 +276,7 @@
         """
         rset = self._execute(self, rql, kwargs, eid_key, build_descr)
         return self.decorate_rset(rset, propagate)
-    
+
     def commit(self, reset_pool=True):
         """commit the current session's transaction"""
         if self.pool is None:
@@ -339,7 +317,7 @@
             self._query_data.clear()
             if reset_pool:
                 self.reset_pool()
-                        
+
     def rollback(self, reset_pool=True):
         """rollback the current session's transaction"""
         if self.pool is None:
@@ -362,19 +340,19 @@
             self._query_data.clear()
             if reset_pool:
                 self.reset_pool()
-        
+
     def close(self):
         """do not close pool on session close, since they are shared now"""
         self.rollback()
-        
+
     # transaction data/operations management ##################################
-    
+
     def add_query_data(self, key, value):
         self._query_data.setdefault(key, []).append(value)
-    
+
     def set_query_data(self, key, value):
         self._query_data[key] = value
-        
+
     def query_data(self, key, default=None, setdefault=False, pop=False):
         if setdefault:
             assert not pop
@@ -383,7 +361,7 @@
             return self._query_data.pop(key, default)
         else:
             return self._query_data.get(key, default)
-        
+
     def add_operation(self, operation, index=None):
         """add an observer"""
         assert self.commit_state != 'commit'
@@ -391,9 +369,9 @@
             self.pending_operations.insert(index, operation)
         else:
             self.pending_operations.append(operation)
-            
+
     # querier helpers #########################################################
-    
+
     def build_description(self, rqlst, args, result):
         """build a description for a given result"""
         if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
@@ -407,7 +385,7 @@
 
     def manual_build_descr(self, rqlst, args, result):
         """build a description for a given result by analysing each row
-        
+
         XXX could probably be done more efficiently during execution of query
         """
         # not so easy, looks for variable which changes from one solution
@@ -432,7 +410,7 @@
         if not todetermine:
             return [tuple(basedescription)] * len(result)
         return self._build_descr(result, basedescription, todetermine)
-    
+
     def _build_descr(self, result, basedescription, todetermine):
         description = []
         etype_from_eid = self.describe
@@ -447,16 +425,20 @@
                 if isfinal:
                     row_descr[index] = etype_from_pyobj(value)
                 else:
-                    row_descr[index] = etype_from_eid(value)[0]
+                    try:
+                        row_descr[index] = etype_from_eid(value)[0]
+                    except UnknownEid:
+                        self.critical('wrong eid %s in repository, should check database' % value)
+                        row_descr[index] = row[index] = None
             description.append(tuple(row_descr))
         return description
 
-    
+
 class ChildSession(Session):
     """child (or internal) session are used to hijack the security system
     """
     cnxtype = 'inmemory'
-    
+
     def __init__(self, parent_session):
         self.id = None
         self.is_internal_session = False
@@ -472,7 +454,7 @@
         self._ = self.__ = parent_session._
         # short cut to querier .execute method
         self._execute = self.repo.querier.execute
-    
+
     @property
     def super_session(self):
         return self
@@ -488,7 +470,7 @@
     def set_commit_state(self, value):
         self.parent_session.set_commit_state(value)
     commit_state = property(get_commit_state, set_commit_state)
-    
+
     @property
     def pool(self):
         return self.parent_session.pool
@@ -498,11 +480,11 @@
     @property
     def _query_data(self):
         return self.parent_session._query_data
-        
+
     def set_pool(self):
         """the session need a pool to execute some queries"""
         self.parent_session.set_pool()
-            
+
     def reset_pool(self):
         """the session has no longer using its pool, at least for some time
         """
@@ -511,19 +493,19 @@
     def actual_session(self):
         """return the original parent session if any, else self"""
         return self.parent_session
-        
+
     def commit(self, reset_pool=True):
         """commit the current session's transaction"""
         self.parent_session.commit(reset_pool)
-        
+
     def rollback(self, reset_pool=True):
         """rollback the current session's transaction"""
         self.parent_session.rollback(reset_pool)
-        
+
     def close(self):
         """do not close pool on session close, since they are shared now"""
         self.rollback()
-        
+
     def user_data(self):
         """returns a dictionnary with this user's information"""
         return self.parent_session.user_data()
@@ -531,14 +513,14 @@
 
 class InternalSession(Session):
     """special session created internaly by the repository"""
-    
+
     def __init__(self, repo, cnxprops=None):
         super(InternalSession, self).__init__(_IMANAGER, repo, cnxprops,
                                               _id='internal')
         self.cnxtype = 'inmemory'
         self.is_internal_session = True
         self.is_super_session = True
-    
+
     @property
     def super_session(self):
         return self
@@ -562,7 +544,7 @@
 
     def owns(self, eid):
         return True
-    
+
     def has_permission(self, pname, contexteid=None):
         return True
 
@@ -571,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	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -1,14 +1,35 @@
 """cubicweb server sources support
 
 :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, timedelta
 from logging import getLogger
 
 from cubicweb import set_log_methods
+from cubicweb.server.sqlutils import SQL_PREFIX
+
+
+class TimedCache(dict):
+    def __init__(self, ttlm, ttls=0):
+        # time to live in minutes
+        self.ttl = timedelta(0, ttlm*60 + ttls, 0)
+
+    def __setitem__(self, key, value):
+        dict.__setitem__(self, key, (datetime.now(), value))
+
+    def __getitem__(self, key):
+        return dict.__getitem__(self, key)[1]
+
+    def clear_expired(self):
+        now_ = datetime.now()
+        ttl = self.ttl
+        for key, (timestamp, value) in self.items():
+            if now_ - timestamp > ttl:
+                del self[key]
 
 
 class AbstractSource(object):
@@ -20,7 +41,7 @@
     # boolean telling if the repository should connect to this source during
     # migration
     connect_for_migration = True
-    
+
     # mappings telling which entities and relations are available in the source
     # keys are supported entity/relation types and values are boolean indicating
     # wether the support is read-only (False) or read-write (True)
@@ -33,32 +54,32 @@
     repo = None
     # a reference to the application'schema (may differs from the source'schema)
     schema = None
-    
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         self.repo = repo
         self.uri = source_config['uri']
         set_log_methods(self, getLogger('cubicweb.sources.'+self.uri))
         self.set_schema(appschema)
         self.support_relations['identity'] = False
-        
+
     def init_creating(self):
         """method called by the repository once ready to create a new instance"""
         pass
- 
+
     def init(self):
         """method called by the repository once ready to handle request"""
         pass
-    
+
     def reset_caches(self):
         """method called during test to reset potential source caches"""
         pass
-    
+
     def clear_eid_cache(self, eid, etype):
         """clear potential caches for the given eid"""
         pass
-    
+
     def __repr__(self):
-        return '<%s source>' % self.uri
+        return '<%s source @%#x>' % (self.uri, id(self))
 
     def __cmp__(self, other):
         """simple comparison function to get predictable source order, with the
@@ -71,11 +92,11 @@
         if other.uri == 'system':
             return -1
         return cmp(self.uri, other.uri)
-        
+
     def set_schema(self, schema):
         """set the application'schema"""
         self.schema = schema
-        
+
     def support_entity(self, etype, write=False):
         """return true if the given entity's type is handled by this adapter
         if write is true, return true only if it's a RW support
@@ -87,13 +108,13 @@
         if write:
             return wsupport
         return True
-    
+
     def support_relation(self, rtype, write=False):
         """return true if the given relation's type is handled by this adapter
         if write is true, return true only if it's a RW support
 
-        current implementation return true if the relation is defined into 
-        `support_relations` or if it is a final relation of a supported entity 
+        current implementation return true if the relation is defined into
+        `support_relations` or if it is a final relation of a supported entity
         type
         """
         try:
@@ -112,13 +133,13 @@
                 return False
         if write:
             return wsupport
-        return True    
-    
+        return True
+
     def eid2extid(self, eid, session=None):
         return self.repo.eid2extid(self, eid, session)
 
-    def extid2eid(self, value, etype, session=None, insert=True):
-        return self.repo.extid2eid(self, value, etype, session, insert)
+    def extid2eid(self, value, etype, session=None, **kwargs):
+        return self.repo.extid2eid(self, value, etype, session, **kwargs)
 
     PUBLIC_KEYS = ('adapter', 'uri')
     def remove_sensitive_information(self, sourcedef):
@@ -139,15 +160,18 @@
         if not myeids:
             return
         # delete relations referencing one of those eids
+        eidcolum = SQL_PREFIX + 'eid'
         for rschema in self.schema.relations():
             if rschema.is_final() or rschema.type == 'identity':
                 continue
             if rschema.inlined:
+                column = SQL_PREFIX + rschema.type
                 for subjtype in rschema.subjects():
+                    table = SQL_PREFIX + str(subjtype)
                     for objtype in rschema.objects(subjtype):
                         if self.support_entity(objtype):
-                            sql = 'UPDATE %s SET %s = NULL WHERE eid IN (%s);' % (
-                                subjtype, rschema.type, myeids)
+                            sql = 'UPDATE %s SET %s=NULL WHERE %s IN (%s);' % (
+                                table, column, eidcolum, myeids)
                             session.system_sql(sql)
                             break
                 continue
@@ -163,7 +187,7 @@
                         rschema.type, myeids)
                     session.system_sql(sql)
                     break
-        
+
     def cleanup_entities_info(self, session):
         """cleanup system tables from information for entities coming from
         this source. This should be called when a source is removed to
@@ -180,13 +204,13 @@
                            {'uri': self.uri})
         session.system_sql('DELETE FROM entities WHERE source=%(uri)s',
                            {'uri': self.uri})
-        
+
     # abstract methods to override (at least) in concrete source classes #######
-    
+
     def get_connection(self):
         """open and return a connection to the source"""
         raise NotImplementedError()
-    
+
     def check_connection(self, cnx):
         """check connection validity, return None if the connection is still valid
         else a new connection (called when the pool using the given connection is
@@ -195,7 +219,7 @@
         do nothing by default
         """
         pass
-    
+
     def pool_reset(self, cnx):
         """the pool using the given connection is being reseted from its current
         attached session
@@ -203,24 +227,24 @@
         do nothing by default
         """
         pass
-    
+
     def authenticate(self, session, login, password):
-        """if the source support EUser entity type, it should implements
-        this method which should return EUser eid for the given login/password
+        """if the source support CWUser entity type, it should implements
+        this method which should return CWUser eid for the given login/password
         if this account is defined in this source and valid login / password is
         given. Else raise `AuthenticationError`
         """
         raise NotImplementedError()
-    
+
     def syntax_tree_search(self, session, union,
                            args=None, cachekey=None, varmap=None, debug=0):
-        """return result from this source for a rql query (actually from a rql 
-        syntax tree and a solution dictionary mapping each used variable to a 
+        """return result from this source for a rql query (actually from a rql
+        syntax tree and a solution dictionary mapping each used variable to a
         possible type). If cachekey is given, the query necessary to fetch the
         results (but not the results themselves) may be cached using this key.
         """
         raise NotImplementedError()
-                
+
     def flying_insert(self, table, session, union, args=None, varmap=None):
         """similar as .syntax_tree_search, but inserts data in the temporary
         table (on-the-fly if possible, eg for the system source whose the given
@@ -230,21 +254,21 @@
         res = self.syntax_tree_search(session, union, args, varmap=varmap)
         session.pool.source('system')._manual_insert(res, table, session)
 
-        
+
     # system source don't have to implement the two methods below
-    
+
     def before_entity_insertion(self, session, lid, etype, eid):
         """called by the repository when an eid has been attributed for an
         entity stored here but the entity has not been inserted in the system
         table yet.
-        
+
         This method must return the an Entity instance representation of this
         entity.
         """
         entity = self.repo.vreg.etype_class(etype)(session, None)
         entity.set_eid(eid)
         return entity
-    
+
     def after_entity_insertion(self, session, lid, entity):
         """called by the repository after an entity stored here has been
         inserted in the system table.
@@ -256,11 +280,11 @@
     def get_extid(self, entity):
         """return the external id for the given newly inserted entity"""
         raise NotImplementedError()
-        
+
     def add_entity(self, session, entity):
         """add a new entity to the source"""
         raise NotImplementedError()
-        
+
     def update_entity(self, session, entity):
         """update an entity in the source"""
         raise NotImplementedError()
@@ -272,7 +296,7 @@
     def add_relation(self, session, subject, rtype, object):
         """add a relation to the source"""
         raise NotImplementedError()
-    
+
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         raise NotImplementedError()
@@ -282,7 +306,7 @@
     def eid_type_source(self, session, eid):
         """return a tuple (type, source, extid) for the entity with id <eid>"""
         raise NotImplementedError()
-    
+
     def create_eid(self, session):
         raise NotImplementedError()
 
@@ -295,18 +319,18 @@
         record from the entities table to the deleted_entities table
         """
         raise NotImplementedError()
-        
+
     def fti_unindex_entity(self, session, eid):
         """remove text content for entity with the given eid from the full text
         index
         """
         raise NotImplementedError()
-        
+
     def fti_index_entity(self, session, entity):
         """add text content of a created/modified entity to the full text index
         """
         raise NotImplementedError()
-        
+
     def modified_entities(self, session, etypes, mtime):
         """return a 2-uple:
         * list of (etype, eid) of entities of the given types which have been
@@ -322,13 +346,13 @@
     def sqlexec(self, session, sql, args=None):
         """execute the query and return its result"""
         raise NotImplementedError()
-    
+
     def temp_table_def(self, selection, solution, table, basemap):
         raise NotImplementedError()
-    
+
     def create_index(self, session, table, column, unique=False):
         raise NotImplementedError()
-            
+
     def drop_index(self, session, table, column, unique=False):
         raise NotImplementedError()
 
@@ -339,14 +363,14 @@
         """remove temporary data, usually associated to temporary tables"""
         pass
 
-        
+
 class TrFunc(object):
     """lower, upper"""
     def __init__(self, trname, index, attrname=None):
         self._tr = trname.lower()
         self.index = index
         self.attrname = attrname
-        
+
     def apply(self, resdict):
         value = resdict.get(self.attrname)
         if value is not None:
@@ -404,7 +428,7 @@
         return SOURCE_TYPES[adapter_type]
     except KeyError:
         raise RuntimeError('Unknown adapter %r' % adapter_type)
-    
+
 def get_source(source_config, global_schema, repo):
     """return a source adapter according to the adapter field in the
     source's configuration
--- a/server/sources/extlite.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/extlite.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """provide an abstract class for external sources using a sqlite database helper
 
 :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"
@@ -12,7 +12,7 @@
 from os.path import join, exists
 
 from cubicweb import server
-from cubicweb.server.sqlutils import sqlexec, SQLAdapterMixIn
+from cubicweb.server.sqlutils import SQL_PREFIX, sqlexec, SQLAdapterMixIn
 from cubicweb.server.sources import AbstractSource, native
 from cubicweb.server.sources.rql2sql import SQLGenerator
 
@@ -22,7 +22,7 @@
         timeout -= 0.2
         if timeout <= 0:
             raise RuntimeError("svn source is busy, can't acquire connection lock")
-        
+
 class ConnectionWrapper(object):
     def __init__(self, source=None):
         self.source = source
@@ -34,19 +34,19 @@
             timeout_acquire(self.source._cnxlock, 5)
             self._cnx = self.source._sqlcnx
         return self._cnx
-    
+
     def commit(self):
         if self._cnx is not None:
             self._cnx.commit()
-        
+
     def rollback(self):
         if self._cnx is not None:
             self._cnx.rollback()
-        
+
     def cursor(self):
         return self.cnx.cursor()
 
-    
+
 class SQLiteAbstractSource(AbstractSource):
     """an abstract class for external sources using a sqlite database helper
     """
@@ -59,7 +59,7 @@
             native.NONSYSTEM_ETYPES.add(etype)
         for rtype in cls.support_relations:
             native.NONSYSTEM_RELATIONS.add(rtype)
-        
+
     options = (
         ('helper-db-path',
          {'type' : 'string',
@@ -69,10 +69,10 @@
           'inputlevel': 2,
           }),
     )
-            
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         # the helper db is used to easy querying and will store everything but
-        # actual file content 
+        # actual file content
         dbpath = source_config.get('helper-db-path')
         if dbpath is None:
             dbpath = join(repo.config.appdatahome,
@@ -91,7 +91,7 @@
         # * create the connection when needed
         # * use a lock to be sure only one connection is used
         self._cnxlock = threading.Lock()
-        
+
     @property
     def _sqlcnx(self):
         # XXX: sqlite connections can only be used in the same thread, so
@@ -122,7 +122,7 @@
         for etype in self.support_entities:
             eschema = schema.eschema(etype)
             createsqls = eschema2sql(self.sqladapter.dbhelper, eschema,
-                                     skip_relations=('data',))
+                                     skip_relations=('data',), prefix=SQL_PREFIX)
             sqlexec(createsqls, cu, withpb=False)
         for rtype in self.support_relations:
             rschema = schema.rschema(rtype)
@@ -138,13 +138,13 @@
                          self.repo.config['uid'])
             chown(self.dbpath, self.repo.config['uid'])
         restrict_perms_to_user(self.dbpath, self.info)
-        
+
     def set_schema(self, schema):
         super(SQLiteAbstractSource, self).set_schema(schema)
-        if self._need_sql_create and self._is_schema_complete():
+        if self._need_sql_create and self._is_schema_complete() and self.dbpath:
             self._create_database()
         self.rqlsqlgen = self.sqlgen_class(schema, self.sqladapter.dbhelper)
-                
+
     def get_connection(self):
         return ConnectionWrapper(self)
 
@@ -168,11 +168,11 @@
                 cnx._cnx = None
             finally:
                 self._cnxlock.release()
-        
+
     def syntax_tree_search(self, session, union,
                            args=None, cachekey=None, varmap=None, debug=0):
-        """return result from this source for a rql query (actually from a rql 
-        syntax tree and a solution dictionary mapping each used variable to a 
+        """return result from this source for a rql query (actually from a rql
+        syntax tree and a solution dictionary mapping each used variable to a
         possible type). If cachekey is given, the query necessary to fetch the
         results (but not the results themselves) may be cached using this key.
         """
@@ -185,7 +185,7 @@
         args = self.sqladapter.merge_args(args, query_args)
         cursor = session.pool[self.uri]
         cursor.execute(sql, args)
-        return self.sqladapter.process_result(cursor) 
+        return self.sqladapter.process_result(cursor)
 
     def local_add_entity(self, session, entity):
         """insert the entity in the local database.
@@ -196,14 +196,14 @@
         """
         cu = session.pool[self.uri]
         attrs = self.sqladapter.preprocess_entity(entity)
-        sql = self.sqladapter.sqlgen.insert(str(entity.e_schema), attrs)
+        sql = self.sqladapter.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
         cu.execute(sql, attrs)
-        
+
     def add_entity(self, session, entity):
         """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,14 +211,16 @@
         and the source implementor may use this method if necessary
         """
         cu = session.pool[self.uri]
-        attrs = self.sqladapter.preprocess_entity(entity)
-        sql = self.sqladapter.sqlgen.update(str(entity.e_schema), attrs, ['eid'])
+        if attrs is None:
+            attrs = self.sqladapter.preprocess_entity(entity)
+        sql = self.sqladapter.sqlgen.update(SQL_PREFIX + str(entity.e_schema),
+                                            attrs, [SQL_PREFIX + 'eid'])
         cu.execute(sql, attrs)
-        
+
     def update_entity(self, session, entity):
         """update an entity in the source"""
         raise NotImplementedError()
-        
+
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source
 
@@ -226,22 +228,23 @@
         source. Main usage is to delete repository content when a Repository
         entity is deleted.
         """
-        sqlcursor = session.pool[self.uri]        
-        attrs = {'eid': eid}
-        sql = self.sqladapter.sqlgen.delete(etype, attrs)
+        sqlcursor = session.pool[self.uri]
+        attrs = {SQL_PREFIX + 'eid': eid}
+        sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + etype, attrs)
         sqlcursor.execute(sql, attrs)
-    
+
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         rschema = self.schema.rschema(rtype)
         if rschema.inlined:
             if subject in session.query_data('pendingeids', ()):
                 return
-            etype = session.describe(subject)[0]
-            sql = 'UPDATE %s SET %s=NULL WHERE eid=%%(eid)s' % (etype, rtype)
+            table = SQL_PREFIX + session.describe(subject)[0]
+            column = SQL_PREFIX + rtype
+            sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column, SQL_PREFIX)
             attrs = {'eid' : subject}
         else:
             attrs = {'eid_from': subject, 'eid_to': object}
             sql = self.sqladapter.sqlgen.delete('%s_relation' % rtype, attrs)
-        sqlcursor = session.pool[self.uri]        
+        sqlcursor = session.pool[self.uri]
         sqlcursor.execute(sql, attrs)
--- a/server/sources/ldapuser.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/ldapuser.py	Thu May 14 12:50:34 2009 +0200
@@ -1,9 +1,9 @@
 """cubicweb ldap user source
 
-this source is for now limited to a read-only EUser source
+this source is for now limited to a read-only CWUser source
 
 :organization: Logilab
-:copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 
@@ -20,8 +20,6 @@
 FOR A PARTICULAR PURPOSE.
 """
 
-from mx.DateTime import now, DateTimeDelta
-
 from logilab.common.textutils import get_csv
 from rql.nodes import Relation, VariableRef, Constant, Function
 
@@ -30,9 +28,10 @@
 from ldap.filter import filter_format, escape_filter_chars
 from ldapurl import LDAPUrl
 
-from cubicweb.common import AuthenticationError, UnknownEid, RepositoryError
-from cubicweb.server.sources import AbstractSource, TrFunc, GlobTrFunc, ConnectionWrapper
+from cubicweb import AuthenticationError, UnknownEid, RepositoryError
 from cubicweb.server.utils import cartesian_product
+from cubicweb.server.sources import (AbstractSource, TrFunc, GlobTrFunc,
+                                     ConnectionWrapper, TimedCache)
 
 # search scopes
 BASE = ldap.SCOPE_BASE
@@ -50,34 +49,17 @@
     2: (0,   'ldapi'),
     }
 
-class TimedCache(dict):
-    def __init__(self, ttlm, ttls=0):
-        # time to live in minutes
-        self.ttl = DateTimeDelta(0, 0, ttlm, ttls)
-        
-    def __setitem__(self, key, value):
-        dict.__setitem__(self, key, (now(), value))
-        
-    def __getitem__(self, key):
-        return dict.__getitem__(self, key)[1]
-    
-    def clear_expired(self):
-        now_ = now()
-        ttl = self.ttl
-        for key, (timestamp, value) in self.items():
-            if now_ - timestamp > ttl:
-                del self[key]
-                
+
 class LDAPUserSource(AbstractSource):
-    """LDAP read-only EUser source"""
-    support_entities = {'EUser': False} 
+    """LDAP read-only CWUser source"""
+    support_entities = {'CWUser': False}
 
     port = None
-    
+
     cnx_mode = 0
     cnx_dn = ''
     cnx_pwd = ''
-    
+
     options = (
         ('host',
          {'type' : 'string',
@@ -137,9 +119,9 @@
           'help': 'life time of query cache in minutes (default to two hours).',
           'group': 'ldap-source', 'inputlevel': 2,
           }),
-        
+
     )
-            
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         AbstractSource.__init__(self, repo, appschema, source_config,
                                 *args, **kwargs)
@@ -168,8 +150,8 @@
 
     def init(self):
         """method called by the repository once ready to handle request"""
-        self.repo.looping_task(self._interval, self.synchronize) 
-        self.repo.looping_task(self._query_cache.ttl.seconds/10, self._query_cache.clear_expired) 
+        self.repo.looping_task(self._interval, self.synchronize)
+        self.repo.looping_task(self._query_cache.ttl.seconds/10, self._query_cache.clear_expired)
 
     def synchronize(self):
         """synchronize content known by this repository with content in the
@@ -187,7 +169,7 @@
             for eid, extid in cursor.fetchall():
                 # if no result found, _search automatically delete entity information
                 res = self._search(session, extid, BASE)
-                if res: 
+                if res:
                     ldapemailaddr = res[0].get(ldap_emailattr)
                     if ldapemailaddr:
                         rset = session.execute('EmailAddress X,A WHERE '
@@ -210,15 +192,15 @@
         finally:
             session.commit()
             session.close()
-            
+
     def get_connection(self):
         """open and return a connection to the source"""
         if self._conn is None:
             self._connect()
         return ConnectionWrapper(self._conn)
-    
+
     def authenticate(self, session, login, password):
-        """return EUser eid for the given login/password if this account is
+        """return CWUser eid for the given login/password if this account is
         defined in this source, else raise `AuthenticationError`
 
         two queries are needed since passwords are stored crypted, so we have
@@ -242,14 +224,14 @@
         except:
             # Something went wrong, most likely bad credentials
             raise AuthenticationError()
-        return self.extid2eid(user['dn'], 'EUser', session)
+        return self.extid2eid(user['dn'], 'CWUser', session)
 
     def ldap_name(self, var):
         if var.stinfo['relations']:
             relname = iter(var.stinfo['relations']).next().r_type
             return self.user_rev_attrs.get(relname)
         return None
-        
+
     def prepare_columns(self, mainvars, rqlst):
         """return two list describin how to build the final results
         from the result of an ldap search (ie a list of dictionnary)
@@ -288,11 +270,11 @@
             #    # probably a bug in rql splitting if we arrive here
             #    raise NotImplementedError
         return columns, global_transforms
-    
+
     def syntax_tree_search(self, session, union,
                            args=None, cachekey=None, varmap=None, debug=0):
-        """return result from this source for a rql query (actually from a rql 
-        syntax tree and a solution dictionary mapping each used variable to a 
+        """return result from this source for a rql query (actually from a rql
+        syntax tree and a solution dictionary mapping each used variable to a
         possible type). If cachekey is given, the query necessary to fetch the
         results (but not the results themselves) may be cached using this key.
         """
@@ -312,7 +294,7 @@
         mainvars = []
         for varname in rqlst.defined_vars:
             for sol in rqlst.solutions:
-                if sol[varname] == 'EUser':
+                if sol[varname] == 'CWUser':
                     mainvars.append(varname)
                     break
         assert mainvars
@@ -344,7 +326,7 @@
             filteredres = []
             for resdict in res:
                 # get sure the entity exists in the system table
-                eid = self.extid2eid(resdict['dn'], 'EUser', session)
+                eid = self.extid2eid(resdict['dn'], 'CWUser', session)
                 for eidfilter in eidfilters:
                     if not eidfilter(eid):
                         break
@@ -379,8 +361,8 @@
             result = trfunc.apply(result)
         #print '--> ldap result', result
         return result
-                
-    
+
+
     def _connect(self, userdn=None, userpwd=None):
         port, protocol = MODES[self.cnx_mode]
         if protocol == 'ldapi':
@@ -421,7 +403,7 @@
         except ldap.PARTIAL_RESULTS:
             res = cnx.result(all=0)[1]
         except ldap.NO_SUCH_OBJECT:
-            eid = self.extid2eid(base, 'EUser', session, insert=False)
+            eid = self.extid2eid(base, 'CWUser', session, insert=False)
             if eid:
                 self.warning('deleting ldap user with eid %s and dn %s',
                              eid, base)
@@ -462,12 +444,12 @@
             result.append(rec_dict)
         #print '--->', result
         return result
-    
+
     def before_entity_insertion(self, session, lid, etype, eid):
         """called by the repository when an eid has been attributed for an
         entity stored here but the entity has not been inserted in the system
         table yet.
-        
+
         This method must return the an Entity instance representation of this
         entity.
         """
@@ -476,7 +458,7 @@
         for attr in entity.e_schema.indexable_attributes():
             entity[attr] = res[self.user_rev_attrs[attr]]
         return entity
-    
+
     def after_entity_insertion(self, session, dn, entity):
         """called by the repository after an entity stored here has been
         inserted in the system table.
@@ -510,13 +492,13 @@
 def _insert_email(session, emailaddr, ueid):
     session.execute('INSERT EmailAddress X: X address %(addr)s, U primary_email X '
                     'WHERE U eid %(x)s', {'addr': emailaddr, 'x': ueid}, 'x')
-    
+
 class GotDN(Exception):
     """exception used when a dn localizing the searched user has been found"""
     def __init__(self, dn):
         self.dn = dn
 
-        
+
 class RQL2LDAPFilter(object):
     """generate an LDAP filter for a rql query"""
     def __init__(self, source, session, args=None, mainvars=()):
@@ -528,7 +510,7 @@
             args = {}
         self._args = args
         self.mainvars = mainvars
-        
+
     def generate(self, selection, mainvarname):
         self._filters = res = self._base_filters[:]
         self._mainvarname = mainvarname
@@ -545,7 +527,7 @@
         if len(res) > 1:
             return self._eidfilters, '(&%s)' % ''.join(res)
         return self._eidfilters, res[0]
-    
+
     def visit_and(self, et):
         """generate filter for a AND subtree"""
         for c in et.children:
@@ -605,7 +587,7 @@
         else:
             raise NotImplementedError(relation)
         return res
-        
+
     def _visit_attribute_relation(self, relation):
         """generate filter for an attribute relation"""
         lhs, rhs = relation.get_parts()
@@ -641,18 +623,18 @@
 
     def visit_comparison(self, cmp):
         """generate filter for a comparaison"""
-        return '%s%s'% (cmp.operator, cmp.children[0].accept(self))            
+        return '%s%s'% (cmp.operator, cmp.children[0].accept(self))
 
     def visit_mathexpression(self, mexpr):
         """generate filter for a mathematic expression"""
         raise NotImplementedError
-        
+
     def visit_function(self, function):
         """generate filter name for a function"""
         if function.name == 'IN':
             return self.visit_in(function)
         raise NotImplementedError
-        
+
     def visit_in(self, function):
         grandpapa = function.parent.parent
         ldapattr = self._ldap_attrs[grandpapa.r_type]
@@ -667,7 +649,7 @@
             else:
                 part = '(%s=%s)' % (ldapattr, res[0])
         return part
-        
+
     def visit_constant(self, constant):
         """generate filter name for a constant"""
         value = constant.value
@@ -685,7 +667,7 @@
         else:
             value = str(value)
         return escape_filter_chars(value)
-        
+
     def visit_variableref(self, variableref):
         """get the sql name for a variable reference"""
         pass
--- a/server/sources/native.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/native.py	Thu May 14 12:50:34 2009 +0200
@@ -1,14 +1,13 @@
 """Adapters for native cubicweb sources.
 
 :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 threading import Lock
-
-from mx.DateTime import now
+from datetime import datetime
 
 from logilab.common.cache import Cache
 from logilab.common.configuration import REQUIRED
@@ -18,7 +17,7 @@
 
 from cubicweb import UnknownEid, AuthenticationError, Binary, server
 from cubicweb.server.utils import crypt_password
-from cubicweb.server.sqlutils import SQLAdapterMixIn
+from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
 from cubicweb.server.rqlannotation import set_qdata
 from cubicweb.server.sources import AbstractSource
 from cubicweb.server.sources.rql2sql import SQLGenerator
@@ -30,7 +29,7 @@
 class LogCursor(object):
     def __init__(self, cursor):
         self.cu = cursor
-        
+
     def execute(self, query, args=None):
         """Execute a query.
         it's a function just so that it shows up in profiling
@@ -43,13 +42,13 @@
             print "sql: %r\n args: %s\ndbms message: %r" % (
                 query, args, ex.args[0])
             raise
-        
+
     def fetchall(self):
         return self.cu.fetchall()
-        
+
     def fetchone(self):
         return self.cu.fetchone()
-    
+
 def make_schema(selected, solution, table, typemap):
     """return a sql schema to store RQL query result"""
     sql = []
@@ -85,11 +84,11 @@
     """
     # need default value on class since migration doesn't call init method
     has_deleted_entitites_table = True
-    
-    passwd_rql = "Any P WHERE X is EUser, X login %(login)s, X upassword P"
-    auth_rql = "Any X WHERE X is EUser, X login %(login)s, X upassword %(pwd)s"
-    _sols = ({'X': 'EUser', 'P': 'Password'},)
-    
+
+    passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
+    auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
+    _sols = ({'X': 'CWUser', 'P': 'Password'},)
+
     options = (
         ('db-driver',
          {'type' : 'string',
@@ -128,7 +127,7 @@
           'group': 'native-source', 'inputlevel': 1,
           }),
     )
-    
+
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
         SQLAdapterMixIn.__init__(self, source_config)
         AbstractSource.__init__(self, repo, appschema, source_config,
@@ -151,18 +150,18 @@
     def reset_caches(self):
         """method called during test to reset potential source caches"""
         self._cache = Cache(self.repo.config['rql-cache-size'])
-    
+
     def clear_eid_cache(self, eid, etype):
         """clear potential caches for the given eid"""
         self._cache.pop('%s X WHERE X eid %s' % (etype, eid), None)
         self._cache.pop('Any X WHERE X eid %s' % eid, None)
-        
+
     def sqlexec(self, session, sql, args=None):
         """execute the query and return its result"""
         cursor = session.pool[self.uri]
         self.doexec(cursor, sql, args)
         return self.process_result(cursor)
-    
+
     def init_creating(self):
         # check full text index availibility
         pool = self.repo._get_pool()
@@ -172,7 +171,7 @@
         self.repo._free_pool(pool)
 
     def init(self):
-        self.init_creating() 
+        self.init_creating()
         pool = self.repo._get_pool()
         # XXX cubicweb < 2.42 compat
         if 'deleted_entities' in self.dbhelper.list_tables(pool['system']):
@@ -180,7 +179,7 @@
         else:
             self.has_deleted_entitites_table = False
         self.repo._free_pool(pool)
-        
+
     # ISource interface #######################################################
 
     def compile_rql(self, rql):
@@ -190,7 +189,7 @@
         self.repo.querier.sqlgen_annotate(rqlst)
         set_qdata(self.schema.rschema, rqlst, ())
         return rqlst
-    
+
     def set_schema(self, schema):
         """set the application'schema"""
         self._cache = Cache(self.repo.config['rql-cache-size'])
@@ -200,17 +199,17 @@
             self._rql_sqlgen.schema = schema
         except AttributeError:
             pass # __init__
-        if 'EUser' in schema: # probably an empty schema if not true...
+        if 'CWUser' in schema: # probably an empty schema if not true...
             # rql syntax trees used to authenticate users
             self._passwd_rqlst = self.compile_rql(self.passwd_rql)
             self._auth_rqlst = self.compile_rql(self.auth_rql)
-                
+
     def support_entity(self, etype, write=False):
         """return true if the given entity's type is handled by this adapter
         if write is true, return true only if it's a RW support
         """
         return not etype in NONSYSTEM_ETYPES
-    
+
     def support_relation(self, rtype, write=False):
         """return true if the given relation's type is handled by this adapter
         if write is true, return true only if it's a RW support
@@ -218,11 +217,11 @@
         if write:
             return not rtype in NONSYSTEM_RELATIONS
         # due to current multi-sources implementation, the system source
-        # can't claim not supporting a relation            
+        # can't claim not supporting a relation
         return True #not rtype == 'content_for'
 
     def authenticate(self, session, login, password):
-        """return EUser eid for the given login/password if this account is
+        """return CWUser eid for the given login/password if this account is
         defined in this source, else raise `AuthenticationError`
 
         two queries are needed since passwords are stored crypted, so we have
@@ -244,8 +243,8 @@
             return rset[0][0]
         except IndexError:
             raise AuthenticationError('bad password')
-    
-    def syntax_tree_search(self, session, union, args=None, cachekey=None, 
+
+    def syntax_tree_search(self, session, union, args=None, cachekey=None,
                            varmap=None):
         """return result from this source for a rql query (actually from
         a rql syntax tree and a solution dictionary mapping each used
@@ -290,7 +289,7 @@
         if server.DEBUG:
             print '------>', res
         return res
-                
+
     def flying_insert(self, table, session, union, args=None, varmap=None):
         """similar as .syntax_tree_search, but inserts data in the
         temporary table (on-the-fly if possible, eg for the system
@@ -320,7 +319,7 @@
         else:
             super(NativeSQLSource, self).flying_insert(table, session, union,
                                                        args, varmap)
-        
+
     def _manual_insert(self, results, table, session):
         """insert given result into a temporary table on the system source"""
         #print 'manual insert', table, results
@@ -363,23 +362,23 @@
                     del self._temp_table_data[table]
                 except KeyError:
                     continue
-    
+
     def add_entity(self, session, entity):
         """add a new entity to the source"""
         attrs = self.preprocess_entity(entity)
-        sql = self.sqlgen.insert(str(entity.e_schema), attrs)
+        sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
         self.doexec(session.pool[self.uri], sql, attrs)
-        
+
     def update_entity(self, session, entity):
         """replace an entity in the source"""
         attrs = self.preprocess_entity(entity)
-        sql = self.sqlgen.update(str(entity.e_schema), attrs, ['eid'])
+        sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, [SQL_PREFIX + 'eid'])
         self.doexec(session.pool[self.uri], sql, attrs)
 
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source"""
-        attrs = {'eid': eid}
-        sql = self.sqlgen.delete(etype, attrs)
+        attrs = {SQL_PREFIX + 'eid': eid}
+        sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs)
         self.doexec(session.pool[self.uri], sql, attrs)
 
     def add_relation(self, session, subject, rtype, object):
@@ -387,18 +386,20 @@
         attrs = {'eid_from': subject, 'eid_to': object}
         sql = self.sqlgen.insert('%s_relation' % rtype, attrs)
         self.doexec(session.pool[self.uri], sql, attrs)
-    
+
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         rschema = self.schema.rschema(rtype)
         if rschema.inlined:
-            etype = session.describe(subject)[0]
-            sql = 'UPDATE %s SET %s=NULL WHERE eid=%%(eid)s' % (etype, rtype)
+            table = SQL_PREFIX + session.describe(subject)[0]
+            column = SQL_PREFIX + rtype
+            sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column,
+                                                                  SQL_PREFIX)
             attrs = {'eid' : subject}
         else:
             attrs = {'eid_from': subject, 'eid_to': object}
             sql = self.sqlgen.delete('%s_relation' % rtype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)    
+        self.doexec(session.pool[self.uri], sql, attrs)
 
     def doexec(self, cursor, query, args=None):
         """Execute a query.
@@ -416,7 +417,7 @@
             self.critical("sql: %r\n args: %s\ndbms message: %r",
                           query, args, ex.args[0])
             raise
-        
+
     def doexecmany(self, cursor, query, args):
         """Execute a query.
         it's a function just so that it shows up in profiling
@@ -432,13 +433,13 @@
         except:
             self.critical("sql many: %r\n args: %s", query, args)
             raise
-        
+
     # short cut to method requiring advanced db helper usage ##################
-            
+
     def create_index(self, session, table, column, unique=False):
         cursor = LogCursor(session.pool[self.uri])
         self.dbhelper.create_index(cursor, table, column, unique)
-            
+
     def drop_index(self, session, table, column, unique=False):
         cursor = LogCursor(session.pool[self.uri])
         self.dbhelper.drop_index(cursor, table, column, unique)
@@ -465,16 +466,16 @@
                                     {'x': str(lid), 's': source.uri})
         # XXX testing rowcount cause strange bug with sqlite, results are there
         #     but rowcount is 0
-        #if cursor.rowcount > 0: 
+        #if cursor.rowcount > 0:
         try:
             result = cursor.fetchone()
             if result:
                 eid = result[0]
-                return eid            
+                return eid
         except:
             pass
         return None
-    
+
     def temp_table_def(self, selected, sol, table):
         return make_schema(selected, sol, table, self.dbhelper.TYPE_MAPPING)
 
@@ -484,7 +485,7 @@
         # on commit
         sql = self.dbhelper.sql_temporary_table(table, schema, False)
         self.doexec(session.pool[self.uri], sql)
-    
+
     def create_eid(self, session):
         self._eid_creation_lock.acquire()
         try:
@@ -499,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):
@@ -510,9 +511,9 @@
         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):
         """remove text content for entity with the given eid from the full text
         index
@@ -522,7 +523,7 @@
         except:
             if self.indexer is not None:
                 self.exception('error while unindexing %s', eid)
-        
+
     def fti_index_entity(self, session, entity):
         """add text content of a created/modified entity to the full text index
         """
@@ -534,9 +535,9 @@
             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):
         """return a 2-uple:
         * list of (etype, eid) of entities of the given types which have been
--- a/server/sources/pyrorql.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/pyrorql.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Source to query another RQL repository using pyro
 
 :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"
@@ -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
 
@@ -21,24 +22,24 @@
 from cubicweb import dbapi, server
 from cubicweb import BadConnectionId, UnknownEid, ConnectionError
 from cubicweb.cwconfig import register_persistent_options
-from cubicweb.server.sources import AbstractSource, ConnectionWrapper
+from cubicweb.server.sources import AbstractSource, ConnectionWrapper, TimedCache
 
-class ReplaceByInOperator:
+class ReplaceByInOperator(Exception):
     def __init__(self, eids):
         self.eids = eids
-        
+
 class PyroRQLSource(AbstractSource):
     """External repository source, using Pyro connection"""
-    
+
     # boolean telling if modification hooks should be called when something is
     # modified in this source
     should_call_hooks = False
     # boolean telling if the repository should connect to this source during
     # migration
     connect_for_migration = False
-    
+
     support_entities = None
-    
+
     options = (
         # XXX pyro-ns host/port
         ('pyro-ns-id',
@@ -100,7 +101,7 @@
 repository (default to 5 minutes).',
           'group': 'pyro-source', 'inputlevel': 2,
           }),
-        
+
     )
 
     PUBLIC_KEYS = AbstractSource.PUBLIC_KEYS + ('base-url',)
@@ -126,33 +127,35 @@
                       {'type' : 'int', 'sitewide': True,
                        'default': 0,
                        'help': _('timestamp of the latest source synchronization.'),
-                       'group': 'sources', 
+                       'group': 'sources',
                        }),)
         register_persistent_options(myoptions)
+        self._query_cache = TimedCache(30)
 
     def last_update_time(self):
         pkey = u'sources.%s.latest-update-time' % self.uri
-        rql = 'Any V WHERE X is EProperty, X value V, X pkey %(k)s'
+        rql = 'Any V WHERE X is CWProperty, X value V, X pkey %(k)s'
         session = self.repo.internal_session()
         try:
             rset = session.execute(rql, {'k': pkey})
             if not rset:
                 # insert it
-                session.execute('INSERT EProperty X: X pkey %(k)s, X value %(v)s',
+                session.execute('INSERT CWProperty X: X pkey %(k)s, X value %(v)s',
                                 {'k': pkey, 'v': u'0'})
                 session.commit()
                 timestamp = 0
             else:
                 assert len(rset) == 1
                 timestamp = int(rset[0][0])
-            return DateTimeFromTicks(timestamp)
+            return datetime.fromtimestamp(timestamp)
         finally:
             session.close()
 
     def init(self):
         """method called by the repository once ready to handle request"""
         interval = int(self.config.get('synchronization-interval', 5*60))
-        self.repo.looping_task(interval, self.synchronize) 
+        self.repo.looping_task(interval, self.synchronize)
+        self.repo.looping_task(self._query_cache.ttl.seconds/10, self._query_cache.clear_expired)
 
     def synchronize(self, mtime=None):
         """synchronize content known by this repository with content in the
@@ -166,6 +169,7 @@
             mtime = self.last_update_time()
         updatetime, modified, deleted = extrepo.entities_modified_since(etypes,
                                                                         mtime)
+        self._query_cache.clear()
         repo = self.repo
         session = repo.internal_session()
         try:
@@ -194,11 +198,11 @@
                     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()
-                
+
     def _get_connection(self):
         """open and return a connection to the source"""
         nshost = self.config.get('pyro-ns-host') or self.repo.config['pyro-ns-host']
@@ -218,7 +222,7 @@
             self.critical("can't get connection to source %s", self.uri,
                           exc_info=1)
             return ConnectionWrapper()
-        
+
     def check_connection(self, cnx):
         """check connection validity, return None if the connection is still valid
         else a new connection
@@ -239,12 +243,21 @@
                 pass
         # try to reconnect
         return self.get_connection()
-        
-    
+
     def syntax_tree_search(self, session, union, args=None, cachekey=None,
                            varmap=None):
-        """return result from this source for a rql query (actually from a rql 
-        syntax tree and a solution dictionary mapping each used variable to a 
+        #assert not varmap, (varmap, union)
+        rqlkey = union.as_string(kwargs=args)
+        try:
+            results = self._query_cache[rqlkey]
+        except KeyError:
+            results = self._syntax_tree_search(session, union, args)
+            self._query_cache[rqlkey] = results
+        return results
+
+    def _syntax_tree_search(self, session, union, args):
+        """return result from this source for a rql query (actually from a rql
+        syntax tree and a solution dictionary mapping each used variable to a
         possible type). If cachekey is given, the query necessary to fetch the
         results (but not the results themselves) may be cached using this key.
         """
@@ -318,23 +331,25 @@
             relations.append('X %s %%(%s)s' % (key, key))
             kwargs[key] = val
         return relations, kwargs
-    
+
     def add_entity(self, session, entity):
         """add a new entity to the source"""
         raise NotImplementedError()
-        
+
     def update_entity(self, session, entity):
         """update an entity in the source"""
         relations, kwargs = self._entity_relations_and_kwargs(session, entity)
         cu = session.pool[self.uri]
         cu.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
                    kwargs, 'x')
+        self._query_cache.clear()
 
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source"""
         cu = session.pool[self.uri]
         cu.execute('DELETE %s X WHERE X eid %%(x)s' % etype,
                    {'x': self.eid2extid(eid, session)}, 'x')
+        self._query_cache.clear()
 
     def add_relation(self, session, subject, rtype, object):
         """add a relation to the source"""
@@ -342,13 +357,15 @@
         cu.execute('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
                    {'x': self.eid2extid(subject, session),
                     'y': self.eid2extid(object, session)}, ('x', 'y'))
-    
+        self._query_cache.clear()
+
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         cu = session.pool[self.uri]
         cu.execute('DELETE X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
                    {'x': self.eid2extid(subject, session),
                     'y': self.eid2extid(object, session)}, ('x', 'y'))
+        self._query_cache.clear()
 
 
 class RQL2RQL(object):
@@ -356,7 +373,7 @@
     def __init__(self, source):
         self.source = source
         self.current_operator = None
-        
+
     def _accept_children(self, node):
         res = []
         for child in node.children:
@@ -364,20 +381,20 @@
             if rql is not None:
                 res.append(rql)
         return res
-        
+
     def generate(self, session, rqlst, args):
-        self._session = session 
+        self._session = session
         self.kwargs = args
         self.cachekey = []
         self.need_translation = False
         return self.visit_union(rqlst), self.cachekey
-    
+
     def visit_union(self, node):
         s = self._accept_children(node)
         if len(s) > 1:
             return ' UNION '.join('(%s)' % q for q in s)
         return s[0]
-    
+
     def visit_select(self, node):
         """return the tree as an encoded rql string"""
         self._varmaker = rqlvar_maker(defined=node.defined_vars.copy())
@@ -404,7 +421,7 @@
                 restrictions.append(nr)
         if restrictions:
             s.append('WHERE %s' % ','.join(restrictions))
-        
+
         if node.having:
             s.append('HAVING %s' % ', '.join(term.accept(self)
                                              for term in node.having))
@@ -415,13 +432,13 @@
         if subqueries:
             s.append('WITH %s' % (','.join(subqueries)))
         return ' '.join(s)
-    
+
     def visit_and(self, node):
         res = self._accept_children(node)
         if res:
             return ', '.join(res)
         return
-    
+
     def visit_or(self, node):
         res = self._accept_children(node)
         if len(res) > 1:
@@ -429,16 +446,16 @@
         elif res:
             return res[0]
         return
-    
+
     def visit_not(self, node):
         rql = node.children[0].accept(self)
         if rql:
             return 'NOT (%s)' % rql
         return
-    
+
     def visit_exists(self, node):
         return 'EXISTS(%s)' % node.children[0].accept(self)
-        
+
     def visit_relation(self, node):
         try:
             if isinstance(node.children[0], Constant):
@@ -485,18 +502,18 @@
         if restr is not None:
             return '%s %s %s, %s' % (lhs, node.r_type, rhs, restr)
         return '%s %s %s' % (lhs, node.r_type, rhs)
-        
+
     def visit_comparison(self, node):
         if node.operator in ('=', 'IS'):
             return node.children[0].accept(self)
         return '%s %s' % (node.operator.encode(),
                           node.children[0].accept(self))
-            
+
     def visit_mathexpression(self, node):
         return '(%s %s %s)' % (node.children[0].accept(self),
                                node.operator.encode(),
                                node.children[1].accept(self))
-        
+
     def visit_function(self, node):
         #if node.name == 'IN':
         res = []
@@ -509,7 +526,7 @@
         if not res:
             raise ex
         return '%s(%s)' % (node.name, ', '.join(res))
-        
+
     def visit_constant(self, node):
         if self.need_translation or node.uidtype:
             if node.type == 'Int':
@@ -546,7 +563,7 @@
 
     def eid2extid(self, eid):
         try:
-            return self.source.eid2extid(eid, self._session)        
+            return self.source.eid2extid(eid, self._session)
         except UnknownEid:
             operator = self.current_operator
             if operator is not None and operator != '=':
@@ -571,4 +588,4 @@
                 if rows:
                     raise ReplaceByInOperator((r[0] for r in rows))
             raise
-                
+
--- a/server/sources/rql2sql.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sources/rql2sql.py	Thu May 14 12:50:34 2009 +0200
@@ -3,21 +3,21 @@
 
 SQL queries optimization
 ~~~~~~~~~~~~~~~~~~~~~~~~
-1. EUser X WHERE X in_group G, G name 'users':
+1. CWUser X WHERE X in_group G, G name 'users':
 
-   EUser is the only subject entity type for the in_group relation,
+   CWUser is the only subject entity type for the in_group relation,
    which allow us to do ::
 
-     SELECT eid_from FROM in_group, EGroup
-     WHERE in_group.eid_to = EGroup.eid_from
-     AND EGroup.name = 'users'
+     SELECT eid_from FROM in_group, CWGroup
+     WHERE in_group.eid_to = CWGroup.eid_from
+     AND CWGroup.name = 'users'
 
 
 2. Any X WHERE X nonfinal1 Y, Y nonfinal2 Z
 
    -> direct join between nonfinal1 and nonfinal2, whatever X,Y, Z (unless
       inlined...)
-      
+
       NOT IMPLEMENTED (and quite hard to implement)
 
 Potential optimization information is collected by the querier, sql generation
@@ -25,7 +25,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"
@@ -38,9 +38,10 @@
                        Variable, ColumnAlias, Relation, SubQuery, Exists)
 
 from cubicweb import server
+from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import cleanup_solutions
 
-def _new_var(select, varname): 
+def _new_var(select, varname):
     newvar = select.get_variable(varname)
     if not 'relations' in newvar.stinfo:
         # not yet initialized
@@ -60,7 +61,7 @@
                 _fill_to_wrap_rel(vref.variable, newselect, towrap, schema)
         elif rschema.is_final():
             towrap.add( (var, rel) )
-   
+
 def rewrite_unstable_outer_join(select, solutions, unstable, schema):
     """if some optional variables are unstable, they should be selected in a
     subquery. This function check this and rewrite the rql syntax tree if
@@ -103,7 +104,7 @@
                 var.stinfo['relations'].add(newrel)
                 var.stinfo['rhsrelations'].add(newrel)
                 if rel.optional in ('right', 'both'):
-                    var.stinfo['optrelations'].add(newrel)                
+                    var.stinfo['optrelations'].add(newrel)
         # extract subquery solutions
         solutions = [sol.copy() for sol in solutions]
         cleanup_solutions(newselect, solutions)
@@ -204,7 +205,7 @@
                 for vref in term.iget_nodes(VariableRef):
                     if not vref in groups:
                         groups.append(vref)
-        
+
 def fix_selection(rqlst, selectedidx, needwrap, sorts, groups, having):
     if sorts:
         sort_term_selection(sorts, selectedidx, rqlst, not needwrap and groups)
@@ -229,7 +230,7 @@
         self.existssols = existssols
         self.unstablevars = unstablevars
         self.subtables = {}
-        
+
     def reset(self, solution):
         """reset some visit variables"""
         self.solution = solution
@@ -245,11 +246,11 @@
         self.aliases = {}
         self.restrictions = []
         self._restr_stack = []
-        
+
     def add_restriction(self, restr):
         if restr:
             self.restrictions.append(restr)
-            
+
     def iter_exists_sols(self, exists):
         if not exists in self.existssols:
             yield 1
@@ -285,8 +286,8 @@
         restrictions = self.restrictions
         self.restrictions = self._restr_stack.pop()
         return restrictions, self.actual_tables.pop()
-    
-    
+
+
 class SQLGenerator(object):
     """
     generation of SQL from the fully expanded RQL syntax tree
@@ -294,13 +295,13 @@
 
     Groups and sort are not handled here since they should not be handled at
     this level (see cubicweb.server.querier)
-    
+
     we should not have errors here !
 
     WARNING: a CubicWebSQLGenerator instance is not thread safe, but generate is
     protected by a lock
     """
-    
+
     def __init__(self, schema, dbms_helper, dbencoding='UTF-8'):
         self.schema = schema
         self.dbms_helper = dbms_helper
@@ -311,7 +312,7 @@
         if not self.dbms_helper.union_parentheses_support:
             self.union_sql = self.noparen_union_sql
         self._lock = threading.Lock()
-        
+
     def generate(self, union, args=None, varmap=None):
         """return SQL queries and a variable dictionnary from a RQL syntax tree
 
@@ -354,7 +355,7 @@
         sqls = (self.select_sql(select, needalias)
                 for i, select in enumerate(union.children))
         return '\nUNION ALL\n'.join(sqls)
-    
+
     def select_sql(self, select, needalias=False):
         """return SQL queries and a variable dictionnary from a RQL syntax tree
 
@@ -387,7 +388,7 @@
                 # query will be necessary
                 if groups or select.has_aggregat:
                     select.select_only_variables()
-                    needwrap = True                        
+                    needwrap = True
         else:
             existssols, unstable = {}, ()
         state = StateInfo(existssols, unstable)
@@ -440,7 +441,7 @@
                 sql += '\nHAVING %s' % having
             # sort
             if sorts:
-                sql += '\nORDER BY %s' % ','.join(self._sortterm_sql(sortterm, 
+                sql += '\nORDER BY %s' % ','.join(self._sortterm_sql(sortterm,
                                                                      fselectidx)
                                                   for sortterm in sorts)
                 if fneedwrap:
@@ -488,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)
@@ -497,7 +497,7 @@
             return '\nUNION\n'.join(sqls)
         else:
             return '\nUNION ALL\n'.join(sqls)
-        
+
     def _selection_sql(self, selected, distinct, needaliasing=False):
         clause = []
         for term in selected:
@@ -546,7 +546,7 @@
                 return '(%s)' % ' OR '.join(res)
             return res[0]
         return ''
-    
+
     def visit_not(self, node):
         self._state.push_scope()
         csql = node.children[0].accept(self)
@@ -581,7 +581,7 @@
         if not sqls:
             return ''
         return 'EXISTS(%s)' % ' UNION '.join(sqls)
-            
+
     def _visit_exists(self, exists):
         self._state.push_scope()
         restriction = exists.children[0].accept(self)
@@ -593,12 +593,12 @@
             return ''
         if not tables:
             # XXX could leave surrounding EXISTS() in this case no?
-            sql = 'SELECT 1 WHERE %s' % restriction 
+            sql = 'SELECT 1 WHERE %s' % restriction
         else:
             sql = 'SELECT 1 FROM %s WHERE %s' % (', '.join(tables), restriction)
         return sql
 
-    
+
     def visit_relation(self, relation):
         """generate SQL for a relation"""
         rtype = relation.r_type
@@ -691,7 +691,7 @@
             extrajoin = self._extra_join_sql(relation, '%s.%s' % (rid, relfield), termvar)
             if extrajoin:
                 yield extrajoin
-        
+
     def _visit_relation(self, relation, rschema):
         """generate SQL for a relation
 
@@ -718,10 +718,10 @@
         """
         left outer join syntax (optional=='right'):
           X relation Y?
-          
+
         right outer join syntax (optional=='left'):
           X? relation Y
-          
+
         full outer join syntaxes (optional=='both'):
           X? relation Y?
 
@@ -765,7 +765,7 @@
             if '%s.%s' % (lhs.name, attr) in self._varmap:
                 lhssql = self._varmap['%s.%s' % (lhs.name, attr)]
             else:
-                lhssql = '%s.%s' % (self._var_table(lhs.variable), attr)
+                lhssql = '%s.%s%s' % (self._var_table(lhs.variable), SQL_PREFIX, attr)
             if not rhsvar is None:
                 t2 = self._var_table(rhsvar)
                 if t2 is None:
@@ -806,7 +806,8 @@
             if rhstable:
                 assert rhstable is not None, rhsvar
                 join += ' %s OUTER JOIN %s ON (%s.%s=%s)' % (
-                    outertype, self._state.tables[rhstable][1], rid, restrattr, rhssql)
+                    outertype, self._state.tables[rhstable][1], rid, restrattr,
+                    rhssql)
                 toreplace.append(rhstable)
         self.replace_tables_by_outer_join(join, maintable, *toreplace)
         return ''
@@ -833,7 +834,7 @@
         lhssql = self._inlined_var_sql(relation.children[0].variable,
                                        relation.r_type)
         return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels))
-    
+
     def _visit_attribute_relation(self, relation):
         """generate SQL for an attribute relation"""
         lhs, rhs = relation.get_parts()
@@ -846,7 +847,10 @@
             try:
                 lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)]
             except KeyError:
-                lhssql = '%s.%s' % (table, relation.r_type)
+                if relation.r_type == 'eid':
+                    lhssql = lhs.variable._q_sql
+                else:
+                    lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type)
         try:
             if relation._q_needcast == 'TODAY':
                 sql = 'DATE(%s)%s' % (lhssql, rhssql)
@@ -893,7 +897,7 @@
             not_ = False
         return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args),
                                                     jointo, not_) + restriction
-        
+
     def visit_comparison(self, cmp, contextrels=None):
         """generate SQL for a comparaison"""
         if len(cmp.children) == 2:
@@ -914,7 +918,7 @@
             return '%s%s'% (operator, rhs.accept(self, contextrels))
         return '%s%s%s'% (lhs.accept(self, contextrels), operator,
                           rhs.accept(self, contextrels))
-            
+
     def visit_mathexpression(self, mexpr, contextrels=None):
         """generate SQL for a mathematic expression"""
         lhs, rhs = mexpr.get_parts()
@@ -927,13 +931,13 @@
             pass
         return '(%s %s %s)'% (lhs.accept(self, contextrels), operator,
                               rhs.accept(self, contextrels))
-        
+
     def visit_function(self, func, contextrels=None):
         """generate SQL name for a function"""
         # function_description will check function is supported by the backend
-        self.dbms_helper.function_description(func.name) 
-        return '%s(%s)' % (func.name, ', '.join(c.accept(self, contextrels)
-                                                for c in func.children))
+        sqlname = self.dbms_helper.func_sqlname(func.name)
+        return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels)
+                                              for c in func.children))
 
     def visit_constant(self, constant, contextrels=None):
         """generate SQL name for a constant"""
@@ -947,6 +951,8 @@
             if rel is not None:
                 rel._q_needcast = value
             return self.keyword_map[value]()
+        if constant.type == 'Boolean':
+            value = self.dbms_helper.boolean_value(value)
         if constant.type == 'Substitute':
             _id = constant.value
             if isinstance(_id, unicode):
@@ -957,7 +963,7 @@
                 value = value.encode(self.dbencoding)
             self._query_attrs[_id] = value
         return '%%(%s)s' % _id
-        
+
     def visit_variableref(self, variableref, contextrels=None):
         """get the sql name for a variable reference"""
         # use accept, .variable may be a variable or a columnalias
@@ -973,7 +979,7 @@
             self.add_table(table)
             return sql
         return colalias._q_sql
-    
+
     def visit_variable(self, variable, contextrels=None):
         """get the table name and sql string for a variable"""
         if contextrels is None and variable.name in self._state.done:
@@ -983,7 +989,7 @@
         self._state.done.add(variable.name)
         vtablename = None
         if contextrels is None and variable.name in self._varmap:
-            sql, vtablename = self._var_info(variable)            
+            sql, vtablename = self._var_info(variable)
         elif variable.stinfo['attrvar']:
             # attribute variable (systematically used in rhs of final
             # relation(s)), get table name and sql from any rhs relation
@@ -993,7 +999,7 @@
             principal = variable.stinfo['principal']
             if principal is None:
                 vtablename = variable.name
-                self.add_table('entities AS %s' % variable.name, vtablename)
+                self.add_table('entities AS %s' % vtablename, vtablename)
                 sql = '%s.eid' % vtablename
                 if variable.stinfo['typerels']:
                     # add additional restriction on entities.type column
@@ -1037,7 +1043,7 @@
             # so nothing to return here
             pass
         return ''
-    
+
     def _var_info(self, var):
         # if current var or one of its attribute is selected , it *must*
         # appear in the toplevel's FROM even if we're currently visiting
@@ -1058,27 +1064,27 @@
             if self.schema.eschema(etype).is_final():
                 raise BadRQLQuery(var.stmt.root)
             table = var.name
-            sql = '%s.eid' % table
-            self.add_table('%s AS %s' % (etype, table), table, scope=scope)
+            sql = '%s.%seid' % (table, SQL_PREFIX)
+            self.add_table('%s%s AS %s' % (SQL_PREFIX, etype, table), table, scope=scope)
         return sql, table
-    
+
     def _inlined_var_sql(self, var, rtype):
         try:
             sql = self._varmap['%s.%s' % (var.name, rtype)]
             scope = var.sqlscope is var.stmt and 0 or -1
             self.add_table(sql.split('.', 1)[0], scope=scope)
         except KeyError:
-            sql = '%s.%s' % (self._var_table(var), rtype)
+            sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype)
             #self._state.done.add(var.name)
         return sql
-        
+
     def _linked_var_sql(self, variable, contextrels=None):
         if contextrels is None:
             try:
-                return self._varmap[variable.name]            
+                return self._varmap[variable.name]
             except KeyError:
                 pass
-        rel = (contextrels and contextrels.get(variable.name) or 
+        rel = (contextrels and contextrels.get(variable.name) or
                variable.stinfo.get('principal') or
                iter(variable.stinfo['rhsrelations']).next())
         linkedvar = rel.children[0].variable
@@ -1090,8 +1096,8 @@
         try:
             sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)]
         except KeyError:
-            linkedvar.accept(self)            
-            sql = '%s.%s' % (linkedvar._q_sqltable, rel.r_type)
+            linkedvar.accept(self)
+            sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type)
         return sql
 
     # tables handling #########################################################
@@ -1101,7 +1107,7 @@
         self._state.count += 1
         self.add_table('%s AS %s' % (tablename, alias), alias)
         return alias
-        
+
     def add_table(self, table, key=None, scope=-1):
         if key is None:
             key = table
@@ -1109,7 +1115,7 @@
             return
         self._state.tables[key] = (len(self._state.actual_tables) - 1, table)
         self._state.actual_tables[scope].append(table)
-    
+
     def replace_tables_by_outer_join(self, substitute, lefttable, *tables):
         for table in tables:
             try:
@@ -1154,8 +1160,8 @@
         for table, outerexpr in self._state.outer_tables.iteritems():
             if outerexpr == oldalias:
                 self._state.outer_tables[table] = newalias
-        self._state.outer_tables[table] = newalias        
-        
+        self._state.outer_tables[table] = newalias
+
     def _var_table(self, var):
         var.accept(self)#.visit_variable(var)
         return var._q_sqltable
@@ -1167,7 +1173,7 @@
         assert not self.schema.rschema(relation.r_type).is_final(), relation.r_type
         rid = 'rel_%s%s' % (relation.r_type, self._state.count)
         # relation's table is belonging to the root scope if it is the principal
-        # table of one of it's variable and if that variable belong's to parent 
+        # table of one of it's variable and if that variable belong's to parent
         # scope
         for varref in relation.iget_nodes(VariableRef):
             var = varref.variable
@@ -1186,7 +1192,7 @@
         relation._q_sqltable = rid
         self._state.done.add(relation)
         return rid
-    
+
     def _fti_table(self, relation):
         if relation in self._state.done:
             try:
@@ -1197,7 +1203,7 @@
         alias = self.alias_and_add_table(self.dbms_helper.fti_table)
         relation._q_sqltable = alias
         return alias
-        
+
     def _varmap_table_scope(self, select, table):
         """since a varmap table may be used for multiple variable, its scope is
         the most outer scope of each variables
--- a/server/sqlutils.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/sqlutils.py	Thu May 14 12:50:34 2009 +0200
@@ -1,22 +1,31 @@
 """SQL utilities functions and classes.
 
 :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 warnings import warn
+from datetime import datetime, date, timedelta
+
+import logilab.common as lgc
 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
 
 from indexer import get_indexer
 
 from cubicweb import Binary, ConfigurationError
+from cubicweb.utils import todate, todatetime
 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
+
+
+lgc.USE_MX_DATETIME = False
+SQL_PREFIX = 'cw_'
 
 
 def sqlexec(sqlstmts, cursor_or_execute, withpb=True, delimiter=';'):
@@ -58,11 +67,11 @@
         indexer = get_indexer(driver)
         w(indexer.sql_grant_user(user))
         w('')
-    w(grant_schema(schema, user, set_owner, skip_entities=skip_entities))
+    w(grant_schema(schema, user, set_owner, skip_entities=skip_entities, prefix=SQL_PREFIX))
     return '\n'.join(output)
 
-                  
-def sqlschema(schema, driver, text_index=True, 
+
+def sqlschema(schema, driver, text_index=True,
               user=None, set_owner=False,
               skip_relations=('has_text', 'identity'), skip_entities=()):
     """return the system sql schema, according to the given parameters"""
@@ -79,7 +88,7 @@
         w(indexer.sql_init_fti())
         w('')
     dbhelper = get_adv_func_helper(driver)
-    w(schema2sql(dbhelper, schema, 
+    w(schema2sql(dbhelper, schema, prefix=SQL_PREFIX,
                  skip_entities=skip_entities, skip_relations=skip_relations))
     if dbhelper.users_support and user:
         w('')
@@ -87,8 +96,8 @@
                     skip_relations, skip_entities))
     return '\n'.join(output)
 
-                  
-def sqldropschema(schema, driver, text_index=True, 
+
+def sqldropschema(schema, driver, text_index=True,
                   skip_relations=('has_text', 'identity'), skip_entities=()):
     """return the sql to drop the schema, according to the given parameters"""
     from yams.schema2sql import dropschema2sql
@@ -101,17 +110,20 @@
         indexer = get_indexer(driver)
         w(indexer.sql_drop_fti())
         w('')
-    w(dropschema2sql(schema,
+    w(dropschema2sql(schema, prefix=SQL_PREFIX,
                      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
     dictionary and handling connection locking
     """
-    
+
     def __init__(self, source_config):
         try:
             self.dbdriver = source_config['db-driver'].lower()
@@ -124,11 +136,11 @@
         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()
-        
+
     def get_connection(self, user=None, password=None):
         """open and return a connection to the database"""
         if user or self.dbuser:
@@ -152,6 +164,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)
@@ -179,7 +201,7 @@
 
     def preprocess_entity(self, entity):
         """return a dictionary to use as extra argument to cursor.execute
-        to insert/update an entity
+        to insert/update an entity into a SQL database
         """
         attrs = {}
         eschema = entity.e_schema
@@ -196,9 +218,24 @@
                         value = value.getvalue()
                     else:
                         value = crypt_password(value)
+                # XXX needed for sqlite but I don't think it is for other backends
+                elif atype == 'Datetime' and isinstance(value, date):
+                    value = todatetime(value)
+                elif atype == 'Date' and isinstance(value, datetime):
+                    value = todate(value)
                 elif isinstance(value, Binary):
                     value = self.binary(value.getvalue())
-            attrs[str(attr)] = value
+                # XXX <3.2 bw compat
+                elif type(value) is DateTimeType:
+                    warn('found mx date time instance, please update to use datetime',
+                         DeprecationWarning)
+                    value = datetime(value.year, value.month, value.day,
+                                   value.hour, value.minute, int(value.second))
+                elif type(value) is DateTimeDeltaType:
+                    warn('found mx date time instance, please update to use datetime',
+                         DeprecationWarning)
+                    value = timedelta(0, int(value.seconds), 0)
+            attrs[SQL_PREFIX+str(attr)] = value
         return attrs
 
 
@@ -224,7 +261,7 @@
     # some time
     cnx.create_aggregate("CONCAT_STRINGS", 1, concat_strings)
     cnx.create_aggregate("GROUP_CONCAT", 1, concat_strings)
-    
+
     def _limit_size(text, maxsize, format='text/plain'):
         if len(text) < maxsize:
             return text
--- a/server/ssplanner.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/ssplanner.py	Thu May 14 12:50:34 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"
@@ -78,35 +78,35 @@
                 rel = newroot.add_type_restriction(var, possibletypes)
             stinfo['typerels'] = frozenset((rel,))
             stinfo['possibletypes'] = possibletypes
-        
+
 class SSPlanner(object):
     """SingleSourcePlanner: build execution plan for rql queries
 
     optimized for single source repositories
     """
-    
+
     def __init__(self, schema, rqlhelper):
         self.schema = schema
         self.rqlhelper = rqlhelper
 
     def build_plan(self, plan):
         """build an execution plan from a RQL query
-        
+
         do nothing here, dispatch according to the statement type
         """
         build_plan = getattr(self, 'build_%s_plan' % plan.rqlst.TYPE)
         for step in build_plan(plan, plan.rqlst):
             plan.add_step(step)
-    
+
     def build_select_plan(self, plan, rqlst):
         """build execution plan for a SELECT RQL query. Suppose only one source
         is available and so avoid work need for query decomposition among sources
-               
+
         the rqlst should not be tagged at this point.
         """
         plan.preprocess(rqlst)
         return (OneFetchStep(plan, rqlst, plan.session.repo.sources),)
-            
+
     def build_insert_plan(self, plan, rqlst):
         """get an execution plan from an INSERT RQL query"""
         # each variable in main variables is a new entity to insert
@@ -123,7 +123,7 @@
         step.children += self._compute_relation_steps(plan, rqlst.solutions,
                                                       rqlst.where, to_select)
         return (step,)
-        
+
     def _compute_relation_steps(self, plan, solutions, restriction, to_select):
         """handle the selection of relations for an insert query"""
         for edef, rdefs in to_select.items():
@@ -146,7 +146,7 @@
             step = RelationsStep(plan, edef, rdefs)
             step.children += self._select_plan(plan, select, solutions)
             yield step
-    
+
     def build_delete_plan(self, plan, rqlst):
         """get an execution plan from a DELETE RQL query"""
         # build a select query to fetch entities to delete
@@ -174,7 +174,7 @@
         if etype != 'Any':
             select.add_type_restriction(varref.variable, etype)
         return self._select_plan(plan, select, solutions)
-        
+
     def _sel_relation_steps(self, plan, solutions, restriction, relation):
         """handle the selection of relations for a delete query"""
         select = Select()
@@ -185,7 +185,7 @@
         if restriction is not None:
             select.add_restriction(restriction.copy(select))
         return self._select_plan(plan, select, solutions)
-    
+
     def build_set_plan(self, plan, rqlst):
         """get an execution plan from an SET RQL query"""
         select = Select()
@@ -222,12 +222,12 @@
         return (step,)
 
     # internal methods ########################################################
-    
+
     def _select_plan(self, plan, select, solutions):
         union = Union()
         union.append(select)
         select.clean_solutions(solutions)
-        add_types_restriction(self.schema, select)        
+        add_types_restriction(self.schema, select)
         self.rqlhelper.annotate(union)
         return self.build_select_plan(plan, union)
 
@@ -260,35 +260,35 @@
         self.limit = limit
         self.offset = offset or None
 
-        
+
 class Step(object):
     """base abstract class for execution step"""
     def __init__(self, plan):
         self.plan = plan
         self.children = []
-        
+
     def execute_child(self):
         assert len(self.children) == 1
         return self.children[0].execute()
-    
+
     def execute_children(self):
         for step in self.children:
             step.execute()
-        
+
     def execute(self):
         """execute this step and store partial (eg this step) results"""
         raise NotImplementedError()
-    
+
     def mytest_repr(self):
         """return a representation of this step suitable for test"""
         return (self.__class__.__name__,)
-    
+
     def test_repr(self):
         """return a representation of this step suitable for test"""
         return self.mytest_repr() + (
             [step.test_repr() for step in self.children],)
 
-        
+
 class OneFetchStep(LimitOffsetMixIn, Step):
     """step consisting in fetching data from sources and directly returning
     results
@@ -305,7 +305,7 @@
         for select in self.union.children:
             select.limit = limit
             select.offset = offset
-        
+
     def execute(self):
         """call .syntax_tree_search with the given syntax tree on each
         source for each solution
@@ -375,22 +375,22 @@
 
     relations values comes from the latest result, with one columns for
     each relation defined in self.r_defs
-    
+
     for one entity definition, we'll construct N entity, where N is the
     number of the latest result
     """
-    
+
     FINAL = 0
     RELATION = 1
     REVERSE_RELATION = 2
-    
+
     def __init__(self, plan, e_def, r_defs):
         Step.__init__(self, plan)
         # partial entity definition to expand
         self.e_def = e_def
         # definition of relations to complete
         self.r_defs = r_defs
-        
+
     def execute(self):
         """execute this step"""
         base_e_def = self.e_def
@@ -415,7 +415,7 @@
 
 class InsertStep(Step):
     """step consisting in inserting new entities / relations"""
-    
+
     def execute(self):
         """execute this step"""
         for step in self.children:
@@ -444,34 +444,34 @@
         pending |= actual
         for eid in actual:
             delete(session, eid)
-            
-    
+
+
 class DeleteRelationsStep(Step):
     """step consisting in deleting relations"""
 
     def __init__(self, plan, rtype):
         Step.__init__(self, plan)
         self.rtype = rtype
-        
+
     def execute(self):
         """execute this step"""
         session = self.plan.session
         delete = session.repo.glob_delete_relation
         for subj, obj in self.execute_child():
             delete(session, subj, self.rtype, obj)
-    
+
 
 class UpdateStep(Step):
     """step consisting in updating entities / adding relations from relations
     definitions and from results fetched in previous step
     """
-    
+
     def __init__(self, plan, attribute_relations, relations, selected_index):
         Step.__init__(self, plan)
         self.attribute_relations = attribute_relations
         self.relations = relations
         self.selected_index = selected_index
-        
+
     def execute(self):
         """execute this step"""
         plan = self.plan
--- a/server/test/data/bootstrap_cubes	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/bootstrap_cubes	Thu May 14 12:50:34 2009 +0200
@@ -1,1 +1,1 @@
-comment,folder,tag,basket,email,file
+card,comment,folder,tag,basket,email,file
--- a/server/test/data/migrschema/Affaire.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/migrschema/Affaire.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
         'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
         'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
         }
-    
+
     ref = String(fulltextindexed=True, indexed=True,
                  constraints=[SizeConstraint(16)])
     sujet = String(fulltextindexed=True,
@@ -18,4 +18,4 @@
         'add':    ('managers', RRQLExpression('U has_update_permission S')),
         'delete': ('managers', RRQLExpression('O owned_by U')),
         }
-    
+
--- a/server/test/data/migrschema/Folder2.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/migrschema/Folder2.py	Thu May 14 12:50:34 2009 +0200
@@ -22,4 +22,4 @@
     # is_about has been renamed into filed_under
     #//* is_about Folder
     #* filed_under Folder
-    
+
--- a/server/test/data/migrschema/Note.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/migrschema/Note.py	Thu May 14 12:50:34 2009 +0200
@@ -1,13 +1,13 @@
 class Note(EntityType):
-    
+
     permissions = {'read':   ('managers', 'users', 'guests',),
                    'update': ('managers', 'owners',),
                    'delete': ('managers', ),
-                   'add':    ('managers', 
+                   'add':    ('managers',
                               ERQLExpression('X ecrit_part PE, U in_group G, '
                                              'PE require_permission P, P name "add_note", '
                                              'P require_group G'),)}
-    
+
     date = Datetime()
     type = String(maxsize=1)
     whatever = Int()
@@ -19,7 +19,7 @@
 class ecrit_par(RelationType):
     permissions = {'read':   ('managers', 'users', 'guests',),
                    'delete': ('managers', ),
-                   'add':    ('managers', 
+                   'add':    ('managers',
                               RRQLExpression('O require_permission P, P name "add_note", '
                                              'U in_group G, P require_group G'),)
                    }
--- a/server/test/data/migrschema/relations.rel	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/migrschema/relations.rel	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 Personne travaille Societe
 Personne evaluee Note
-EUser evaluee Note
+CWUser evaluee Note
 Societe evaluee Note
 Personne concerne Affaire
 Affaire concerne Societe
@@ -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	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/schema/Affaire.py	Thu May 14 12:50:34 2009 +0200
@@ -1,21 +1,18 @@
 from cubicweb.schema import format_constraint
 
-class Affaire(EntityType):
+class Affaire(WorkflowableEntityType):
     permissions = {
-        'read':   ('managers', 
+        'read':   ('managers',
                    ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
         'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
         'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')),
         'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
         }
-    
+
     ref = String(fulltextindexed=True, indexed=True,
                  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,16 +20,15 @@
 
     duration = Int()
     invoiced = Int()
-        
-    wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
+
     depends_on = SubjectRelation('Affaire')
-    require_permission = SubjectRelation('EPermission')
-    
+    require_permission = SubjectRelation('CWPermission')
+
 class concerne(RelationType):
     permissions = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers', RRQLExpression('U has_update_permission S')),
         'delete': ('managers', RRQLExpression('O owned_by U')),
         }
-    
 
+
--- a/server/test/data/schema/Societe.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/schema/Societe.py	Thu May 14 12:50:34 2009 +0200
@@ -5,10 +5,10 @@
         'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
         'add': ('managers', 'users',)
         }
-    
+
     nom  = String(maxsize=64, fulltextindexed=True)
     web  = String(maxsize=128)
-    type  = String(maxsize=128) # attribute in common with Note 
+    type  = String(maxsize=128) # attribute in common with Note
     tel  = Int()
     fax  = Int()
     rncs = String(maxsize=128)
--- a/server/test/data/schema/custom.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/schema/custom.py	Thu May 14 12:50:34 2009 +0200
@@ -30,5 +30,5 @@
     subject = ('Bookmark', 'Note')
     object = ('Bookmark', 'Note')
 
-_euser = import_schema('base').EUser
+_euser = import_schema('base').CWUser
 _euser.__relations__[0].fulltextindexed = True
--- a/server/test/data/schema/note.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/schema/note.py	Thu May 14 12:50:34 2009 +0200
@@ -11,7 +11,7 @@
     object = 'State'
     cardinality = '1*'
     constraints=[RQLConstraint('S is ET, O state_of ET')]
-    
+
 class wf_info_for(RelationDefinition):
     subject = 'TrInfo'
     object = 'Note'
--- a/server/test/data/schema/relations.rel	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/data/schema/relations.rel	Thu May 14 12:50:34 2009 +0200
@@ -1,13 +1,13 @@
 Personne travaille Societe
 Personne evaluee Note
-EUser evaluee Note
+CWUser evaluee Note
 Societe evaluee Note
 Personne concerne Affaire
 Affaire concerne Societe
 Affaire concerne Note
 
 Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P
-Note ecrit_par EUser inline CONSTRAINT
+Note ecrit_par CWUser inline CONSTRAINT
 Personne connait Personne symetric
 
 # not inlined intentionaly
@@ -16,7 +16,7 @@
 Note inline1 Affaire inline
 Personne inline2 Affaire inline
 
-Note todo_by EUser
+Note todo_by CWUser
 Affaire todo_by Personne
 
 Folder see_also Folder
@@ -24,10 +24,10 @@
 
 Affaire documented_by Card
 
-EUser copain EUser
+CWUser copain CWUser
 
-Tag tags EUser
-Tag tags EGroup
+Tag tags CWUser
+Tag tags CWGroup
 Tag tags State
 Tag tags Note
 Tag tags Card
@@ -36,11 +36,11 @@
 Note filed_under Folder
 Affaire filed_under Folder
 
-Card require_permission EPermission
-Note require_permission EPermission
-Personne require_permission EPermission
+Card require_permission CWPermission
+Note require_permission CWPermission
+Personne require_permission CWPermission
 
-EPermission require_state State
+CWPermission require_state State
 
 Note migrated_from Note
 
--- a/server/test/runtests.py	Thu May 14 12:50:14 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_checkintegrity.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_checkintegrity.py	Thu May 14 12:50:34 2009 +0200
@@ -17,6 +17,6 @@
         finally:
             sys.stderr = sys.__stderr__
             sys.stdout = sys.__stdout__
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_config.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_config.py	Thu May 14 12:50:34 2009 +0200
@@ -13,7 +13,7 @@
             @staticmethod
             def registry_objects(registry):
                 return []
-            
+
         cfg1 = TestServerConfiguration('data/config1')
         cfg1.bootstrap_cubes()
         cfg2 = TestServerConfiguration('data/config2')
@@ -21,7 +21,7 @@
         self.failIf(cfg1.load_hooks(vreg) is cfg2.load_hooks(vreg))
         self.failUnless('after_add_relation' in cfg1.load_hooks(vreg))
         self.failUnless('after_delete_relation' in cfg2.load_hooks(vreg))
-        
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hookhelper.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_hookhelper.py	Thu May 14 12:50:34 2009 +0200
@@ -4,23 +4,23 @@
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.apptest import RepositoryBasedTC
 
-from cubicweb.server.pool import LateOperation
+from cubicweb.server.pool import LateOperation, Operation, SingleLastOperation
 from cubicweb.server.hookhelper import *
 
 
 class HookHelpersTC(RepositoryBasedTC):
-    
+
     def setUp(self):
         RepositoryBasedTC.setUp(self)
         self.hm = self.repo.hm
-    
+
     def test_late_operation(self):
         session = self.session
         l1 = LateOperation(session)
         l2 = LateOperation(session)
         l3 = Operation(session)
         self.assertEquals(session.pending_operations, [l3, l1, l2])
-        
+
     def test_single_last_operation(self):
         session = self.session
         l0 = SingleLastOperation(session)
@@ -30,7 +30,7 @@
         self.assertEquals(session.pending_operations, [l3, l1, l2, l0])
         l4 = SingleLastOperation(session)
         self.assertEquals(session.pending_operations, [l3, l1, l2, l4])
-        
+
     def test_global_operation_order(self):
         from cubicweb.server import hooks, schemahooks
         session = self.session
@@ -42,8 +42,8 @@
         op4 = hooks.DelayedDeleteOp(session)
         op5 = hooks.CheckORelationOp(session)
         self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
-                          
-       
+
+
     def test_in_state_notification(self):
         result = []
         # test both email notification and transition_information
@@ -59,7 +59,7 @@
             SendMailOp(session, msg=content, recipients=['test@logilab.fr'])
         self.hm.register_hook(in_state_changed,
                              'before_add_relation', 'in_state')
-        self.execute('INSERT EUser X: X login "paf", X upassword "wouf", X in_state S, X in_group G WHERE S name "activated", G name "users"')
+        self.execute('INSERT CWUser X: X login "paf", X upassword "wouf", X in_state S, X in_group G WHERE S name "activated", G name "users"')
         self.assertEquals(result, [None])
         searchedops = [op for op in self.session.pending_operations
                        if isinstance(op, SendMailOp)]
@@ -78,6 +78,6 @@
                        if isinstance(op, SendMailOp)]
         self.assertEquals(len(searchedops), 0,
                           self.session.pending_operations)
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_hooks.py	Thu May 14 12:50:34 2009 +0200
@@ -7,8 +7,9 @@
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
 
-from cubicweb.common import ConnectionError, RepositoryError, ValidationError
-from cubicweb.server.repository import *
+from cubicweb import ConnectionError, RepositoryError, ValidationError, AuthenticationError, BadConnectionId
+from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.repository import Repository
 
 orig_get_versions = Repository.get_versions
 
@@ -19,19 +20,19 @@
     Repository.get_versions = orig_get_versions
 
 
-    
+
 class CoreHooksTC(RepositoryBasedTC):
-        
+
     def test_delete_internal_entities(self):
         self.assertRaises(RepositoryError, self.execute,
-                          'DELETE EEType X WHERE X name "EEType"')
+                          'DELETE CWEType X WHERE X name "CWEType"')
         self.assertRaises(RepositoryError, self.execute,
-                          'DELETE ERType X WHERE X name "relation_type"')
+                          'DELETE CWRType X WHERE X name "relation_type"')
         self.assertRaises(RepositoryError, self.execute,
-                          'DELETE EGroup X WHERE X name "owners"')
+                          'DELETE CWGroup X WHERE X name "owners"')
 
     def test_delete_required_relations_subject(self):
-        self.execute('INSERT EUser X: X login "toto", X upassword "hop", X in_group Y, X in_state S '
+        self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y, X in_state S '
                      'WHERE Y name "users", S name "activated"')
         self.commit()
         self.execute('DELETE X in_group Y WHERE X login "toto", Y name "users"')
@@ -39,24 +40,24 @@
         self.execute('DELETE X in_group Y WHERE X login "toto"')
         self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"')
         self.commit()
-        
+
     def test_delete_required_relations_object(self):
         self.skip('no sample in the schema ! YAGNI ? Kermaat ?')
-    
+
     def test_static_vocabulary_check(self):
         self.assertRaises(ValidationError,
                           self.execute,
-                          'SET X composite "whatever" WHERE X from_entity FE, FE name "EUser", X relation_type RT, RT name "in_group"')
-    
+                          'SET X composite "whatever" WHERE X from_entity FE, FE name "CWUser", X relation_type RT, RT name "in_group"')
+
     def test_missing_required_relations_subject_inline(self):
-        # missing in_group relation 
-        self.execute('INSERT EUser X: X login "toto", X upassword "hop"')
+        # missing in_group relation
+        self.execute('INSERT CWUser X: X login "toto", X upassword "hop"')
         self.assertRaises(ValidationError,
                           self.commit)
 
     def test_delete_if_singlecard1(self):
         self.assertEquals(self.repo.schema['in_state'].inlined, False)
-        ueid, = self.execute('INSERT EUser X: X login "toto", X upassword "hop", X in_group Y, X in_state S '
+        ueid, = self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y, X in_state S '
                              'WHERE Y name "users", S name "activated"')[0]
         self.commit()
         self.execute('SET X in_state S WHERE S name "deactivated", X eid %(x)s', {'x': ueid})
@@ -75,7 +76,7 @@
         self.execute('SET X sender Y WHERE X is Email, Y is EmailAddress')
         rset = self.execute('Any S WHERE X sender S, X eid %s' % eeid)
         self.assertEquals(len(rset), 1)
-        
+
     def test_composite_1(self):
         self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
         self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
@@ -89,7 +90,7 @@
         self.commit()
         rset = self.execute('Any X WHERE X is EmailPart')
         self.assertEquals(len(rset), 0)
-            
+
     def test_composite_2(self):
         self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
         self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
@@ -101,7 +102,7 @@
         self.commit()
         rset = self.execute('Any X WHERE X is EmailPart')
         self.assertEquals(len(rset), 0)
-            
+
     def test_composite_redirection(self):
         self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
         self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
@@ -118,7 +119,7 @@
         self.assertEquals(rset.get_entity(0, 0).reverse_parts[0].messageid, '<2345>')
 
     def test_unsatisfied_constraints(self):
-        self.execute('INSERT ENFRDef X: X from_entity FE, X relation_type RT, X to_entity TE '
+        self.execute('INSERT CWRelation X: X from_entity FE, X relation_type RT, X to_entity TE '
                      'WHERE FE name "Affaire", RT name "concerne", TE name "String"')
         self.assertRaises(ValidationError,
                           self.commit)
@@ -147,11 +148,11 @@
         self.execute('SET A descr "R&D<p>yo" WHERE A eid %s' % entity.eid)
         entity = self.execute('Any A WHERE A eid %s' % entity.eid).get_entity(0, 0)
         self.assertEquals(entity.descr, u'R&amp;D<p>yo</p>')
-        
+
+
 
-        
 class UserGroupHooksTC(RepositoryBasedTC):
-    
+
     def test_user_synchronization(self):
         self.create_user('toto', password='hop', commit=False)
         self.assertRaises(AuthenticationError,
@@ -159,7 +160,7 @@
         self.commit()
         cnxid = self.repo.connect(u'toto', 'hop')
         self.failIfEqual(cnxid, self.cnxid)
-        self.execute('DELETE EUser X WHERE X login "toto"')
+        self.execute('DELETE CWUser X WHERE X login "toto"')
         self.repo.execute(cnxid, 'State X')
         self.commit()
         self.assertRaises(BadConnectionId,
@@ -192,48 +193,48 @@
         self.execute('DELETE EmailAddress X WHERE X eid %s' % eid)
         self.commit()
         self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid}))
-        
-class EPropertyHooksTC(RepositoryBasedTC):
-    
+
+class CWPropertyHooksTC(RepositoryBasedTC):
+
     def test_unexistant_eproperty(self):
         ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT EProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
         self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
         ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT EProperty X: X pkey "bla.bla", X value "hop"')
+                          self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
         self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
-        
+
     def test_site_wide_eproperty(self):
         ex = self.assertRaises(ValidationError,
-                               self.execute, 'INSERT EProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
+                               self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
         self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
-        
+
     def test_bad_type_eproperty(self):
         ex = self.assertRaises(ValidationError,
-                               self.execute, 'INSERT EProperty X: X pkey "ui.language", X value "hop", X for_user U')
+                               self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
         self.assertEquals(ex.errors, {'value': u'unauthorized value'})
         ex = self.assertRaises(ValidationError,
-                          self.execute, 'INSERT EProperty X: X pkey "ui.language", X value "hop"')
+                          self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
         self.assertEquals(ex.errors, {'value': u'unauthorized value'})
-        
-        
+
+
 class SchemaHooksTC(RepositoryBasedTC):
-        
+
     def test_duplicate_etype_error(self):
-        # check we can't add a EEType or ERType entity if it already exists one
+        # check we can't add a CWEType or CWRType entity if it already exists one
         # with the same name
         #
         # according to hook order, we'll get a repository or validation error
         self.assertRaises((ValidationError, RepositoryError),
-                          self.execute, 'INSERT EEType X: X name "Societe"')
+                          self.execute, 'INSERT CWEType X: X name "Societe"')
         self.assertRaises((ValidationError, RepositoryError),
-                          self.execute, 'INSERT ERType X: X name "in_group"')
-        
+                          self.execute, 'INSERT CWRType X: X name "in_group"')
+
     def test_validation_unique_constraint(self):
         self.assertRaises(ValidationError,
-                          self.execute, 'INSERT EUser X: X login "admin"')
+                          self.execute, 'INSERT CWUser X: X login "admin"')
         try:
-            self.execute('INSERT EUser X: X login "admin"')
+            self.execute('INSERT CWUser X: X login "admin"')
         except ValidationError, ex:
             self.assertIsInstance(ex.entity, int)
             self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
@@ -250,42 +251,47 @@
             repo.config._cubes = None
             repo.fill_schema()
         RepositoryBasedTC.setUp(self)
-            
+
+    def index_exists(self, etype, attr, unique=False):
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
+
     def test_base(self):
         schema = self.repo.schema
-        dbhelper = self.session.pool.source('system').dbhelper    
+        dbhelper = self.session.pool.source('system').dbhelper
         sqlcursor = self.session.pool['system']
         self.failIf(schema.has_entity('Societe2'))
         self.failIf(schema.has_entity('concerne2'))
         # schema should be update on insertion (after commit)
-        self.execute('INSERT EEType X: X name "Societe2", X description "", X meta FALSE, X final FALSE')
-        self.execute('INSERT ERType X: X name "concerne2", X description "", X meta FALSE, X final FALSE, X symetric FALSE')
+        self.execute('INSERT CWEType X: X name "Societe2", X description "", X meta FALSE, X final FALSE')
+        self.execute('INSERT CWRType X: X name "concerne2", X description "", X meta FALSE, X final FALSE, X symetric FALSE')
         self.failIf(schema.has_entity('Societe2'))
         self.failIf(schema.has_entity('concerne2'))
-        self.execute('SET X read_permission G WHERE X is EEType, X name "Societe2", G is EGroup')
-        self.execute('SET X read_permission G WHERE X is ERType, X name "concerne2", G is EGroup')
-        self.execute('SET X add_permission G WHERE X is EEType, X name "Societe2", G is EGroup, G name "managers"')
-        self.execute('SET X add_permission G WHERE X is ERType, X name "concerne2", G is EGroup, G name "managers"')
-        self.execute('SET X delete_permission G WHERE X is EEType, X name "Societe2", G is EGroup, G name "owners"')
-        self.execute('SET X delete_permission G WHERE X is ERType, X name "concerne2", G is EGroup, G name "owners"')
+        self.execute('SET X read_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup')
+        self.execute('SET X read_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup')
+        self.execute('SET X add_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "managers"')
+        self.execute('SET X add_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "managers"')
+        self.execute('SET X delete_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "owners"')
+        self.execute('SET X delete_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "owners"')
         # have to commit before adding definition relations
         self.commit()
         self.failUnless(schema.has_entity('Societe2'))
         self.failUnless(schema.has_relation('concerne2'))
-        self.execute('INSERT EFRDef X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+        self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
                      'WHERE RT name "nom", E name "Societe2", F name "String"')
         concerne2_rdef_eid = self.execute(
-            'INSERT ENFRDef X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+            'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
             'WHERE RT name "concerne2", E name "Societe2"')[0][0]
-        self.execute('INSERT ENFRDef X: X cardinality "?*", X relation_type RT, X from_entity E, X to_entity C '
+        self.execute('INSERT CWRelation X: X cardinality "?*", X relation_type RT, X from_entity E, X to_entity C '
                      'WHERE RT name "comments", E name "Societe2", C name "Comment"')
         self.failIf('nom' in schema['Societe2'].subject_relations())
         self.failIf('concerne2' in schema['Societe2'].subject_relations())
-        self.failIf(dbhelper.index_exists(sqlcursor, 'Societe2', 'nom'))
+        self.failIf(self.index_exists('Societe2', 'nom'))
         self.commit()
         self.failUnless('nom' in schema['Societe2'].subject_relations())
         self.failUnless('concerne2' in schema['Societe2'].subject_relations())
-        self.failUnless(dbhelper.index_exists(sqlcursor, 'Societe2', 'nom'))
+        self.failUnless(self.index_exists('Societe2', 'nom'))
         # now we should be able to insert and query Societe2
         s2eid = self.execute('INSERT Societe2 X: X nom "logilab"')[0][0]
         self.execute('Societe2 X WHERE X nom "logilab"')
@@ -293,22 +299,22 @@
         rset = self.execute('Any X WHERE X concerne2 Y')
         self.assertEquals(rset.rows, [[s2eid]])
         # check that when a relation definition is deleted, existing relations are deleted
-        self.execute('INSERT ENFRDef X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+        self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
                      'WHERE RT name "concerne2", E name "Societe"')
         self.commit()
-        self.execute('DELETE ENFRDef X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
+        self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
         self.commit()
         self.failUnless('concerne2' in schema['Societe'].subject_relations())
         self.failIf('concerne2' in schema['Societe2'].subject_relations())
         self.failIf(self.execute('Any X WHERE X concerne2 Y'))
         # schema should be cleaned on delete (after commit)
-        self.execute('DELETE EEType X WHERE X name "Societe2"')
-        self.execute('DELETE ERType X WHERE X name "concerne2"')
-        self.failUnless(dbhelper.index_exists(sqlcursor, 'Societe2', 'nom'))
+        self.execute('DELETE CWEType X WHERE X name "Societe2"')
+        self.execute('DELETE CWRType X WHERE X name "concerne2"')
+        self.failUnless(self.index_exists('Societe2', 'nom'))
         self.failUnless(schema.has_entity('Societe2'))
         self.failUnless(schema.has_relation('concerne2'))
         self.commit()
-        self.failIf(dbhelper.index_exists(sqlcursor, 'Societe2', 'nom'))
+        self.failIf(self.index_exists('Societe2', 'nom'))
         self.failIf(schema.has_entity('Societe2'))
         self.failIf(schema.has_entity('concerne2'))
 
@@ -326,51 +332,51 @@
         self.failUnless('subdiv' in snames)
         snames = [name for name, in self.execute('Any N WHERE S is_instance_of Division, S nom N')]
         self.failUnless('subdiv' in snames)
-        
-        
+
+
     def test_perms_synchronization_1(self):
         schema = self.repo.schema
-        self.assertEquals(schema['EUser'].get_groups('read'), set(('managers', 'users')))
-        self.failUnless(self.execute('Any X, Y WHERE X is EEType, X name "EUser", Y is EGroup, Y name "users"')[0])
-        self.execute('DELETE X read_permission Y WHERE X is EEType, X name "EUser", Y name "users"')
-        self.assertEquals(schema['EUser'].get_groups('read'), set(('managers', 'users', )))
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
+        self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
+        self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
         self.commit()
-        self.assertEquals(schema['EUser'].get_groups('read'), set(('managers', )))
-        self.execute('SET X read_permission Y WHERE X is EEType, X name "EUser", Y name "users"')
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
+        self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
         self.commit()
-        self.assertEquals(schema['EUser'].get_groups('read'), set(('managers', 'users',)))
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
 
     def test_perms_synchronization_2(self):
         schema = self.repo.schema['in_group']
         self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-        self.execute('DELETE X read_permission Y WHERE X is ERType, X name "in_group", Y name "guests"')
+        self.execute('DELETE X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
         self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
         self.commit()
         self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
-        self.execute('SET X read_permission Y WHERE X is ERType, X name "in_group", Y name "guests"')
+        self.execute('SET X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
         self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
         self.commit()
         self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
 
     def test_nonregr_user_edit_itself(self):
         ueid = self.session.user.eid
-        groupeids = [eid for eid, in self.execute('EGroup G WHERE G name in ("managers", "users")')]
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
         self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
         self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
         self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
         self.commit()
-        eeid = self.execute('Any X WHERE X is EEType, X name "EEType"')[0][0]
+        eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
         self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
         self.execute('SET X final FALSE WHERE X eid %s' % eeid)
         self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
                      % (eeid, groupeids[0], groupeids[1]))
         self.commit()
-        self.execute('Any X WHERE X is EEType, X name "EEType"')
+        self.execute('Any X WHERE X is CWEType, X name "CWEType"')
 
     # schema modification hooks tests #########################################
-    
+
     def test_uninline_relation(self):
-        dbhelper = self.session.pool.source('system').dbhelper    
+        dbhelper = self.session.pool.source('system').dbhelper
         sqlcursor = self.session.pool['system']
         # Personne inline2 Affaire inline
         # insert a person without inline2 relation (not mandatory)
@@ -385,7 +391,7 @@
                 self.failUnless(self.schema['inline2'].inlined)
                 self.commit()
                 self.failIf(self.schema['inline2'].inlined)
-                self.failIf(dbhelper.index_exists(sqlcursor, 'Personne', 'inline2'))
+                self.failIf(self.index_exists('Personne', 'inline2'))
                 rset = self.execute('Any X, Y WHERE X inline2 Y')
                 self.assertEquals(len(rset), 1)
                 self.assertEquals(rset.rows[0], [peid, aeid])
@@ -398,51 +404,56 @@
             self.failIf(self.schema['inline2'].inlined)
             self.commit()
             self.failUnless(self.schema['inline2'].inlined)
-            self.failUnless(dbhelper.index_exists(sqlcursor, 'Personne', 'inline2'))
+            self.failUnless(self.index_exists('Personne', 'inline2'))
             rset = self.execute('Any X, Y WHERE X inline2 Y')
             self.assertEquals(len(rset), 1)
             self.assertEquals(rset.rows[0], [peid, aeid])
 
     def test_indexed_change(self):
-        dbhelper = self.session.pool.source('system').dbhelper    
+        dbhelper = self.session.pool.source('system').dbhelper
         sqlcursor = self.session.pool['system']
         try:
             self.execute('SET X indexed TRUE WHERE X relation_type R, R name "sujet"')
             self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failIf(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet'))
+            self.failIf(self.index_exists('Affaire', 'sujet'))
             self.commit()
             self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failUnless(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet'))
+            self.failUnless(self.index_exists('Affaire', 'sujet'))
         finally:
             self.execute('SET X indexed FALSE WHERE X relation_type R, R name "sujet"')
             self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failUnless(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet'))
+            self.failUnless(self.index_exists('Affaire', 'sujet'))
             self.commit()
             self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
-            self.failIf(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet'))
+            self.failIf(self.index_exists('Affaire', 'sujet'))
 
     def test_unique_change(self):
-        dbhelper = self.session.pool.source('system').dbhelper    
+        dbhelper = self.session.pool.source('system').dbhelper
         sqlcursor = self.session.pool['system']
         try:
-            self.execute('INSERT EConstraint X: X cstrtype CT, DEF constrained_by X '
-                         'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
-                         'RT name "sujet", E name "Affaire"')
-            self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failIf(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet', unique=True))
-            self.commit()
-            self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failUnless(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet', unique=True))
+            try:
+                self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
+                             'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+                             'RT name "sujet", E name "Affaire"')
+                self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
+                self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
+                self.commit()
+                self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
+                self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
+            except:
+                import traceback
+                traceback.print_exc()
+                raise
         finally:
             self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
                          'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
                          'RT name "sujet", E name "Affaire"')
             self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failUnless(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet', unique=True))
+            self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
             self.commit()
             self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
-            self.failIf(dbhelper.index_exists(sqlcursor, 'Affaire', 'sujet', unique=True))
-        
+            self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
+
 
 class WorkflowHooksTC(RepositoryBasedTC):
 
@@ -450,21 +461,21 @@
         RepositoryBasedTC.setUp(self)
         self.s_activated = self.execute('State X WHERE X name "activated"')[0][0]
         self.s_deactivated = self.execute('State X WHERE X name "deactivated"')[0][0]
-        self.s_dummy = self.execute('INSERT State X: X name "dummy", X state_of E WHERE E name "EUser"')[0][0]
+        self.s_dummy = self.execute('INSERT State X: X name "dummy", X state_of E WHERE E name "CWUser"')[0][0]
         self.create_user('stduser')
         # give access to users group on the user's wf transitions
         # so we can test wf enforcing on euser (managers don't have anymore this
         # enforcement
-        self.execute('SET X require_group G WHERE G name "users", X transition_of ET, ET name "EUser"')
+        self.execute('SET X require_group G WHERE G name "users", X transition_of ET, ET name "CWUser"')
         self.commit()
-        
+
     def tearDown(self):
-        self.execute('DELETE X require_group G WHERE G name "users", X transition_of ET, ET name "EUser"')
+        self.execute('DELETE X require_group G WHERE G name "users", X transition_of ET, ET name "CWUser"')
         self.commit()
         RepositoryBasedTC.tearDown(self)
 
     def test_set_initial_state(self):
-        ueid = self.execute('INSERT EUser E: E login "x", E upassword "x", E in_group G '
+        ueid = self.execute('INSERT CWUser E: E login "x", E upassword "x", E in_group G '
                             'WHERE G name "users"')[0][0]
         self.failIf(self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
                                  {'x' : ueid}))
@@ -472,19 +483,19 @@
         initialstate = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
                                     {'x' : ueid})[0][0]
         self.assertEquals(initialstate, u'activated')
-        
+
     def test_initial_state(self):
         cnx = self.login('stduser')
         cu = cnx.cursor()
         self.assertRaises(ValidationError, cu.execute,
-                          'INSERT EUser X: X login "badaboum", X upassword %(pwd)s, '
+                          'INSERT CWUser X: X login "badaboum", X upassword %(pwd)s, '
                           'X in_state S WHERE S name "deactivated"', {'pwd': 'oops'})
         cnx.close()
         # though managers can do whatever he want
-        self.execute('INSERT EUser X: X login "badaboum", X upassword %(pwd)s, '
+        self.execute('INSERT CWUser X: X login "badaboum", X upassword %(pwd)s, '
                      'X in_state S, X in_group G WHERE S name "deactivated", G name "users"', {'pwd': 'oops'})
         self.commit()
-        
+
     # test that the workflow is correctly enforced
     def test_transition_checking1(self):
         cnx = self.login('stduser')
@@ -494,7 +505,7 @@
                           cu.execute, 'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                           {'x': ueid, 's': self.s_activated}, 'x')
         cnx.close()
-        
+
     def test_transition_checking2(self):
         cnx = self.login('stduser')
         cu = cnx.cursor()
@@ -503,7 +514,7 @@
                           cu.execute, 'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                           {'x': ueid, 's': self.s_dummy}, 'x')
         cnx.close()
-        
+
     def test_transition_checking3(self):
         cnx = self.login('stduser')
         cu = cnx.cursor()
@@ -519,7 +530,7 @@
                       {'x': ueid, 's': self.s_activated}, 'x')
         cnx.commit()
         cnx.close()
-        
+
     def test_transition_checking4(self):
         cnx = self.login('stduser')
         cu = cnx.cursor()
@@ -548,7 +559,7 @@
         self.assertEquals(tr.comment, None)
         self.assertEquals(tr.from_state[0].eid, self.s_activated)
         self.assertEquals(tr.to_state[0].eid, self.s_deactivated)
-        
+
         self.session.set_shared_data('trcomment', u'il est pas sage celui-la')
         self.session.set_shared_data('trcommentformat', u'text/plain')
         self.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
@@ -580,6 +591,6 @@
         cu = cnx.cursor()
         self.failUnless(cu.execute("INSERT Note X: X type 'a', X in_state S WHERE S name 'todo'"))
         cnx.commit()
-    
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hooksmanager.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_hooksmanager.py	Thu May 14 12:50:34 2009 +0200
@@ -16,7 +16,7 @@
 class HooksManagerTC(TestCase):
     args = (None,)
     kwargs = {'a': 1}
-    
+
     def setUp(self):
         """ called before each test from this class """
         self.o = HooksManager(schema)
@@ -25,29 +25,29 @@
         self.assertRaises(AssertionError,
                           self.o.register_hook, self._hook, 'before_add_entiti')
         self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'session_login', 'EEType')
+                          self.o.register_hook, self._hook, 'session_login', 'CWEType')
         self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'session_logout', 'EEType')
+                          self.o.register_hook, self._hook, 'session_logout', 'CWEType')
         self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'server_startup', 'EEType')
+                          self.o.register_hook, self._hook, 'server_startup', 'CWEType')
         self.assertRaises(AssertionError,
-                          self.o.register_hook, self._hook, 'server_shutdown', 'EEType')
-        
+                          self.o.register_hook, self._hook, 'server_shutdown', 'CWEType')
+
     def test_register_hook1(self):
         self.o.register_hook(self._hook, 'before_add_entity')
         self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
         self._test_called_hooks()
-        
+
     def test_register_hook2(self):
         self.o.register_hook(self._hook, 'before_add_entity', '')
         self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
         self._test_called_hooks()
-        
+
     def test_register_hook3(self):
         self.o.register_hook(self._hook, 'before_add_entity', None)
         self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
         self._test_called_hooks()
-        
+
     def test_register_hooks(self):
         self.o.register_hooks({'before_add_entity' : {'': [self._hook]},
                                'before_delete_entity' : {'Personne': [self._hook]},
@@ -62,7 +62,7 @@
         self.o.unregister_hook(self._hook, 'after_delete_entity', 'Personne')
         # no hook should be called there
         self.o.call_hooks('after_delete_entity', 'Personne')
-        
+
 
     def _test_called_hooks(self):
         self.assertRaises(HookCalled,
@@ -102,9 +102,9 @@
                              'before_add_relation', 'concerne')
         self.assertEquals(self.called, [])
         self.o.call_hooks('before_add_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)        
+                          1, 'concerne', 2)
         self.assertEquals(self.called, [(1, 'concerne', 2)])
-        
+
     def test_after_add_relation(self):
         """make sure after_xxx_relation hooks are deferred"""
         self.o.register_hook(self._after_relation_hook,
@@ -115,14 +115,14 @@
         self.o.call_hooks('after_add_relation', 'concerne', 'USER',
                           3, 'concerne', 4)
         self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
-    
+
     def test_before_delete_relation(self):
         """make sure before_xxx_relation hooks are called directly"""
         self.o.register_hook(self._before_relation_hook,
                              'before_delete_relation', 'concerne')
         self.assertEquals(self.called, [])
         self.o.call_hooks('before_delete_relation', 'concerne', 'USER',
-                          1, 'concerne', 2)        
+                          1, 'concerne', 2)
         self.assertEquals(self.called, [(1, 'concerne', 2)])
 
     def test_after_delete_relation(self):
@@ -166,7 +166,7 @@
     schema = schema # set for actual hooks at registration time
     events = ('whatever', 'another')
     accepts = ('Societe', 'Division')
-    
+
 class HookTC(RepositoryBasedTC):
     def test_inheritance(self):
         self.assertEquals(list(MyHook.register_to()),
--- a/server/test/unittest_ldapuser.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_ldapuser.py	Thu May 14 12:50:34 2009 +0200
@@ -24,7 +24,7 @@
         # no such user
         raise AuthenticationError()
     # don't check upassword !
-    return self.extid2eid(user['dn'], 'EUser', session)
+    return self.extid2eid(user['dn'], 'CWUser', session)
 
 
 
@@ -34,40 +34,40 @@
 
 class LDAPUserSourceTC(RepositoryBasedTC):
     repo, cnx = repo, cnx
-    
+
     def patch_authenticate(self):
         self._orig_authenticate = LDAPUserSource.authenticate
         LDAPUserSource.authenticate = nopwd_authenticate
 
     def setUp(self):
         self._prepare()
-        # XXX: need this first query else we get 'database is locked' from 
+        # XXX: need this first query else we get 'database is locked' from
         # sqlite since it doesn't support multiple connections on the same
         # database
         # so doing, ldap inserted users don't get removed between each test
-        rset = self.execute('EUser X')
+        rset = self.execute('CWUser X')
         self.commit()
         # check we get some users from ldap
         self.assert_(len(rset) > 1)
         self.maxeid = self.execute('Any MAX(X)')[0][0]
-        
+
     def tearDown(self):
         if hasattr(self, '_orig_authenticate'):
             LDAPUserSource.authenticate = self._orig_authenticate
         RepositoryBasedTC.tearDown(self)
-            
+
     def test_authenticate(self):
         source = self.repo.sources_by_uri['ldapuser']
         self.assertRaises(AuthenticationError,
                           source.authenticate, self.session, 'toto', 'toto')
-        
+
     def test_synchronize(self):
         source = self.repo.sources_by_uri['ldapuser']
         source.synchronize()
-        
+
     def test_base(self):
         # check a known one
-        e = self.execute('EUser X WHERE X login "syt"').get_entity(0, 0)
+        e = self.execute('CWUser X WHERE X login "syt"').get_entity(0, 0)
         self.assertEquals(e.login, 'syt')
         e.complete()
         self.assertEquals(e.creation_date, None)
@@ -79,49 +79,49 @@
         self.assertEquals(e.created_by, [])
         self.assertEquals(e.primary_email[0].address, 'Sylvain Thenault')
         # email content should be indexed on the user
-        rset = self.execute('EUser X WHERE X has_text "thenault"')
+        rset = self.execute('CWUser X WHERE X has_text "thenault"')
         self.assertEquals(rset.rows, [[e.eid]])
 
     def test_not(self):
-        eid = self.execute('EUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('EUser X WHERE NOT X eid %s' % eid)
+        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.execute('CWUser X WHERE NOT X eid %s' % eid)
         self.assert_(rset)
         self.assert_(not eid in (r[0] for r in rset))
 
     def test_multiple(self):
-        seid = self.execute('EUser X WHERE X login "syt"')[0][0]
-        aeid = self.execute('EUser X WHERE X login "adim"')[0][0]
-        rset = self.execute('EUser X, Y WHERE X login "syt", Y login "adim"')
+        seid = self.execute('CWUser X WHERE X login "syt"')[0][0]
+        aeid = self.execute('CWUser X WHERE X login "adim"')[0][0]
+        rset = self.execute('CWUser X, Y WHERE X login "syt", Y login "adim"')
         self.assertEquals(rset.rows, [[seid, aeid]])
         rset = self.execute('Any X,Y,L WHERE X login L, X login "syt", Y login "adim"')
         self.assertEquals(rset.rows, [[seid, aeid, 'syt']])
 
     def test_in(self):
-        seid = self.execute('EUser X WHERE X login "syt"')[0][0]
-        aeid = self.execute('EUser X WHERE X login "adim"')[0][0]
+        seid = self.execute('CWUser X WHERE X login "syt"')[0][0]
+        aeid = self.execute('CWUser X WHERE X login "adim"')[0][0]
         rset = self.execute('Any X,L ORDERBY L WHERE X login IN("syt", "adim"), X login L')
         self.assertEquals(rset.rows, [[aeid, 'adim'], [seid, 'syt']])
 
     def test_relations(self):
-        eid = self.execute('EUser X WHERE X login "syt"')[0][0]
-        rset = self.execute('Any X,E WHERE X is EUser, X login L, X primary_email E')
+        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
+        rset = self.execute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
         self.assert_(eid in (r[0] for r in rset))
-        rset = self.execute('Any X,L,E WHERE X is EUser, X login L, X primary_email E')
+        rset = self.execute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
         self.assert_('syt' in (r[1] for r in rset))
 
     def test_count(self):
-        nbusers = self.execute('Any COUNT(X) WHERE X is EUser')[0][0]
+        nbusers = self.execute('Any COUNT(X) WHERE X is CWUser')[0][0]
         # just check this is a possible number
         self.assert_(nbusers > 1, nbusers)
         self.assert_(nbusers < 30, nbusers)
 
     def test_upper(self):
-        eid = self.execute('EUser X WHERE X login "syt"')[0][0]
+        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
         rset = self.execute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
         self.assertEquals(rset[0][0], 'SYT')
 
     def test_unknown_attr(self):
-        eid = self.execute('EUser X WHERE X login "syt"')[0][0]
+        eid = self.execute('CWUser X WHERE X login "syt"')[0][0]
         rset = self.execute('Any L,C,M WHERE X eid %s, X login L, '
                             'X creation_date C, X modification_date M' % eid)
         self.assertEquals(rset[0][0], 'syt')
@@ -139,13 +139,13 @@
     def test_or(self):
         rset = self.execute('DISTINCT Any X WHERE X login "syt" OR (X in_group G, G name "managers")')
         self.assertEquals(len(rset), 2, rset.rows) # syt + admin
-        
+
     def test_nonregr_set_owned_by(self):
         # test that when a user coming from ldap is triggering a transition
         # the related TrInfo has correct owner information
         self.execute('SET X in_group G WHERE X login "syt", G name "managers"')
         self.commit()
-        syt = self.execute('EUser X WHERE X login "syt"').get_entity(0, 0)
+        syt = self.execute('CWUser X WHERE X login "syt"').get_entity(0, 0)
         self.assertEquals([g.name for g in syt.in_group], ['managers', 'users'])
         self.patch_authenticate()
         cnx = self.login('syt', 'dummypassword')
@@ -153,7 +153,7 @@
         cu.execute('SET X in_state S WHERE X login "alf", S name "deactivated"')
         try:
             cnx.commit()
-            alf = self.execute('EUser X WHERE X login "alf"').get_entity(0, 0)
+            alf = self.execute('CWUser X WHERE X login "alf"').get_entity(0, 0)
             self.assertEquals(alf.in_state[0].name, 'deactivated')
             trinfo = alf.latest_trinfo()
             self.assertEquals(trinfo.owned_by[0].login, 'syt')
@@ -175,10 +175,10 @@
     def test_multiple_entities_from_different_sources(self):
         self.create_user('cochon')
         self.failUnless(self.execute('Any X,Y WHERE X login "syt", Y login "cochon"'))
-        
+
     def test_exists1(self):
-        self.add_entity('EGroup', name=u'bougloup1')
-        self.add_entity('EGroup', name=u'bougloup2')
+        self.add_entity('CWGroup', name=u'bougloup1')
+        self.add_entity('CWGroup', name=u'bougloup2')
         self.execute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
         self.execute('SET U in_group G WHERE G name = "bougloup1", U login "syt"')
         rset = self.execute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
@@ -210,9 +210,9 @@
         self.execute('SET X copain Y WHERE X login "comme", Y login "billy"')
         self.execute('SET X copain Y WHERE X login "syt", Y login "billy"')
         # search for group name, login where
-        #   EUser copain with "comme" or "cochon" AND same login as the copain
+        #   CWUser copain with "comme" or "cochon" AND same login as the copain
         # OR
-        #   EUser in_state activated AND not copain with billy
+        #   CWUser in_state activated AND not copain with billy
         #
         # SO we expect everybody but "comme" and "syt"
         rset= self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, '
@@ -241,15 +241,15 @@
         self.assertEquals(sorted(rset.rows), [['guests', 'cochon'],
                                               ['users', 'cochon'],
                                               ['users', 'syt']])
-        
+
     def test_cd_restriction(self):
-        rset = self.execute('EUser X WHERE X creation_date > "2009-02-01"')
+        rset = self.execute('CWUser X WHERE X creation_date > "2009-02-01"')
         self.assertEquals(len(rset), 2) # admin/anon but no ldap user since it doesn't support creation_date
-        
+
     def test_union(self):
         afeids = self.execute('State X')
-        ueids = self.execute('EUser X')
-        rset = self.execute('(Any X WHERE X is State) UNION (Any X WHERE X is EUser)')
+        ueids = self.execute('CWUser X')
+        rset = self.execute('(Any X WHERE X is State) UNION (Any X WHERE X is CWUser)')
         self.assertEquals(sorted(r[0] for r in rset.rows),
                           sorted(r[0] for r in afeids + ueids))
 
@@ -257,21 +257,21 @@
         self.create_user('iaminguestsgrouponly', groups=('guests',))
         cnx = self.login('iaminguestsgrouponly')
         return cnx.cursor()
-    
+
     def test_security1(self):
         cu = self._init_security_test()
         rset = cu.execute('Any X WHERE X login "syt"')
         self.assertEquals(rset.rows, [])
         rset = cu.execute('Any X WHERE X login "iaminguestsgrouponly"')
         self.assertEquals(len(rset.rows), 1)
-    
+
     def test_security2(self):
         cu = self._init_security_test()
         rset = cu.execute('Any X WHERE X has_text "syt"')
         self.assertEquals(rset.rows, [])
         rset = cu.execute('Any X WHERE X has_text "iaminguestsgrouponly"')
         self.assertEquals(len(rset.rows), 1)
-    
+
     def test_security3(self):
         cu = self._init_security_test()
         rset = cu.execute('Any F WHERE X has_text "syt", X firstname F')
@@ -298,18 +298,18 @@
         emaileid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org"')[0][0]
         self.execute('Any X,AA WHERE X use_email Y, Y eid %(x)s, X modification_date AA',
                      {'x': emaileid})
-        
+
     def test_nonregr5(self):
         # original jpl query:
-        # Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is EUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
+        # Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is CWUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
         rql = 'Any X, NOW - CD, P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, U login "%s", P is X, X creation_date CD' % self.session.user.login
         self.execute(rql, )#{'x': })
-        
+
     def test_nonregr6(self):
         self.execute('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
                      'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (EXISTS(U identity ME) '
                      'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
-                     'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is EUser)',
+                     'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is CWUser)',
                      {'x': self.session.user.eid})
 
 
@@ -350,33 +350,33 @@
 
 class RQL2LDAPFilterTC(RQLGeneratorTC):
     schema = repo.schema
-    
+
     def setUp(self):
         RQLGeneratorTC.setUp(self)
         ldapsource = repo.sources[-1]
         self.pool = repo._get_pool()
         session = mock_object(pool=self.pool)
         self.o = RQL2LDAPFilter(ldapsource, session)
-        
+
     def tearDown(self):
         repo._free_pool(self.pool)
         RQLGeneratorTC.tearDown(self)
-        
+
     def test_base(self):
-        rqlst = self._prepare('EUser X WHERE X login "toto"').children[0]
+        rqlst = self._prepare('CWUser X WHERE X login "toto"').children[0]
         self.assertEquals(self.o.generate(rqlst, 'X')[1],
                           '(&(objectClass=top)(objectClass=posixAccount)(uid=toto))')
-        
+
     def test_kwargs(self):
-        rqlst = self._prepare('EUser X WHERE X login %(x)s').children[0]
+        rqlst = self._prepare('CWUser X WHERE X login %(x)s').children[0]
         self.o._args = {'x': "toto"}
         self.assertEquals(self.o.generate(rqlst, 'X')[1],
                           '(&(objectClass=top)(objectClass=posixAccount)(uid=toto))')
-        
+
     def test_get_attr(self):
         rqlst = self._prepare('Any X WHERE E firstname X, E eid 12').children[0]
         self.assertRaises(UnknownEid, self.o.generate, rqlst, 'E')
-        
-        
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_migractions.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_migractions.py	Thu May 14 12:50:34 2009 +0200
@@ -1,11 +1,13 @@
 """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
 
+from cubicweb.schema import CubicWebSchemaLoader
+from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.repository import Repository
 from cubicweb.server.migractions import *
 
@@ -17,10 +19,10 @@
 def teardown_module(*args):
     Repository.get_versions = orig_get_versions
 
-    
+
 class MigrationCommandsTC(RepositoryBasedTC):
     copy_schema = True
-    
+
     def setUp(self):
         if not hasattr(self, '_repo'):
             # first initialization
@@ -29,7 +31,6 @@
             repo.config._cubes = None
             repo.fill_schema()
             # hack to read the schema from data/migrschema
-            from cubicweb.schema import CubicWebSchemaLoader
             CubicWebSchemaLoader.main_schema_directory = 'migrschema'
             global migrschema
             migrschema = self.repo.config.load_schema()
@@ -42,7 +43,7 @@
                                         interactive=False)
         assert self.cnx is self.mh._cnx
         assert self.session is self.mh.session, (self.session.id, self.mh.session.id)
-        
+
     def test_add_attribute_int(self):
         self.failIf('whatever' in self.schema)
         paraordernum = self.mh.rqlexec('Any O WHERE X name "Note", RT name "para", RDEF from_entity X, RDEF relation_type RT, RDEF ordernum O')[0][0]
@@ -68,26 +69,26 @@
         self.assertEquals(self.schema['shortpara'].subjects(), ('Note', ))
         self.assertEquals(self.schema['shortpara'].objects(), ('String', ))
         # test created column is actually a varchar(64)
-        notesql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' and name='Note'")[0][0]
+        notesql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' and name='%sNote'" % SQL_PREFIX)[0][0]
         fields = dict(x.strip().split()[:2] for x in notesql.split('(', 1)[1].rsplit(')', 1)[0].split(','))
-        self.assertEquals(fields['shortpara'], 'varchar(64)')
+        self.assertEquals(fields['%sshortpara' % SQL_PREFIX], 'varchar(64)')
         self.mh.rollback()
-        
+
     def test_add_datetime_with_default_value_attribute(self):
         self.failIf('mydate' in self.schema)
         self.mh.cmd_add_attribute('Note', 'mydate')
         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()
-            
+
     def test_rename_attribute(self):
         self.failIf('civility' in self.schema)
         eid1 = self.mh.rqlexec('INSERT Personne X: X nom "lui", X sexe "M"')[0][0]
@@ -120,15 +121,15 @@
             self.assertEquals(t1, "baz")
         gn = self.mh.rqlexec('Any GN WHERE T require_group G, G name GN, T eid %s' % baz)[0][0]
         self.assertEquals(gn, 'managers')
-        
+
     def test_add_entity_type(self):
         self.failIf('Folder2' in self.schema)
         self.failIf('filed_under2' in self.schema)
         self.mh.cmd_add_entity_type('Folder2')
         self.failUnless('Folder2' in self.schema)
-        self.failUnless(self.execute('EEType X WHERE X name "Folder2"'))
+        self.failUnless(self.execute('CWEType X WHERE X name "Folder2"'))
         self.failUnless('filed_under2' in self.schema)
-        self.failUnless(self.execute('ERType X WHERE X name "filed_under2"'))
+        self.failUnless(self.execute('CWRType X WHERE X name "filed_under2"'))
         self.assertEquals(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()),
                           ['created_by', 'creation_date', 'description', 'description_format', 'eid',
                            'filed_under2', 'has_text', 'identity', 'is', 'is_instance_of',
@@ -136,7 +137,7 @@
         self.assertEquals([str(rs) for rs in self.schema['Folder2'].object_relations()],
                           ['filed_under2', 'identity'])
         self.assertEquals(sorted(str(e) for e in self.schema['filed_under2'].subjects()),
-                          ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File', 
+                          ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
                            'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
         self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
         eschema = self.schema.eschema('Folder2')
@@ -153,7 +154,7 @@
         eschema = self.schema.eschema('Folder2')
         self.mh.cmd_drop_entity_type('Folder2')
         self.failIf('Folder2' in self.schema)
-        self.failIf(self.execute('EEType X WHERE X name "Folder2"'))
+        self.failIf(self.execute('CWEType X WHERE X name "Folder2"'))
         # test automatic workflow deletion
         self.failIf(self.execute('State X WHERE NOT X state_of ET'))
         self.failIf(self.execute('Transition X WHERE NOT X transition_of ET'))
@@ -163,7 +164,7 @@
         self.mh.cmd_add_relation_type('filed_under2')
         self.failUnless('filed_under2' in self.schema)
         self.assertEquals(sorted(str(e) for e in self.schema['filed_under2'].subjects()),
-                          ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File', 
+                          ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
                            'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
         self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
 
@@ -177,8 +178,8 @@
 
     def test_add_relation_definition(self):
         self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State')
-        self.assertEquals(sorted(self.schema['in_state'].subjects()),
-                          ['Affaire', 'Division', 'EUser', 'Note', 'Societe', 'SubDivision'])
+        self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()),
+                          ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision'])
         self.assertEquals(self.schema['in_state'].objects(), ('State',))
 
     def test_add_relation_definition_nortype(self):
@@ -194,7 +195,7 @@
         self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision'])
-        
+
     def test_drop_relation_definition_with_specialization(self):
         self.failUnless('concerne' in self.schema)
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
@@ -203,13 +204,13 @@
         self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note'])
-        
+
     def test_drop_relation_definition2(self):
         self.failUnless('evaluee' in self.schema)
         self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note')
         self.failUnless('evaluee' in self.schema)
         self.assertEquals(sorted(self.schema['evaluee'].subjects()),
-                          ['Division', 'EUser', 'Societe', 'SubDivision'])
+                          ['CWUser', 'Division', 'Societe', 'SubDivision'])
         self.assertEquals(sorted(self.schema['evaluee'].objects()),
                           ['Note'])
 
@@ -228,7 +229,7 @@
         finally:
             self.mh.cmd_change_relation_props('Affaire', 'concerne', 'Societe',
                                               cardinality='**')
-            
+
     def test_change_relation_props_final(self):
         rschema = self.schema['adel']
         card = rschema.rproperty('Personne', 'String', 'fulltextindexed')
@@ -254,18 +255,18 @@
 #         self.assertEquals([rs.type for rs in migrschema['Personne'].ordered_relations() if rs.is_final()],
 #                           expected)
         migrschema['Personne'].description = 'blabla bla'
-        migrschema['titre'].description = 'usually a title' 
+        migrschema['titre'].description = 'usually a title'
         migrschema['titre']._rproperties[('Personne', 'String')]['description'] = 'title for this person'
-#         rinorderbefore = cursor.execute('Any O,N WHERE X is EFRDef, X relation_type RT, RT name N,'
+#         rinorderbefore = cursor.execute('Any O,N WHERE X is CWAttribute, X relation_type RT, RT name N,'
 #                                         'X from_entity FE, FE name "Personne",'
 #                                         'X ordernum O ORDERBY O')
 #         expected = [u'creation_date', u'modification_date', u'nom', u'prenom',
 #                     u'sexe', u'promo', u'titre', u'adel', u'ass', u'web', u'tel',
 #                     u'fax', u'datenaiss', u'test', u'description']
 #        self.assertListEquals(rinorderbefore, map(list, zip([0, 0]+range(1, len(expected)), expected)))
-        
-        self.mh.cmd_synchronize_schema(commit=False)
-        
+
+        self.mh.cmd_sync_schema_props_perms(commit=False)
+
         self.assertEquals(cursor.execute('Any D WHERE X name "Personne", X description D')[0][0],
                           'blabla bla')
         self.assertEquals(cursor.execute('Any D WHERE X name "titre", X description D')[0][0],
@@ -278,7 +279,7 @@
         # schema and so behaviour is undefined
         # "civility" is also skipped since it may have been added by
         # test_rename_attribut :o/
-        rinorder = [n for n, in cursor.execute('Any N ORDERBY O WHERE X is EFRDef, X relation_type RT, RT name N,'
+        rinorder = [n for n, in cursor.execute('Any N ORDERBY O WHERE X is CWAttribute, X relation_type RT, RT name N,'
                                                'X from_entity FE, FE name "Personne",'
                                                'X ordernum O') if n not in ('sexe', 'description', 'civility')]
         expected = [u'nom', u'prenom', u'promo', u'ass', u'adel', u'titre',
@@ -299,7 +300,7 @@
         # no more rqlexpr to delete and add para attribute
         self.failIf(self._rrqlexpr_rset('add', 'para'))
         self.failIf(self._rrqlexpr_rset('delete', 'para'))
-        # new rql expr to add ecrit_par relation        
+        # new rql expr to add ecrit_par relation
         rexpr = self._rrqlexpr_entity('add', 'ecrit_par')
         self.assertEquals(rexpr.expression,
                           'O require_permission P, P name "add_note", '
@@ -332,36 +333,36 @@
         self.assertEquals(len(cursor.execute('RQLExpression X WHERE NOT ET1 read_permission X, NOT ET2 add_permission X, '
                                              'NOT ET3 delete_permission X, NOT ET4 update_permission X')), 8+1)
         # finally
-        self.assertEquals(len(cursor.execute('RQLExpression X')), nbrqlexpr_start + 1 + 2) 
-                          
+        self.assertEquals(len(cursor.execute('RQLExpression X')), nbrqlexpr_start + 1 + 2)
+
         self.mh.rollback()
 
     def _erqlexpr_rset(self, action, ertype):
-        rql = 'RQLExpression X WHERE ET is EEType, ET %s_permission X, ET name %%(name)s' % action
+        rql = 'RQLExpression X WHERE ET is CWEType, ET %s_permission X, ET name %%(name)s' % action
         return self.mh.rqlcursor.execute(rql, {'name': ertype})
     def _erqlexpr_entity(self, action, ertype):
         rset = self._erqlexpr_rset(action, ertype)
         self.assertEquals(len(rset), 1)
         return rset.get_entity(0, 0)
     def _rrqlexpr_rset(self, action, ertype):
-        rql = 'RQLExpression X WHERE ET is ERType, ET %s_permission X, ET name %%(name)s' % action
+        rql = 'RQLExpression X WHERE ET is CWRType, ET %s_permission X, ET name %%(name)s' % action
         return self.mh.rqlcursor.execute(rql, {'name': ertype})
     def _rrqlexpr_entity(self, action, ertype):
         rset = self._rrqlexpr_rset(action, ertype)
         self.assertEquals(len(rset), 1)
         return rset.get_entity(0, 0)
-    
+
     def test_set_size_constraint(self):
         # existing previous value
         try:
-            self.mh.cmd_set_size_constraint('EEType', 'name', 128)
+            self.mh.cmd_set_size_constraint('CWEType', 'name', 128)
         finally:
-            self.mh.cmd_set_size_constraint('EEType', 'name', 64)
+            self.mh.cmd_set_size_constraint('CWEType', 'name', 64)
         # non existing previous value
         try:
-            self.mh.cmd_set_size_constraint('EEType', 'description', 256)
+            self.mh.cmd_set_size_constraint('CWEType', 'description', 256)
         finally:
-            self.mh.cmd_set_size_constraint('EEType', 'description', None)
+            self.mh.cmd_set_size_constraint('CWEType', 'description', None)
 
     def test_add_remove_cube(self):
         cubes = set(self.config.cubes())
@@ -377,7 +378,7 @@
                 cubes.remove('email')
                 cubes.remove('file')
                 self.assertEquals(set(self.config.cubes()), cubes)
-                for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image', 
+                for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
                                'sender', 'in_thread', 'reply_to', 'data_format'):
                     self.failIf(ertype in schema, ertype)
                 self.assertEquals(sorted(schema['see_also']._rproperties.keys()),
@@ -401,7 +402,7 @@
             cubes.add('email')
             cubes.add('file')
             self.assertEquals(set(self.config.cubes()), cubes)
-            for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image', 
+            for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
                            'sender', 'in_thread', 'reply_to', 'data_format'):
                 self.failUnless(ertype in schema, ertype)
             self.assertEquals(sorted(schema['see_also']._rproperties.keys()),
@@ -425,17 +426,13 @@
             self.maxeid = self.execute('Any MAX(X)')[0][0]
             # why this commit is necessary is unclear to me (though without it
             # next test may fail complaining of missing tables
-            self.commit() 
+            self.commit()
 
     def test_set_state(self):
         user = self.session.user
-        self.set_debug(True)
         self.mh.set_state(user.eid, 'deactivated')
         user.clear_related_cache('in_state', 'subject')
-        try:
-            self.assertEquals(user.state, 'deactivated')
-        finally:
-            self.set_debug(False)
-        
+        self.assertEquals(user.state, 'deactivated')
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_msplanner.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_msplanner.py	Thu May 14 12:50:34 2009 +0200
@@ -19,47 +19,53 @@
 
 class FakeUserROSource(AbstractSource):
     uri = 'zzz'
-    support_entities = {'EUser': False}
+    support_entities = {'CWUser': False}
     support_relations = {}
     def syntax_tree_search(self, *args, **kwargs):
         return []
 
-        
+
 class FakeCardSource(AbstractSource):
     uri = 'ccc'
     support_entities = {'Card': True, 'Note': True, 'State': True}
     support_relations = {'in_state': True, 'multisource_rel': True, 'multisource_inlined_rel': True,
                          'multisource_crossed_rel': True}
-    dont_cross_relations = set(('fiche',))
+    dont_cross_relations = set(('fiche', 'in_state'))
     cross_relations = set(('multisource_crossed_rel',))
-    
+
     def syntax_tree_search(self, *args, **kwargs):
         return []
 
 X_ALL_SOLS = sorted([{'X': 'Affaire'}, {'X': 'Basket'}, {'X': 'Bookmark'},
                      {'X': 'Card'}, {'X': 'Comment'}, {'X': 'Division'},
-                     {'X': 'ECache'}, {'X': 'EConstraint'}, {'X': 'EConstraintType'},
-                     {'X': 'EEType'}, {'X': 'EFRDef'}, {'X': 'EGroup'},
-                     {'X': 'ENFRDef'}, {'X': 'EPermission'}, {'X': 'EProperty'},
-                     {'X': 'ERType'}, {'X': 'EUser'}, {'X': 'Email'},
+                     {'X': 'CWCache'}, {'X': 'CWConstraint'}, {'X': 'CWConstraintType'},
+                     {'X': 'CWEType'}, {'X': 'CWAttribute'}, {'X': 'CWGroup'},
+                     {'X': 'CWRelation'}, {'X': 'CWPermission'}, {'X': 'CWProperty'},
+                     {'X': 'CWRType'}, {'X': 'CWUser'}, {'X': 'Email'},
                      {'X': 'EmailAddress'}, {'X': 'EmailPart'}, {'X': 'EmailThread'},
                      {'X': 'File'}, {'X': 'Folder'}, {'X': 'Image'},
                      {'X': 'Note'}, {'X': 'Personne'}, {'X': 'RQLExpression'},
                      {'X': 'Societe'}, {'X': 'State'}, {'X': 'SubDivision'},
                      {'X': 'Tag'}, {'X': 'TrInfo'}, {'X': 'Transition'}])
 
+def clear_ms_caches(repo):
+    clear_cache(repo, 'rel_type_sources')
+    clear_cache(repo, 'can_cross_relation')
+    clear_cache(repo, 'is_multi_sources_relation')
+    # XXX source_defs
+
 # keep cnx so it's not garbage collected and the associated session is closed
 repo, cnx = init_test_database('sqlite')
 
 class BaseMSPlannerTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
-    
+
     * system source supporting everything
-    * ldap source supporting EUser
+    * ldap source supporting CWUser
     * rql source supporting Card
     """
     repo = repo
-    
+
     def setUp(self):
         #_QuerierTC.setUp(self)
         clear_cache(repo, 'rel_type_sources')
@@ -73,12 +79,12 @@
         # add access to type attribute so S can't be invariant
         affreadperms[-1] = ERQLExpression('X concerne S?, S owned_by U, S type "X"')
         self.schema['Affaire']._groups['read'] = tuple(affreadperms)
-        # hijack EUser security
-        userreadperms = list(self.schema['EUser']._groups['read'])
+        # hijack CWUser security
+        userreadperms = list(self.schema['CWUser']._groups['read'])
         self.prevrqlexpr_user = userreadperms[-1]
         userreadperms[-1] = ERQLExpression('X owned_by U')
-        self.schema['EUser']._groups['read'] = tuple(userreadperms)
-        
+        self.schema['CWUser']._groups['read'] = tuple(userreadperms)
+
         self.sources = self.o._repo.sources
         self.system = self.sources[-1]
         self.sources.append(FakeUserROSource(self.o._repo, self.o.schema,
@@ -90,7 +96,8 @@
         repo.sources_by_uri['cards'] = self.sources[-1]
         self.rql = self.sources[-1]
         do_monkey_patch()
-        
+        clear_ms_caches(repo)
+
     def tearDown(self):
         undo_monkey_patch()
         del self.sources[-1]
@@ -100,93 +107,93 @@
         # restore hijacked security
         self.restore_orig_affaire_security()
         self.restore_orig_euser_security()
-        
+
     def restore_orig_affaire_security(self):
         affreadperms = list(self.schema['Affaire']._groups['read'])
         affreadperms[-1] = self.prevrqlexpr_affaire
         self.schema['Affaire']._groups['read'] = tuple(affreadperms)
         clear_cache(self.schema['Affaire'], 'ERSchema_get_rqlexprs')
-        
+
     def restore_orig_euser_security(self):
-        userreadperms = list(self.schema['EUser']._groups['read'])
+        userreadperms = list(self.schema['CWUser']._groups['read'])
         userreadperms[-1] = self.prevrqlexpr_user
-        self.schema['EUser']._groups['read'] = tuple(userreadperms)
-        clear_cache(self.schema['EUser'], 'ERSchema_get_rqlexprs')
+        self.schema['CWUser']._groups['read'] = tuple(userreadperms)
+        clear_cache(self.schema['CWUser'], 'ERSchema_get_rqlexprs')
 
-                  
+
 class PartPlanInformationTC(BaseMSPlannerTC):
 
     def _test(self, rql, *args):
         if len(args) == 3:
-            kwargs, sourcesvars, needsplit = args
+            kwargs, sourcesterms, needsplit = args
         else:
-            sourcesvars, needsplit = args
+            sourcesterms, needsplit = args
             kwargs = None
         plan = self._prepare_plan(rql, kwargs)
         union = plan.rqlst
         plan.preprocess(union)
         ppi = PartPlanInformation(plan, union.children[0])
-        for sourcevars in ppi._sourcesvars.itervalues():
+        for sourcevars in ppi._sourcesterms.itervalues():
             for var in sourcevars.keys():
                 solindices = sourcevars.pop(var)
                 sourcevars[var._ms_table_key()] = solindices
-        self.assertEquals(ppi._sourcesvars, sourcesvars)
+        self.assertEquals(ppi._sourcesterms, sourcesterms)
         self.assertEquals(ppi.needsplit, needsplit)
 
-        
+
     def test_simple_system_only(self):
         """retrieve entities only supported by the system source"""
-        self._test('EGroup X',
+        self._test('CWGroup X',
                    {self.system: {'X': s[0]}}, False)
-        
+
     def test_simple_system_ldap(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
-        self._test('EUser X',
+        self._test('CWUser X',
                    {self.system: {'X': s[0]}, self.ldap: {'X': s[0]}}, False)
-        
+
     def test_simple_system_rql(self):
         """retrieve Card X from both sources and return concatenation of results
         """
         self._test('Any X, XT WHERE X is Card, X title XT',
                    {self.system: {'X': s[0]}, self.rql: {'X': s[0]}}, False)
-        
+
     def test_simple_eid_specified(self):
-        """retrieve EUser X from system source (eid is specified, can locate the entity)
+        """retrieve CWUser X from system source (eid is specified, can locate the entity)
         """
         ueid = self.session.user.eid
         self._test('Any X,L WHERE X eid %(x)s, X login L', {'x': ueid},
                    {self.system: {'X': s[0]}}, False)
-        
+
     def test_simple_eid_invariant(self):
-        """retrieve EUser X from system source (eid is specified, can locate the entity)
+        """retrieve CWUser X from system source (eid is specified, can locate the entity)
         """
         ueid = self.session.user.eid
         self._test('Any X WHERE X eid %(x)s', {'x': ueid},
-                   {}, False)
-        
+                   {self.system: {'x': s[0]}}, False)
+
     def test_simple_invariant(self):
-        """retrieve EUser X from system source only (X is invariant and in_group not supported by ldap source)
+        """retrieve CWUser X from system source only (X is invariant and in_group not supported by ldap source)
         """
-        self._test('Any X WHERE X is EUser, X in_group G, G name "users"',
+        self._test('Any X WHERE X is CWUser, X in_group G, G name "users"',
                    {self.system: {'X': s[0], 'G': s[0], 'in_group': s[0]}}, False)
-        
+
     def test_security_has_text(self):
-        """retrieve EUser X from system source only (has_text not supported by ldap source)
+        """retrieve CWUser X from system source only (has_text not supported by ldap source)
         """
-        # specify EUser instead of any since the way this test is written we aren't well dealing
+        # specify CWUser instead of any since the way this test is written we aren't well dealing
         # with ambigous query (eg only considering the first solution)
-        self._test('EUser X WHERE X has_text "bla"',
+        self._test('CWUser X WHERE X has_text "bla"',
                    {self.system: {'X': s[0]}}, False)
-        
+
     def test_complex_base(self):
         """
-        1. retrieve Any X, L WHERE X is EUser, X login L from system and ldap sources, store
+        1. retrieve Any X, L WHERE X is CWUser, X login L from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X, L WHERE X is TMP, X login L, X in_group G,
            G name 'users' on the system source
         """
-        self._test('Any X,L WHERE X is EUser, X in_group G, X login L, G name "users"',
+        self._test('Any X,L WHERE X is CWUser, X in_group G, X login L, G name "users"',
                    {self.system: {'X': s[0], 'G': s[0], 'in_group': s[0]},
                     self.ldap : {'X': s[0]}}, True)
 
@@ -195,7 +202,7 @@
         1. retrieve Any X,AA WHERE X modification_date AA from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X,AA ORDERBY AA WHERE %s owned_by X, X modification_date AA
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,AA ORDERBY AA WHERE E eid %(x)s, E owned_by X, X modification_date AA', {'x': ueid},
@@ -207,7 +214,7 @@
         1. retrieve Any X,L,AA WHERE X login L, X modification_date AA from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X,L,AA WHERE %s owned_by X, X login L, X modification_date AA
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,L,AA WHERE E eid %(x)s, E owned_by X, X login L, X modification_date AA', {'x': ueid},
@@ -215,7 +222,7 @@
                     self.ldap : {'X': s[0]}}, True)
 
     def test_complex_ambigous(self):
-        """retrieve EUser X from system and ldap sources, Person X from system source only
+        """retrieve CWUser X from system and ldap sources, Person X from system source only
         """
         self._test('Any X,F WHERE X firstname F',
                    {self.system: {'X': s[0, 1]},
@@ -226,22 +233,25 @@
         1. retrieve Any X,A,Y,B WHERE X login A, Y login B from system and ldap sources, store
            cartesian product of results into a temporary table
         2. return the result of Any X,Y WHERE X login 'syt', Y login 'adim'
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,Y WHERE X login "syt", Y login "adim"', {'x': ueid},
                    {self.system: {'Y': s[0], 'X': s[0]},
                     self.ldap: {'Y': s[0], 'X': s[0]}}, True)
-        
+
     def test_complex_aggregat(self):
         solindexes = set(range(len([e for e in self.schema.entities() if not e.is_final()])))
         self._test('Any MAX(X)',
                    {self.system: {'X': solindexes}}, False)
-                   
+
     def test_complex_optional(self):
         ueid = self.session.user.eid
         self._test('Any U WHERE WF wf_info_for X, X eid %(x)s, WF owned_by U?, WF from_state FS', {'x': ueid},
-                   {self.system: {'WF': s[0], 'FS': s[0], 'U': s[0], 'from_state': s[0], 'owned_by': s[0], 'wf_info_for': s[0]}}, False)
+                   {self.system: {'WF': s[0], 'FS': s[0], 'U': s[0],
+                                  'from_state': s[0], 'owned_by': s[0], 'wf_info_for': s[0],
+                                  'x': s[0]}},
+                   False)
 
     def test_exists4(self):
         """
@@ -252,35 +262,105 @@
         self._test('Any G,L WHERE X in_group G, X login L, G name "managers", '
                    'EXISTS(X copain T, T login L, T login in ("comme", "cochon")) OR '
                    'EXISTS(X in_state S, S name "pascontent", NOT X copain T2, T2 login "billy")',
-                   {self.system: {'X': s[0], 'S': s[0], 'T2': s[0], 'T': s[0], 'G': s[0], 'copain': s[0], 'in_group': s[0]}, 
-                    self.ldap: {'X': s[0], 'T2': s[0], 'T': s[0]}}, True)
+                   {self.system: {'X': s[0], 'S': s[0], 'T2': s[0], 'T': s[0], 'G': s[0], 'copain': s[0], 'in_group': s[0]},
+                    self.ldap: {'X': s[0], 'T2': s[0], 'T': s[0]}},
+                   True)
 
     def test_relation_need_split(self):
         self._test('Any X, S WHERE X in_state S',
                    {self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2]},
-                     self.rql: {'X': s[2], 'S': s[2]}}, True)
+                    self.rql: {'X': s[2], 'S': s[2]}},
+                   True)
+
+    def test_not_relation_need_split(self):
+        self._test('Any SN WHERE NOT X in_state S, S name SN',
+                   {self.rql: {'X': s[2], 'S': s[0, 1, 2]},
+                    self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2]}},
+                   True)
+
+    def test_not_relation_no_split_external(self):
+        repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        # similar to the above test but with an eid coming from the external source.
+        # the same plan may be used, since we won't find any record in the system source
+        # linking 9999999 to a state
+        self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
+                   {'x': 999999},
+                   {self.rql: {'x': s[0], 'S': s[0]},
+                    self.system: {'x': s[0], 'S': s[0]}},
+                   False)
 
     def test_relation_restriction_ambigous_need_split(self):
         self._test('Any X,T WHERE X in_state S, S name "pending", T tags X',
                    {self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2], 'T': s[0, 1, 2], 'tags': s[0, 1, 2]},
-                    self.rql: {'X': s[2], 'S': s[2]}}, True)
+                    self.rql: {'X': s[2], 'S': s[2]}},
+                   True)
 
     def test_simplified_var(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         self._test('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
                    {'x': 999999, 'u': self.session.user.eid},
-                   {self.system: {'P': s[0], 'G': s[0], 'X': s[0], 'require_permission': s[0], 'in_group': s[0], 'P': s[0], 'require_group': s[0]}}, False)
-        
+                   {self.system: {'P': s[0], 'G': s[0], 'X': s[0],
+                                  'require_permission': s[0], 'in_group': s[0], 'P': s[0], 'require_group': s[0],
+                                  'u': s[0]}},
+                   False)
+
     def test_delete_relation1(self):
         ueid = self.session.user.eid
         self._test('Any X, Y WHERE X created_by Y, X eid %(x)s, NOT Y eid %(y)s',
                    {'x': ueid, 'y': ueid},
-                   {self.system: {'Y': s[0], 'created_by': s[0]}}, False)
-                   
+                   {self.system: {'Y': s[0], 'created_by': s[0], 'x': s[0]}},
+                   False)
+
+    def test_crossed_relation_eid_1_needattr(self):
+        repo._type_source_cache[999999] = ('Note', 'system', 999999)
+        ueid = self.session.user.eid
+        self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
+                   {'x': 999999,},
+                   {self.rql: {'Y': s[0]}, self.system: {'Y': s[0], 'x': s[0]}},
+                   True)
+
+    def test_crossed_relation_eid_1_invariant(self):
+        repo._type_source_cache[999999] = ('Note', 'system', 999999)
+        self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
+                   {'x': 999999},
+                   {self.system: {'Y': s[0], 'x': s[0]}},
+                   False)
+
+    def test_crossed_relation_eid_2_invariant(self):
+        repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
+                   {'x': 999999,},
+                   {self.rql: {'Y': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
+                    self.system: {'Y': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]}},
+                   False)
 
-        
+    def test_version_crossed_depends_on_1(self):
+        repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
+                   {'x': 999999},
+                   {self.rql: {'X': s[0], 'AD': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
+                    self.system: {'X': s[0], 'AD': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]}},
+                   True)
+
+    def test_version_crossed_depends_on_2(self):
+        repo._type_source_cache[999999] = ('Note', 'system', 999999)
+        self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
+                   {'x': 999999},
+                   {self.rql: {'X': s[0], 'AD': s[0]},
+                    self.system: {'X': s[0], 'AD': s[0], 'x': s[0]}},
+                    True)
+
+    def test_simplified_var_3(self):
+        repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        repo._type_source_cache[999998] = ('State', 'cards', 999998)
+        self._test('Any S,T WHERE S eid %(s)s, N eid %(n)s, N type T, N is Note, S is State',
+                   {'n': 999999, 's': 999998},
+                   {self.rql: {'s': s[0], 'N': s[0]}}, False)
+
+
+
 class MSPlannerTC(BaseMSPlannerTC):
-    
+
     def setUp(self):
         BaseMSPlannerTC.setUp(self)
         self.planner = MSPlanner(self.o.schema, self.o._rqlhelper)
@@ -290,150 +370,150 @@
     def test_simple_system_only(self):
         """retrieve entities only supported by the system source
         """
-        self._test('EGroup X',
-                   [('OneFetchStep', [('Any X WHERE X is EGroup', [{'X': 'EGroup'}])],
+        self._test('CWGroup X',
+                   [('OneFetchStep', [('Any X WHERE X is CWGroup', [{'X': 'CWGroup'}])],
                      None, None, [self.system], {}, [])])
 
     def test_simple_system_only_limit(self):
         """retrieve entities only supported by the system source
         """
-        self._test('EGroup X LIMIT 10',
-                   [('OneFetchStep', [('Any X LIMIT 10 WHERE X is EGroup', [{'X': 'EGroup'}])],
+        self._test('CWGroup X LIMIT 10',
+                   [('OneFetchStep', [('Any X LIMIT 10 WHERE X is CWGroup', [{'X': 'CWGroup'}])],
                      10, None, [self.system], {}, [])])
 
     def test_simple_system_only_limit_offset(self):
         """retrieve entities only supported by the system source
         """
-        self._test('EGroup X LIMIT 10 OFFSET 10',
-                   [('OneFetchStep', [('Any X LIMIT 10 OFFSET 10 WHERE X is EGroup', [{'X': 'EGroup'}])],
+        self._test('CWGroup X LIMIT 10 OFFSET 10',
+                   [('OneFetchStep', [('Any X LIMIT 10 OFFSET 10 WHERE X is CWGroup', [{'X': 'CWGroup'}])],
                      10, 10, [self.system], {}, [])])
-        
+
     def test_simple_system_ldap(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
-        self._test('EUser X',
-                   [('OneFetchStep', [('Any X WHERE X is EUser', [{'X': 'EUser'}])],
+        self._test('CWUser X',
+                   [('OneFetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
                      None, None, [self.ldap, self.system], {}, [])])
-        
+
     def test_simple_system_ldap_limit(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
-        self._test('EUser X LIMIT 10',
-                   [('OneFetchStep', [('Any X LIMIT 10 WHERE X is EUser', [{'X': 'EUser'}])],
+        self._test('CWUser X LIMIT 10',
+                   [('OneFetchStep', [('Any X LIMIT 10 WHERE X is CWUser', [{'X': 'CWUser'}])],
                      10, None, [self.ldap, self.system], {}, [])])
 
     def test_simple_system_ldap_limit_offset(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
-        self._test('EUser X LIMIT 10 OFFSET 10',
-                   [('OneFetchStep', [('Any X LIMIT 10 OFFSET 10 WHERE X is EUser', [{'X': 'EUser'}])],
+        self._test('CWUser X LIMIT 10 OFFSET 10',
+                   [('OneFetchStep', [('Any X LIMIT 10 OFFSET 10 WHERE X is CWUser', [{'X': 'CWUser'}])],
                      10, 10, [self.ldap, self.system], {}, [])])
 
     def test_simple_system_ldap_ordered_limit_offset(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
-        self._test('EUser X ORDERBY X LIMIT 10 OFFSET 10',
+        self._test('CWUser X ORDERBY X LIMIT 10 OFFSET 10',
                    [('AggrStep', 'Any X ORDERBY X', 10, 10, 'table0', None, [
-                       ('FetchStep', [('Any X WHERE X is EUser', [{'X': 'EUser'}])],
+                       ('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
                         [self.ldap, self.system], {}, {'X': 'table0.C0'}, []),
                        ]),
                    ])
     def test_simple_system_ldap_aggregat(self):
-        """retrieve EUser X from both sources and return concatenation of results
+        """retrieve CWUser X from both sources and return concatenation of results
         """
         # COUNT(X) is kept in sub-step and transformed into SUM(X) in the AggrStep
-        self._test('Any COUNT(X) WHERE X is EUser',
+        self._test('Any COUNT(X) WHERE X is CWUser',
                    [('AggrStep', 'Any COUNT(X)', None, None, 'table0', None, [
-                       ('FetchStep', [('Any COUNT(X) WHERE X is EUser', [{'X': 'EUser'}])],
+                       ('FetchStep', [('Any COUNT(X) WHERE X is CWUser', [{'X': 'CWUser'}])],
                         [self.ldap, self.system], {}, {'COUNT(X)': 'table0.C0'}, []),
                        ]),
                    ])
-        
+
     def test_simple_system_rql(self):
         """retrieve Card X from both sources and return concatenation of results
         """
         self._test('Any X, XT WHERE X is Card, X title XT',
                    [('OneFetchStep', [('Any X,XT WHERE X is Card, X title XT', [{'X': 'Card', 'XT': 'String'}])],
                      None, None, [self.rql, self.system], {}, [])])
-        
+
     def test_simple_eid_specified(self):
-        """retrieve EUser X from system source (eid is specified, can locate the entity)
+        """retrieve CWUser X from system source (eid is specified, can locate the entity)
         """
         ueid = self.session.user.eid
         self._test('Any X,L WHERE X eid %(x)s, X login L',
-                   [('OneFetchStep', [('Any X,L WHERE X eid %s, X login L'%ueid, [{'X': 'EUser', 'L': 'String'}])],
+                   [('OneFetchStep', [('Any X,L WHERE X eid %s, X login L'%ueid, [{'X': 'CWUser', 'L': 'String'}])],
                      None, None, [self.system], {}, [])],
                    {'x': ueid})
-        
+
     def test_simple_eid_invariant(self):
-        """retrieve EUser X from system source (eid is specified, can locate the entity)
+        """retrieve CWUser X from system source (eid is specified, can locate the entity)
         """
         ueid = self.session.user.eid
         self._test('Any X WHERE X eid %(x)s',
                    [('OneFetchStep', [('Any %s'%ueid, [{}])],
                      None, None, [self.system], {}, [])],
                    {'x': ueid})
-        
+
     def test_simple_invariant(self):
-        """retrieve EUser X from system source only (X is invariant and in_group not supported by ldap source)
+        """retrieve CWUser X from system source only (X is invariant and in_group not supported by ldap source)
         """
-        self._test('Any X WHERE X is EUser, X in_group G, G name "users"',
-                   [('OneFetchStep', [('Any X WHERE X is EUser, X in_group G, G name "users"',
-                                       [{'X': 'EUser', 'G': 'EGroup'}])],
+        self._test('Any X WHERE X is CWUser, X in_group G, G name "users"',
+                   [('OneFetchStep', [('Any X WHERE X is CWUser, X in_group G, G name "users"',
+                                       [{'X': 'CWUser', 'G': 'CWGroup'}])],
                      None, None, [self.system], {}, [])])
-        
+
     def test_complex_base(self):
         """
-        1. retrieve Any X, L WHERE X is EUser, X login L from system and ldap sources, store
+        1. retrieve Any X, L WHERE X is CWUser, X login L from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X, L WHERE X is TMP, X login LX in_group G,
            G name 'users' on the system source
         """
-        self._test('Any X,L WHERE X is EUser, X in_group G, X login L, G name "users"',
-                   [('FetchStep', [('Any X,L WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+        self._test('Any X,L WHERE X is CWUser, X in_group G, X login L, G name "users"',
+                   [('FetchStep', [('Any X,L WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, []),
-                    ('OneFetchStep', [('Any X,L WHERE X in_group G, X login L, G name "users", G is EGroup, X is EUser',
-                                       [{'X': 'EUser', 'L': 'String', 'G': 'EGroup'}])],
+                    ('OneFetchStep', [('Any X,L WHERE X in_group G, X login L, G name "users", G is CWGroup, X is CWUser',
+                                       [{'X': 'CWUser', 'L': 'String', 'G': 'CWGroup'}])],
                      None, None, [self.system],
                      {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, [])
                     ])
 
     def test_complex_base_limit_offset(self):
         """
-        1. retrieve Any X, L WHERE X is EUser, X login L from system and ldap sources, store
+        1. retrieve Any X, L WHERE X is CWUser, X login L from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X, L WHERE X is TMP, X login LX in_group G,
            G name 'users' on the system source
         """
-        self._test('Any X,L LIMIT 10 OFFSET 10 WHERE X is EUser, X in_group G, X login L, G name "users"',
-                   [('FetchStep', [('Any X,L WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+        self._test('Any X,L LIMIT 10 OFFSET 10 WHERE X is CWUser, X in_group G, X login L, G name "users"',
+                   [('FetchStep', [('Any X,L WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, []),
-                    ('OneFetchStep', [('Any X,L LIMIT 10 OFFSET 10 WHERE X in_group G, X login L, G name "users", G is EGroup, X is EUser',
-                                       [{'X': 'EUser', 'L': 'String', 'G': 'EGroup'}])],
+                    ('OneFetchStep', [('Any X,L LIMIT 10 OFFSET 10 WHERE X in_group G, X login L, G name "users", G is CWGroup, X is CWUser',
+                                       [{'X': 'CWUser', 'L': 'String', 'G': 'CWGroup'}])],
                      10, 10,
                      [self.system], {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, [])
                     ])
 
     def test_complex_ordered(self):
         self._test('Any L ORDERBY L WHERE X login L',
-                   [('AggrStep', 'Any L ORDERBY L', None, None, 'table0', None, 
-                     [('FetchStep', [('Any L WHERE X login L, X is EUser',
-                                      [{'X': 'EUser', 'L': 'String'}])],
+                   [('AggrStep', 'Any L ORDERBY L', None, None, 'table0', None,
+                     [('FetchStep', [('Any L WHERE X login L, X is CWUser',
+                                      [{'X': 'CWUser', 'L': 'String'}])],
                        [self.ldap, self.system], {}, {'X.login': 'table0.C0', 'L': 'table0.C0'}, []),
                       ])
                     ])
 
     def test_complex_ordered_limit_offset(self):
         self._test('Any L ORDERBY L LIMIT 10 OFFSET 10 WHERE X login L',
-                   [('AggrStep', 'Any L ORDERBY L', 10, 10, 'table0', None, 
-                     [('FetchStep', [('Any L WHERE X login L, X is EUser',
-                                      [{'X': 'EUser', 'L': 'String'}])],
+                   [('AggrStep', 'Any L ORDERBY L', 10, 10, 'table0', None,
+                     [('FetchStep', [('Any L WHERE X login L, X is CWUser',
+                                      [{'X': 'CWUser', 'L': 'String'}])],
                        [self.ldap, self.system], {}, {'X.login': 'table0.C0', 'L': 'table0.C0'}, []),
                       ])
                     ])
-        
+
     def test_complex_invariant_ordered(self):
         """
         1. retrieve Any X,AA WHERE X modification_date AA from system and ldap sources, store
@@ -446,13 +526,13 @@
         ueid = self.session.user.eid
         self._test('Any X,AA ORDERBY AA WHERE E eid %(x)s, E owned_by X, X modification_date AA',
                    [('FetchStep',
-                     [('Any X,AA WHERE X modification_date AA, X is EUser',
-                       [{'AA': 'Datetime', 'X': 'EUser'}])],
+                     [('Any X,AA WHERE X modification_date AA, X is CWUser',
+                       [{'AA': 'Datetime', 'X': 'CWUser'}])],
                      [self.ldap, self.system], None,
                      {'AA': 'table0.C1', 'X': 'table0.C0', 'X.modification_date': 'table0.C1'}, []),
                     ('OneFetchStep',
-                     [('Any X,AA ORDERBY AA WHERE 5 owned_by X, X modification_date AA, X is EUser',
-                       [{'AA': 'Datetime', 'X': 'EUser'}])],
+                     [('Any X,AA ORDERBY AA WHERE 5 owned_by X, X modification_date AA, X is CWUser',
+                       [{'AA': 'Datetime', 'X': 'CWUser'}])],
                      None, None, [self.system],
                      {'AA': 'table0.C1', 'X': 'table0.C0', 'X.modification_date': 'table0.C1'}, []),
                     ],
@@ -463,27 +543,27 @@
         1. retrieve Any X,L,AA WHERE X login L, X modification_date AA from system and ldap sources, store
            concatenation of results into a temporary table
         2. return the result of Any X,L,AA WHERE %s owned_by X, X login L, X modification_date AA
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,L,AA WHERE E eid %(x)s, E owned_by X, X login L, X modification_date AA',
-                   [('FetchStep', [('Any X,L,AA WHERE X login L, X modification_date AA, X is EUser',
-                                    [{'AA': 'Datetime', 'X': 'EUser', 'L': 'String'}])],
+                   [('FetchStep', [('Any X,L,AA WHERE X login L, X modification_date AA, X is CWUser',
+                                    [{'AA': 'Datetime', 'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'AA': 'table0.C2', 'X': 'table0.C0', 'X.login': 'table0.C1', 'X.modification_date': 'table0.C2', 'L': 'table0.C1'}, []),
-                    ('OneFetchStep', [('Any X,L,AA WHERE %s owned_by X, X login L, X modification_date AA, X is EUser'%ueid,
-                                       [{'AA': 'Datetime', 'X': 'EUser', 'L': 'String'}])],
+                    ('OneFetchStep', [('Any X,L,AA WHERE %s owned_by X, X login L, X modification_date AA, X is CWUser'%ueid,
+                                       [{'AA': 'Datetime', 'X': 'CWUser', 'L': 'String'}])],
                      None, None, [self.system],
                      {'AA': 'table0.C2', 'X': 'table0.C0', 'X.login': 'table0.C1', 'X.modification_date': 'table0.C2', 'L': 'table0.C1'}, [])],
                    {'x': ueid})
 
     def test_complex_ambigous(self):
-        """retrieve EUser X from system and ldap sources, Person X from system source only
+        """retrieve CWUser X from system and ldap sources, Person X from system source only
         """
         self._test('Any X,F WHERE X firstname F',
                    [('UnionStep', None, None, [
-                       ('OneFetchStep', [('Any X,F WHERE X firstname F, X is EUser',
-                                          [{'X': 'EUser', 'F': 'String'}])],
+                       ('OneFetchStep', [('Any X,F WHERE X firstname F, X is CWUser',
+                                          [{'X': 'CWUser', 'F': 'String'}])],
                         None, None, [self.ldap, self.system], {}, []),
                        ('OneFetchStep', [('Any X,F WHERE X firstname F, X is Personne',
                                           [{'X': 'Personne', 'F': 'String'}])],
@@ -492,12 +572,12 @@
                     ])
 
     def test_complex_ambigous_limit_offset(self):
-        """retrieve EUser X from system and ldap sources, Person X from system source only
+        """retrieve CWUser X from system and ldap sources, Person X from system source only
         """
         self._test('Any X,F LIMIT 10 OFFSET 10 WHERE X firstname F',
                    [('UnionStep', 10, 10, [
-                       ('OneFetchStep', [('Any X,F WHERE X firstname F, X is EUser',
-                                          [{'X': 'EUser', 'F': 'String'}])],
+                       ('OneFetchStep', [('Any X,F WHERE X firstname F, X is CWUser',
+                                          [{'X': 'CWUser', 'F': 'String'}])],
                         None, None,
                         [self.ldap, self.system], {}, []),
                        ('OneFetchStep', [('Any X,F WHERE X firstname F, X is Personne',
@@ -508,14 +588,14 @@
 
     def test_complex_ambigous_ordered(self):
         """
-        1. retrieve EUser X from system and ldap sources, Person X from system source only, store
+        1. retrieve CWUser X from system and ldap sources, Person X from system source only, store
            each result in the same temp table
         2. return content of the table sorted
         """
         self._test('Any X,F ORDERBY F WHERE X firstname F',
-                   [('AggrStep', 'Any X,F ORDERBY F', None, None, 'table0', None, 
-                     [('FetchStep', [('Any X,F WHERE X firstname F, X is EUser',
-                                      [{'X': 'EUser', 'F': 'String'}])],
+                   [('AggrStep', 'Any X,F ORDERBY F', None, None, 'table0', None,
+                     [('FetchStep', [('Any X,F WHERE X firstname F, X is CWUser',
+                                      [{'X': 'CWUser', 'F': 'String'}])],
                        [self.ldap, self.system], {},
                        {'X': 'table0.C0', 'X.firstname': 'table0.C1', 'F': 'table0.C1'}, []),
                       ('FetchStep', [('Any X,F WHERE X firstname F, X is Personne',
@@ -524,58 +604,58 @@
                        {'X': 'table0.C0', 'X.firstname': 'table0.C1', 'F': 'table0.C1'}, []),
                       ]),
                     ])
-        
+
     def test_complex_multiple(self):
         """
         1. retrieve Any X,A,Y,B WHERE X login A, Y login B from system and ldap sources, store
            cartesian product of results into a temporary table
         2. return the result of Any X,Y WHERE X login 'syt', Y login 'adim'
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,Y WHERE X login "syt", Y login "adim"',
                    [('FetchStep',
-                     [('Any X WHERE X login "syt", X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE X login "syt", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table0.C0'}, []),
                     ('FetchStep',
-                     [('Any Y WHERE Y login "adim", Y is EUser', [{'Y': 'EUser'}])],
+                     [('Any Y WHERE Y login "adim", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system], None,
                      {'Y': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X,Y WHERE X is EUser, Y is EUser', [{'X': 'EUser', 'Y': 'EUser'}])],
+                     [('Any X,Y WHERE X is CWUser, Y is CWUser', [{'X': 'CWUser', 'Y': 'CWUser'}])],
                      None, None, [self.system],
                      {'X': 'table0.C0', 'Y': 'table1.C0'}, [])
                     ], {'x': ueid})
-        
+
     def test_complex_multiple_limit_offset(self):
         """
         1. retrieve Any X,A,Y,B WHERE X login A, Y login B from system and ldap sources, store
            cartesian product of results into a temporary table
         2. return the result of Any X,Y WHERE X login 'syt', Y login 'adim'
-           on the system source   
+           on the system source
         """
         ueid = self.session.user.eid
         self._test('Any X,Y LIMIT 10 OFFSET 10 WHERE X login "syt", Y login "adim"',
                    [('FetchStep',
-                     [('Any X WHERE X login "syt", X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE X login "syt", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
                     ('FetchStep',
-                     [('Any Y WHERE Y login "adim", Y is EUser', [{'Y': 'EUser'}])],
+                     [('Any Y WHERE Y login "adim", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system], None, {'Y': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X,Y LIMIT 10 OFFSET 10 WHERE X is EUser, Y is EUser', [{'X': 'EUser', 'Y': 'EUser'}])],
+                     [('Any X,Y LIMIT 10 OFFSET 10 WHERE X is CWUser, Y is CWUser', [{'X': 'CWUser', 'Y': 'CWUser'}])],
                      10, 10, [self.system],
                      {'X': 'table0.C0', 'Y': 'table1.C0'}, [])
                     ], {'x': ueid})
-        
+
     def test_complex_aggregat(self):
         self._test('Any MAX(X)',
                    [('OneFetchStep',
                      [('Any MAX(X)', X_ALL_SOLS)],
                      None, None, [self.system], {}, [])
                     ])
-        
+
     def test_complex_typed_aggregat(self):
         self._test('Any MAX(X) WHERE X is Card',
                    [('AggrStep', 'Any MAX(X)', None, None, 'table0',  None,
@@ -584,26 +664,26 @@
                        [self.rql, self.system], {}, {'MAX(X)': 'table0.C0'}, [])
                       ])
                     ])
-        
+
     def test_complex_greater_eid(self):
         self._test('Any X WHERE X eid > 12',
                    [('OneFetchStep',
                      [('Any X WHERE X eid > 12', X_ALL_SOLS)],
                      None, None, [self.system], {}, [])
                     ])
-        
+
     def test_complex_greater_typed_eid(self):
         self._test('Any X WHERE X eid > 12, X is Card',
                    [('OneFetchStep',
                      [('Any X WHERE X eid > 12, X is Card', [{'X': 'Card'}])],
                      None, None, [self.system], {}, [])
                     ])
-        
+
     def test_complex_optional(self):
         ueid = self.session.user.eid
         self._test('Any U WHERE WF wf_info_for X, X eid %(x)s, WF owned_by U?, WF from_state FS',
                    [('OneFetchStep', [('Any U WHERE WF wf_info_for 5, WF owned_by U?, WF from_state FS',
-                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'EUser'}])],
+                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser'}])],
                      None, None, [self.system], {}, [])],
                    {'x': ueid})
 
@@ -611,26 +691,26 @@
         ueid = self.session.user.eid
         self._test('Any U WHERE WF wf_info_for X, X eid %(x)s, WF owned_by U?, WF from_state FS',
                    [('OneFetchStep', [('Any U WHERE WF wf_info_for 5, WF owned_by U?, WF from_state FS',
-                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'EUser'}])],
+                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser'}])],
                      None, None, [self.system], {}, [])],
                    {'x': ueid})
 
-    
+
     def test_3sources_ambigous(self):
         self._test('Any X,T WHERE X owned_by U, U login "syt", X title T',
                    [('FetchStep', [('Any X,T WHERE X title T, X is Card', [{'X': 'Card', 'T': 'String'}])],
                      [self.rql, self.system], None,
                      {'T': 'table0.C1', 'X': 'table0.C0', 'X.title': 'table0.C1'}, []),
-                    ('FetchStep', [('Any U WHERE U login "syt", U is EUser', [{'U': 'EUser'}])],
+                    ('FetchStep', [('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
                      [self.ldap, self.system], None,
                      {'U': 'table1.C0'}, []),
                     ('UnionStep', None, None, [
-                        ('OneFetchStep', [('Any X,T WHERE X owned_by U, X title T, U is EUser, X is IN(Bookmark, EmailThread)',
-                                           [{'T': 'String', 'U': 'EUser', 'X': 'Bookmark'},
-                                            {'T': 'String', 'U': 'EUser', 'X': 'EmailThread'}])],
+                        ('OneFetchStep', [('Any X,T WHERE X owned_by U, X title T, U is CWUser, X is IN(Bookmark, EmailThread)',
+                                           [{'T': 'String', 'U': 'CWUser', 'X': 'Bookmark'},
+                                            {'T': 'String', 'U': 'CWUser', 'X': 'EmailThread'}])],
                          None, None, [self.system], {'U': 'table1.C0'}, []),
-                        ('OneFetchStep', [('Any X,T WHERE X owned_by U, X title T, U is EUser, X is Card',
-                                           [{'X': 'Card', 'U': 'EUser', 'T': 'String'}])],
+                        ('OneFetchStep', [('Any X,T WHERE X owned_by U, X title T, U is CWUser, X is Card',
+                                           [{'X': 'Card', 'U': 'CWUser', 'T': 'String'}])],
                          None, None, [self.system],
                          {'X': 'table0.C0', 'X.title': 'table0.C1', 'T': 'table0.C1', 'U': 'table1.C0'}, []),
                         ]),
@@ -656,7 +736,7 @@
                     ])
 
     def test_outer_supported_rel1(self):
-        # both system and rql support all variables, can be 
+        # both system and rql support all variables, can be
         self._test('Any X, R WHERE X is Note, X in_state S, X type R, '
                    'NOT EXISTS(Y is Note, Y in_state S, Y type R, X identity Y)',
                    [('OneFetchStep', [('Any X,R WHERE X is Note, X in_state S, X type R, NOT EXISTS(Y is Note, Y in_state S, Y type R, X identity Y), S is State',
@@ -666,10 +746,10 @@
                     ])
 
     def test_not_identity(self):
-        # both system and rql support all variables, can be 
+        # both system and rql support all variables, can be
         self._test('Any X WHERE NOT X identity U, U eid %s' % self.session.user.eid,
                    [('OneFetchStep',
-                     [('Any X WHERE NOT X identity 5, X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE NOT X identity 5, X is CWUser', [{'X': 'CWUser'}])],
                      None, None,
                      [self.ldap, self.system], {}, [])
                     ])
@@ -681,15 +761,15 @@
                                     [{'Y': 'Note', 'A': 'State', 'R': 'String'}])],
                      [self.rql, self.system], None,
                      {'A': 'table0.C0', 'R': 'table0.C1', 'Y.type': 'table0.C1'}, []),
-                    ('FetchStep', [('Any X,R WHERE X login R, X is EUser', [{'X': 'EUser', 'R': 'String'}])],
+                    ('FetchStep', [('Any X,R WHERE X login R, X is CWUser', [{'X': 'CWUser', 'R': 'String'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table1.C0', 'X.login': 'table1.C1', 'R': 'table1.C1'}, []),
-                    ('OneFetchStep', [('Any X,MAX(R) GROUPBY X WHERE X in_state S, X login R, NOT EXISTS(Y type R, S identity A, A is State, Y is Note), S is State, X is EUser',
-                                       [{'Y': 'Note', 'X': 'EUser', 'S': 'State', 'R': 'String', 'A': 'State'}])],
+                    ('OneFetchStep', [('Any X,MAX(R) GROUPBY X WHERE X in_state S, X login R, NOT EXISTS(Y type R, S identity A, A is State, Y is Note), S is State, X is CWUser',
+                                       [{'Y': 'Note', 'X': 'CWUser', 'S': 'State', 'R': 'String', 'A': 'State'}])],
                      None, None, [self.system],
                      {'A': 'table0.C0', 'X': 'table1.C0', 'X.login': 'table1.C1', 'R': 'table1.C1', 'Y.type': 'table0.C1'}, [])
                     ])
-            
+
     def test_security_has_text(self):
         # use a guest user
         self.session = self._user_session()[1]
@@ -704,8 +784,8 @@
                       ('OneFetchStep',
                        [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is Basket',
                          [{'X': 'Basket'}]),
-                        ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is EUser',
-                         [{'X': 'EUser'}]),
+                        ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser',
+                         [{'X': 'CWUser'}]),
                         ('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Image, Note, Personne, Societe, State, SubDivision, Tag, Transition)',
                          [{'X': 'Card'}, {'X': 'Comment'}, {'X': 'Division'},
                           {'X': 'Email'}, {'X': 'EmailThread'}, {'X': 'File'},
@@ -715,7 +795,7 @@
                        None, None, [self.system], {}, []),
                       ])
                      ])
-        
+
     def test_security_has_text_limit_offset(self):
         # use a guest user
         self.session = self._user_session()[1]
@@ -730,8 +810,8 @@
                          ('FetchStep',
                           [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is Basket',
                          [{'X': 'Basket'}]),
-                        ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is EUser',
-                         [{'X': 'EUser'}]),
+                        ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser',
+                         [{'X': 'CWUser'}]),
                         ('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Image, Note, Personne, Societe, State, SubDivision, Tag, Transition)',
                          [{'X': 'Card'}, {'X': 'Comment'}, {'X': 'Division'},
                           {'X': 'Email'}, {'X': 'EmailThread'}, {'X': 'File'},
@@ -743,34 +823,34 @@
                     ('OneFetchStep',
                      [('Any X LIMIT 10 OFFSET 10',
                        [{'X': 'Affaire'}, {'X': 'Basket'}, {'X': 'Card'},
-                        {'X': 'Comment'}, {'X': 'Division'}, {'X': 'EUser'},
+                        {'X': 'Comment'}, {'X': 'Division'}, {'X': 'CWUser'},
                         {'X': 'Email'}, {'X': 'EmailThread'}, {'X': 'File'},
                         {'X': 'Folder'}, {'X': 'Image'}, {'X': 'Note'},
                         {'X': 'Personne'}, {'X': 'Societe'}, {'X': 'State'},
                         {'X': 'SubDivision'}, {'X': 'Tag'}, {'X': 'Transition'}])],
-                     10, 10, [self.system], {'X': 'table0.C0'}, [])                    
+                     10, 10, [self.system], {'X': 'table0.C0'}, [])
                      ])
-        
+
     def test_security_user(self):
         """a guest user trying to see another user: EXISTS(X owned_by U) is automatically inserted"""
         # use a guest user
         self.session = self._user_session()[1]
         self._test('Any X WHERE X login "bla"',
                    [('FetchStep',
-                     [('Any X WHERE X login "bla", X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE X login "bla", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X WHERE EXISTS(X owned_by 5), X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
                      None, None, [self.system], {'X': 'table0.C0'}, [])])
-                
+
     def test_security_complex_has_text(self):
         # use a guest user
         self.session = self._user_session()[1]
         self._test('Any X WHERE X has_text "bla", X firstname "bla"',
-                   [('FetchStep', [('Any X WHERE X firstname "bla", X is EUser', [{'X': 'EUser'}])],
+                   [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
                     ('UnionStep', None, None, [
-                        ('OneFetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is EUser', [{'X': 'EUser'}])],
+                        ('OneFetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
                          None, None, [self.system], {'X': 'table0.C0'}, []),
                         ('OneFetchStep', [('Any X WHERE X has_text "bla", X firstname "bla", X is Personne', [{'X': 'Personne'}])],
                          None, None, [self.system], {}, []),
@@ -781,16 +861,16 @@
         # use a guest user
         self.session = self._user_session()[1]
         self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla", X firstname "bla"',
-                   [('FetchStep', [('Any X WHERE X firstname "bla", X is EUser', [{'X': 'EUser'}])],
+                   [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table1.C0'}, []),
                     ('UnionFetchStep', [
-                        ('FetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is EUser', [{'X': 'EUser'}])],
+                        ('FetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
                          [self.system], {'X': 'table1.C0'}, {'X': 'table0.C0'}, []),
                         ('FetchStep', [('Any X WHERE X has_text "bla", X firstname "bla", X is Personne', [{'X': 'Personne'}])],
                          [self.system], {}, {'X': 'table0.C0'}, []),
                         ]),
                      ('OneFetchStep',
-                      [('Any X LIMIT 10 OFFSET 10', [{'X': 'EUser'}, {'X': 'Personne'}])],
+                      [('Any X LIMIT 10 OFFSET 10', [{'X': 'CWUser'}, {'X': 'Personne'}])],
                       10, 10, [self.system], {'X': 'table0.C0'}, [])
                     ])
 
@@ -799,109 +879,109 @@
         self.session = self._user_session()[1]
         self._test('Any MAX(X)',
                    [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
-                     [self.rql, self.system],  None, {'E': 'table1.C0'}, []), 
-                    ('FetchStep', [('Any X WHERE X is EUser', [{'X': 'EUser'}])],
+                     [self.rql, self.system],  None, {'E': 'table1.C0'}, []),
+                    ('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table2.C0'}, []),
                     ('UnionFetchStep', [
                         ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is Basket', [{'X': 'Basket'}])],
-                          [self.system], {}, {'X': 'table0.C0'}, []),                        
+                          [self.system], {}, {'X': 'table0.C0'}, []),
                         ('UnionFetchStep',
                          [('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
                                           [{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])],
                            [self.rql, self.system], {}, {'X': 'table0.C0'}, []),
                           ('FetchStep',
-                           [('Any X WHERE X is IN(Bookmark, Comment, Division, ECache, EConstraint, EConstraintType, EEType, EFRDef, EGroup, ENFRDef, EPermission, EProperty, ERType, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
+                           [('Any X WHERE X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
                              sorted([{'X': 'Bookmark'}, {'X': 'Comment'}, {'X': 'Division'},
-                                      {'X': 'ECache'}, {'X': 'EConstraint'}, {'X': 'EConstraintType'},
-                                      {'X': 'EEType'}, {'X': 'EFRDef'}, {'X': 'EGroup'},
-                                      {'X': 'ENFRDef'}, {'X': 'EPermission'}, {'X': 'EProperty'},
-                                      {'X': 'ERType'}, {'X': 'Email'}, {'X': 'EmailAddress'},
+                                      {'X': 'CWCache'}, {'X': 'CWConstraint'}, {'X': 'CWConstraintType'},
+                                      {'X': 'CWEType'}, {'X': 'CWAttribute'}, {'X': 'CWGroup'},
+                                      {'X': 'CWRelation'}, {'X': 'CWPermission'}, {'X': 'CWProperty'},
+                                      {'X': 'CWRType'}, {'X': 'Email'}, {'X': 'EmailAddress'},
                                       {'X': 'EmailPart'}, {'X': 'EmailThread'}, {'X': 'File'},
                                       {'X': 'Folder'}, {'X': 'Image'}, {'X': 'Personne'},
                                       {'X': 'RQLExpression'}, {'X': 'Societe'}, {'X': 'SubDivision'},
                                       {'X': 'Tag'}, {'X': 'TrInfo'}, {'X': 'Transition'}]))],
                            [self.system], {}, {'X': 'table0.C0'}, []),
                           ]),
-                        ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is EUser', [{'X': 'EUser'}])],
+                        ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
                          [self.system], {'X': 'table2.C0'}, {'X': 'table0.C0'}, []),
                         ('FetchStep', [('Any X WHERE (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
                                         [{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
-                         [self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),                        
+                         [self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),
                         ]),
                     ('OneFetchStep', [('Any MAX(X)', X_ALL_SOLS)],
                      None, None, [self.system], {'X': 'table0.C0'}, [])
                     ])
-            
+
     def test_security_complex_aggregat2(self):
         # use a guest user
         self.session = self._user_session()[1]
-        self._test('Any ET, COUNT(X) GROUPBY ET ORDERBY ET WHERE X is ET',                   
+        self._test('Any ET, COUNT(X) GROUPBY ET ORDERBY ET WHERE X is ET',
                    [('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
                                     [{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])],
                      [self.rql, self.system], None, {'X': 'table1.C0'}, []),
                     ('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
                      [self.rql, self.system],  None, {'E': 'table2.C0'}, []),
-                    ('FetchStep', [('Any X WHERE X is EUser', [{'X': 'EUser'}])],
+                    ('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table3.C0'}, []),
                     ('UnionFetchStep',
-                     [('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is EEType, X is Basket',
-                                      [{'ET': 'EEType', 'X': 'Basket'}])],
+                     [('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is CWEType, X is Basket',
+                                      [{'ET': 'CWEType', 'X': 'Basket'}])],
                        [self.system], {}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
-                      ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), ET is EEType, X is Affaire',
+                      ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), ET is CWEType, X is Affaire',
                                       [{'C': 'Division', 'E': 'Note', 'D': 'Affaire',
                                         'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire',
                                         'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire',
-                                        'ET': 'EEType'}])],
+                                        'ET': 'CWEType'}])],
                        [self.system], {'E': 'table2.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'},
                        []),
-                      ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is EEType, X is EUser',
-                                      [{'ET': 'EEType', 'X': 'EUser'}])],
+                      ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is CWEType, X is CWUser',
+                                      [{'ET': 'CWEType', 'X': 'CWUser'}])],
                        [self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
                       # extra UnionFetchStep could be avoided but has no cost, so don't care
                       ('UnionFetchStep',
-                       [('FetchStep', [('Any ET,X WHERE X is ET, ET is EEType, X is IN(Bookmark, Comment, Division, ECache, EConstraint, EConstraintType, EEType, EFRDef, EGroup, ENFRDef, EPermission, EProperty, ERType, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
-                                        [{'X': 'Bookmark', 'ET': 'EEType'}, {'X': 'Comment', 'ET': 'EEType'},
-                                         {'X': 'Division', 'ET': 'EEType'}, {'X': 'ECache', 'ET': 'EEType'},
-                                         {'X': 'EConstraint', 'ET': 'EEType'}, {'X': 'EConstraintType', 'ET': 'EEType'},
-                                         {'X': 'EEType', 'ET': 'EEType'}, {'X': 'EFRDef', 'ET': 'EEType'},
-                                         {'X': 'EGroup', 'ET': 'EEType'}, {'X': 'ENFRDef', 'ET': 'EEType'},
-                                         {'X': 'EPermission', 'ET': 'EEType'}, {'X': 'EProperty', 'ET': 'EEType'},
-                                         {'X': 'ERType', 'ET': 'EEType'}, {'X': 'Email', 'ET': 'EEType'},
-                                         {'X': 'EmailAddress', 'ET': 'EEType'}, {'X': 'EmailPart', 'ET': 'EEType'},
-                                         {'X': 'EmailThread', 'ET': 'EEType'}, {'X': 'File', 'ET': 'EEType'},
-                                         {'X': 'Folder', 'ET': 'EEType'}, {'X': 'Image', 'ET': 'EEType'},
-                                         {'X': 'Personne', 'ET': 'EEType'}, {'X': 'RQLExpression', 'ET': 'EEType'},
-                                         {'X': 'Societe', 'ET': 'EEType'}, {'X': 'SubDivision', 'ET': 'EEType'},
-                                         {'X': 'Tag', 'ET': 'EEType'}, {'X': 'TrInfo', 'ET': 'EEType'},
-                                         {'X': 'Transition', 'ET': 'EEType'}])],
+                       [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
+                                        [{'X': 'Bookmark', 'ET': 'CWEType'}, {'X': 'Comment', 'ET': 'CWEType'},
+                                         {'X': 'Division', 'ET': 'CWEType'}, {'X': 'CWCache', 'ET': 'CWEType'},
+                                         {'X': 'CWConstraint', 'ET': 'CWEType'}, {'X': 'CWConstraintType', 'ET': 'CWEType'},
+                                         {'X': 'CWEType', 'ET': 'CWEType'}, {'X': 'CWAttribute', 'ET': 'CWEType'},
+                                         {'X': 'CWGroup', 'ET': 'CWEType'}, {'X': 'CWRelation', 'ET': 'CWEType'},
+                                         {'X': 'CWPermission', 'ET': 'CWEType'}, {'X': 'CWProperty', 'ET': 'CWEType'},
+                                         {'X': 'CWRType', 'ET': 'CWEType'}, {'X': 'Email', 'ET': 'CWEType'},
+                                         {'X': 'EmailAddress', 'ET': 'CWEType'}, {'X': 'EmailPart', 'ET': 'CWEType'},
+                                         {'X': 'EmailThread', 'ET': 'CWEType'}, {'X': 'File', 'ET': 'CWEType'},
+                                         {'X': 'Folder', 'ET': 'CWEType'}, {'X': 'Image', 'ET': 'CWEType'},
+                                         {'X': 'Personne', 'ET': 'CWEType'}, {'X': 'RQLExpression', 'ET': 'CWEType'},
+                                         {'X': 'Societe', 'ET': 'CWEType'}, {'X': 'SubDivision', 'ET': 'CWEType'},
+                                         {'X': 'Tag', 'ET': 'CWEType'}, {'X': 'TrInfo', 'ET': 'CWEType'},
+                                         {'X': 'Transition', 'ET': 'CWEType'}])],
                          [self.system], {}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
                         ('FetchStep',
-                         [('Any ET,X WHERE X is ET, ET is EEType, X is IN(Card, Note, State)',
-                           [{'ET': 'EEType', 'X': 'Card'},
-                            {'ET': 'EEType', 'X': 'Note'},
-                            {'ET': 'EEType', 'X': 'State'}])],
+                         [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(Card, Note, State)',
+                           [{'ET': 'CWEType', 'X': 'Card'},
+                            {'ET': 'CWEType', 'X': 'Note'},
+                            {'ET': 'CWEType', 'X': 'State'}])],
                          [self.system], {'X': 'table1.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
                         ]),
                     ]),
                     ('OneFetchStep',
                      [('Any ET,COUNT(X) GROUPBY ET ORDERBY ET',
-                       sorted([{'ET': 'EEType', 'X': 'Affaire'}, {'ET': 'EEType', 'X': 'Basket'},
-                               {'ET': 'EEType', 'X': 'Bookmark'}, {'ET': 'EEType', 'X': 'Card'},
-                               {'ET': 'EEType', 'X': 'Comment'}, {'ET': 'EEType', 'X': 'Division'},
-                               {'ET': 'EEType', 'X': 'ECache'}, {'ET': 'EEType', 'X': 'EConstraint'},
-                               {'ET': 'EEType', 'X': 'EConstraintType'}, {'ET': 'EEType', 'X': 'EEType'},
-                               {'ET': 'EEType', 'X': 'EFRDef'}, {'ET': 'EEType', 'X': 'EGroup'},
-                               {'ET': 'EEType', 'X': 'ENFRDef'}, {'ET': 'EEType', 'X': 'EPermission'},
-                               {'ET': 'EEType', 'X': 'EProperty'}, {'ET': 'EEType', 'X': 'ERType'},
-                               {'ET': 'EEType', 'X': 'EUser'}, {'ET': 'EEType', 'X': 'Email'},
-                               {'ET': 'EEType', 'X': 'EmailAddress'}, {'ET': 'EEType', 'X': 'EmailPart'},
-                               {'ET': 'EEType', 'X': 'EmailThread'}, {'ET': 'EEType', 'X': 'File'},
-                               {'ET': 'EEType', 'X': 'Folder'}, {'ET': 'EEType', 'X': 'Image'},
-                               {'ET': 'EEType', 'X': 'Note'}, {'ET': 'EEType', 'X': 'Personne'},
-                               {'ET': 'EEType', 'X': 'RQLExpression'}, {'ET': 'EEType', 'X': 'Societe'},
-                               {'ET': 'EEType', 'X': 'State'}, {'ET': 'EEType', 'X': 'SubDivision'},
-                               {'ET': 'EEType', 'X': 'Tag'}, {'ET': 'EEType', 'X': 'TrInfo'},
-                               {'ET': 'EEType', 'X': 'Transition'}]))],
+                       sorted([{'ET': 'CWEType', 'X': 'Affaire'}, {'ET': 'CWEType', 'X': 'Basket'},
+                               {'ET': 'CWEType', 'X': 'Bookmark'}, {'ET': 'CWEType', 'X': 'Card'},
+                               {'ET': 'CWEType', 'X': 'Comment'}, {'ET': 'CWEType', 'X': 'Division'},
+                               {'ET': 'CWEType', 'X': 'CWCache'}, {'ET': 'CWEType', 'X': 'CWConstraint'},
+                               {'ET': 'CWEType', 'X': 'CWConstraintType'}, {'ET': 'CWEType', 'X': 'CWEType'},
+                               {'ET': 'CWEType', 'X': 'CWAttribute'}, {'ET': 'CWEType', 'X': 'CWGroup'},
+                               {'ET': 'CWEType', 'X': 'CWRelation'}, {'ET': 'CWEType', 'X': 'CWPermission'},
+                               {'ET': 'CWEType', 'X': 'CWProperty'}, {'ET': 'CWEType', 'X': 'CWRType'},
+                               {'ET': 'CWEType', 'X': 'CWUser'}, {'ET': 'CWEType', 'X': 'Email'},
+                               {'ET': 'CWEType', 'X': 'EmailAddress'}, {'ET': 'CWEType', 'X': 'EmailPart'},
+                               {'ET': 'CWEType', 'X': 'EmailThread'}, {'ET': 'CWEType', 'X': 'File'},
+                               {'ET': 'CWEType', 'X': 'Folder'}, {'ET': 'CWEType', 'X': 'Image'},
+                               {'ET': 'CWEType', 'X': 'Note'}, {'ET': 'CWEType', 'X': 'Personne'},
+                               {'ET': 'CWEType', 'X': 'RQLExpression'}, {'ET': 'CWEType', 'X': 'Societe'},
+                               {'ET': 'CWEType', 'X': 'State'}, {'ET': 'CWEType', 'X': 'SubDivision'},
+                               {'ET': 'CWEType', 'X': 'Tag'}, {'ET': 'CWEType', 'X': 'TrInfo'},
+                               {'ET': 'CWEType', 'X': 'Transition'}]))],
                      None, None, [self.system], {'ET': 'table0.C0', 'X': 'table0.C1'}, [])
                     ])
 
@@ -913,11 +993,11 @@
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
                     ('FetchStep',
-                     [('Any U WHERE U login "syt", U is EUser', [{'U': 'EUser'}])],
+                     [('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
                      [self.ldap, self.system], None, {'U': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X,XT WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is EUser, X is Card',
-                       [{'X': 'Card', 'U': 'EUser', 'XT': 'String'}])],
+                     [('Any X,XT WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is CWUser, X is Card',
+                       [{'X': 'Card', 'U': 'CWUser', 'XT': 'String'}])],
                      None, None, [self.system],
                      {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1', 'U': 'table1.C0'}, [])
                     ])
@@ -931,8 +1011,8 @@
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
                     ('OneFetchStep',
-                     [('Any X,XT WHERE X owned_by U, X title XT, U login "syt", EXISTS(U identity 5), U is EUser, X is Card',
-                       [{'U': 'EUser', 'X': 'Card', 'XT': 'String'}])],
+                     [('Any X,XT WHERE X owned_by U, X title XT, U login "syt", EXISTS(U identity 5), U is CWUser, X is Card',
+                       [{'U': 'CWUser', 'X': 'Card', 'XT': 'String'}])],
                      None, None, [self.system], {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, [])
                     ])
 
@@ -942,8 +1022,8 @@
         self.session = self._user_session()[1]
         self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L',
                    [('FetchStep',
-                     [('Any U,L WHERE U identity 5, U login L, U is EUser',
-                       [{'L': 'String', u'U': 'EUser'}])],
+                     [('Any U,L WHERE U identity 5, U login L, U is CWUser',
+                       [{'L': 'String', u'U': 'CWUser'}])],
                      [self.system], {}, {'L': 'table0.C1', 'U': 'table0.C0', 'U.login': 'table0.C1'}, []),
                     ('FetchStep',
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -966,47 +1046,47 @@
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
                     ('FetchStep',
-                     [('Any U WHERE U login "syt", U is EUser', [{'U': 'EUser'}])],
+                     [('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
                      [self.ldap, self.system], None, {'U': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X,XT LIMIT 10 OFFSET 10 WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is EUser, X is Card',
-                       [{'X': 'Card', 'U': 'EUser', 'XT': 'String'}])],
+                     [('Any X,XT LIMIT 10 OFFSET 10 WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is CWUser, X is Card',
+                       [{'X': 'Card', 'U': 'CWUser', 'XT': 'String'}])],
                      10, 10, [self.system],
                      {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1', 'U': 'table1.C0'}, [])
                     ])
-    
+
     def test_exists_base(self):
         self._test('Any X,L,S WHERE X in_state S, X login L, EXISTS(X in_group G, G name "bougloup")',
-                   [('FetchStep', [('Any X,L WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+                   [('FetchStep', [('Any X,L WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, []),
                     ('OneFetchStep', [("Any X,L,S WHERE X in_state S, X login L, "
-                                      'EXISTS(X in_group G, G name "bougloup", G is EGroup), S is State, X is EUser',
-                                       [{'X': 'EUser', 'L': 'String', 'S': 'State', 'G': 'EGroup'}])],
+                                      'EXISTS(X in_group G, G name "bougloup", G is CWGroup), S is State, X is CWUser',
+                                       [{'X': 'CWUser', 'L': 'String', 'S': 'State', 'G': 'CWGroup'}])],
                      None, None, [self.system],
                      {'X': 'table0.C0', 'X.login': 'table0.C1', 'L': 'table0.C1'}, [])])
 
     def test_exists_complex(self):
         self._test('Any G WHERE X in_group G, G name "managers", EXISTS(X copain T, T login in ("comme", "cochon"))',
-                   [('FetchStep', [('Any T WHERE T login IN("comme", "cochon"), T is EUser', [{'T': 'EUser'}])],
+                   [('FetchStep', [('Any T WHERE T login IN("comme", "cochon"), T is CWUser', [{'T': 'CWUser'}])],
                      [self.ldap, self.system], None, {'T': 'table0.C0'}, []),
                     ('OneFetchStep',
-                     [('Any G WHERE X in_group G, G name "managers", EXISTS(X copain T, T is EUser), G is EGroup, X is EUser',
-                       [{'X': 'EUser', 'T': 'EUser', 'G': 'EGroup'}])],
+                     [('Any G WHERE X in_group G, G name "managers", EXISTS(X copain T, T is CWUser), G is CWGroup, X is CWUser',
+                       [{'X': 'CWUser', 'T': 'CWUser', 'G': 'CWGroup'}])],
                      None, None, [self.system], {'T': 'table0.C0'}, [])])
 
     def test_exists3(self):
         self._test('Any G,L WHERE X in_group G, X login L, G name "managers", EXISTS(X copain T, T login in ("comme", "cochon"))',
                    [('FetchStep',
-                     [('Any T WHERE T login IN("comme", "cochon"), T is EUser',
-                       [{'T': 'EUser'}])],
+                     [('Any T WHERE T login IN("comme", "cochon"), T is CWUser',
+                       [{'T': 'CWUser'}])],
                      [self.ldap, self.system], None, {'T': 'table0.C0'}, []),
                     ('FetchStep',
-                     [('Any L,X WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+                     [('Any L,X WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table1.C1', 'X.login': 'table1.C0', 'L': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any G,L WHERE X in_group G, X login L, G name "managers", EXISTS(X copain T, T is EUser), G is EGroup, X is EUser',
-                       [{'G': 'EGroup', 'L': 'String', 'T': 'EUser', 'X': 'EUser'}])],
+                     [('Any G,L WHERE X in_group G, X login L, G name "managers", EXISTS(X copain T, T is CWUser), G is CWGroup, X is CWUser',
+                       [{'G': 'CWGroup', 'L': 'String', 'T': 'CWUser', 'X': 'CWUser'}])],
                      None, None,
                      [self.system], {'T': 'table0.C0', 'X': 'table1.C1', 'X.login': 'table1.C0', 'L': 'table1.C0'}, [])])
 
@@ -1015,18 +1095,18 @@
                    'EXISTS(X copain T, T login L, T login in ("comme", "cochon")) OR '
                    'EXISTS(X in_state S, S name "pascontent", NOT X copain T2, T2 login "billy")',
                    [('FetchStep',
-                     [('Any T,L WHERE T login L, T login IN("comme", "cochon"), T is EUser', [{'T': 'EUser', 'L': 'String'}])],
+                     [('Any T,L WHERE T login L, T login IN("comme", "cochon"), T is CWUser', [{'T': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'T': 'table0.C0', 'T.login': 'table0.C1', 'L': 'table0.C1'}, []),
                     ('FetchStep',
-                     [('Any T2 WHERE T2 login "billy", T2 is EUser', [{'T2': 'EUser'}])],
+                     [('Any T2 WHERE T2 login "billy", T2 is CWUser', [{'T2': 'CWUser'}])],
                      [self.ldap, self.system], None, {'T2': 'table1.C0'}, []),
                     ('FetchStep',
-                     [('Any L,X WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+                     [('Any L,X WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None, {'X': 'table2.C1', 'X.login': 'table2.C0', 'L': 'table2.C0'}, []),
                     ('OneFetchStep',
-                     [('Any G,L WHERE X in_group G, X login L, G name "managers", (EXISTS(X copain T, T login L, T is EUser)) OR (EXISTS(X in_state S, S name "pascontent", NOT X copain T2, S is State, T2 is EUser)), G is EGroup, X is EUser',
-                       [{'G': 'EGroup', 'L': 'String', 'S': 'State', 'T': 'EUser', 'T2': 'EUser', 'X': 'EUser'}])],
+                     [('Any G,L WHERE X in_group G, X login L, G name "managers", (EXISTS(X copain T, T login L, T is CWUser)) OR (EXISTS(X in_state S, S name "pascontent", NOT X copain T2, S is State, T2 is CWUser)), G is CWGroup, X is CWUser',
+                       [{'G': 'CWGroup', 'L': 'String', 'S': 'State', 'T': 'CWUser', 'T2': 'CWUser', 'X': 'CWUser'}])],
                      None, None, [self.system],
                      {'T2': 'table1.C0', 'L': 'table2.C0',
                       'T': 'table0.C0', 'T.login': 'table0.C1', 'X': 'table2.C1', 'X.login': 'table2.C0'}, [])])
@@ -1035,29 +1115,29 @@
         self._test('Any GN,L WHERE X in_group G, X login L, G name GN, '
                    'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
                    'NOT EXISTS(X copain T2, T2 login "billy")',
-                   [('FetchStep', [('Any T WHERE T login IN("comme", "cochon"), T is EUser',
-                                    [{'T': 'EUser'}])],
+                   [('FetchStep', [('Any T WHERE T login IN("comme", "cochon"), T is CWUser',
+                                    [{'T': 'CWUser'}])],
                      [self.ldap, self.system], None, {'T': 'table0.C0'}, []),
-                    ('FetchStep', [('Any T2 WHERE T2 login "billy", T2 is EUser', [{'T2': 'EUser'}])],
+                    ('FetchStep', [('Any T2 WHERE T2 login "billy", T2 is CWUser', [{'T2': 'CWUser'}])],
                      [self.ldap, self.system], None, {'T2': 'table1.C0'}, []),
-                    ('FetchStep', [('Any L,X WHERE X login L, X is EUser', [{'X': 'EUser', 'L': 'String'}])],
+                    ('FetchStep', [('Any L,X WHERE X login L, X is CWUser', [{'X': 'CWUser', 'L': 'String'}])],
                      [self.ldap, self.system], None,
                      {'X': 'table2.C1', 'X.login': 'table2.C0', 'L': 'table2.C0'}, []),
-                    ('OneFetchStep', [('Any GN,L WHERE X in_group G, X login L, G name GN, EXISTS(X copain T, T is EUser), NOT EXISTS(X copain T2, T2 is EUser), G is EGroup, X is EUser',
-                       [{'G': 'EGroup', 'GN': 'String', 'L': 'String', 'T': 'EUser', 'T2': 'EUser', 'X': 'EUser'}])],
+                    ('OneFetchStep', [('Any GN,L WHERE X in_group G, X login L, G name GN, EXISTS(X copain T, T is CWUser), NOT EXISTS(X copain T2, T2 is CWUser), G is CWGroup, X is CWUser',
+                       [{'G': 'CWGroup', 'GN': 'String', 'L': 'String', 'T': 'CWUser', 'T2': 'CWUser', 'X': 'CWUser'}])],
                      None, None, [self.system],
                      {'T': 'table0.C0', 'T2': 'table1.C0',
                       'X': 'table2.C1', 'X.login': 'table2.C0', 'L': 'table2.C0'}, [])])
-            
+
     def test_exists_security_no_invariant(self):
         ueid = self.session.user.eid
-        self._test('Any X,AA,AB,AC,AD ORDERBY AA WHERE X is EUser, X login AA, X firstname AB, X surname AC, X modification_date AD, A eid %(B)s, \
+        self._test('Any X,AA,AB,AC,AD ORDERBY AA WHERE X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD, A eid %(B)s, \
     EXISTS(((X identity A) OR \
-            (EXISTS(X in_group C, C name IN("managers", "staff"), C is EGroup))) OR \
-            (EXISTS(X in_group D, A in_group D, NOT D name "users", D is EGroup)))',
-               [('FetchStep', [('Any X,AA,AB,AC,AD WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, X is EUser',
+            (EXISTS(X in_group C, C name IN("managers", "staff"), C is CWGroup))) OR \
+            (EXISTS(X in_group D, A in_group D, NOT D name "users", D is CWGroup)))',
+               [('FetchStep', [('Any X,AA,AB,AC,AD WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, X is CWUser',
                                 [{'AA': 'String', 'AB': 'String', 'AC': 'String', 'AD': 'Datetime',
-                                  'X': 'EUser'}])],
+                                  'X': 'CWUser'}])],
                  [self.ldap, self.system], None, {'AA': 'table0.C1', 'AB': 'table0.C2',
                                                   'AC': 'table0.C3', 'AD': 'table0.C4',
                                                   'X': 'table0.C0',
@@ -1065,9 +1145,9 @@
                                                   'X.login': 'table0.C1',
                                                   'X.modification_date': 'table0.C4',
                                                   'X.surname': 'table0.C3'}, []),
-                ('OneFetchStep', [('Any X,AA,AB,AC,AD ORDERBY AA WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, EXISTS(((X identity 5) OR (EXISTS(X in_group C, C name IN("managers", "staff"), C is EGroup))) OR (EXISTS(X in_group D, 5 in_group D, NOT D name "users", D is EGroup))), X is EUser',
+                ('OneFetchStep', [('Any X,AA,AB,AC,AD ORDERBY AA WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, EXISTS(((X identity 5) OR (EXISTS(X in_group C, C name IN("managers", "staff"), C is CWGroup))) OR (EXISTS(X in_group D, 5 in_group D, NOT D name "users", D is CWGroup))), X is CWUser',
                                    [{'AA': 'String', 'AB': 'String', 'AC': 'String', 'AD': 'Datetime',
-                                     'C': 'EGroup', 'D': 'EGroup', 'X': 'EUser'}])],
+                                     'C': 'CWGroup', 'D': 'CWGroup', 'X': 'CWUser'}])],
                  None, None, [self.system],
                  {'AA': 'table0.C1', 'AB': 'table0.C2', 'AC': 'table0.C3', 'AD': 'table0.C4',
                   'X': 'table0.C0',
@@ -1078,11 +1158,11 @@
     def test_relation_need_split(self):
         self._test('Any X, S WHERE X in_state S',
                    [('UnionStep', None, None, [
-                       ('OneFetchStep', [('Any X,S WHERE X in_state S, S is State, X is IN(Affaire, EUser)',
-                                          [{'X': 'Affaire', 'S': 'State'}, {'X': 'EUser', 'S': 'State'}])], 
+                       ('OneFetchStep', [('Any X,S WHERE X in_state S, S is State, X is IN(Affaire, CWUser)',
+                                          [{'X': 'Affaire', 'S': 'State'}, {'X': 'CWUser', 'S': 'State'}])],
                         None, None, [self.system], {}, []),
                        ('OneFetchStep', [('Any X,S WHERE X in_state S, S is State, X is Note',
-                                          [{'X': 'Note', 'S': 'State'}])], 
+                                          [{'X': 'Note', 'S': 'State'}])],
                         None, None, [self.rql, self.system], {}, []),
                     ])])
 
@@ -1092,8 +1172,8 @@
                                     [{'X': 'Note', 'S': 'State'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0', 'S': 'table0.C1'}, []),
                      ('UnionStep', None, None,
-                      [('OneFetchStep', [('Any X,S,U WHERE X in_state S, X todo_by U, S is State, U is EUser, X is Note',
-                                          [{'X': 'Note', 'S': 'State', 'U': 'EUser'}])],
+                      [('OneFetchStep', [('Any X,S,U WHERE X in_state S, X todo_by U, S is State, U is CWUser, X is Note',
+                                          [{'X': 'Note', 'S': 'State', 'U': 'CWUser'}])],
                         None, None, [self.system], {'X': 'table0.C0', 'S': 'table0.C1'}, []),
                        ('OneFetchStep', [('Any X,S,U WHERE X in_state S, X todo_by U, S is State, U is Personne, X is Affaire',
                                           [{'X': 'Affaire', 'S': 'State', 'U': 'Personne'}])],
@@ -1107,8 +1187,8 @@
                                     [{'X': 'Note', 'S': 'State'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0'}, []),
                      ('UnionStep', None, None,
-                      [('OneFetchStep', [('Any X,U WHERE X todo_by U, U is EUser, X is Note',
-                                          [{'X': 'Note', 'U': 'EUser'}])],
+                      [('OneFetchStep', [('Any X,U WHERE X todo_by U, U is CWUser, X is Note',
+                                          [{'X': 'Note', 'U': 'CWUser'}])],
                         None, None, [self.system], {'X': 'table0.C0'}, []),
                        ('OneFetchStep', [('Any X,U WHERE X in_state S, S name "pending", X todo_by U, S is State, U is Personne, X is Affaire',
                                           [{'S': 'State', 'U': 'Personne', 'X': 'Affaire'}])],
@@ -1126,9 +1206,9 @@
                                            [{'X': 'Note', 'T': 'Tag'}])],
                          None, None,
                          [self.system], {'X': 'table0.C0'}, []),
-                        ('OneFetchStep', [('Any X,T WHERE X in_state S, S name "pending", T tags X, S is State, T is Tag, X is IN(Affaire, EUser)',
+                        ('OneFetchStep', [('Any X,T WHERE X in_state S, S name "pending", T tags X, S is State, T is Tag, X is IN(Affaire, CWUser)',
                                            [{'X': 'Affaire', 'S': 'State', 'T': 'Tag'},
-                                            {'X': 'EUser', 'S': 'State', 'T': 'Tag'}])],
+                                            {'X': 'CWUser', 'S': 'State', 'T': 'Tag'}])],
                          None, None,
                          [self.system], {}, []),
                         ])
@@ -1142,52 +1222,42 @@
         # in the source where %(x)s is not coming from and will be removed during rql
         # generation for the external source
         self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
-                   [('OneFetchStep', [('Any SN WHERE NOT 5 in_state S, S name SN, S is State', [{'S': 'State', 'SN': 'String'}])], 
+                   [('OneFetchStep', [('Any SN WHERE NOT 5 in_state S, S name SN, S is State',
+                                       [{'S': 'State', 'SN': 'String'}])],
                      None, None, [self.rql, self.system], {}, [])],
                    {'x': ueid})
 
     def test_not_relation_no_split_external(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
-        # similar to the above test but with an eid coming from the external source
+        # similar to the above test but with an eid coming from the external source.
+        # the same plan may be used, since we won't find any record in the system source
+        # linking 9999999 to a state
         self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
-                   [('UnionStep', None, None,
-                     [('OneFetchStep',
-                       [('Any SN WHERE NOT 999999 in_state S, S name SN, S is State',
-                         [{'S': 'State', 'SN': 'String'}])],
-                       None, None, [self.rql], {},
-                       []),
-                      ('OneFetchStep',
-                       [('Any SN WHERE S name SN, S is State',
-                         [{'S': 'State', 'SN': 'String'}])],
-                       None, None, [self.system], {},
-                       [])]
-                     )],
+                   [('OneFetchStep', [('Any SN WHERE NOT 999999 in_state S, S name SN, S is State',
+                                       [{'S': 'State', 'SN': 'String'}])],
+                     None, None, [self.rql, self.system], {}, [])],
                    {'x': 999999})
 
     def test_not_relation_need_split(self):
-        ueid = self.session.user.eid
         self._test('Any SN WHERE NOT X in_state S, S name SN',
-                   [('FetchStep', [('Any SN,S WHERE S name SN, S is State', [{'S': 'State', 'SN': 'String'}])],
+                   [('FetchStep', [('Any SN,S WHERE S name SN, S is State',
+                                    [{'S': 'State', 'SN': 'String'}])],
                      [self.rql, self.system], None, {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
                      []),
-                    ('FetchStep', [('Any X WHERE X is Note', [{'X': 'Note'}])],
-                     [self.rql, self.system], None, {'X': 'table1.C0'},
-                     []),
                     ('IntersectStep', None, None,
                      [('OneFetchStep',
-                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is IN(Affaire, EUser)',
-                         [{'S': 'State', 'SN': 'String', 'X': 'Affaire'},
-                          {'S': 'State', 'SN': 'String', 'X': 'EUser'}])],
-                       None, None, [self.system], {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
+                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is Note',
+                         [{'S': 'State', 'SN': 'String', 'X': 'Note'}])],
+                       None, None, [self.rql, self.system], {},
                        []),
                       ('OneFetchStep',
-                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is Note',
-                         [{'S': 'State', 'SN': 'String', 'X': 'Note'}])],
-                       None, None, [self.system], {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0',
-                                                   'X': 'table1.C0'},
-                       [])]
+                       [('Any SN WHERE NOT X in_state S, S name SN, S is State, X is IN(Affaire, CWUser)',
+                         [{'S': 'State', 'SN': 'String', 'X': 'Affaire'},
+                          {'S': 'State', 'SN': 'String', 'X': 'CWUser'}])],
+                       None, None, [self.system], {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
+                       []),]
                      )])
-            
+
     def test_external_attributes_and_relation(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         self._test('Any A,B,C,D WHERE A eid %(x)s,A creation_date B,A modification_date C, A todo_by D?',
@@ -1195,10 +1265,10 @@
                                     [{'A': 'Note', 'C': 'Datetime', 'B': 'Datetime'}])],
                      [self.rql], None,
                      {'A': 'table0.C0', 'A.creation_date': 'table0.C1', 'A.modification_date': 'table0.C2', 'C': 'table0.C2', 'B': 'table0.C1'}, []),
-                    #('FetchStep', [('Any D WHERE D is EUser', [{'D': 'EUser'}])],
+                    #('FetchStep', [('Any D WHERE D is CWUser', [{'D': 'CWUser'}])],
                     # [self.ldap, self.system], None, {'D': 'table1.C0'}, []),
-                    ('OneFetchStep', [('Any A,B,C,D WHERE A creation_date B, A modification_date C, A todo_by D?, A is Note, D is EUser',
-                                       [{'A': 'Note', 'C': 'Datetime', 'B': 'Datetime', 'D': 'EUser'}])],
+                    ('OneFetchStep', [('Any A,B,C,D WHERE A creation_date B, A modification_date C, A todo_by D?, A is Note, D is CWUser',
+                                       [{'A': 'Note', 'C': 'Datetime', 'B': 'Datetime', 'D': 'CWUser'}])],
                      None, None, [self.system],
                      {'A': 'table0.C0', 'A.creation_date': 'table0.C1', 'A.modification_date': 'table0.C2', 'C': 'table0.C2', 'B': 'table0.C1'}, [])],
                    {'x': 999999})
@@ -1208,7 +1278,7 @@
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         self._test('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
                    [('OneFetchStep', [('Any 5 WHERE 5 in_group G, (G name IN("managers", "logilab")) OR (X require_permission P?, P name "bla", P require_group G), X eid 999999',
-                                       [{'X': 'Note', 'G': 'EGroup', 'P': 'EPermission'}])],
+                                       [{'X': 'Note', 'G': 'CWGroup', 'P': 'CWPermission'}])],
                      None, None, [self.system], {}, [])],
                    {'x': 999999, 'u': self.session.user.eid})
 
@@ -1217,7 +1287,7 @@
                    [('OneFetchStep', [('Any X WHERE X has_text "toto", X is Card',
                                        [{'X': 'Card'}])],
                      None, None, [self.system], {}, [])])
-        
+
     def test_has_text_3(self):
         self._test('Any X WHERE X has_text "toto", X title "zoubidou"',
                    [('FetchStep', [(u'Any X WHERE X title "zoubidou", X is Card',
@@ -1232,7 +1302,7 @@
                          None, None, [self.system], {}, []),
                         ]),
                     ])
-        
+
     def test_sort_func(self):
         self._test('Note X ORDERBY DUMB_SORT(RF) WHERE X type RF',
                    [('AggrStep', 'Any X ORDERBY DUMB_SORT(RF)', None, None, 'table0', None, [
@@ -1292,7 +1362,7 @@
 
     def test_attr_unification_neq_1(self):
         self._test('Any X,Y WHERE X is Bookmark, Y is Card, X creation_date D, Y creation_date > D',
-                   [('FetchStep', 
+                   [('FetchStep',
                      [('Any Y,D WHERE Y creation_date > D, Y is Card',
                        [{'D': 'Datetime', 'Y': 'Card'}])],
                      [self.rql,self.system], None,
@@ -1312,11 +1382,11 @@
                                     [{'T': 'String', 'X': 'Bookmark'}])],
                      [self.system], {}, {'N': 'table0.C1', 'X': 'table0.C0', 'X.name': 'table0.C1'}, []),
                     ('FetchStep',
-                     [('Any B,C WHERE B login C, B is EUser', [{'B': 'EUser', 'C': 'String'}])],
+                     [('Any B,C WHERE B login C, B is CWUser', [{'B': 'CWUser', 'C': 'String'}])],
                      [self.ldap, self.system], None, {'B': 'table1.C0', 'B.login': 'table1.C1', 'C': 'table1.C1'}, []),
-                    ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is EUser',
-                                       [{'A': 'Bookmark', 'B': 'EUser', 'C': 'String'},
-                                        {'A': 'Tag', 'B': 'EUser', 'C': 'String'}])],
+                    ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is CWUser',
+                                       [{'A': 'Bookmark', 'B': 'CWUser', 'C': 'String'},
+                                        {'A': 'Tag', 'B': 'CWUser', 'C': 'String'}])],
                      None, None, [self.system],
                      {'A': 'table0.C0',
                       'B': 'table1.C0', 'B.login': 'table1.C1',
@@ -1346,11 +1416,11 @@
                         'X.title': 'table0.C1'}, []),
                       ]),
                     ('FetchStep',
-                     [('Any B,C WHERE B login C, B is EUser', [{'B': 'EUser', 'C': 'String'}])],
+                     [('Any B,C WHERE B login C, B is CWUser', [{'B': 'CWUser', 'C': 'String'}])],
                      [self.ldap, self.system], None, {'B': 'table1.C0', 'B.login': 'table1.C1', 'C': 'table1.C1'}, []),
-                    ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is EUser',
-                                       [{'A': 'Card', 'B': 'EUser', 'C': 'String'},
-                                        {'A': 'Tag', 'B': 'EUser', 'C': 'String'}])],
+                    ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is CWUser',
+                                       [{'A': 'Card', 'B': 'CWUser', 'C': 'String'},
+                                        {'A': 'Tag', 'B': 'CWUser', 'C': 'String'}])],
                      None, None, [self.system],
                      {'A': 'table0.C0',
                       'B': 'table1.C0', 'B.login': 'table1.C1',
@@ -1369,10 +1439,9 @@
 
 
     # external source w/ .cross_relations == ['multisource_crossed_rel'] ######
-    
+
     def test_crossed_relation_eid_1_invariant(self):
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
-        ueid = self.session.user.eid
         self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
                    [('OneFetchStep', [('Any Y WHERE 999999 multisource_crossed_rel Y', [{u'Y': 'Note'}])],
                       None, None, [self.system], {}, [])
@@ -1381,7 +1450,6 @@
 
     def test_crossed_relation_eid_1_needattr(self):
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
-        ueid = self.session.user.eid
         self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
                    [('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
                      [self.rql, self.system], None,
@@ -1395,7 +1463,6 @@
 
     def test_crossed_relation_eid_2_invariant(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
-        ueid = self.session.user.eid
         self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
                    [('OneFetchStep', [('Any Y WHERE 999999 multisource_crossed_rel Y, Y is Note', [{'Y': 'Note'}])],
                       None, None, [self.rql, self.system], {}, [])
@@ -1404,22 +1471,25 @@
 
     def test_crossed_relation_eid_2_needattr(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
-        ueid = self.session.user.eid
         self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
                    [('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
                      [self.rql, self.system], None,
                      {'T': 'table0.C1', 'Y': 'table0.C0', 'Y.type': 'table0.C1'}, []),
-                    ('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
-                                       [{'T': 'String', 'Y': 'Note'}])],
-                     None, None, [self.rql, self.system],
-                     {'T': 'table0.C1', 'Y': 'table0.C0', 'Y.type': 'table0.C1'},
-                     [])
-                    ],
+                    ('UnionStep', None, None,
+                     [('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
+                                         [{'T': 'String', 'Y': 'Note'}])],
+                       None, None, [self.rql], None,
+                       []),
+                      ('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
+                                         [{'T': 'String', 'Y': 'Note'}])],
+                       None, None, [self.system],
+                       {'T': 'table0.C1', 'Y': 'table0.C0', 'Y.type': 'table0.C1'},
+                       [])]
+                     )],
                    {'x': 999999,})
 
     def test_crossed_relation_eid_not_1(self):
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
-        ueid = self.session.user.eid
         self._test('Any Y WHERE X eid %(x)s, NOT X multisource_crossed_rel Y',
                    [('FetchStep', [('Any Y WHERE Y is Note', [{'Y': 'Note'}])],
                      [self.rql, self.system], None, {'Y': 'table0.C0'}, []),
@@ -1431,14 +1501,12 @@
 
 #     def test_crossed_relation_eid_not_2(self):
 #         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
-#         ueid = self.session.user.eid
 #         self._test('Any Y WHERE X eid %(x)s, NOT X multisource_crossed_rel Y',
 #                    [],
 #                    {'x': 999999,})
 
-    def test_crossed_relation_base(self):
+    def test_crossed_relation_base_XXXFIXME(self):
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
-        ueid = self.session.user.eid
         self._test('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T',
                    [('FetchStep', [('Any X,T WHERE X type T, X is Note', [{'T': 'String', 'X': 'Note'}])],
                      [self.rql, self.system], None,
@@ -1446,14 +1514,20 @@
                     ('FetchStep',  [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
                      [self.rql, self.system], None,
                      {'T': 'table1.C1', 'Y': 'table1.C0', 'Y.type': 'table1.C1'},  []),
-                    ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
-                                       [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
-                     None, None, [self.rql, self.system],
-                     {'T': 'table1.C1', 'X': 'table0.C0', 'X.type': 'table0.C1',
-                      'Y': 'table1.C0', 'Y.type': 'table1.C1'},
-                    [])],
+                    ('UnionStep', None,  None,
+                     [('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
+                                         [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
+                       None, None, [self.rql], None,
+                       []),
+                      ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
+                                         [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
+                       None, None, [self.system],
+                       {'T': 'table1.C1', 'X': 'table0.C0', 'X.type': 'table0.C1',
+                        'Y': 'table1.C0', 'Y.type': 'table1.C1'},
+                       [])]
+                     )],
                     {'x': 999999,})
-        
+
     # edition queries tests ###################################################
 
     def test_insert_simplified_var_1(self):
@@ -1528,25 +1602,25 @@
                        )]
                      )],
                    {'n': 999999, 's': 999998})
-    
+
     def test_delete_relation1(self):
         ueid = self.session.user.eid
         self._test('DELETE X created_by Y WHERE X eid %(x)s, NOT Y eid %(y)s',
                    [('DeleteRelationsStep', [
-                       ('OneFetchStep', [('Any 5,Y WHERE %s created_by Y, NOT Y eid %s, Y is EUser'%(ueid, ueid),
-                                          [{'Y': 'EUser'}])],
+                       ('OneFetchStep', [('Any 5,Y WHERE %s created_by Y, NOT Y eid %s, Y is CWUser'%(ueid, ueid),
+                                          [{'Y': 'CWUser'}])],
                         None, None, [self.system], {}, []),
                        ]),
                     ],
                    {'x': ueid, 'y': ueid})
-        
+
     def test_delete_relation2(self):
         ueid = self.session.user.eid
         self._test('DELETE X created_by Y WHERE X eid %(x)s, NOT Y login "syt"',
-                   [('FetchStep', [('Any Y WHERE NOT Y login "syt", Y is EUser', [{'Y': 'EUser'}])],
+                   [('FetchStep', [('Any Y WHERE NOT Y login "syt", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system], None, {'Y': 'table0.C0'}, []),
                     ('DeleteRelationsStep', [
-                        ('OneFetchStep', [('Any %s,Y WHERE %s created_by Y, Y is EUser'%(ueid,ueid), [{'Y': 'EUser'}])],
+                        ('OneFetchStep', [('Any %s,Y WHERE %s created_by Y, Y is CWUser'%(ueid,ueid), [{'Y': 'CWUser'}])],
                          None, None, [self.system], {'Y': 'table0.C0'}, []),
                         ]),
                     ],
@@ -1562,7 +1636,7 @@
                       ])
                     ],
                    {'x': 999999})
-        
+
     def test_delete_entity2(self):
         repo._type_source_cache[999999] = ('Note', 'system', 999999)
         self._test('DELETE Note X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y',
@@ -1573,30 +1647,30 @@
                       ])
                     ],
                    {'x': 999999})
-                   
+
     def test_update(self):
         self._test('SET X copain Y WHERE X login "comme", Y login "cochon"',
                    [('FetchStep',
-                     [('Any X WHERE X login "comme", X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE X login "comme", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
                     ('FetchStep',
-                     [('Any Y WHERE Y login "cochon", Y is EUser', [{'Y': 'EUser'}])],
+                     [('Any Y WHERE Y login "cochon", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system], None, {'Y': 'table1.C0'}, []),
                     ('UpdateStep',
                      [('OneFetchStep',
-                       [('DISTINCT Any X,Y WHERE X is EUser, Y is EUser',
-                         [{'X': 'EUser', 'Y': 'EUser'}])],
+                       [('DISTINCT Any X,Y WHERE X is CWUser, Y is CWUser',
+                         [{'X': 'CWUser', 'Y': 'CWUser'}])],
                        None, None, [self.system], {'X': 'table0.C0', 'Y': 'table1.C0'}, [])
                       ])
                     ])
 
     def test_update2(self):
         self._test('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"',
-                   [('FetchStep', [('Any U WHERE U login "admin", U is EUser', [{'U': 'EUser'}])],
+                   [('FetchStep', [('Any U WHERE U login "admin", U is CWUser', [{'U': 'CWUser'}])],
                      [self.ldap, self.system], None, {'U': 'table0.C0'}, []),
                      ('UpdateStep', [
-                        ('OneFetchStep', [('DISTINCT Any U,G WHERE G name ILIKE "bougloup%", G is EGroup, U is EUser',
-                                           [{'U': 'EUser', 'G': 'EGroup'}])],
+                        ('OneFetchStep', [('DISTINCT Any U,G WHERE G name ILIKE "bougloup%", G is CWGroup, U is CWUser',
+                                           [{'U': 'CWUser', 'G': 'CWGroup'}])],
                          None, None, [self.system], {'U': 'table0.C0'}, []),
                         ]),
                     ])
@@ -1616,42 +1690,42 @@
 
 #     def test_update4(self):
 #         # since we are adding a in_state relation with a state from the system
-#         # source, EUser should only be searched only in the system source as well
+#         # source, CWUser should only be searched only in the system source as well
 #         rset = self.execute('State X WHERE X name "activated"')
 #         assert len(rset) == 1, rset
 #         activatedeid = rset[0][0]
-#         self._test('SET X in_state S WHERE X is EUser, S eid %s' % activatedeid,
+#         self._test('SET X in_state S WHERE X is CWUser, S eid %s' % activatedeid,
 #                    [('UpdateStep', [
-#                        ('OneFetchStep', [('DISTINCT Any X,%s WHERE X is EUser' % activatedeid,
-#                                           [{'X': 'EUser'}])],
+#                        ('OneFetchStep', [('DISTINCT Any X,%s WHERE X is CWUser' % activatedeid,
+#                                           [{'X': 'CWUser'}])],
 #                         None, None, [self.system], {}, []),
 #                        ]),
 #                     ])
-        
+
     # non regression tests ####################################################
-    
+
     def test_nonregr1(self):
         self._test('Any X, Y WHERE X copain Y, X login "syt", Y login "cochon"',
                    [('FetchStep',
-                     [('Any X WHERE X login "syt", X is EUser', [{'X': 'EUser'}])],
+                     [('Any X WHERE X login "syt", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
                     ('FetchStep',
-                     [('Any Y WHERE Y login "cochon", Y is EUser', [{'Y': 'EUser'}])],
+                     [('Any Y WHERE Y login "cochon", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system], None, {'Y': 'table1.C0'}, []),
                     ('OneFetchStep',
-                     [('Any X,Y WHERE X copain Y, X is EUser, Y is EUser',
-                       [{'X': 'EUser', 'Y': 'EUser'}])],
+                     [('Any X,Y WHERE X copain Y, X is CWUser, Y is CWUser',
+                       [{'X': 'CWUser', 'Y': 'CWUser'}])],
                      None, None, [self.system], {'X': 'table0.C0', 'Y': 'table1.C0'}, [])
                     ])
-    
+
     def test_nonregr2(self):
         treid = self.session.user.latest_trinfo().eid
         self._test('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
                    [('FetchStep', [('Any X,D WHERE X modification_date D, X is Note',
                                     [{'X': 'Note', 'D': 'Datetime'}])],
                      [self.rql, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'D': 'table0.C1'}, []),
-                    ('FetchStep', [('Any X,D WHERE X modification_date D, X is EUser',
-                                    [{'X': 'EUser', 'D': 'Datetime'}])],
+                    ('FetchStep', [('Any X,D WHERE X modification_date D, X is CWUser',
+                                    [{'X': 'CWUser', 'D': 'Datetime'}])],
                      [self.ldap, self.system], None, {'X': 'table1.C0', 'X.modification_date': 'table1.C1', 'D': 'table1.C1'}, []),
                     ('AggrStep', 'Any X ORDERBY D DESC', None, None, 'table2', None, [
                         ('FetchStep', [('Any X,D WHERE E eid %s, E wf_info_for X, X modification_date D, E is TrInfo, X is Affaire'%treid,
@@ -1659,8 +1733,8 @@
                          [self.system],
                          {},
                          {'X': 'table2.C0', 'X.modification_date': 'table2.C1', 'D': 'table2.C1', 'E.wf_info_for': 'table2.C0'}, []),
-                        ('FetchStep', [('Any X,D WHERE E eid %s, E wf_info_for X, X modification_date D, E is TrInfo, X is EUser'%treid,
-                                        [{'X': 'EUser', 'E': 'TrInfo', 'D': 'Datetime'}])],
+                        ('FetchStep', [('Any X,D WHERE E eid %s, E wf_info_for X, X modification_date D, E is TrInfo, X is CWUser'%treid,
+                                        [{'X': 'CWUser', 'E': 'TrInfo', 'D': 'Datetime'}])],
                          [self.system],
                          {'X': 'table1.C0', 'X.modification_date': 'table1.C1', 'D': 'table1.C1'},
                          {'X': 'table2.C0', 'X.modification_date': 'table2.C1', 'D': 'table2.C1', 'E.wf_info_for': 'table2.C0'}, []),
@@ -1672,32 +1746,32 @@
                         ]),
                     ],
                    {'x': treid})
-        
+
     def test_nonregr3(self):
         # original jpl query:
-        # Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is EUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
+        # Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is CWUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
         self._test('Any X, NOW - CD, P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, U login "admin", P is X, X creation_date CD',
-                   [('FetchStep', [('Any U WHERE U login "admin", U is EUser', [{'U': 'EUser'}])],
+                   [('FetchStep', [('Any U WHERE U login "admin", U is CWUser', [{'U': 'CWUser'}])],
                      [self.ldap, self.system], None, {'U': 'table0.C0'}, []),
-                    ('OneFetchStep', [('Any X,(NOW - CD),P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, P is X, X creation_date CD, P is Bookmark, U is EUser, X is EEType',
-                                       [{'P': 'Bookmark', 'U': 'EUser', 'X': 'EEType', 'CD': 'Datetime'}])],
+                    ('OneFetchStep', [('Any X,(NOW - CD),P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, P is X, X creation_date CD, P is Bookmark, U is CWUser, X is CWEType',
+                                       [{'P': 'Bookmark', 'U': 'CWUser', 'X': 'CWEType', 'CD': 'Datetime'}])],
                      5, None,  [self.system], {'U': 'table0.C0'}, [])]
                    )
-        
+
     def test_nonregr4(self):
         self._test('Any U ORDERBY D DESC WHERE WF wf_info_for X, WF creation_date D, WF from_state FS, '
                    'WF owned_by U?, X eid %(x)s',
-                   [#('FetchStep', [('Any U WHERE U is EUser', [{'U': 'EUser'}])],
+                   [#('FetchStep', [('Any U WHERE U is CWUser', [{'U': 'CWUser'}])],
                     # [self.ldap, self.system], None, {'U': 'table0.C0'}, []),
                     ('OneFetchStep', [('Any U ORDERBY D DESC WHERE WF wf_info_for 5, WF creation_date D, WF from_state FS, WF owned_by U?',
-                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'EUser', 'D': 'Datetime'}])],
+                                       [{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser', 'D': 'Datetime'}])],
                      None, None,
                      [self.system], {}, [])],
                    {'x': self.session.user.eid})
 
     def test_nonregr5(self):
         # original jpl query:
-        # DISTINCT Version V WHERE MB done_in MV, MV eid %(x)s, 
+        # DISTINCT Version V WHERE MB done_in MV, MV eid %(x)s,
         # MB depends_on B, B done_in V, V version_of P, NOT P eid %(p)s'
         cardeid = self.execute('INSERT Card X: X title "hop"')[0][0]
         noteeid = self.execute('INSERT Note X')[0][0]
@@ -1748,7 +1822,7 @@
                                        [{'Z': 'Affaire'}])],
                      None, None, [self.system], {}, [])],
                    {'x': 999999})
-        
+
     def test_nonregr9(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         repo._type_source_cache[999998] = ('Note', 'cards', 999998)
@@ -1763,43 +1837,43 @@
                    {'x': 999999, 'z': 999998})
 
     def test_nonregr10(self):
-        repo._type_source_cache[999999] = ('EUser', 'ldapuser', 999999)
+        repo._type_source_cache[999999] = ('CWUser', 'ldapuser', 999999)
         self._test('Any X,AA,AB ORDERBY AA WHERE E eid %(x)s, E owned_by X, X login AA, X modification_date AB',
                    [('FetchStep',
-                     [('Any X,AA,AB WHERE X login AA, X modification_date AB, X is EUser',
-                       [{'AA': 'String', 'AB': 'Datetime', 'X': 'EUser'}])],
-                     [self.ldap], None, {'AA': 'table0.C1', 'AB': 'table0.C2',
-                                         'X': 'table0.C0', 'X.login': 'table0.C1', 'X.modification_date': 'table0.C2'},
+                     [('Any X,AA,AB WHERE X login AA, X modification_date AB, X is CWUser',
+                       [{'AA': 'String', 'AB': 'Datetime', 'X': 'CWUser'}])],
+                     [self.ldap, self.system], None, {'AA': 'table0.C1', 'AB': 'table0.C2',
+                                                      'X': 'table0.C0', 'X.login': 'table0.C1', 'X.modification_date': 'table0.C2'},
                      []),
                     ('OneFetchStep',
-                     [('Any X,AA,AB ORDERBY AA WHERE 999999 owned_by X, X login AA, X modification_date AB, X is EUser',
-                       [{'AA': 'String', 'AB': 'Datetime', 'X': 'EUser'}])],
+                     [('Any X,AA,AB ORDERBY AA WHERE 999999 owned_by X, X login AA, X modification_date AB, X is CWUser',
+                       [{'AA': 'String', 'AB': 'Datetime', 'X': 'CWUser'}])],
                      None, None, [self.system], {'AA': 'table0.C1', 'AB': 'table0.C2',
                                                  'X': 'table0.C0', 'X.login': 'table0.C1', 'X.modification_date': 'table0.C2'},
                      [])
                     ],
                    {'x': 999999})
-        
+
     def test_nonregr11(self):
         repo._type_source_cache[999999] = ('Bookmark', 'system', 999999)
         self._test('SET X bookmarked_by Y WHERE X eid %(x)s, Y login "hop"',
                    [('FetchStep',
-                     [('Any Y WHERE Y login "hop", Y is EUser', [{'Y': 'EUser'}])],
+                     [('Any Y WHERE Y login "hop", Y is CWUser', [{'Y': 'CWUser'}])],
                      [self.ldap, self.system],
                      None, {'Y': 'table0.C0'}, []),
                     ('UpdateStep',
-                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y is EUser', [{'Y': 'EUser'}])],
+                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y is CWUser', [{'Y': 'CWUser'}])],
                        None, None, [self.system], {'Y': 'table0.C0'},
                        [])]
                      )],
                    {'x': 999999})
-        
+
     def test_nonregr12(self):
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         self._test('Any X ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E see_also X',
                    [('FetchStep', [('Any X,Z WHERE X modification_date Z, X is Note',
                                     [{'X': 'Note', 'Z': 'Datetime'}])],
-                     [self.rql], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'Z': 'table0.C1'},
+                     [self.rql, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'Z': 'table0.C1'},
                      []),
                     ('AggrStep', 'Any X ORDERBY Z DESC',
                      None, None, 'table1', None,
@@ -1824,25 +1898,25 @@
         self._test('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
                    'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (EXISTS(U identity ME) '
                    'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
-                   'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is EUser)',
-                   [('FetchStep', [('Any U,UL WHERE U login UL, U is EUser',
-                                    [{'U': 'EUser', 'UL': 'String'}])],
+                   'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is CWUser)',
+                   [('FetchStep', [('Any U,UL WHERE U login UL, U is CWUser',
+                                    [{'U': 'CWUser', 'UL': 'String'}])],
                      [self.ldap, self.system], None,
                      {'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
                      []),
-                    ('FetchStep', [('Any U,UL WHERE ((EXISTS(U identity 5)) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is EGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is EGroup)), U login UL, U is EUser',
-                                    [{'G': 'EGroup', 'H': 'EGroup', 'U': 'EUser', 'UL': 'String'}])],
+                    ('FetchStep', [('Any U,UL WHERE ((EXISTS(U identity 5)) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser',
+                                    [{'G': 'CWGroup', 'H': 'CWGroup', 'U': 'CWUser', 'UL': 'String'}])],
                      [self.system],
                      {'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
                      {'U': 'table1.C0', 'U.login': 'table1.C1', 'UL': 'table1.C1'},
                      []),
                     ('OneFetchStep', [('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File',
-                                       [{'B': 'File', 'U': 'EUser', 'UL': 'String'}])],
+                                       [{'B': 'File', 'U': 'CWUser', 'UL': 'String'}])],
                      None, None, [self.system],
                      {'U': 'table1.C0', 'UL': 'table1.C1'},
                      [])],
                    {'x': self.session.user.eid})
-        
+
     def test_nonregr13_2(self):
         # identity *not* wrapped into exists.
         #
@@ -1853,25 +1927,25 @@
         # IMO this is normal, unless we introduce a special case for the
         # identity relation. BUT I think it's better to leave it as is and to
         # explain constraint propagation rules, and so why this should be
-        # wrapped in exists() is used in multi-source
+        # wrapped in exists() if used in multi-source
         self.skip('take a look at me if you wish')
         self._test('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
                    'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (U identity ME '
                    'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
-                   'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is EUser)',
-                   [('FetchStep', [('Any U,UL WHERE U login UL, U is EUser',
-                                    [{'U': 'EUser', 'UL': 'String'}])],
+                   'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is CWUser)',
+                   [('FetchStep', [('Any U,UL WHERE U login UL, U is CWUser',
+                                    [{'U': 'CWUser', 'UL': 'String'}])],
                      [self.ldap, self.system], None,
                      {'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
                      []),
-                    ('FetchStep', [('Any U,UL WHERE ((U identity 5) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is EGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is EGroup)), U login UL, U is EUser',
-                                    [{'G': 'EGroup', 'H': 'EGroup', 'U': 'EUser', 'UL': 'String'}])],
+                    ('FetchStep', [('Any U,UL WHERE ((U identity 5) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser',
+                                    [{'G': 'CWGroup', 'H': 'CWGroup', 'U': 'CWUser', 'UL': 'String'}])],
                      [self.system],
                      {'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
                      {'U': 'table1.C0', 'U.login': 'table1.C1', 'UL': 'table1.C1'},
                      []),
                     ('OneFetchStep', [('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File',
-                                       [{'B': 'File', 'U': 'EUser', 'UL': 'String'}])],
+                                       [{'B': 'File', 'U': 'CWUser', 'UL': 'String'}])],
                      None, None, [self.system],
                      {'U': 'table1.C0', 'UL': 'table1.C1'},
                      [])],
@@ -1880,13 +1954,12 @@
 
 class MSPlannerTwoSameExternalSourcesTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
-    
-    * 2 rql source supporting Card
+
+    * 2 rql sources supporting Card
     """
     repo = repo
-    
+
     def setUp(self):
-        #_QuerierTC.setUp(self)
         self.o = repo.querier
         self.session = repo._sessions.values()[0]
         self.pool = self.session.set_pool()
@@ -1903,9 +1976,13 @@
         self.rql2 = self.sources[-1]
         do_monkey_patch()
         self.planner = MSPlanner(self.o.schema, self.o._rqlhelper)
-
+        assert repo.sources_by_uri['cards2'].support_relation('multisource_crossed_rel')
+        assert 'multisource_crossed_rel' in repo.sources_by_uri['cards2'].cross_relations
+        assert repo.sources_by_uri['cards'].support_relation('multisource_crossed_rel')
+        assert 'multisource_crossed_rel' in repo.sources_by_uri['cards'].cross_relations
+        clear_ms_caches(repo)
     _test = test_plan
-        
+
     def tearDown(self):
         undo_monkey_patch()
         del self.sources[-1]
@@ -1928,7 +2005,110 @@
                      {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'},
                      [])],
                    {'t': 999999})
- 
+
+    def test_version_depends_on(self):
+        self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any X,AD,AE WHERE E eid %(x)s, E migrated_from X, X in_state AD, AD name AE',
+                   [('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
+                                    [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     [self.rql, self.rql2, self.system],
+                     None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
+                            'AE': 'table0.C2', 'X': 'table0.C0'},
+                     []),
+                    ('OneFetchStep', [('Any X,AD,AE WHERE 999999 migrated_from X, AD name AE, AD is State, X is Note',
+                                       [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     None, None, [self.system],
+                     {'AD': 'table0.C1', 'AD.name': 'table0.C2', 'AE': 'table0.C2', 'X': 'table0.C0'},
+                     [])],
+                   {'x': 999999})
+
+    def test_version_crossed_depends_on_1(self):
+        self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
+                   [('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
+                                    [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     [self.rql, self.rql2, self.system],
+                     None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
+                            'AE': 'table0.C2', 'X': 'table0.C0'},
+                     []),
+                    ('UnionStep', None, None,
+                     [('OneFetchStep', [('Any X,AD,AE WHERE 999999 multisource_crossed_rel X, AD name AE, AD is State, X is Note',
+                                         [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                       None, None, [self.rql], None,
+                       []),
+                      ('OneFetchStep', [('Any X,AD,AE WHERE 999999 multisource_crossed_rel X, AD name AE, AD is State, X is Note',
+                                         [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                       None, None, [self.system],
+                       {'AD': 'table0.C1', 'AD.name': 'table0.C2',
+                        'AE': 'table0.C2', 'X': 'table0.C0'},
+                       [])]
+                     )],
+                   {'x': 999999})
+
+    def test_version_crossed_depends_on_2(self):
+        self.repo._type_source_cache[999999] = ('Note', 'system', 999999)
+        self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
+                   [('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
+                                    [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     [self.rql, self.rql2, self.system],
+                     None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
+                            'AE': 'table0.C2', 'X': 'table0.C0'},
+                     []),
+                    ('OneFetchStep', [('Any X,AD,AE WHERE 999999 multisource_crossed_rel X, AD name AE, AD is State, X is Note',
+                                       [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     None, None, [self.system],
+                     {'AD': 'table0.C1', 'AD.name': 'table0.C2', 'AE': 'table0.C2', 'X': 'table0.C0'},
+                     [])],
+                   {'x': 999999})
+
+    def test_version_crossed_depends_on_3(self):
+        self._test('Any X,AD,AE WHERE E multisource_crossed_rel X, X in_state AD, AD name AE, E is Note',
+                   [('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
+                                    [{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+                     [self.rql, self.rql2, self.system],
+                     None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
+                            'AE': 'table0.C2', 'X': 'table0.C0'},
+                     []),
+                    ('FetchStep', [('Any E WHERE E is Note', [{'E': 'Note'}])],
+                     [self.rql, self.rql2, self.system],
+                     None, {'E': 'table1.C0'},
+                     []),
+                    ('UnionStep', None, None,
+                     [('OneFetchStep', [('Any X,AD,AE WHERE E multisource_crossed_rel X, AD name AE, AD is State, E is Note, X is Note',
+                                         [{'AD': 'State', 'AE': 'String', 'E': 'Note', 'X': 'Note'}])],
+                       None, None, [self.rql, self.rql2], None,
+                       []),
+                      ('OneFetchStep', [('Any X,AD,AE WHERE E multisource_crossed_rel X, AD name AE, AD is State, E is Note, X is Note',
+                                         [{'AD': 'State', 'AE': 'String', 'E': 'Note', 'X': 'Note'}])],
+                       None, None, [self.system],
+                       {'AD': 'table0.C1',
+                        'AD.name': 'table0.C2',
+                        'AE': 'table0.C2',
+                        'E': 'table1.C0',
+                        'X': 'table0.C0'},
+                       [])]
+                     )]
+                   )
+
+    def test_nonregr_dont_cross_rel_source_filtering_1(self):
+        self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any S WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
+                   [('OneFetchStep', [('Any S WHERE 999999 in_state S, NOT S name "moved", S is State',
+                                       [{'S': 'State'}])],
+                     None, None, [self.rql], {}, []
+                     )],
+                   {'x': 999999})
+
+    def test_nonregr_dont_cross_rel_source_filtering_2(self):
+        self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self._test('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
+                   [('OneFetchStep', [('Any X,AA,AB WHERE 999999 in_state X, X name AA, X modification_date AB, X is State',
+                                       [{'AA': 'String', 'AB': 'Datetime', 'X': 'State'}])],
+                     None, None, [self.rql], {}, []
+                     )],
+                   {'x': 999999})
+
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/test/unittest_multisources.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_multisources.py	Thu May 14 12:50:34 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
@@ -9,10 +10,10 @@
 class TwoSourcesConfiguration(TestServerConfiguration):
     sourcefile = 'sources_multi'
 
-        
+
 class ExternalSource1Configuration(TestServerConfiguration):
     sourcefile = 'sources_extern'
-        
+
 class ExternalSource2Configuration(TestServerConfiguration):
     sourcefile = 'sources_multi2'
 
@@ -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'))
 
@@ -40,6 +41,8 @@
 
     def setUp(self):
         RepositoryBasedTC.setUp(self)
+        self.repo.sources[-1]._query_cache.clear()
+        self.repo.sources[-2]._query_cache.clear()
         # trigger discovery
         self.execute('Card X')
         self.execute('Affaire X')
@@ -52,7 +55,7 @@
         self.ic2 = self.execute('INSERT Card X: X title "C2: Ze internal card", X wikiid "zzzi"')[0][0]
         self.commit()
         do_monkey_patch()
-        
+
     def tearDown(self):
         RepositoryBasedTC.tearDown(self)
         undo_monkey_patch()
@@ -62,15 +65,15 @@
         self.assertEquals(len(rset), 4)
         rset = self.execute('Any X,T WHERE X title T, X eid > 1')
         self.assertEquals(len(rset), 4)
-        
+
     def test_metainformation(self):
         rset = self.execute('Card X ORDERBY T WHERE X title T')
         # 2 added to the system source, 2 added to the external source
         self.assertEquals(len(rset), 4)
         # since they are orderd by eid, we know the 3 first one is coming from the system source
         # and the others from external source
-        self.assertEquals(rset.get_entity(0, 0).metainformation(), 
-                          {'source': {'adapter': 'native', 'uri': 'system'}, 
+        self.assertEquals(rset.get_entity(0, 0).metainformation(),
+                          {'source': {'adapter': 'native', 'uri': 'system'},
                            'type': u'Card', 'extid': None})
         externent = rset.get_entity(3, 0)
         metainf = externent.metainformation()
@@ -80,7 +83,7 @@
         etype = self.execute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
                              {'x': externent.eid}, 'x')[0][0]
         self.assertEquals(etype, 'Card')
-        
+
     def test_order_limit_offset(self):
         rsetbase = self.execute('Any W,X ORDERBY W,X WHERE X wikiid W')
         self.assertEquals(len(rsetbase), 4)
@@ -107,6 +110,7 @@
 
     def test_synchronization(self):
         cu = cnx2.cursor()
+        assert cu.execute('Any X WHERE X eid %(x)s', {'x': aff1}, 'x')
         cu.execute('SET X ref "BLAH" WHERE X eid %(x)s', {'x': aff1}, 'x')
         aff2 = cu.execute('INSERT Affaire X: X ref "AFFREUX", X in_state S WHERE S name "pitetre"')[0][0]
         cnx2.commit()
@@ -140,7 +144,7 @@
 
     def test_sort_func(self):
         self.execute('Affaire X ORDERBY DUMB_SORT(RF) WHERE X ref RF')
-        
+
     def test_sort_func_ambigous(self):
         self.execute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF')
 
@@ -149,7 +153,7 @@
                                    'Card', self.session)
         rset = self.execute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1))
         self.assertEquals(sorted(r[0] for r in rset.rows), sorted([iec1, self.ic1]))
-        
+
     def test_greater_eid(self):
         rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
         self.assertEquals(len(rset.rows), 2) # self.ic1 and self.ic2
@@ -160,13 +164,13 @@
         self.assertEquals(len(rset.rows), 2)
         # trigger discovery using another query
         crset = self.execute('Card X WHERE X title "glup"')
-        self.assertEquals(len(crset.rows), 1) 
+        self.assertEquals(len(crset.rows), 1)
         rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
         self.assertEquals(len(rset.rows), 3)
         rset = self.execute('Any MAX(X)')
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(rset.rows[0][0], crset[0][0])
-        
+
     def test_attr_unification_1(self):
         n1 = self.execute('INSERT Note X: X type "AFFREF"')[0][0]
         n2 = self.execute('INSERT Note X: X type "AFFREU"')[0][0]
@@ -191,14 +195,14 @@
     def test_attr_unification_neq_2(self):
         # XXX complete
         self.execute('Any X,Y WHERE X is Card, Y is Affaire, X creation_date D, Y creation_date > D')
-        
+
     def test_union(self):
         afeids = self.execute('Affaire X')
-        ueids = self.execute('EUser X')
-        rset = self.execute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is EUser)')
+        ueids = self.execute('CWUser X')
+        rset = self.execute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is CWUser)')
         self.assertEquals(sorted(r[0] for r in rset.rows),
                           sorted(r[0] for r in afeids + ueids))
-        
+
     def test_subquery1(self):
         rsetbase = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
         self.assertEquals(len(rsetbase), 4)
@@ -209,11 +213,11 @@
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
         rset = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W)')
         self.assertEquals(rset.rows, rsetbase.rows[2:4])
-        
+
     def test_subquery2(self):
         affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
-        rset =self.execute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
-                           {'x': affeid})
+        rset = self.execute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
+                            {'x': affeid})
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset[0][1], "pitetre")
 
@@ -233,13 +237,13 @@
                                                        {'x': aff1}, 'x'))
         self.set_debug(False)
         self.assertSetEquals(notstates, states)
-        
+
     def test_nonregr1(self):
         ueid = self.session.user.eid
         affaire = self.execute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
         self.execute('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
                      {'x': affaire.eid, 'u': ueid})
-        
+
     def test_nonregr2(self):
         treid = self.session.user.latest_trinfo().eid
         rset = self.execute('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
@@ -250,7 +254,7 @@
 
     def test_nonregr3(self):
         self.execute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/test/unittest_querier.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_querier.py	Thu May 14 12:50:34 2009 +0200
@@ -1,18 +1,20 @@
 # -*- 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 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
 
-from mx.DateTime import today, now, DateTimeType
-from rql import BadRQLQuery, RQLSyntaxError
-from cubicweb import QueryError, Unauthorized
-from cubicweb.server.utils import crypt_password
-from cubicweb.server.sources.native import make_schema
-
 
 # register priority/severity sorting registered procedure
 from rql.utils import register_function, FunctionDescr
@@ -37,11 +39,11 @@
 
 class MakeSchemaTC(TestCase):
     def test_known_values(self):
-        solution = {'A': 'String', 'B': 'EUser'}
-        self.assertEquals(make_schema((Variable('A'), Variable('B')), solution, 
+        solution = {'A': 'String', 'B': 'CWUser'}
+        self.assertEquals(make_schema((Variable('A'), Variable('B')), solution,
                                       'table0', TYPEMAP),
                           ('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
-        
+
 
 repo, cnx = init_test_database('sqlite')
 
@@ -49,29 +51,29 @@
 
 class UtilsTC(BaseQuerierTC):
     repo = repo
-    
+
     def get_max_eid(self):
         # no need for cleanup here
         return None
     def cleanup(self):
         # no need for cleanup here
         pass
-    
+
     def test_preprocess_1(self):
-        reid = self.execute('Any X WHERE X is ERType, X name "owned_by"')[0][0]
+        reid = self.execute('Any X WHERE X is CWRType, X name "owned_by"')[0][0]
         rqlst = self._prepare('Any COUNT(RDEF) WHERE RDEF relation_type X, X eid %(x)s', {'x': reid})
-        self.assertEquals(rqlst.solutions, [{'RDEF': 'EFRDef'}, {'RDEF': 'ENFRDef'}])
-        
+        self.assertEquals(rqlst.solutions, [{'RDEF': 'CWAttribute'}, {'RDEF': 'CWRelation'}])
+
     def test_preprocess_2(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
-        #geid = self.execute("EGroup G WHERE G name 'users'")[0][0]
+        #geid = self.execute("CWGroup G WHERE G name 'users'")[0][0]
         #self.execute("SET X tags Y WHERE X eid %(t)s, Y eid %(g)s",
         #             {'g': geid, 't': teid}, 'g')
         rqlst = self._prepare('Any X WHERE E eid %(x)s, E tags X', {'x': teid})
         # the query may be optimized, should keep only one solution
         # (any one, etype will be discarded)
         self.assertEquals(len(rqlst.solutions), 1)
-        
+
     def test_preprocess_security(self):
         plan = self._prepare_plan('Any ETN,COUNT(X) GROUPBY ETN '
                                   'WHERE X is ET, ET name ETN')
@@ -94,7 +96,7 @@
                           ' OR (EXISTS(H concerne G?, G owned_by %(B)s, G is SubDivision, X identity H, H is Affaire)))'
                           ' OR (EXISTS(I concerne F?, F owned_by %(B)s, F is Societe, X identity I, I is Affaire)))'
                           ' OR (EXISTS(J concerne E?, E owned_by %(B)s, E is Note, X identity J, J is Affaire)))'
-                          ', ET is EEType, X is Affaire')
+                          ', ET is CWEType, X is Affaire')
         self.assertEquals(solutions, [{'C': 'Division',
                                        'D': 'Affaire',
                                        'E': 'Note',
@@ -104,47 +106,47 @@
                                        'I': 'Affaire',
                                        'J': 'Affaire',
                                        'X': 'Affaire',
-                                       'ET': 'EEType', 'ETN': 'String'}])
+                                       'ET': 'CWEType', 'ETN': 'String'}])
         rql, solutions = partrqls[1]
-        self.assertEquals(rql,  'Any ETN,X WHERE X is ET, ET name ETN, ET is EEType, '
-                          'X is IN(Bookmark, Card, Comment, Division, ECache, EConstraint, EConstraintType, EEType, EFRDef, EGroup, ENFRDef, EPermission, EProperty, ERType, EUser, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)')
+        self.assertEquals(rql,  'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, '
+                          'X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)')
         self.assertListEquals(sorted(solutions),
-                              sorted([{'X': 'Bookmark', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Card', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Comment', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Division', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'ECache', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EConstraint', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EConstraintType', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EEType', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EFRDef', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EGroup', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Email', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EmailAddress', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EmailPart', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EmailThread', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'ENFRDef', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EPermission', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EProperty', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'ERType', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'EUser', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'File', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Folder', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Image', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Note', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Personne', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'RQLExpression', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Societe', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'State', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'SubDivision', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Tag', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'Transition', 'ETN': 'String', 'ET': 'EEType'},
-                                      {'X': 'TrInfo', 'ETN': 'String', 'ET': 'EEType'}]))
+                              sorted([{'X': 'Bookmark', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Card', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Comment', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Division', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWCache', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWConstraint', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWConstraintType', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWEType', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWAttribute', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWGroup', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Email', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'EmailAddress', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'EmailPart', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'EmailThread', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWRelation', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWPermission', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWProperty', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWRType', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'CWUser', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'File', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Folder', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Image', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Note', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Personne', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'RQLExpression', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Societe', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'State', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'SubDivision', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Tag', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'Transition', 'ETN': 'String', 'ET': 'CWEType'},
+                                      {'X': 'TrInfo', 'ETN': 'String', 'ET': 'CWEType'}]))
         rql, solutions = partrqls[2]
         self.assertEquals(rql,
                           'Any ETN,X WHERE X is ET, ET name ETN, EXISTS(X owned_by %(C)s), '
-                          'ET is EEType, X is Basket')
-        self.assertEquals(solutions, [{'ET': 'EEType',
+                          'ET is CWEType, X is Basket')
+        self.assertEquals(solutions, [{'ET': 'CWEType',
                                        'X': 'Basket',
                                        'ETN': 'String',
                                        }])
@@ -160,15 +162,15 @@
         self.assertEquals(len(subq.children), 3)
         self.assertEquals([t.as_string() for t in union.children[0].selection],
                           ['MAX(X)'])
-        
+
     def test_preprocess_nonregr(self):
         rqlst = self._prepare('Any S ORDERBY SI WHERE NOT S ecrit_par O, S para SI')
         self.assertEquals(len(rqlst.solutions), 1)
-    
+
     def test_build_description(self):
         # should return an empty result set
         rset = self.execute('Any X WHERE X eid %(x)s', {'x': self.session.user.eid})
-        self.assertEquals(rset.description[0][0], 'EUser')
+        self.assertEquals(rset.description[0][0], 'CWUser')
         rset = self.execute('Any 1')
         self.assertEquals(rset.description[0][0], 'Int')
         rset = self.execute('Any TRUE')
@@ -187,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')
@@ -200,71 +202,71 @@
 
     def test_encoding_pb(self):
         self.assertRaises(RQLSyntaxError, self.execute,
-                          'Any X WHERE X is ERType, X name "öwned_by"')
+                          'Any X WHERE X is CWRType, X name "öwned_by"')
 
     def test_unknown_eid(self):
         # should return an empty result set
         self.failIf(self.execute('Any X WHERE X eid 99999999'))
-        
+
     # selection queries tests #################################################
-    
+
     def test_select_1(self):
-        rset = self.execute('Any X ORDERBY X WHERE X is EGroup')
+        rset = self.execute('Any X ORDERBY X WHERE X is CWGroup')
         result, descr = rset.rows, rset.description
         self.assertEquals(tuplify(result), [(1,), (2,), (3,), (4,)])
-        self.assertEquals(descr, [('EGroup',), ('EGroup',), ('EGroup',), ('EGroup',)])
-        
+        self.assertEquals(descr, [('CWGroup',), ('CWGroup',), ('CWGroup',), ('CWGroup',)])
+
     def test_select_2(self):
-        rset = self.execute('Any X ORDERBY N WHERE X is EGroup, X name N')
+        rset = self.execute('Any X ORDERBY N WHERE X is CWGroup, X name N')
         self.assertEquals(tuplify(rset.rows), [(3,), (1,), (4,), (2,)])
-        self.assertEquals(rset.description, [('EGroup',), ('EGroup',), ('EGroup',), ('EGroup',)])
-        rset = self.execute('Any X ORDERBY N DESC WHERE X is EGroup, X name N')
+        self.assertEquals(rset.description, [('CWGroup',), ('CWGroup',), ('CWGroup',), ('CWGroup',)])
+        rset = self.execute('Any X ORDERBY N DESC WHERE X is CWGroup, X name N')
         self.assertEquals(tuplify(rset.rows), [(2,), (4,), (1,), (3,)])
-        
+
     def test_select_3(self):
-        rset = self.execute('Any N GROUPBY N WHERE X is EGroup, X name N')
+        rset = self.execute('Any N GROUPBY N WHERE X is CWGroup, X name N')
         result, descr = rset.rows, rset.description
         result.sort()
         self.assertEquals(tuplify(result), [('guests',), ('managers',), ('owners',), ('users',)])
         self.assertEquals(descr, [('String',), ('String',), ('String',), ('String',)])
-        
+
     def test_select_is(self):
         rset = self.execute('Any X, TN ORDERBY TN LIMIT 10 WHERE X is T, T name TN')
         result, descr = rset.rows, rset.description
         self.assertEquals(result[0][1], descr[0][0])
-        
+
     def test_select_is_aggr(self):
         rset = self.execute('Any TN, COUNT(X) GROUPBY TN ORDERBY 2 DESC WHERE X is T, T name TN')
         result, descr = rset.rows, rset.description
         self.assertEquals(descr[0][0], 'String')
         self.assertEquals(descr[0][1], 'Int')
-        self.assertEquals(result[0][0], 'ENFRDef')
-        
+        self.assertEquals(result[0][0], 'CWRelation')
+
     def test_select_groupby_orderby(self):
-        rset = self.execute('Any N GROUPBY N ORDERBY N WHERE X is EGroup, X name N')
+        rset = self.execute('Any N GROUPBY N ORDERBY N WHERE X is CWGroup, X name N')
         self.assertEquals(tuplify(rset.rows), [('guests',), ('managers',), ('owners',), ('users',)])
         self.assertEquals(rset.description, [('String',), ('String',), ('String',), ('String',)])
-        
+
     def test_select_complex_groupby(self):
         rset = self.execute('Any N GROUPBY N WHERE X name N')
         rset = self.execute('Any N,MAX(D) GROUPBY N LIMIT 5 WHERE X name N, X creation_date D')
-        
+
     def test_select_inlined_groupby(self):
         seid = self.execute('State X WHERE X name "deactivated"')[0][0]
         rset = self.execute('Any U,L,S GROUPBY U,L,S WHERE X in_state S, U login L, S eid %s' % seid)
-        
+
     def test_select_complex_orderby(self):
         rset1 = self.execute('Any N ORDERBY N WHERE X name N')
         self.assertEquals(sorted(rset1.rows), rset1.rows)
         rset = self.execute('Any N ORDERBY N LIMIT 5 OFFSET 1 WHERE X name N')
-        self.assertEquals(rset.rows[0][0], rset1.rows[1][0]) 
+        self.assertEquals(rset.rows[0][0], rset1.rows[1][0])
         self.assertEquals(len(rset), 5)
-        
+
     def test_select_5(self):
-        rset = self.execute('Any X, TMP ORDERBY TMP WHERE X name TMP, X is EGroup')
+        rset = self.execute('Any X, TMP ORDERBY TMP WHERE X name TMP, X is CWGroup')
         self.assertEquals(tuplify(rset.rows), [(3, 'guests',), (1, 'managers',), (4, 'owners',), (2, 'users',)])
-        self.assertEquals(rset.description, [('EGroup', 'String',), ('EGroup', 'String',), ('EGroup', 'String',), ('EGroup', 'String',)])
-        
+        self.assertEquals(rset.description, [('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',)])
+
     def test_select_6(self):
         self.execute("INSERT Personne X: X nom 'bidule'")[0]
         rset = self.execute('Any Y where X name TMP, Y nom in (TMP, "bidule")')
@@ -272,7 +274,7 @@
         self.assert_(('Personne',) in rset.description)
         rset = self.execute('DISTINCT Any Y where X name TMP, Y nom in (TMP, "bidule")')
         self.assert_(('Personne',) in rset.description)
-        
+
     def test_select_not_attr(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Societe X: X nom 'chouette'")
@@ -283,13 +285,13 @@
         self.execute("SET P travaille S WHERE P nom 'bidule', S nom 'chouette'")
         rset = self.execute('Personne X WHERE NOT X travaille S')
         self.assertEquals(len(rset.rows), 0, rset.rows)
-        
+
     def test_select_is_in(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Societe X: X nom 'chouette'")
         self.assertEquals(len(self.execute("Any X WHERE X is IN (Personne, Societe)")),
                           2)
-        
+
     def test_select_not_rel(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Societe X: X nom 'chouette'")
@@ -299,24 +301,24 @@
         self.assertEquals(len(rset.rows), 1, rset.rows)
         rset = self.execute('Personne X WHERE NOT X travaille S, S nom "chouette"')
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_nonregr_inlined(self):
         self.execute("INSERT Note X: X para 'bidule'")
         self.execute("INSERT Personne X: X nom 'chouette'")
         self.execute("INSERT Personne X: X nom 'autre'")
         self.execute("SET X ecrit_par P WHERE X para 'bidule', P nom 'chouette'")
-        rset = self.execute('Any U,T ORDERBY T DESC WHERE U is EUser, '
+        rset = self.execute('Any U,T ORDERBY T DESC WHERE U is CWUser, '
                             'N ecrit_par U, N type T')#, {'x': self.ueid})
         self.assertEquals(len(rset.rows), 0)
-        
+
     def test_select_nonregr_edition_not(self):
         groupeids = set((1, 2, 3))
-        groupreadperms = set(r[0] for r in self.execute('Any Y WHERE X name "EGroup", Y eid IN(1, 2, 3), X read_permission Y'))
-        rset = self.execute('DISTINCT Any Y WHERE X is EEType, X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+        groupreadperms = set(r[0] for r in self.execute('Any Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), X read_permission Y'))
+        rset = self.execute('DISTINCT Any Y WHERE X is CWEType, X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
         self.assertEquals(sorted(r[0] for r in rset.rows), sorted(groupeids - groupreadperms))
-        rset = self.execute('DISTINCT Any Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+        rset = self.execute('DISTINCT Any Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
         self.assertEquals(sorted(r[0] for r in rset.rows), sorted(groupeids - groupreadperms))
-                     
+
     def test_select_outer_join(self):
         peid1 = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
         peid2 = self.execute("INSERT Personne X: X nom 'autre'")[0][0]
@@ -329,7 +331,7 @@
         self.assertEquals(rset.rows, [[peid1, seid1], [peid2, None]])
         rset = self.execute('Any S,X ORDERBY S WHERE X? travaille S')
         self.assertEquals(rset.rows, [[seid1, peid1], [seid2, None]])
-        
+
     def test_select_outer_join_optimized(self):
         peid1 = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
         rset = self.execute('Any X WHERE X eid %(x)s, P? connait X', {'x':peid1}, 'x')
@@ -338,7 +340,7 @@
         self.assertEquals(rset.rows, [[peid1]])
 
     def test_select_left_outer_join(self):
-        ueid = self.execute("INSERT EUser X: X login 'bob', X upassword 'toto', X in_group G "
+        ueid = self.execute("INSERT CWUser X: X login 'bob', X upassword 'toto', X in_group G "
                             "WHERE G name 'users'")[0][0]
         self.commit()
         try:
@@ -357,21 +359,21 @@
                                 {'x': ueid}, 'x')
             self.assertEquals(len(rset), 2)
         finally:
-            self.execute('DELETE EUser X WHERE X eid %s' % ueid)
+            self.execute('DELETE CWUser X WHERE X eid %s' % ueid)
             self.commit()
 
     def test_select_ambigous_outer_join(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
         self.execute("INSERT Tag X: X name 'tagbis'")[0][0]
-        geid = self.execute("EGroup G WHERE G name 'users'")[0][0]
+        geid = self.execute("CWGroup G WHERE G name 'users'")[0][0]
         self.execute("SET X tags Y WHERE X eid %(t)s, Y eid %(g)s",
                      {'g': geid, 't': teid}, 'g')
         rset = self.execute("Any GN,TN ORDERBY GN WHERE T? tags G, T name TN, G name GN")
         self.failUnless(['users', 'tag'] in rset.rows)
         self.failUnless(['activated', None] in rset.rows)
         rset = self.execute("Any GN,TN ORDERBY GN WHERE T tags G?, T name TN, G name GN")
-        self.assertEquals(rset.rows, [[None, 'tagbis'], ['users', 'tag']])            
-        
+        self.assertEquals(rset.rows, [[None, 'tagbis'], ['users', 'tag']])
+
     def test_select_not_inline_rel(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Note X: X type 'a'")
@@ -379,7 +381,7 @@
         self.execute("SET X ecrit_par Y WHERE X type 'a', Y nom 'bidule'")
         rset = self.execute('Note X WHERE NOT X ecrit_par P')
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_not_unlinked_multiple_solutions(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Note X: X type 'a'")
@@ -393,13 +395,13 @@
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(len(rset.rows[0]), 1)
         self.assertEquals(rset.description, [('Int',)])
-        
+
     def test_select_aggregat_sum(self):
         rset = self.execute('Any SUM(O) WHERE X ordernum O')
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(len(rset.rows[0]), 1)
         self.assertEquals(rset.description, [('Int',)])
-        
+
     def test_select_aggregat_min(self):
         rset = self.execute('Any MIN(X) WHERE X is Personne')
         self.assertEquals(len(rset.rows), 1)
@@ -409,7 +411,7 @@
         self.assertEquals(len(rset.rows), 1)
         self.assertEquals(len(rset.rows[0]), 1)
         self.assertEquals(rset.description, [('Int',)])
-        
+
     def test_select_aggregat_max(self):
         rset = self.execute('Any MAX(X) WHERE X is Personne')
         self.assertEquals(len(rset.rows), 1)
@@ -421,13 +423,13 @@
         self.assertEquals(rset.description, [('Int',)])
 
     def test_select_custom_aggregat_concat_string(self):
-        rset = self.execute('Any CONCAT_STRINGS(N) WHERE X is EGroup, X name N')
+        rset = self.execute('Any CONCAT_STRINGS(N) WHERE X is CWGroup, X name N')
         self.failUnless(rset)
         self.failUnlessEqual(sorted(rset[0][0].split(', ')), ['guests', 'managers',
                                                              'owners', 'users'])
 
     def test_select_custom_regproc_limit_size(self):
-        rset = self.execute('Any TEXT_LIMIT_SIZE(N, 3) WHERE X is EGroup, X name N, X name "managers"')
+        rset = self.execute('Any TEXT_LIMIT_SIZE(N, 3) WHERE X is CWGroup, X name N, X name "managers"')
         self.failUnless(rset)
         self.failUnlessEqual(rset[0][0], 'man...')
         self.execute("INSERT Basket X: X name 'bidule', X description '<b>hop hop</b>', X description_format 'text/html'")
@@ -436,18 +438,18 @@
         self.failUnlessEqual(rset[0][0], 'hop...')
 
     def test_select_regproc_orderby(self):
-        rset = self.execute('DISTINCT Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is EGroup, X name N, X name "managers"')
+        rset = self.execute('DISTINCT Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is CWGroup, X name N, X name "managers"')
         self.failUnlessEqual(len(rset), 1)
         self.failUnlessEqual(rset[0][1], 'managers')
-        rset = self.execute('Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is EGroup, X name N, NOT U in_group X, U login "admin"')
+        rset = self.execute('Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is CWGroup, X name N, NOT U in_group X, U login "admin"')
         self.failUnlessEqual(len(rset), 3)
         self.failUnlessEqual(rset[0][1], 'owners')
-        
+
     def test_select_aggregat_sort(self):
         rset = self.execute('Any G, COUNT(U) GROUPBY G ORDERBY 2 WHERE U in_group G')
         self.assertEquals(len(rset.rows), 2)
         self.assertEquals(len(rset.rows[0]), 2)
-        self.assertEquals(rset.description[0], ('EGroup', 'Int',))
+        self.assertEquals(rset.description[0], ('CWGroup', 'Int',))
 
     def test_select_aggregat_having(self):
         rset = self.execute('Any N,COUNT(RDEF) GROUPBY N ORDERBY 2,N '
@@ -468,18 +470,19 @@
         self.assertEquals(rset.rows[0][0], self.ueid)
 
     def test_select_complex_sort(self):
+        self.skip('retry me once http://www.sqlite.org/cvstrac/tktview?tn=3773 is fixed')
         rset = self.execute('Any X ORDERBY X,D LIMIT 5 WHERE X creation_date D')
         result = rset.rows
         result.sort()
         self.assertEquals(tuplify(result), [(1,), (2,), (3,), (4,), (5,)])
-        
+
     def test_select_upper(self):
-        rset = self.execute('Any X, UPPER(L) ORDERBY L WHERE X is EUser, X login L')
+        rset = self.execute('Any X, UPPER(L) ORDERBY L WHERE X is CWUser, X login L')
         self.assertEquals(len(rset.rows), 2)
         self.assertEquals(rset.rows[0][1], 'ADMIN')
-        self.assertEquals(rset.description[0], ('EUser', 'String',))
+        self.assertEquals(rset.description[0], ('CWUser', 'String',))
         self.assertEquals(rset.rows[1][1], 'ANON')
-        self.assertEquals(rset.description[1], ('EUser', 'String',))
+        self.assertEquals(rset.description[1], ('CWUser', 'String',))
         eid = rset.rows[0][0]
         rset = self.execute('Any UPPER(L) WHERE X eid %s, X login L'%eid)
         self.assertEquals(rset.rows[0][0], 'ADMIN')
@@ -491,7 +494,7 @@
 ##         self.assertEquals(rset.rows[0][0], 'admin')
 ##         rset = self.execute('Any L WHERE %(x)s login L', {'x':ueid})
 ##         self.assertEquals(rset.rows[0][0], 'admin')
-        
+
     def test_select_searchable_text_1(self):
         rset = self.execute(u"INSERT Personne X: X nom 'bidüle'")
         rset = self.execute(u"INSERT Societe X: X nom 'bidüle'")
@@ -506,7 +509,7 @@
         self.failIf([r[0] for r in rset.rows if r[0] in biduleeids])
         # duh?
         rset = self.execute('Any X WHERE X has_text %(text)s', {'text': u'ça'})
-        
+
     def test_select_searchable_text_2(self):
         rset = self.execute("INSERT Personne X: X nom 'bidule'")
         rset = self.execute("INSERT Personne X: X nom 'chouette'")
@@ -514,7 +517,7 @@
         self.commit()
         rset = self.execute('Personne N where N has_text "bidule"')
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_searchable_text_3(self):
         rset = self.execute("INSERT Personne X: X nom 'bidule', X sexe 'M'")
         rset = self.execute("INSERT Personne X: X nom 'bidule', X sexe 'F'")
@@ -522,7 +525,7 @@
         self.commit()
         rset = self.execute('Any X where X has_text "bidule" and X sexe "M"')
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_multiple_searchable_text(self):
         self.execute(u"INSERT Personne X: X nom 'bidüle'")
         self.execute("INSERT Societe X: X nom 'chouette', S travaille X")
@@ -533,20 +536,20 @@
                              'text2': u'chouette',}
                             )
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_no_descr(self):
-        rset = self.execute('Any X WHERE X is EGroup', build_descr=0)
+        rset = self.execute('Any X WHERE X is CWGroup', build_descr=0)
         rset.rows.sort()
         self.assertEquals(tuplify(rset.rows), [(1,), (2,), (3,), (4,)])
         self.assertEquals(rset.description, ())
 
     def test_select_limit_offset(self):
-        rset = self.execute('EGroup X ORDERBY N LIMIT 2 WHERE X name N')
+        rset = self.execute('CWGroup X ORDERBY N LIMIT 2 WHERE X name N')
         self.assertEquals(tuplify(rset.rows), [(3,), (1,)])
-        self.assertEquals(rset.description, [('EGroup',), ('EGroup',)])
-        rset = self.execute('EGroup X ORDERBY N LIMIT 2 OFFSET 2 WHERE X name N')
+        self.assertEquals(rset.description, [('CWGroup',), ('CWGroup',)])
+        rset = self.execute('CWGroup X ORDERBY N LIMIT 2 OFFSET 2 WHERE X name N')
         self.assertEquals(tuplify(rset.rows), [(4,), (2,)])
-        
+
     def test_select_symetric(self):
         self.execute("INSERT Personne X: X nom 'machin'")
         self.execute("INSERT Personne X: X nom 'bidule'")
@@ -566,14 +569,14 @@
         self.assertEquals(len(rset.rows), 2, rset.rows)
         rset = self.execute('Any P where P2 connait P, P2 nom "chouette"')
         self.assertEquals(len(rset.rows), 2, rset.rows)
-        
+
     def test_select_inline(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Note X: X type 'a'")
         self.execute("SET X ecrit_par Y WHERE X type 'a', Y nom 'bidule'")
         rset = self.execute('Any N where N ecrit_par X, X nom "bidule"')
         self.assertEquals(len(rset.rows), 1, rset.rows)
-        
+
     def test_select_creation_date(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         rset = self.execute('Any D WHERE X nom "bidule", X creation_date D')
@@ -590,7 +593,7 @@
         self.execute("SET P travaille S WHERE P nom 'chouette', S nom 'caesium'")
         rset = self.execute('DISTINCT Any P WHERE P travaille S1 OR P travaille S2, S1 nom "logilab", S2 nom "caesium"')
         self.assertEqual(len(rset.rows), 2)
-        
+
     def test_select_or_sym_relation(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
         self.execute("INSERT Personne X: X nom 'chouette'")
@@ -605,7 +608,7 @@
         self.assertEqual(len(rset.rows), 2, rset.rows)
         rset = self.execute('DISTINCT Any P WHERE P connait S OR S connait P, S nom "chouette"')
         self.assertEqual(len(rset.rows), 2, rset.rows)
-            
+
     def test_select_follow_relation(self):
         self.execute("INSERT Affaire X: X sujet 'cool'")
         self.execute("INSERT Societe X: X nom 'chouette'")
@@ -632,9 +635,9 @@
         self.execute("INSERT Affaire X: X sujet 'abcd'")
         rset = self.execute('DISTINCT Any S ORDERBY S WHERE A is Affaire, A sujet S')
         self.assertEqual(rset.rows, [['abcd'], ['important'], ['minor'], ['normal'], ['zou']])
-        
+
     def test_select_ordered_distinct_3(self):
-        rset = self.execute('DISTINCT Any N ORDERBY GROUP_SORT_VALUE(N) WHERE X is EGroup, X name N')
+        rset = self.execute('DISTINCT Any N ORDERBY GROUP_SORT_VALUE(N) WHERE X is CWGroup, X name N')
         self.assertEqual(rset.rows, [['owners'], ['guests'], ['users'], ['managers']])
 
     def test_select_or_value(self):
@@ -647,22 +650,22 @@
         rset = self.execute('Any X,E WHERE X owned_by U, X eid E, U eid %(u)s', {'u': self.session.user.eid})
         self.failUnless(rset)
         self.assertEquals(rset.description[0][1], 'Int')
-        
+
 #     def test_select_rewritten_optional(self):
 #         eid = self.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
 #         rset = self.execute('Any X WHERE X eid %(x)s, EXISTS(X owned_by U) OR EXISTS(X concerne S?, S owned_by U)',
 #                             {'x': eid}, 'x')
 #         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")
@@ -671,7 +674,7 @@
         self.assertEqual(len(rset.rows), 2)
 
     def test_select_boolean(self):
-        rset = self.execute('Any N WHERE X is EEType, X name N, X final %(val)s',
+        rset = self.execute('Any N WHERE X is CWEType, X name N, X final %(val)s',
                             {'val': True})
         self.assertEquals(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
                                                             'Date', 'Datetime',
@@ -679,50 +682,50 @@
                                                             'Int', 'Interval',
                                                             'Password', 'String',
                                                             'Time'])
-        rset = self.execute('Any N WHERE X is EEType, X name N, X final TRUE')
+        rset = self.execute('Any N WHERE X is CWEType, X name N, X final TRUE')
         self.assertEquals(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
                                                             'Date', 'Datetime',
                                                             'Decimal', 'Float',
                                                             'Int', 'Interval',
                                                             'Password', 'String',
                                                             'Time'])
-        
+
     def test_select_constant(self):
-        rset = self.execute('Any X, "toto" ORDERBY X WHERE X is EGroup')
+        rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup')
         self.assertEquals(rset.rows,
                           map(list, zip((1,2,3,4), ('toto','toto','toto','toto',))))
         self.assertIsInstance(rset[0][1], unicode)
         self.assertEquals(rset.description,
-                          zip(('EGroup', 'EGroup', 'EGroup', 'EGroup'),
+                          zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'),
                               ('String', 'String', 'String', 'String',)))
-        rset = self.execute('Any X, %(value)s ORDERBY X WHERE X is EGroup', {'value': 'toto'})
+        rset = self.execute('Any X, %(value)s ORDERBY X WHERE X is CWGroup', {'value': 'toto'})
         self.assertEquals(rset.rows,
                           map(list, zip((1,2,3,4), ('toto','toto','toto','toto',))))
         self.assertIsInstance(rset[0][1], unicode)
         self.assertEquals(rset.description,
-                          zip(('EGroup', 'EGroup', 'EGroup', 'EGroup'),
+                          zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'),
                               ('String', 'String', 'String', 'String',)))
-        rset = self.execute('Any X,GN WHERE X is EUser, G is EGroup, X login "syt", X in_group G, G name GN')
+        rset = self.execute('Any X,GN WHERE X is CWUser, G is CWGroup, X login "syt", X in_group G, G name GN')
 
     def test_select_union(self):
         rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
                             '((Any X,N WHERE X name N, X transition_of E, E name %(name)s)'
                             ' UNION '
                             '(Any X,N WHERE X name N, X state_of E, E name %(name)s))',
-                            {'name': 'EUser'})
+                            {'name': 'CWUser'})
         self.assertEquals([x[1] for x in rset.rows],
                           ['activate', 'activated', 'deactivate', 'deactivated'])
         self.assertEquals(rset.description,
                           [('Transition', 'String'), ('State', 'String'),
                            ('Transition', 'String'), ('State', 'String')])
-        
+
     def test_select_union_aggregat(self):
         # meaningless, the goal in to have group by done on different attribute
         # for each sub-query
         self.execute('(Any N,COUNT(X) GROUPBY N WHERE X name N, X is State)'
                      ' UNION '
                      '(Any N,COUNT(X) GROUPBY N ORDERBY 2 WHERE X login N)')
-        
+
     def test_select_union_aggregat_independant_group(self):
         self.execute('INSERT State X: X name "hop"')
         self.execute('INSERT State X: X name "hop"')
@@ -733,7 +736,7 @@
                             ' UNION '
                             '(Any N,COUNT(X) GROUPBY N WHERE X name N, X is Transition HAVING COUNT(X)>1))')
         self.assertEquals(rset.rows, [[u'hop', 2], [u'hop', 2]])
-        
+
     def test_select_union_selection_with_diff_variables(self):
         rset = self.execute('(Any N WHERE X name N, X is State)'
                             ' UNION '
@@ -743,9 +746,9 @@
                            'deactivate', 'deactivated', 'done', 'en cours',
                            'end', 'finie', 'markasdone', 'pitetre', 'redoit',
                            'start', 'todo'])
-        
+
     def test_exists(self):
-        geid = self.execute("INSERT EGroup X: X name 'lulufanclub'")[0][0]
+        geid = self.execute("INSERT CWGroup X: X name 'lulufanclub'")[0][0]
         self.execute("SET U in_group G WHERE G name 'lulufanclub'")
         peid = self.execute("INSERT Personne X: X prenom 'lulu', X nom 'petit'")[0][0]
         rset = self.execute("Any X WHERE X prenom 'lulu',"
@@ -761,23 +764,19 @@
         self.assertEquals(login, 'admin')
 
     def test_select_date_mathexp(self):
-        rset = self.execute('Any X, TODAY - CD WHERE X is EUser, X creation_date CD')
+        rset = self.execute('Any X, TODAY - CD WHERE X is CWUser, X creation_date CD')
         self.failUnless(rset)
         self.failUnlessEqual(rset.description[0][1], 'Interval')
         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
         self.execute('SET X in_group G WHERE G name "users"')
         rset = self.execute('Any GN, COUNT(X)*100/T GROUPBY GN ORDERBY 2,1'
                             ' WHERE G name GN, X in_group G'
-                            ' WITH T BEING (Any COUNT(U) WHERE U is EUser)')
+                            ' WITH T BEING (Any COUNT(U) WHERE U is CWUser)')
         self.assertEquals(rset.rows, [[u'guests', 50], [u'managers', 50], [u'users', 100]])
         self.assertEquals(rset.description, [('String', 'Int'), ('String', 'Int'), ('String', 'Int')])
 
@@ -785,15 +784,15 @@
         rset = self.execute('Any X WITH X BEING ((Any NULL) UNION (Any "toto"))')
         self.assertEquals(rset.rows, [[None], ['toto']])
         self.assertEquals(rset.description, [(None,), ('String',)])
-                          
+
     # insertion queries tests #################################################
-    
+
     def test_insert_is(self):
         eid, = self.execute("INSERT Personne X: X nom 'bidule'")[0]
         etype, = self.execute("Any TN WHERE X is T, X eid %s, T name TN" % eid)[0]
         self.assertEquals(etype, 'Personne')
         self.execute("INSERT Personne X: X nom 'managers'")
-    
+
     def test_insert_1(self):
         rset = self.execute("INSERT Personne X: X nom 'bidule'")
         self.assertEquals(len(rset.rows), 1)
@@ -820,7 +819,7 @@
         self.execute("INSERT Personne X: X nom Y WHERE U login 'admin', U login Y")
         rset = self.execute('Personne X WHERE X nom "admin"')
         self.assert_(rset.rows)
-        self.assertEquals(rset.description, [('Personne',)])        
+        self.assertEquals(rset.description, [('Personne',)])
 
     def test_insert_4(self):
         self.execute("INSERT Societe Y: Y nom 'toto'")
@@ -828,7 +827,7 @@
         rset = self.execute('Any X, Y WHERE X nom "bidule", Y nom "toto", X travaille Y')
         self.assert_(rset.rows)
         self.assertEquals(rset.description, [('Personne', 'Societe',)])
-        
+
     def test_insert_4bis(self):
         peid = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
         seid = self.execute("INSERT Societe Y: Y nom 'toto', X travaille Y WHERE X eid %(x)s",
@@ -837,7 +836,7 @@
         self.execute("INSERT Personne X: X nom 'chouette', X travaille Y WHERE Y eid %(x)s",
                       {'x': str(seid)})
         self.assertEqual(len(self.execute('Any X, Y WHERE X travaille Y')), 2)
-        
+
     def test_insert_4ter(self):
         peid = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
         seid = self.execute("INSERT Societe Y: Y nom 'toto', X travaille Y WHERE X eid %(x)s",
@@ -885,14 +884,14 @@
 
         self.assertRaises(QueryError,
                           self.execute,
-                          "INSERT EUser X: X login 'toto', X eid %s" % cnx.user(self.session).eid)
+                          "INSERT CWUser X: X login 'toto', X eid %s" % cnx.user(self.session).eid)
 
     def test_insertion_description_with_where(self):
-        rset = self.execute('INSERT EUser E, EmailAddress EM: E login "X", E upassword "X", '
+        rset = self.execute('INSERT CWUser E, EmailAddress EM: E login "X", E upassword "X", '
                             'E primary_email EM, EM address "X", E in_group G '
                             'WHERE G name "managers"')
-        self.assertEquals(list(rset.description[0]), ['EUser', 'EmailAddress'])
-    
+        self.assertEquals(list(rset.description[0]), ['CWUser', 'EmailAddress'])
+
     # deletion queries tests ##################################################
 
     def test_delete_1(self):
@@ -902,7 +901,7 @@
         self.execute("DELETE Personne Y WHERE Y nom 'toto'")
         rset = self.execute('Personne X WHERE X nom "toto"')
         self.assertEqual(len(rset.rows), 0)
-        
+
     def test_delete_2(self):
         rset = self.execute("INSERT Personne X, Personne Y, Societe Z : X nom 'syt', Y nom 'adim', Z nom 'Logilab', X travaille Z, Y travaille Z")
         self.assertEquals(len(rset), 1)
@@ -961,7 +960,7 @@
         self.assertEquals(len(sqlc.fetchall()), 0)
         sqlc.execute('SELECT * FROM owned_by_relation WHERE eid_from=%s'%eeid)
         self.assertEquals(len(sqlc.fetchall()), 0)
-            
+
     def test_nonregr_delete_cache2(self):
         eid = self.execute("INSERT Folder T: T name 'toto'")[0][0]
         self.commit()
@@ -980,7 +979,7 @@
         self.assertEquals(rset.rows, [])
         rset = self.execute("Folder X WHERE X eid %s" %eid)
         self.assertEquals(rset.rows, [])
-        
+
     # update queries tests ####################################################
 
     def test_update_1(self):
@@ -990,7 +989,7 @@
         self.execute("SET X nom 'tutu', X prenom 'original' WHERE X is Personne, X nom 'toto'")
         rset = self.execute('Any Y, Z WHERE X is Personne, X nom Y, X prenom Z')
         self.assertEqual(tuplify(rset.rows), [('tutu', 'original')])
-        
+
     def test_update_2(self):
         self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'")
         #rset = self.execute('Any X, Y WHERE X nom "bidule", Y nom "toto"')
@@ -1000,7 +999,7 @@
         self.execute("SET X travaille Y WHERE X nom 'bidule', Y nom 'toto'")
         rset = self.execute('Any X, Y WHERE X travaille Y')
         self.assertEqual(len(rset.rows), 1)
-        
+
     def test_update_2bis(self):
         rset = self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'")
         eid1, eid2 = rset[0][0], rset[0][1]
@@ -1008,7 +1007,7 @@
                       {'x': str(eid1), 'y': str(eid2)})
         rset = self.execute('Any X, Y WHERE X travaille Y')
         self.assertEqual(len(rset.rows), 1)
-        
+
     def test_update_2ter(self):
         rset = self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'")
         eid1, eid2 = rset[0][0], rset[0][1]
@@ -1016,10 +1015,10 @@
                       {'x': unicode(eid1), 'y': unicode(eid2)})
         rset = self.execute('Any X, Y WHERE X travaille Y')
         self.assertEqual(len(rset.rows), 1)
-        
+
 ##     def test_update_4(self):
 ##         self.execute("SET X know Y WHERE X ami Y")
-        
+
     def test_update_multiple1(self):
         peid1 = self.execute("INSERT Personne Y: Y nom 'tutu'")[0][0]
         peid2 = self.execute("INSERT Personne Y: Y nom 'toto'")[0][0]
@@ -1028,7 +1027,7 @@
         self.assertEquals(self.execute('Any X WHERE X nom "tutu"').rows, [[peid2]])
 
     def test_update_multiple2(self):
-        ueid = self.execute("INSERT EUser X: X login 'bob', X upassword 'toto'")[0][0]
+        ueid = self.execute("INSERT CWUser X: X login 'bob', X upassword 'toto'")[0][0]
         peid1 = self.execute("INSERT Personne Y: Y nom 'turlu'")[0][0]
         peid2 = self.execute("INSERT Personne Y: Y nom 'tutu'")[0][0]
         self.execute('SET P1 owned_by U, P2 owned_by U '
@@ -1055,45 +1054,47 @@
         self.execute('SET X title XN + %(suffix)s WHERE X is Bookmark, X title XN', {'suffix': u'-moved'})
         newname = self.execute('Any XN WHERE X eid %(x)s, X title XN', {'x': beid}, 'x')[0][0]
         self.assertEquals(newname, 'toto-moved')
-                       
+
     def test_update_query_error(self):
         self.execute("INSERT Personne Y: Y nom 'toto'")
         self.assertRaises(Exception, self.execute, "SET X nom 'toto', X is Personne")
         self.assertRaises(QueryError, self.execute, "SET X nom 'toto', X has_text 'tutu' WHERE X is Personne")
         self.assertRaises(QueryError, self.execute, "SET X login 'tutu', X eid %s" % cnx.user(self.session).eid)
 
-       
+
     # upassword encryption tests #################################################
-    
+
     def test_insert_upassword(self):
-        rset = self.execute("INSERT EUser X: X login 'bob', X upassword 'toto'")
+        rset = self.execute("INSERT CWUser X: X login 'bob', X upassword 'toto'")
         self.assertEquals(len(rset.rows), 1)
-        self.assertEquals(rset.description, [('EUser',)])
+        self.assertEquals(rset.description, [('CWUser',)])
         self.assertRaises(Unauthorized,
-                          self.execute, "Any P WHERE X is EUser, X login 'bob', X upassword P")
+                          self.execute, "Any P WHERE X is CWUser, X login 'bob', X upassword P")
         cursor = self.pool['system']
-        cursor.execute("SELECT upassword from EUser WHERE login='bob'")
+        cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
+                       % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = cursor.fetchone()[0].getvalue()
-        self.assertEquals(passwd, crypt_password('toto', passwd[:2])) 
-        rset = self.execute("Any X WHERE X is EUser, X login 'bob', X upassword '%s'" % passwd)
+        self.assertEquals(passwd, crypt_password('toto', passwd[:2]))
+        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd)
         self.assertEquals(len(rset.rows), 1)
-        self.assertEquals(rset.description, [('EUser',)])
-        
+        self.assertEquals(rset.description, [('CWUser',)])
+
     def test_update_upassword(self):
         cursor = self.pool['system']
-        rset = self.execute("INSERT EUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'})
-        self.assertEquals(rset.description[0][0], 'EUser')
-        rset = self.execute("SET X upassword %(pwd)s WHERE X is EUser, X login 'bob'",
+        rset = self.execute("INSERT CWUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'})
+        self.assertEquals(rset.description[0][0], 'CWUser')
+        rset = self.execute("SET X upassword %(pwd)s WHERE X is CWUser, X login 'bob'",
                             {'pwd': 'tutu'})
-        cursor.execute("SELECT upassword from EUser WHERE login='bob'")
+        cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
+                       % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = cursor.fetchone()[0].getvalue()
-        self.assertEquals(passwd, crypt_password('tutu', passwd[:2])) 
-        rset = self.execute("Any X WHERE X is EUser, X login 'bob', X upassword '%s'" % passwd)
+        self.assertEquals(passwd, crypt_password('tutu', passwd[:2]))
+        rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd)
         self.assertEquals(len(rset.rows), 1)
-        self.assertEquals(rset.description, [('EUser',)])
+        self.assertEquals(rset.description, [('CWUser',)])
 
     # non regression tests ####################################################
-    
+
     def test_nonregr_1(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
         self.execute("SET X tags Y WHERE X name 'tag', Y is State, Y name 'activated'")
@@ -1106,18 +1107,18 @@
 
     def test_nonregr_2(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
-        geid = self.execute("EGroup G WHERE G name 'users'")[0][0]
+        geid = self.execute("CWGroup G WHERE G name 'users'")[0][0]
         self.execute("SET X tags Y WHERE X eid %(t)s, Y eid %(g)s",
                        {'g': geid, 't': teid})
         rset = self.execute('Any X WHERE E eid %(x)s, E tags X',
                               {'x': teid})
         self.assertEquals(rset.rows, [[geid]])
-        
+
     def test_nonregr_3(self):
         """bad sql generated on the second query (destination_state is not
         detected as an inlined relation)
         """
-        rset = self.execute('Any S,ES,T WHERE S state_of ET, ET name "EUser",'
+        rset = self.execute('Any S,ES,T WHERE S state_of ET, ET name "CWUser",'
                              'ES allowed_transition T, T destination_state S')
         self.assertEquals(len(rset.rows), 2)
 
@@ -1126,7 +1127,7 @@
         # union queries and that make for instance a 266Ko sql query which is refused
         # by the server (or client lib)
         rset = self.execute('Any ER,SE,OE WHERE SE name "Comment", ER name "comments", OE name "Comment",'
-                            'ER is ERType, SE is EEType, OE is EEType')
+                            'ER is CWRType, SE is CWEType, OE is CWEType')
         self.assertEquals(len(rset), 1)
 
     def test_nonregr_5(self):
@@ -1150,10 +1151,10 @@
         self.assertEquals(rset1.rows, rset2.rows)
         self.assertEquals(rset1.rows, rset3.rows)
         self.assertEquals(rset1.rows, rset4.rows)
-        
+
     def test_nonregr_6(self):
         self.execute('Any N,COUNT(S) GROUPBY N ORDERBY COUNT(N) WHERE S name N, S is State')
-        
+
     def test_sqlite_encoding(self):
         """XXX this test was trying to show a bug on use of lower which only
         occurs with non ascii string and misconfigured locale
@@ -1212,8 +1213,9 @@
         cause: old variable ref inserted into a fresh rqlst copy
         (in RQLSpliter._complex_select_plan)
         """
+        self.skip('retry me once http://www.sqlite.org/cvstrac/tktview?tn=3773 is fixed')
         self.execute('Any X ORDERBY D DESC WHERE X creation_date D')
-    
+
     def test_nonregr_extra_joins(self):
         ueid = self.session.user.eid
         teid1 = self.execute("INSERT Folder X: X name 'folder1'")[0][0]
@@ -1241,19 +1243,19 @@
 
     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]
+        ueid = self.execute("INSERT CWUser X: X login 'bob', X upassword 'toto'")[0][0]
         self.execute("SET E in_group G, E in_state S, "
                       "E firstname %(firstname)s, E surname %(surname)s "
                       "WHERE E eid %(x)s, G name 'users', S name 'activated'",
                       {'x':ueid, 'firstname': u'jean', 'surname': u'paul'}, 'x')
-        
+
     def test_nonregr_u_owned_by_u(self):
-        ueid = self.execute("INSERT EUser X: X login 'bob', X upassword 'toto', X in_group G "
+        ueid = self.execute("INSERT CWUser X: X login 'bob', X upassword 'toto', X in_group G "
                              "WHERE G name 'users'")[0][0]
-        rset = self.execute("EUser U")
+        rset = self.execute("CWUser U")
         self.assertEquals(len(rset), 3) # bob + admin + anon
         rset = self.execute("Any U WHERE NOT U owned_by U")
         self.assertEquals(len(rset), 0) # even admin created at repo initialization time should belong to itself
@@ -1263,18 +1265,18 @@
         self.execute("SET X description D WHERE X is State, X description D")
 
     def test_nonregr_is(self):
-        uteid = self.execute('Any ET WHERE ET name "EUser"')[0][0]
+        uteid = self.execute('Any ET WHERE ET name "CWUser"')[0][0]
         self.execute('Any X, ET WHERE X is ET, ET eid %s' % uteid)
 
     def test_nonregr_orderby(self):
         seid = self.execute('Any X WHERE X name "activated"')[0][0]
-        self.execute('Any X,S, MAX(T) GROUPBY X,S ORDERBY S WHERE X is EUser, T tags X, S eid IN(%s), X in_state S' % seid)
+        self.execute('Any X,S, MAX(T) GROUPBY X,S ORDERBY S WHERE X is CWUser, T tags X, S eid IN(%s), X in_state S' % seid)
 
     def test_nonregr_solution_cache(self):
         self.skip('XXX should be fixed or documented') # (doesn't occur if cache key is provided.)
-        rset = self.execute('Any X WHERE X is EUser, X eid %(x)s', {'x':self.ueid})
+        rset = self.execute('Any X WHERE X is CWUser, X eid %(x)s', {'x':self.ueid})
         self.assertEquals(len(rset), 1)
-        rset = self.execute('Any X WHERE X is EUser, X eid %(x)s', {'x':12345})
+        rset = self.execute('Any X WHERE X is CWUser, X eid %(x)s', {'x':12345})
         self.assertEquals(len(rset), 0)
 
     def test_nonregr_final_norestr(self):
--- a/server/test/unittest_repository.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_repository.py	Thu May 14 12:50:34 2009 +0200
@@ -6,19 +6,19 @@
 import threading
 import time
 from copy import deepcopy
+from datetime import datetime
 
-from mx.DateTime import DateTimeType, now
 from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools.apptest import RepositoryBasedTC
-from cubicweb.devtools.repotest import tuplify
 
 from yams.constraints import UniqueConstraint
 
 from cubicweb import BadConnectionId, RepositoryError, ValidationError, UnknownEid, AuthenticationError
 from cubicweb.schema import CubicWebSchema, RQLConstraint
 from cubicweb.dbapi import connect, repo_connect
-
-from cubicweb.server import repository 
+from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.repotest import tuplify
+from cubicweb.server import repository
+from cubicweb.server.sqlutils import SQL_PREFIX
 
 
 # start name server anyway, process will fail if already running
@@ -29,10 +29,10 @@
     """ singleton providing access to a persistent storage for entities
     and relation
     """
-    
+
 #     def setUp(self):
 #         pass
-    
+
 #     def tearDown(self):
 #         self.repo.config.db_perms = True
 #         cnxid = self.repo.connect(*self.default_user_password())
@@ -46,11 +46,16 @@
         self.repo.config._cubes = None # avoid assertion error
         self.repo.fill_schema()
         pool = self.repo._get_pool()
+        table = SQL_PREFIX + 'CWEType'
+        namecol = SQL_PREFIX + 'name'
+        finalcol = SQL_PREFIX + 'final'
         try:
             sqlcursor = pool['system']
-            sqlcursor.execute('SELECT name FROM EEType WHERE final is NULL')
+            sqlcursor.execute('SELECT %s FROM %s WHERE %s is NULL' % (
+                namecol, table, finalcol))
             self.assertEquals(sqlcursor.fetchall(), [])
-            sqlcursor.execute('SELECT name FROM EEType WHERE final=%(final)s ORDER BY name', {'final': 'TRUE'})
+            sqlcursor.execute('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+                              % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
             self.assertEquals(sqlcursor.fetchall(), [(u'Boolean',), (u'Bytes',),
                                                      (u'Date',), (u'Datetime',),
                                                      (u'Decimal',),(u'Float',),
@@ -59,17 +64,17 @@
                                                      (u'String',), (u'Time',)])
         finally:
             self.repo._free_pool(pool)
-            
+
     def test_schema_has_owner(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
-        self.failIf(repo.execute(cnxid, 'EEType X WHERE NOT X owned_by U'))
-        self.failIf(repo.execute(cnxid, 'ERType X WHERE NOT X owned_by U'))
-        self.failIf(repo.execute(cnxid, 'EFRDef X WHERE NOT X owned_by U'))
-        self.failIf(repo.execute(cnxid, 'ENFRDef X WHERE NOT X owned_by U'))
-        self.failIf(repo.execute(cnxid, 'EConstraint X WHERE NOT X owned_by U'))
-        self.failIf(repo.execute(cnxid, 'EConstraintType X WHERE NOT X owned_by U'))
-        
+        self.failIf(repo.execute(cnxid, 'CWEType X WHERE NOT X owned_by U'))
+        self.failIf(repo.execute(cnxid, 'CWRType X WHERE NOT X owned_by U'))
+        self.failIf(repo.execute(cnxid, 'CWAttribute X WHERE NOT X owned_by U'))
+        self.failIf(repo.execute(cnxid, 'CWRelation X WHERE NOT X owned_by U'))
+        self.failIf(repo.execute(cnxid, 'CWConstraint X WHERE NOT X owned_by U'))
+        self.failIf(repo.execute(cnxid, 'CWConstraintType X WHERE NOT X owned_by U'))
+
     def test_connect(self):
         login, passwd = self.default_user_password()
         self.assert_(self.repo.connect(login, passwd))
@@ -79,7 +84,7 @@
                           self.repo.connect, login, None)
         self.assertRaises(AuthenticationError,
                           self.repo.connect, None, None)
-    
+
     def test_execute(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
@@ -88,36 +93,37 @@
         repo.execute(cnxid, 'Any X where X is Personne, X nom ~= "to"')
         repo.execute(cnxid, 'Any X WHERE X has_text %(text)s', {'text': u'\xe7a'})
         repo.close(cnxid)
-        
+
     def test_login_upassword_accent(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
-        repo.execute(cnxid, 'INSERT EUser X: X login %(login)s, X upassword %(passwd)s, X in_state S, X in_group G WHERE S name "activated", G name "users"',
+        repo.execute(cnxid, 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s, X in_state S, X in_group G WHERE S name "activated", G name "users"',
                      {'login': u"barnabé", 'passwd': u"héhéhé".encode('UTF8')})
         repo.commit(cnxid)
         repo.close(cnxid)
         self.assert_(repo.connect(u"barnabé", u"héhéhé".encode('UTF8')))
-    
+
     def test_invalid_entity_rollback(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
-        repo.execute(cnxid, 'INSERT EUser X: X login %(login)s, X upassword %(passwd)s, X in_state S WHERE S name "activated"',
+        # no group
+        repo.execute(cnxid, 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s, X in_state S WHERE S name "activated"',
                      {'login': u"tutetute", 'passwd': 'tutetute'})
         self.assertRaises(ValidationError, repo.commit, cnxid)
-        rset = repo.execute(cnxid, 'EUser X WHERE X login "tutetute"')
+        rset = repo.execute(cnxid, 'CWUser X WHERE X login "tutetute"')
         self.assertEquals(rset.rowcount, 0)
-        
+
     def test_close(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
         self.assert_(cnxid)
         repo.close(cnxid)
         self.assertRaises(BadConnectionId, repo.execute, cnxid, 'Any X')
-    
+
     def test_invalid_cnxid(self):
         self.assertRaises(BadConnectionId, self.repo.execute, 0, 'Any X')
         self.assertRaises(BadConnectionId, self.repo.close, None)
-    
+
     def test_shared_data(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
@@ -174,7 +180,7 @@
         repo.rollback(cnxid)
         result = repo.execute(cnxid, "Any U WHERE U in_group G, U login 'admin', G name 'guests'")
         self.assertEquals(result.rowcount, 0, result.rows)
-        
+
     def test_transaction_base3(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
@@ -189,26 +195,26 @@
         repo.rollback(cnxid)
         rset = repo.execute(cnxid, 'TrInfo T WHERE T wf_info_for X, X eid %(x)s', {'x': ueid})
         self.assertEquals(len(rset), 1)
-        
+
     def test_transaction_interleaved(self):
         self.skip('implement me')
 
     def test_initial_schema(self):
         schema = self.repo.schema
         # check order of attributes is respected
-        self.assertListEquals([r.type for r in schema.eschema('EFRDef').ordered_relations()
-                               if not r.type in ('eid', 'is', 'is_instance_of', 'identity', 
+        self.assertListEquals([r.type for r in schema.eschema('CWAttribute').ordered_relations()
+                               if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
                                                  'creation_date', 'modification_date',
                                                  'owned_by', 'created_by')],
                               ['relation_type', 'from_entity', 'to_entity', 'constrained_by',
-                               'cardinality', 'ordernum', 
+                               'cardinality', 'ordernum',
                                'indexed', 'fulltextindexed', 'internationalizable',
                                'defaultval', 'description_format', 'description'])
 
-        self.assertEquals(schema.eschema('EEType').main_attribute(), 'name')
+        self.assertEquals(schema.eschema('CWEType').main_attribute(), 'name')
         self.assertEquals(schema.eschema('State').main_attribute(), 'name')
 
-        constraints = schema.rschema('name').rproperty('EEType', 'String', 'constraints')
+        constraints = schema.rschema('name').rproperty('CWEType', 'String', 'constraints')
         self.assertEquals(len(constraints), 2)
         for cstr in constraints[:]:
             if isinstance(cstr, UniqueConstraint):
@@ -220,14 +226,14 @@
         self.assertEquals(sizeconstraint.min, None)
         self.assertEquals(sizeconstraint.max, 64)
 
-        constraints = schema.rschema('relation_type').rproperty('EFRDef', 'ERType', 'constraints')
+        constraints = schema.rschema('relation_type').rproperty('CWAttribute', 'CWRType', 'constraints')
         self.assertEquals(len(constraints), 1)
         cstr = constraints[0]
         self.assert_(isinstance(cstr, RQLConstraint))
         self.assertEquals(cstr.restriction, 'O final TRUE')
 
         ownedby = schema.rschema('owned_by')
-        self.assertEquals(ownedby.objects('EEType'), ('EUser',))
+        self.assertEquals(ownedby.objects('CWEType'), ('CWUser',))
 
     def test_pyro(self):
         import Pyro
@@ -249,21 +255,21 @@
             t.join()
         finally:
             repository.pyro_unregister(self.repo.config)
-            
+
     def _pyro_client(self, lock):
         cnx = connect(self.repo.config.appid, u'admin', 'gingkow')
         # check we can get the schema
         schema = cnx.get_schema()
         self.assertEquals(schema.__hashmode__, None)
         rset = cnx.cursor().execute('Any U,G WHERE U in_group G')
-        
+
 
     def test_internal_api(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
         session = repo._get_session(cnxid, setpool=True)
-        self.assertEquals(repo.type_and_source_from_eid(1, session), ('EGroup', 'system', None))
-        self.assertEquals(repo.type_from_eid(1, session), 'EGroup')
+        self.assertEquals(repo.type_and_source_from_eid(1, session), ('CWGroup', 'system', None))
+        self.assertEquals(repo.type_from_eid(1, session), 'CWGroup')
         self.assertEquals(repo.source_from_eid(1, session).uri, 'system')
         self.assertEquals(repo.eid2extid(repo.system_source, 1, session), None)
         class dummysource: uri = 'toto'
@@ -273,13 +279,13 @@
         self.assertEquals(self.repo.get_schema(), self.repo.schema)
         self.assertEquals(self.repo.source_defs(), {'system': {'adapter': 'native', 'uri': 'system'}})
         # .properties() return a result set
-        self.assertEquals(self.repo.properties().rql, 'Any K,V WHERE P is EProperty,P pkey K, P value V, NOT P for_user U')
+        self.assertEquals(self.repo.properties().rql, 'Any K,V WHERE P is CWProperty,P pkey K, P value V, NOT P for_user U')
 
     def test_session_api(self):
         repo = self.repo
         cnxid = repo.connect(*self.default_user_password())
         self.assertEquals(repo.user_info(cnxid), (5, 'admin', set([u'managers']), {}))
-        self.assertEquals(repo.describe(cnxid, 1), (u'EGroup', u'system', None))
+        self.assertEquals(repo.describe(cnxid, 1), (u'CWGroup', u'system', None))
         repo.close(cnxid)
         self.assertRaises(BadConnectionId, repo.user_info, cnxid)
         self.assertRaises(BadConnectionId, repo.describe, cnxid, 1)
@@ -296,10 +302,10 @@
         repo.close(cnxid)
         self.assertRaises(BadConnectionId, repo.set_shared_data, cnxid, 'data', 0)
         self.assertRaises(BadConnectionId, repo.get_shared_data, cnxid, 'data')
-        
+
 
 class DataHelpersTC(RepositoryBasedTC):
-    
+
     def setUp(self):
         """ called before each test from this class """
         cnxid = self.repo.connect(*self.default_user_password())
@@ -308,7 +314,7 @@
 
     def tearDown(self):
         self.session.rollback()
-        
+
     def test_create_eid(self):
         self.assert_(self.repo.system_source.create_eid(self.session))
 
@@ -320,11 +326,11 @@
         self.assertRaises(UnknownEid, self.repo.source_from_eid, -2, self.session)
 
     def test_type_from_eid(self):
-        self.assertEquals(self.repo.type_from_eid(1, self.session), 'EGroup')
-        
+        self.assertEquals(self.repo.type_from_eid(1, self.session), 'CWGroup')
+
     def test_type_from_eid_raise(self):
         self.assertRaises(UnknownEid, self.repo.type_from_eid, -2, self.session)
-        
+
     def test_add_delete_info(self):
         entity = self.repo.vreg.etype_class('Personne')(self.session, None, None)
         entity.eid = -1
@@ -333,7 +339,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)])
@@ -345,12 +351,12 @@
 
 
 class FTITC(RepositoryBasedTC):
-    
+
     def test_reindex_and_modified_since(self):
         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]
@@ -395,18 +401,18 @@
         self.commit()
         rset = self.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
         self.assertEquals(rset.rows, [[self.session.user.eid]])
-        
-        
+
+
 class DBInitTC(RepositoryBasedTC):
-    
+
     def test_versions_inserted(self):
         inserted = [r[0] for r in self.execute('Any K ORDERBY K WHERE P pkey K, P pkey ~= "system.version.%"')]
         self.assertEquals(inserted,
-                          [u'system.version.basket', u'system.version.comment', 
-                           u'system.version.cubicweb', u'system.version.email', 
-                           u'system.version.file', u'system.version.folder', 
+                          [u'system.version.basket', u'system.version.card', u'system.version.comment',
+                           u'system.version.cubicweb', u'system.version.email',
+                           u'system.version.file', u'system.version.folder',
                            u'system.version.tag'])
-        
+
 class InlineRelHooksTC(RepositoryBasedTC):
     """test relation hooks are called for inlined relations
     """
@@ -414,13 +420,13 @@
         RepositoryBasedTC.setUp(self)
         self.hm = self.repo.hm
         self.called = []
-    
+
     def _before_relation_hook(self, pool, fromeid, rtype, toeid):
         self.called.append((fromeid, rtype, toeid))
 
     def _after_relation_hook(self, pool, fromeid, rtype, toeid):
         self.called.append((fromeid, rtype, toeid))
-        
+
     def test_before_add_inline_relation(self):
         """make sure before_<event>_relation hooks are called directly"""
         self.hm.register_hook(self._before_relation_hook,
@@ -429,7 +435,7 @@
         eidn = self.execute('INSERT Note X: X type "T"')[0][0]
         self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
         self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp)])
-        
+
     def test_after_add_inline_relation(self):
         """make sure after_<event>_relation hooks are deferred"""
         self.hm.register_hook(self._after_relation_hook,
@@ -439,15 +445,15 @@
         self.assertEquals(self.called, [])
         self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
         self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
-        
+
     def test_after_add_inline(self):
         """make sure after_<event>_relation hooks are deferred"""
         self.hm.register_hook(self._after_relation_hook,
                              'after_add_relation', 'in_state')
-        eidp = self.execute('INSERT EUser X: X login "toto", X upassword "tutu", X in_state S WHERE S name "activated"')[0][0]
+        eidp = self.execute('INSERT CWUser X: X login "toto", X upassword "tutu", X in_state S WHERE S name "activated"')[0][0]
         eids = self.execute('State X WHERE X name "activated"')[0][0]
         self.assertEquals(self.called, [(eidp, 'in_state', eids,)])
-    
+
     def test_before_delete_inline_relation(self):
         """make sure before_<event>_relation hooks are called directly"""
         self.hm.register_hook(self._before_relation_hook,
@@ -472,6 +478,6 @@
         self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
         self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
 
-    
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_rql2sql.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Thu May 14 12:50:34 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
 
@@ -31,111 +30,111 @@
 
 PARSER = [
     (r"Personne P WHERE P nom 'Zig\'oto';",
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE P.nom=Zig\'oto'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE P.cw_nom=Zig\'oto'''),
 
     (r'Personne P WHERE P nom ~= "Zig\"oto%";',
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE P.nom ILIKE Zig"oto%'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE P.cw_nom ILIKE Zig"oto%'''),
     ]
 
 BASIC = [
-    
+
     ("Any X WHERE X is Affaire",
-     '''SELECT X.eid
-FROM Affaire AS X'''),
-    
+     '''SELECT X.cw_eid
+FROM cw_Affaire AS X'''),
+
     ("Any X WHERE X eid 0",
      '''SELECT 0'''),
-    
+
     ("Personne P",
-     '''SELECT P.eid
-FROM Personne AS P'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P'''),
 
     ("Personne P WHERE P test TRUE",
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE P.test=True'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE P.cw_test=TRUE'''),
 
     ("Personne P WHERE P test false",
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE P.test=False'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE P.cw_test=FALSE'''),
 
     ("Personne P WHERE P eid -1",
      '''SELECT -1'''),
 
     ("Personne P LIMIT 20 OFFSET 10",
-     '''SELECT P.eid
-FROM Personne AS P
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
 LIMIT 20
 OFFSET 10'''),
 
     ("Personne P WHERE S is Societe, P travaille S, S nom 'Logilab';",
      '''SELECT rel_travaille0.eid_from
-FROM Societe AS S, travaille_relation AS rel_travaille0
-WHERE rel_travaille0.eid_to=S.eid AND S.nom=Logilab'''),
+FROM cw_Societe AS S, travaille_relation AS rel_travaille0
+WHERE rel_travaille0.eid_to=S.cw_eid AND S.cw_nom=Logilab'''),
 
     ("Personne P WHERE P concerne A, A concerne S, S nom 'Logilab', S is Societe;",
      '''SELECT rel_concerne0.eid_from
-FROM Societe AS S, concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1
-WHERE rel_concerne0.eid_to=rel_concerne1.eid_from AND rel_concerne1.eid_to=S.eid AND S.nom=Logilab'''),
+FROM concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1, cw_Societe AS S
+WHERE rel_concerne0.eid_to=rel_concerne1.eid_from AND rel_concerne1.eid_to=S.cw_eid AND S.cw_nom=Logilab'''),
 
     ("Note N WHERE X evaluee N, X nom 'Logilab';",
      '''SELECT rel_evaluee0.eid_to
-FROM Division AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom=Logilab
+FROM cw_Division AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom=Logilab
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM Personne AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom=Logilab
+FROM cw_Personne AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom=Logilab
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM Societe AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom=Logilab
+FROM cw_Societe AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom=Logilab
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM SubDivision AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom=Logilab'''),
+FROM cw_SubDivision AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom=Logilab'''),
 
     ("Note N WHERE X evaluee N, X nom in ('Logilab', 'Caesium');",
      '''SELECT rel_evaluee0.eid_to
-FROM Division AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom IN(Logilab, Caesium)
+FROM cw_Division AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom IN(Logilab, Caesium)
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM Personne AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom IN(Logilab, Caesium)
+FROM cw_Personne AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom IN(Logilab, Caesium)
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM Societe AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom IN(Logilab, Caesium)
+FROM cw_Societe AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom IN(Logilab, Caesium)
 UNION ALL
 SELECT rel_evaluee0.eid_to
-FROM SubDivision AS X, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=X.eid AND X.nom IN(Logilab, Caesium)'''),
+FROM cw_SubDivision AS X, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=X.cw_eid AND X.cw_nom IN(Logilab, Caesium)'''),
 
     ("Any X WHERE X creation_date TODAY, X is Affaire",
-     '''SELECT X.eid
-FROM Affaire AS X
-WHERE DATE(X.creation_date)=CURRENT_DATE'''),
+     '''SELECT X.cw_eid
+FROM cw_Affaire AS X
+WHERE DATE(X.cw_creation_date)=CURRENT_DATE'''),
 
-    ("Any N WHERE G is EGroup, G name N, E eid 12, E read_permission G",
-     '''SELECT G.name
-FROM EGroup AS G, read_permission_relation AS rel_read_permission0
-WHERE rel_read_permission0.eid_from=12 AND rel_read_permission0.eid_to=G.eid'''),
+    ("Any N WHERE G is CWGroup, G name N, E eid 12, E read_permission G",
+     '''SELECT G.cw_name
+FROM cw_CWGroup AS G, read_permission_relation AS rel_read_permission0
+WHERE rel_read_permission0.eid_from=12 AND rel_read_permission0.eid_to=G.cw_eid'''),
 
     ('Any Y WHERE U login "admin", U login Y', # stupid but valid...
-     """SELECT U.login
-FROM EUser AS U
-WHERE U.login=admin"""),
+     """SELECT U.cw_login
+FROM cw_CWUser AS U
+WHERE U.cw_login=admin"""),
 
     ('Any T WHERE T tags X, X is State',
      '''SELECT rel_tags0.eid_from
-FROM State AS X, tags_relation AS rel_tags0
-WHERE rel_tags0.eid_to=X.eid'''),
+FROM cw_State AS X, tags_relation AS rel_tags0
+WHERE rel_tags0.eid_to=X.cw_eid'''),
 
     ('Any X,Y WHERE X eid 0, Y eid 1, X concerne Y',
      '''SELECT 0, 1
@@ -144,40 +143,40 @@
 
     ("Any X WHERE X prenom 'lulu',"
      "EXISTS(X owned_by U, U in_group G, G name 'lulufanclub' OR G name 'managers');",
-     '''SELECT X.eid
-FROM Personne AS X
-WHERE X.prenom=lulu AND EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, EGroup AS G WHERE rel_owned_by0.eid_from=X.eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=G.eid AND ((G.name=lulufanclub) OR (G.name=managers)))'''),
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE X.cw_prenom=lulu AND EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS G WHERE rel_owned_by0.eid_from=X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=G.cw_eid AND ((G.cw_name=lulufanclub) OR (G.cw_name=managers)))'''),
 
     ("Any X WHERE X prenom 'lulu',"
      "NOT EXISTS(X owned_by U, U in_group G, G name 'lulufanclub' OR G name 'managers');",
-     '''SELECT X.eid
-FROM Personne AS X
-WHERE X.prenom=lulu AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, EGroup AS G WHERE rel_owned_by0.eid_from=X.eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=G.eid AND ((G.name=lulufanclub) OR (G.name=managers)))'''),
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE X.cw_prenom=lulu AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS G WHERE rel_owned_by0.eid_from=X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=G.cw_eid AND ((G.cw_name=lulufanclub) OR (G.cw_name=managers)))'''),
 ]
 
 ADVANCED= [
 
     ("Societe S WHERE S nom 'Logilab' OR S nom 'Caesium'",
-     '''SELECT S.eid
-FROM Societe AS S
-WHERE ((S.nom=Logilab) OR (S.nom=Caesium))'''),
-    
+     '''SELECT S.cw_eid
+FROM cw_Societe AS S
+WHERE ((S.cw_nom=Logilab) OR (S.cw_nom=Caesium))'''),
+
     ('Any X WHERE X nom "toto", X eid IN (9700, 9710, 1045, 674)',
-    '''SELECT X.eid
-FROM Division AS X
-WHERE X.nom=toto AND X.eid IN(9700, 9710, 1045, 674)
+    '''SELECT X.cw_eid
+FROM cw_Division AS X
+WHERE X.cw_nom=toto AND X.cw_eid IN(9700, 9710, 1045, 674)
 UNION ALL
-SELECT X.eid
-FROM Personne AS X
-WHERE X.nom=toto AND X.eid IN(9700, 9710, 1045, 674)
+SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE X.cw_nom=toto AND X.cw_eid IN(9700, 9710, 1045, 674)
 UNION ALL
-SELECT X.eid
-FROM Societe AS X
-WHERE X.nom=toto AND X.eid IN(9700, 9710, 1045, 674)
+SELECT X.cw_eid
+FROM cw_Societe AS X
+WHERE X.cw_nom=toto AND X.cw_eid IN(9700, 9710, 1045, 674)
 UNION ALL
-SELECT X.eid
-FROM SubDivision AS X
-WHERE X.nom=toto AND X.eid IN(9700, 9710, 1045, 674)'''),
+SELECT X.cw_eid
+FROM cw_SubDivision AS X
+WHERE X.cw_nom=toto AND X.cw_eid IN(9700, 9710, 1045, 674)'''),
 
     ('Any Y, COUNT(N) GROUPBY Y WHERE Y evaluee N;',
      '''SELECT rel_evaluee0.eid_from, COUNT(rel_evaluee0.eid_to)
@@ -185,34 +184,34 @@
 GROUP BY rel_evaluee0.eid_from'''),
 
     ("Any X WHERE X concerne B or C concerne X",
-     '''SELECT X.eid
-FROM Affaire AS X, concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1
-WHERE ((rel_concerne0.eid_from=X.eid) OR (rel_concerne1.eid_to=X.eid))'''),
+     '''SELECT X.cw_eid
+FROM concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1, cw_Affaire AS X
+WHERE ((rel_concerne0.eid_from=X.cw_eid) OR (rel_concerne1.eid_to=X.cw_eid))'''),
 
     ("Any X WHERE X travaille S or X concerne A",
-     '''SELECT X.eid
-FROM Personne AS X, concerne_relation AS rel_concerne1, travaille_relation AS rel_travaille0
-WHERE ((rel_travaille0.eid_from=X.eid) OR (rel_concerne1.eid_from=X.eid))'''),
+     '''SELECT X.cw_eid
+FROM concerne_relation AS rel_concerne1, cw_Personne AS X, travaille_relation AS rel_travaille0
+WHERE ((rel_travaille0.eid_from=X.cw_eid) OR (rel_concerne1.eid_from=X.cw_eid))'''),
 
     ("Any N WHERE A evaluee N or N ecrit_par P",
-     '''SELECT N.eid
-FROM Note AS N, evaluee_relation AS rel_evaluee0
-WHERE ((rel_evaluee0.eid_to=N.eid) OR (N.ecrit_par IS NOT NULL))'''),
+     '''SELECT N.cw_eid
+FROM cw_Note AS N, evaluee_relation AS rel_evaluee0
+WHERE ((rel_evaluee0.eid_to=N.cw_eid) OR (N.cw_ecrit_par IS NOT NULL))'''),
 
     ("Any N WHERE A evaluee N or EXISTS(N todo_by U)",
-     '''SELECT N.eid
-FROM Note AS N, evaluee_relation AS rel_evaluee0
-WHERE ((rel_evaluee0.eid_to=N.eid) OR (EXISTS(SELECT 1 FROM todo_by_relation AS rel_todo_by1 WHERE rel_todo_by1.eid_from=N.eid)))'''),
+     '''SELECT N.cw_eid
+FROM cw_Note AS N, evaluee_relation AS rel_evaluee0
+WHERE ((rel_evaluee0.eid_to=N.cw_eid) OR (EXISTS(SELECT 1 FROM todo_by_relation AS rel_todo_by1 WHERE rel_todo_by1.eid_from=N.cw_eid)))'''),
 
     ("Any N WHERE A evaluee N or N todo_by U",
-     '''SELECT N.eid
-FROM Note AS N, evaluee_relation AS rel_evaluee0, todo_by_relation AS rel_todo_by1
-WHERE ((rel_evaluee0.eid_to=N.eid) OR (rel_todo_by1.eid_from=N.eid))'''),
-    
+     '''SELECT N.cw_eid
+FROM cw_Note AS N, evaluee_relation AS rel_evaluee0, todo_by_relation AS rel_todo_by1
+WHERE ((rel_evaluee0.eid_to=N.cw_eid) OR (rel_todo_by1.eid_from=N.cw_eid))'''),
+
     ("Any X WHERE X concerne B or C concerne X, B eid 12, C eid 13",
-     '''SELECT X.eid
-FROM Affaire AS X, concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1
-WHERE ((rel_concerne0.eid_from=X.eid AND rel_concerne0.eid_to=12) OR (rel_concerne1.eid_from=13 AND rel_concerne1.eid_to=X.eid))'''),
+     '''SELECT X.cw_eid
+FROM concerne_relation AS rel_concerne0, concerne_relation AS rel_concerne1, cw_Affaire AS X
+WHERE ((rel_concerne0.eid_from=X.cw_eid AND rel_concerne0.eid_to=12) OR (rel_concerne1.eid_from=13 AND rel_concerne1.eid_to=X.cw_eid))'''),
 
     ('Any X WHERE X created_by U, X concerne B OR C concerne X, B eid 12, C eid 13',
      '''SELECT rel_created_by0.eid_from
@@ -220,155 +219,155 @@
 WHERE ((rel_concerne1.eid_from=rel_created_by0.eid_from AND rel_concerne1.eid_to=12) OR (rel_concerne2.eid_from=13 AND rel_concerne2.eid_to=rel_created_by0.eid_from))'''),
 
     ('Any P WHERE P travaille_subdivision S1 OR P travaille_subdivision S2, S1 nom "logilab", S2 nom "caesium"',
-     '''SELECT P.eid
-FROM Personne AS P, SubDivision AS S1, SubDivision AS S2, travaille_subdivision_relation AS rel_travaille_subdivision0, travaille_subdivision_relation AS rel_travaille_subdivision1
-WHERE ((rel_travaille_subdivision0.eid_from=P.eid AND rel_travaille_subdivision0.eid_to=S1.eid) OR (rel_travaille_subdivision1.eid_from=P.eid AND rel_travaille_subdivision1.eid_to=S2.eid)) AND S1.nom=logilab AND S2.nom=caesium'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P, cw_SubDivision AS S1, cw_SubDivision AS S2, travaille_subdivision_relation AS rel_travaille_subdivision0, travaille_subdivision_relation AS rel_travaille_subdivision1
+WHERE ((rel_travaille_subdivision0.eid_from=P.cw_eid AND rel_travaille_subdivision0.eid_to=S1.cw_eid) OR (rel_travaille_subdivision1.eid_from=P.cw_eid AND rel_travaille_subdivision1.eid_to=S2.cw_eid)) AND S1.cw_nom=logilab AND S2.cw_nom=caesium'''),
 
     ('Any X WHERE T tags X',
      '''SELECT rel_tags0.eid_to
 FROM tags_relation AS rel_tags0'''),
-    
+
     ('Any X WHERE X in_basket B, B eid 12',
      '''SELECT rel_in_basket0.eid_from
 FROM in_basket_relation AS rel_in_basket0
 WHERE rel_in_basket0.eid_to=12'''),
-    
+
     ('Any SEN,RN,OEN WHERE X from_entity SE, SE eid 44, X relation_type R, R eid 139, X to_entity OE, OE eid 42, R name RN, SE name SEN, OE name OEN',
-     '''SELECT SE.name, R.name, OE.name
-FROM EEType AS OE, EEType AS SE, EFRDef AS X, ERType AS R
-WHERE X.from_entity=44 AND SE.eid=44 AND X.relation_type=139 AND R.eid=139 AND X.to_entity=42 AND OE.eid=42
+     '''SELECT SE.cw_name, R.cw_name, OE.cw_name
+FROM cw_CWAttribute AS X, cw_CWEType AS OE, cw_CWEType AS SE, cw_CWRType AS R
+WHERE X.cw_from_entity=44 AND SE.cw_eid=44 AND X.cw_relation_type=139 AND R.cw_eid=139 AND X.cw_to_entity=42 AND OE.cw_eid=42
 UNION ALL
-SELECT SE.name, R.name, OE.name
-FROM EEType AS OE, EEType AS SE, ENFRDef AS X, ERType AS R
-WHERE X.from_entity=44 AND SE.eid=44 AND X.relation_type=139 AND R.eid=139 AND X.to_entity=42 AND OE.eid=42'''),
+SELECT SE.cw_name, R.cw_name, OE.cw_name
+FROM cw_CWEType AS OE, cw_CWEType AS SE, cw_CWRType AS R, cw_CWRelation AS X
+WHERE X.cw_from_entity=44 AND SE.cw_eid=44 AND X.cw_relation_type=139 AND R.cw_eid=139 AND X.cw_to_entity=42 AND OE.cw_eid=42'''),
 
     # Any O WHERE NOT S corrected_in O, S eid %(x)s, S concerns P, O version_of P, O in_state ST, NOT ST name "published", O modification_date MTIME ORDERBY MTIME DESC LIMIT 9
     ('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P',
-     '''SELECT DISTINCT O.eid
-FROM Note AS S, Personne AS O
-WHERE (S.ecrit_par IS NULL OR S.ecrit_par!=O.eid) AND S.eid=1 AND O.inline2=S.inline1'''),
+     '''SELECT DISTINCT O.cw_eid
+FROM cw_Note AS S, cw_Personne AS O
+WHERE (S.cw_ecrit_par IS NULL OR S.cw_ecrit_par!=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''),
 
     ('DISTINCT Any S ORDERBY stockproc(SI) WHERE NOT S ecrit_par O, S para SI',
-     '''SELECT T1.C0 FROM (SELECT DISTINCT S.eid AS C0, STOCKPROC(S.para) AS C1
-FROM Note AS S
-WHERE S.ecrit_par IS NULL
+     '''SELECT T1.C0 FROM (SELECT DISTINCT S.cw_eid AS C0, STOCKPROC(S.cw_para) AS C1
+FROM cw_Note AS S
+WHERE S.cw_ecrit_par IS NULL
 ORDER BY 2) AS T1'''),
 
     ('Any N WHERE N todo_by U, N is Note, U eid 2, N filed_under T, T eid 3',
      # N would actually be invarient if U eid 2 had given a specific type to U
-     '''SELECT N.eid
-FROM Note AS N, filed_under_relation AS rel_filed_under1, todo_by_relation AS rel_todo_by0
-WHERE rel_todo_by0.eid_from=N.eid AND rel_todo_by0.eid_to=2 AND rel_filed_under1.eid_from=N.eid AND rel_filed_under1.eid_to=3'''),
+     '''SELECT N.cw_eid
+FROM cw_Note AS N, filed_under_relation AS rel_filed_under1, todo_by_relation AS rel_todo_by0
+WHERE rel_todo_by0.eid_from=N.cw_eid AND rel_todo_by0.eid_to=2 AND rel_filed_under1.eid_from=N.cw_eid AND rel_filed_under1.eid_to=3'''),
 
     ('Any N WHERE N todo_by U, U eid 2, P evaluee N, P eid 3',
      '''SELECT rel_evaluee1.eid_to
 FROM evaluee_relation AS rel_evaluee1, todo_by_relation AS rel_todo_by0
 WHERE rel_evaluee1.eid_to=rel_todo_by0.eid_from AND rel_todo_by0.eid_to=2 AND rel_evaluee1.eid_from=3'''),
 
-    
+
     (' Any X,U WHERE C owned_by U, NOT X owned_by U, C eid 1, X eid 2',
      '''SELECT 2, rel_owned_by0.eid_to
 FROM owned_by_relation AS rel_owned_by0
 WHERE rel_owned_by0.eid_from=1 AND NOT EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by1 WHERE rel_owned_by1.eid_from=2 AND rel_owned_by0.eid_to=rel_owned_by1.eid_to)'''),
 
     ('Any GN WHERE X in_group G, G name GN, (G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))',
-     '''SELECT G.name
-FROM EGroup AS G, in_group_relation AS rel_in_group0
-WHERE rel_in_group0.eid_to=G.eid AND ((G.name=managers) OR (EXISTS(SELECT 1 FROM copain_relation AS rel_copain1, EUser AS T WHERE rel_copain1.eid_from=rel_in_group0.eid_from AND rel_copain1.eid_to=T.eid AND T.login IN(comme, cochon))))'''),
+     '''SELECT G.cw_name
+FROM cw_CWGroup AS G, in_group_relation AS rel_in_group0
+WHERE rel_in_group0.eid_to=G.cw_eid AND ((G.cw_name=managers) OR (EXISTS(SELECT 1 FROM copain_relation AS rel_copain1, cw_CWUser AS T WHERE rel_copain1.eid_from=rel_in_group0.eid_from AND rel_copain1.eid_to=T.cw_eid AND T.cw_login IN(comme, cochon))))'''),
 
     ('Any C WHERE C is Card, EXISTS(X documented_by C)',
-      """SELECT C.eid
-FROM Card AS C
-WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_to=C.eid)"""),
-    
+      """SELECT C.cw_eid
+FROM cw_Card AS C
+WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_to=C.cw_eid)"""),
+
     ('Any C WHERE C is Card, EXISTS(X documented_by C, X eid 12)',
-      """SELECT C.eid
-FROM Card AS C
-WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_from=12 AND rel_documented_by0.eid_to=C.eid)"""),
+      """SELECT C.cw_eid
+FROM cw_Card AS C
+WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_from=12 AND rel_documented_by0.eid_to=C.cw_eid)"""),
 
     ('Any T WHERE C is Card, C title T, EXISTS(X documented_by C, X eid 12)',
-      """SELECT C.title
-FROM Card AS C
-WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_from=12 AND rel_documented_by0.eid_to=C.eid)"""),
+      """SELECT C.cw_title
+FROM cw_Card AS C
+WHERE EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by0 WHERE rel_documented_by0.eid_from=12 AND rel_documented_by0.eid_to=C.cw_eid)"""),
 
     ('Any GN,L WHERE X in_group G, X login L, G name GN, EXISTS(X copain T, T login L, T login IN("comme", "cochon"))',
-     '''SELECT G.name, X.login
-FROM EGroup AS G, EUser AS X, in_group_relation AS rel_in_group0
-WHERE rel_in_group0.eid_from=X.eid AND rel_in_group0.eid_to=G.eid AND EXISTS(SELECT 1 FROM copain_relation AS rel_copain1, EUser AS T WHERE rel_copain1.eid_from=X.eid AND rel_copain1.eid_to=T.eid AND T.login=X.login AND T.login IN(comme, cochon))'''),
+     '''SELECT G.cw_name, X.cw_login
+FROM cw_CWGroup AS G, cw_CWUser AS X, in_group_relation AS rel_in_group0
+WHERE rel_in_group0.eid_from=X.cw_eid AND rel_in_group0.eid_to=G.cw_eid AND EXISTS(SELECT 1 FROM copain_relation AS rel_copain1, cw_CWUser AS T WHERE rel_copain1.eid_from=X.cw_eid AND rel_copain1.eid_to=T.cw_eid AND T.cw_login=X.cw_login AND T.cw_login IN(comme, cochon))'''),
 
-    ('Any X,S, MAX(T) GROUPBY X,S ORDERBY S WHERE X is EUser, T tags X, S eid IN(32), X in_state S',
-     '''SELECT X.eid, 32, MAX(rel_tags0.eid_from)
-FROM EUser AS X, tags_relation AS rel_tags0
-WHERE rel_tags0.eid_to=X.eid AND X.in_state=32
-GROUP BY X.eid'''),
+    ('Any X,S, MAX(T) GROUPBY X,S ORDERBY S WHERE X is CWUser, T tags X, S eid IN(32), X in_state S',
+     '''SELECT X.cw_eid, 32, MAX(rel_tags0.eid_from)
+FROM cw_CWUser AS X, tags_relation AS rel_tags0
+WHERE rel_tags0.eid_to=X.cw_eid AND X.cw_in_state=32
+GROUP BY X.cw_eid'''),
 
     ('Any COUNT(S),CS GROUPBY CS ORDERBY 1 DESC LIMIT 10 WHERE S is Affaire, C is Societe, S concerne C, C nom CS, (EXISTS(S owned_by 1)) OR (EXISTS(S documented_by N, N title "published"))',
-     '''SELECT COUNT(rel_concerne0.eid_from), C.nom
-FROM Societe AS C, concerne_relation AS rel_concerne0
-WHERE rel_concerne0.eid_to=C.eid AND ((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by1 WHERE rel_concerne0.eid_from=rel_owned_by1.eid_from AND rel_owned_by1.eid_to=1)) OR (EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by2, Card AS N WHERE rel_concerne0.eid_from=rel_documented_by2.eid_from AND rel_documented_by2.eid_to=N.eid AND N.title=published)))
-GROUP BY C.nom
+     '''SELECT COUNT(rel_concerne0.eid_from), C.cw_nom
+FROM concerne_relation AS rel_concerne0, cw_Societe AS C
+WHERE rel_concerne0.eid_to=C.cw_eid AND ((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by1 WHERE rel_concerne0.eid_from=rel_owned_by1.eid_from AND rel_owned_by1.eid_to=1)) OR (EXISTS(SELECT 1 FROM documented_by_relation AS rel_documented_by2, cw_Card AS N WHERE rel_concerne0.eid_from=rel_documented_by2.eid_from AND rel_documented_by2.eid_to=N.cw_eid AND N.cw_title=published)))
+GROUP BY C.cw_nom
 ORDER BY 1 DESC
 LIMIT 10'''),
 
-    ('Any X WHERE Y evaluee X, Y is EUser',
+    ('Any X WHERE Y evaluee X, Y is CWUser',
      '''SELECT rel_evaluee0.eid_to
-FROM EUser AS Y, evaluee_relation AS rel_evaluee0
-WHERE rel_evaluee0.eid_from=Y.eid'''),
+FROM cw_CWUser AS Y, evaluee_relation AS rel_evaluee0
+WHERE rel_evaluee0.eid_from=Y.cw_eid'''),
 
     ('Any L WHERE X login "admin", X identity Y, Y login L',
-     '''SELECT Y.login
-FROM EUser AS X, EUser AS Y
-WHERE X.login=admin AND X.eid=Y.eid'''),
+     '''SELECT Y.cw_login
+FROM cw_CWUser AS X, cw_CWUser AS Y
+WHERE X.cw_login=admin AND X.cw_eid=Y.cw_eid'''),
 
     ('Any L WHERE X login "admin", NOT X identity Y, Y login L',
-     '''SELECT Y.login
-FROM EUser AS X, EUser AS Y
-WHERE X.login=admin AND NOT X.eid=Y.eid'''),
-    
+     '''SELECT Y.cw_login
+FROM cw_CWUser AS X, cw_CWUser AS Y
+WHERE X.cw_login=admin AND NOT X.cw_eid=Y.cw_eid'''),
+
     ('Any L WHERE X login "admin", X identity Y?, Y login L',
-     '''SELECT Y.login
-FROM EUser AS X LEFT OUTER JOIN EUser AS Y ON (X.eid=Y.eid)
-WHERE X.login=admin'''),
+     '''SELECT Y.cw_login
+FROM cw_CWUser AS X LEFT OUTER JOIN cw_CWUser AS Y ON (X.cw_eid=Y.cw_eid)
+WHERE X.cw_login=admin'''),
 
     ('Any XN ORDERBY XN WHERE X name XN',
-     '''SELECT X.name
-FROM Basket AS X
+     '''SELECT X.cw_name
+FROM cw_Basket AS X
 UNION ALL
-SELECT X.name
-FROM ECache AS X
+SELECT X.cw_name
+FROM cw_CWCache AS X
 UNION ALL
-SELECT X.name
-FROM EConstraintType AS X
+SELECT X.cw_name
+FROM cw_CWConstraintType AS X
 UNION ALL
-SELECT X.name
-FROM EEType AS X
+SELECT X.cw_name
+FROM cw_CWEType AS X
 UNION ALL
-SELECT X.name
-FROM EGroup AS X
+SELECT X.cw_name
+FROM cw_CWGroup AS X
 UNION ALL
-SELECT X.name
-FROM EPermission AS X
+SELECT X.cw_name
+FROM cw_CWPermission AS X
 UNION ALL
-SELECT X.name
-FROM ERType AS X
+SELECT X.cw_name
+FROM cw_CWRType AS X
 UNION ALL
-SELECT X.name
-FROM File AS X
+SELECT X.cw_name
+FROM cw_File AS X
 UNION ALL
-SELECT X.name
-FROM Folder AS X
+SELECT X.cw_name
+FROM cw_Folder AS X
 UNION ALL
-SELECT X.name
-FROM Image AS X
+SELECT X.cw_name
+FROM cw_Image AS X
 UNION ALL
-SELECT X.name
-FROM State AS X
+SELECT X.cw_name
+FROM cw_State AS X
 UNION ALL
-SELECT X.name
-FROM Tag AS X
+SELECT X.cw_name
+FROM cw_Tag AS X
 UNION ALL
-SELECT X.name
-FROM Transition AS X
+SELECT X.cw_name
+FROM cw_Transition AS X
 ORDER BY 1'''),
 
 #    ('Any XN WHERE X name XN GROUPBY XN',
@@ -377,204 +376,204 @@
 #     ''''''),
 
     # DISTINCT, can use relatin under exists scope as principal
-    ('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
-     '''SELECT DISTINCT X.eid, rel_read_permission0.eid_to
-FROM EEType AS X, read_permission_relation AS rel_read_permission0
-WHERE X.name=EGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=X.eid)
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
+     '''SELECT DISTINCT X.cw_eid, rel_read_permission0.eid_to
+FROM cw_CWEType AS X, read_permission_relation AS rel_read_permission0
+WHERE X.cw_name=CWGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=X.cw_eid)
 UNION
-SELECT DISTINCT X.eid, rel_read_permission0.eid_to
-FROM ERType AS X, read_permission_relation AS rel_read_permission0
-WHERE X.name=EGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=X.eid)'''),
+SELECT DISTINCT X.cw_eid, rel_read_permission0.eid_to
+FROM cw_CWRType AS X, read_permission_relation AS rel_read_permission0
+WHERE X.cw_name=CWGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=X.cw_eid)'''),
 
     # no distinct, Y can't be invariant
-    ('Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
-     '''SELECT X.eid, Y.eid
-FROM EEType AS X, EGroup AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+    ('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
+     '''SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_CWGroup AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM EEType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM EGroup AS Y, ERType AS X
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWGroup AS Y, cw_CWRType AS X
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM ERType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)'''),
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWRType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)'''),
 
     # DISTINCT but NEGED exists, can't be invariant
-    ('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)',
-     '''SELECT DISTINCT X.eid, Y.eid
-FROM EEType AS X, EGroup AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)',
+     '''SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_CWGroup AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM EEType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM EGroup AS Y, ERType AS X
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWGroup AS Y, cw_CWRType AS X
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM ERType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)'''),
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWRType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)'''),
 
     # should generate the same query as above
-    ('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
-     '''SELECT DISTINCT X.eid, Y.eid
-FROM EEType AS X, EGroup AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+    ('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
+     '''SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_CWGroup AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM EEType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM EGroup AS Y, ERType AS X
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWGroup AS Y, cw_CWRType AS X
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION
-SELECT DISTINCT X.eid, Y.eid
-FROM ERType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)'''),
-    
+SELECT DISTINCT X.cw_eid, Y.cw_eid
+FROM cw_CWRType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)'''),
+
     # neged relation, can't be inveriant
-    ('Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
-     '''SELECT X.eid, Y.eid
-FROM EEType AS X, EGroup AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+    ('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y',
+     '''SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_CWGroup AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM EEType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWEType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM EGroup AS Y, ERType AS X
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWGroup AS Y, cw_CWRType AS X
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)
 UNION ALL
-SELECT X.eid, Y.eid
-FROM ERType AS X, RQLExpression AS Y
-WHERE X.name=EGroup AND Y.eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.eid AND rel_read_permission0.eid_to=Y.eid)'''),
+SELECT X.cw_eid, Y.cw_eid
+FROM cw_CWRType AS X, cw_RQLExpression AS Y
+WHERE X.cw_name=CWGroup AND Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=X.cw_eid AND rel_read_permission0.eid_to=Y.cw_eid)'''),
 
     ('Any MAX(X)+MIN(X), N GROUPBY N WHERE X name N;',
-     '''SELECT (MAX(T1.C0) + MIN(T1.C0)), T1.C1 FROM (SELECT X.eid AS C0, X.name AS C1
-FROM Basket AS X
+     '''SELECT (MAX(T1.C0) + MIN(T1.C0)), T1.C1 FROM (SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Basket AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM ECache AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWCache AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM EConstraintType AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWConstraintType AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM EEType AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWEType AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM EGroup AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWGroup AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM EPermission AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWPermission AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM ERType AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_CWRType AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM File AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_File AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM Folder AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Folder AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM Image AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Image AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM State AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_State AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM Tag AS X
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Tag AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM Transition AS X) AS T1
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Transition AS X) AS T1
 GROUP BY T1.C1'''),
-    
+
     ('Any MAX(X)+MIN(LENGTH(D)), N GROUPBY N ORDERBY 1, N, DF WHERE X name N, X data D, X data_format DF;',
-     '''SELECT (MAX(T1.C1) + MIN(LENGTH(T1.C0))), T1.C2 FROM (SELECT X.data AS C0, X.eid AS C1, X.name AS C2, X.data_format AS C3
-FROM File AS X
+     '''SELECT (MAX(T1.C1) + MIN(LENGTH(T1.C0))), T1.C2 FROM (SELECT X.cw_data AS C0, X.cw_eid AS C1, X.cw_name AS C2, X.cw_data_format AS C3
+FROM cw_File AS X
 UNION ALL
-SELECT X.data AS C0, X.eid AS C1, X.name AS C2, X.data_format AS C3
-FROM Image AS X) AS T1
+SELECT X.cw_data AS C0, X.cw_eid AS C1, X.cw_name AS C2, X.cw_data_format AS C3
+FROM cw_Image AS X) AS T1
 GROUP BY T1.C2
 ORDER BY 1,2,T1.C3'''),
 
     ('DISTINCT Any S ORDERBY R WHERE A is Affaire, A sujet S, A ref R',
-     '''SELECT T1.C0 FROM (SELECT DISTINCT A.sujet AS C0, A.ref AS C1
-FROM Affaire AS A
+     '''SELECT T1.C0 FROM (SELECT DISTINCT A.cw_sujet AS C0, A.cw_ref AS C1
+FROM cw_Affaire AS A
 ORDER BY 2) AS T1'''),
-    
+
     ('DISTINCT Any MAX(X)+MIN(LENGTH(D)), N GROUPBY N ORDERBY 2, DF WHERE X name N, X data D, X data_format DF;',
-     '''SELECT T1.C0,T1.C1 FROM (SELECT DISTINCT (MAX(T1.C1) + MIN(LENGTH(T1.C0))) AS C0, T1.C2 AS C1, T1.C3 AS C2 FROM (SELECT DISTINCT X.data AS C0, X.eid AS C1, X.name AS C2, X.data_format AS C3
-FROM File AS X
+     '''SELECT T1.C0,T1.C1 FROM (SELECT DISTINCT (MAX(T1.C1) + MIN(LENGTH(T1.C0))) AS C0, T1.C2 AS C1, T1.C3 AS C2 FROM (SELECT DISTINCT X.cw_data AS C0, X.cw_eid AS C1, X.cw_name AS C2, X.cw_data_format AS C3
+FROM cw_File AS X
 UNION
-SELECT DISTINCT X.data AS C0, X.eid AS C1, X.name AS C2, X.data_format AS C3
-FROM Image AS X) AS T1
+SELECT DISTINCT X.cw_data AS C0, X.cw_eid AS C1, X.cw_name AS C2, X.cw_data_format AS C3
+FROM cw_Image AS X) AS T1
 GROUP BY T1.C2,T1.C3
 ORDER BY 2,3) AS T1
 '''),
 
     # ambiguity in EXISTS() -> should union the sub-query
-    ('Any T WHERE T is Tag, NOT T name in ("t1", "t2"), EXISTS(T tags X, X is IN (EUser, EGroup))',
-     '''SELECT T.eid
-FROM Tag AS T
-WHERE NOT (T.name IN(t1, t2)) AND EXISTS(SELECT 1 FROM tags_relation AS rel_tags0, EGroup AS X WHERE rel_tags0.eid_from=T.eid AND rel_tags0.eid_to=X.eid UNION SELECT 1 FROM tags_relation AS rel_tags1, EUser AS X WHERE rel_tags1.eid_from=T.eid AND rel_tags1.eid_to=X.eid)'''),
+    ('Any T WHERE T is Tag, NOT T name in ("t1", "t2"), EXISTS(T tags X, X is IN (CWUser, CWGroup))',
+     '''SELECT T.cw_eid
+FROM cw_Tag AS T
+WHERE NOT (T.cw_name IN(t1, t2)) AND EXISTS(SELECT 1 FROM tags_relation AS rel_tags0, cw_CWGroup AS X WHERE rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=X.cw_eid UNION SELECT 1 FROM tags_relation AS rel_tags1, cw_CWUser AS X WHERE rel_tags1.eid_from=T.cw_eid AND rel_tags1.eid_to=X.cw_eid)'''),
 
-    # must not use a relation in EXISTS scope to inline a variable 
+    # must not use a relation in EXISTS scope to inline a variable
     ('Any U WHERE U eid IN (1,2), EXISTS(X owned_by U)',
-     '''SELECT U.eid
-FROM EUser AS U
-WHERE U.eid IN(1, 2) AND EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_to=U.eid)'''),
+     '''SELECT U.cw_eid
+FROM cw_CWUser AS U
+WHERE U.cw_eid IN(1, 2) AND EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_to=U.cw_eid)'''),
 
     ('Any U WHERE EXISTS(U eid IN (1,2), X owned_by U)',
-     '''SELECT U.eid
-FROM EUser AS U
-WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE U.eid IN(1, 2) AND rel_owned_by0.eid_to=U.eid)'''),
+     '''SELECT U.cw_eid
+FROM cw_CWUser AS U
+WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE U.cw_eid IN(1, 2) AND rel_owned_by0.eid_to=U.cw_eid)'''),
 
     ('Any COUNT(U) WHERE EXISTS (P owned_by U, P is IN (Note, Affaire))',
-     '''SELECT COUNT(U.eid)
-FROM EUser AS U
-WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, Affaire AS P WHERE rel_owned_by0.eid_from=P.eid AND rel_owned_by0.eid_to=U.eid UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, Note AS P WHERE rel_owned_by1.eid_from=P.eid AND rel_owned_by1.eid_to=U.eid)'''),
+     '''SELECT COUNT(U.cw_eid)
+FROM cw_CWUser AS U
+WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=U.cw_eid UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=U.cw_eid)'''),
 
     ('Any MAX(X)',
      '''SELECT MAX(X.eid)
 FROM entities AS X'''),
 
     ('Any MAX(X) WHERE X is Note',
-     '''SELECT MAX(X.eid)
-FROM Note AS X'''),
-    
+     '''SELECT MAX(X.cw_eid)
+FROM cw_Note AS X'''),
+
     ('Any X WHERE X eid > 12',
      '''SELECT X.eid
 FROM entities AS X
 WHERE X.eid>12'''),
-    
+
     ('Any X WHERE X eid > 12, X is Note',
      """SELECT X.eid
 FROM entities AS X
 WHERE X.type='Note' AND X.eid>12"""),
-    
+
     ('Any X, T WHERE X eid > 12, X title T',
-     """SELECT X.eid, X.title
-FROM Bookmark AS X
-WHERE X.eid>12
+     """SELECT X.cw_eid, X.cw_title
+FROM cw_Bookmark AS X
+WHERE X.cw_eid>12
 UNION ALL
-SELECT X.eid, X.title
-FROM Card AS X
-WHERE X.eid>12
+SELECT X.cw_eid, X.cw_title
+FROM cw_Card AS X
+WHERE X.cw_eid>12
 UNION ALL
-SELECT X.eid, X.title
-FROM EmailThread AS X
-WHERE X.eid>12"""),
+SELECT X.cw_eid, X.cw_title
+FROM cw_EmailThread AS X
+WHERE X.cw_eid>12"""),
 
     ('Any X',
      '''SELECT X.eid
@@ -582,14 +581,14 @@
 
     ('Any X GROUPBY X WHERE X eid 12',
      '''SELECT 12'''),
-    
+
     ('Any X GROUPBY X ORDERBY Y WHERE X eid 12, X login Y',
-     '''SELECT X.eid
-FROM EUser AS X
-WHERE X.eid=12
-GROUP BY X.eid
-ORDER BY X.login'''),
-    
+     '''SELECT X.cw_eid
+FROM cw_CWUser AS X
+WHERE X.cw_eid=12
+GROUP BY X.cw_eid
+ORDER BY X.cw_login'''),
+
     ('Any U,COUNT(X) GROUPBY U WHERE U eid 12, X owned_by U HAVING COUNT(X) > 10',
      '''SELECT rel_owned_by0.eid_to, COUNT(rel_owned_by0.eid_from)
 FROM owned_by_relation AS rel_owned_by0
@@ -598,360 +597,362 @@
 HAVING COUNT(rel_owned_by0.eid_from)>10'''),
 
     ('DISTINCT Any X ORDERBY stockproc(X) WHERE U login X',
-     '''SELECT T1.C0 FROM (SELECT DISTINCT U.login AS C0, STOCKPROC(U.login) AS C1
-FROM EUser AS U
+     '''SELECT T1.C0 FROM (SELECT DISTINCT U.cw_login AS C0, STOCKPROC(U.cw_login) AS C1
+FROM cw_CWUser AS U
 ORDER BY 2) AS T1'''),
-    
+
     ('DISTINCT Any X ORDERBY Y WHERE B bookmarked_by X, X login Y',
-     '''SELECT T1.C0 FROM (SELECT DISTINCT X.eid AS C0, X.login AS C1
-FROM EUser AS X, bookmarked_by_relation AS rel_bookmarked_by0
-WHERE rel_bookmarked_by0.eid_to=X.eid
+     '''SELECT T1.C0 FROM (SELECT DISTINCT X.cw_eid AS C0, X.cw_login AS C1
+FROM bookmarked_by_relation AS rel_bookmarked_by0, cw_CWUser AS X
+WHERE rel_bookmarked_by0.eid_to=X.cw_eid
 ORDER BY 2) AS T1'''),
 
     ('DISTINCT Any X ORDERBY SN WHERE X in_state S, S name SN',
-     '''SELECT T1.C0 FROM (SELECT DISTINCT X.eid AS C0, S.name AS C1
-FROM Affaire AS X, State AS S
-WHERE X.in_state=S.eid
+     '''SELECT T1.C0 FROM (SELECT DISTINCT X.cw_eid AS C0, S.cw_name AS C1
+FROM cw_Affaire AS X, cw_State AS S
+WHERE X.cw_in_state=S.cw_eid
 UNION
-SELECT DISTINCT X.eid AS C0, S.name AS C1
-FROM EUser AS X, State AS S
-WHERE X.in_state=S.eid
+SELECT DISTINCT X.cw_eid AS C0, S.cw_name AS C1
+FROM cw_CWUser AS X, cw_State AS S
+WHERE X.cw_in_state=S.cw_eid
 UNION
-SELECT DISTINCT X.eid AS C0, S.name AS C1
-FROM Note AS X, State AS S
-WHERE X.in_state=S.eid
+SELECT DISTINCT X.cw_eid AS C0, S.cw_name AS C1
+FROM cw_Note AS X, cw_State AS S
+WHERE X.cw_in_state=S.cw_eid
 ORDER BY 2) AS T1'''),
 
     ]
 
 MULTIPLE_SEL = [
     ("DISTINCT Any X,Y where P is Personne, P nom X , P prenom Y;",
-     '''SELECT DISTINCT P.nom, P.prenom
-FROM Personne AS P'''),
+     '''SELECT DISTINCT P.cw_nom, P.cw_prenom
+FROM cw_Personne AS P'''),
     ("Any X,Y where P is Personne, P nom X , P prenom Y, not P nom NULL;",
-     '''SELECT P.nom, P.prenom
-FROM Personne AS P
-WHERE NOT (P.nom IS NULL)'''),
+     '''SELECT P.cw_nom, P.cw_prenom
+FROM cw_Personne AS P
+WHERE NOT (P.cw_nom IS NULL)'''),
     ("Personne X,Y where X nom NX, Y nom NX, X eid XE, not Y eid XE",
-     '''SELECT X.eid, Y.eid
-FROM Personne AS X, Personne AS Y
-WHERE Y.nom=X.nom AND NOT (Y.eid=X.eid)''')
+     '''SELECT X.cw_eid, Y.cw_eid
+FROM cw_Personne AS X, cw_Personne AS Y
+WHERE Y.cw_nom=X.cw_nom AND NOT (Y.cw_eid=X.cw_eid)''')
     ]
 
 NEGATIONS = [
     ("Personne X WHERE NOT X evaluee Y;",
-     '''SELECT X.eid
-FROM Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=X.eid)'''),
-    
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=X.cw_eid)'''),
+
     ("Note N WHERE NOT X evaluee N, X eid 0",
-     '''SELECT N.eid
-FROM Note AS N
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=0 AND rel_evaluee0.eid_to=N.eid)'''),
-    
+     '''SELECT N.cw_eid
+FROM cw_Note AS N
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=0 AND rel_evaluee0.eid_to=N.cw_eid)'''),
+
     ('Any X WHERE NOT X travaille S, X is Personne',
-     '''SELECT X.eid
-FROM Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid)'''),
-    
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.cw_eid)'''),
+
     ("Personne P where not P datenaiss TODAY",
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE NOT (DATE(P.datenaiss)=CURRENT_DATE)'''),
-    
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE NOT (DATE(P.cw_datenaiss)=CURRENT_DATE)'''),
+
     ("Personne P where NOT P concerne A",
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=P.eid)'''),
-    
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=P.cw_eid)'''),
+
     ("Affaire A where not P concerne A",
-     '''SELECT A.eid
-FROM Affaire AS A
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_to=A.eid)'''),
+     '''SELECT A.cw_eid
+FROM cw_Affaire AS A
+WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_to=A.cw_eid)'''),
     ("Personne P where not P concerne A, A sujet ~= 'TEST%'",
-     '''SELECT P.eid
-FROM Affaire AS A, Personne AS P
-WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=P.eid AND rel_concerne0.eid_to=A.eid) AND A.sujet ILIKE TEST%'''),
+     '''SELECT P.cw_eid
+FROM cw_Affaire AS A, cw_Personne AS P
+WHERE NOT EXISTS(SELECT 1 FROM concerne_relation AS rel_concerne0 WHERE rel_concerne0.eid_from=P.cw_eid AND rel_concerne0.eid_to=A.cw_eid) AND A.cw_sujet ILIKE TEST%'''),
 
     ('Any S WHERE NOT T eid 28258, T tags S',
      '''SELECT rel_tags0.eid_to
 FROM tags_relation AS rel_tags0
 WHERE NOT (rel_tags0.eid_from=28258)'''),
-    
+
     ('Any S WHERE T is Tag, T name TN, NOT T eid 28258, T tags S, S name SN',
-     '''SELECT S.eid
-FROM EGroup AS S, Tag AS T, tags_relation AS rel_tags0
-WHERE NOT (T.eid=28258) AND rel_tags0.eid_from=T.eid AND rel_tags0.eid_to=S.eid
+     '''SELECT S.cw_eid
+FROM cw_CWGroup AS S, cw_Tag AS T, tags_relation AS rel_tags0
+WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid
 UNION ALL
-SELECT S.eid
-FROM State AS S, Tag AS T, tags_relation AS rel_tags0
-WHERE NOT (T.eid=28258) AND rel_tags0.eid_from=T.eid AND rel_tags0.eid_to=S.eid
+SELECT S.cw_eid
+FROM cw_State AS S, cw_Tag AS T, tags_relation AS rel_tags0
+WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid
 UNION ALL
-SELECT S.eid
-FROM Tag AS S, Tag AS T, tags_relation AS rel_tags0
-WHERE NOT (T.eid=28258) AND rel_tags0.eid_from=T.eid AND rel_tags0.eid_to=S.eid'''),
+SELECT S.cw_eid
+FROM cw_Tag AS S, cw_Tag AS T, tags_relation AS rel_tags0
+WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid'''),
 
-    
+
     ('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid 6',
      '''SELECT 5, rel_created_by0.eid_to
 FROM created_by_relation AS rel_created_by0
 WHERE rel_created_by0.eid_from=5 AND NOT (rel_created_by0.eid_to=6)'''),
 
     ('Note X WHERE NOT Y evaluee X',
-     '''SELECT X.eid
-FROM Note AS X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_to=X.eid)'''),
+     '''SELECT X.cw_eid
+FROM cw_Note AS X
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_to=X.cw_eid)'''),
 
     ('Any Y WHERE NOT Y evaluee X',
-     '''SELECT Y.eid
-FROM Division AS Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.eid)
+     '''SELECT Y.cw_eid
+FROM cw_CWUser AS Y
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.cw_eid)
 UNION ALL
-SELECT Y.eid
-FROM EUser AS Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.eid)
+SELECT Y.cw_eid
+FROM cw_Division AS Y
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.cw_eid)
 UNION ALL
-SELECT Y.eid
-FROM Personne AS Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.eid)
+SELECT Y.cw_eid
+FROM cw_Personne AS Y
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.cw_eid)
 UNION ALL
-SELECT Y.eid
-FROM Societe AS Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.eid)
+SELECT Y.cw_eid
+FROM cw_Societe AS Y
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.cw_eid)
 UNION ALL
-SELECT Y.eid
-FROM SubDivision AS Y
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.eid)'''),
+SELECT Y.cw_eid
+FROM cw_SubDivision AS Y
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0 WHERE rel_evaluee0.eid_from=Y.cw_eid)'''),
 
-    ('Any X WHERE NOT Y evaluee X, Y is EUser',
-     '''SELECT X.eid
-FROM Note AS X
-WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,EUser AS Y WHERE rel_evaluee0.eid_from=Y.eid AND rel_evaluee0.eid_to=X.eid)'''),
-    
+    ('Any X WHERE NOT Y evaluee X, Y is CWUser',
+     '''SELECT X.cw_eid
+FROM cw_Note AS X
+WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS Y WHERE rel_evaluee0.eid_from=Y.cw_eid AND rel_evaluee0.eid_to=X.cw_eid)'''),
+
     ('Any X,T WHERE X title T, NOT X is Bookmark',
-     '''SELECT DISTINCT X.eid, X.title
-FROM Card AS X
+     '''SELECT DISTINCT X.cw_eid, X.cw_title
+FROM cw_Card AS X
 UNION
-SELECT DISTINCT X.eid, X.title
-FROM EmailThread AS X'''),
+SELECT DISTINCT X.cw_eid, X.cw_title
+FROM cw_EmailThread AS X'''),
 
-    ('Any K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U',
-     '''SELECT DISTINCT P.pkey, P.value
-FROM EProperty AS P
-WHERE P.for_user IS NULL'''),
+    ('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U',
+     '''SELECT DISTINCT P.cw_pkey, P.cw_value
+FROM cw_CWProperty AS P
+WHERE P.cw_for_user IS NULL'''),
 
-    ('Any S WHERE NOT X in_state S, X is IN(Affaire, EUser)',
-     '''SELECT DISTINCT S.eid
-FROM Affaire AS X, State AS S
-WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+    ('Any S WHERE NOT X in_state S, X is IN(Affaire, CWUser)',
+     '''SELECT DISTINCT S.cw_eid
+FROM cw_Affaire AS X, cw_State AS S
+WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.eid
-FROM EUser AS X, State AS S
-WHERE (X.in_state IS NULL OR X.in_state!=S.eid)'''),
+SELECT DISTINCT S.cw_eid
+FROM cw_CWUser AS X, cw_State AS S
+WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
     ]
 
 OUTER_JOIN = [
     ('Any X,S WHERE X travaille S?',
-     '''SELECT X.eid, rel_travaille0.eid_to
-FROM Personne AS X LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=X.eid)'''
-#SELECT X.eid, S.eid
-#FROM Personne AS X LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=X.eid) LEFT OUTER JOIN Societe AS S ON (rel_travaille0.eid_to=S.eid)'''
+     '''SELECT X.cw_eid, rel_travaille0.eid_to
+FROM cw_Personne AS X LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=X.cw_eid)'''
+#SELECT X.cw_eid, S.cw_eid
+#FROM cw_Personne AS X LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=X.cw_eid) LEFT OUTER JOIN cw_Societe AS S ON (rel_travaille0.eid_to=S.cw_eid)'''
     ),
     ('Any S,X WHERE X? travaille S, S is Societe',
-     '''SELECT S.eid, rel_travaille0.eid_from
-FROM Societe AS S LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_to=S.eid)'''
-#SELECT S.eid, X.eid
-#FROM Societe AS S LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_to=S.eid) LEFT OUTER JOIN Personne AS X ON (rel_travaille0.eid_from=X.eid)'''
+     '''SELECT S.cw_eid, rel_travaille0.eid_from
+FROM cw_Societe AS S LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_to=S.cw_eid)'''
+#SELECT S.cw_eid, X.cw_eid
+#FROM cw_Societe AS S LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_to=S.cw_eid) LEFT OUTER JOIN cw_Personne AS X ON (rel_travaille0.eid_from=X.cw_eid)'''
     ),
 
     ('Any N,A WHERE N inline1 A?',
-     '''SELECT N.eid, N.inline1
-FROM Note AS N'''),
+     '''SELECT N.cw_eid, N.cw_inline1
+FROM cw_Note AS N'''),
 
     ('Any SN WHERE X from_state S?, S name SN',
-     '''SELECT S.name
-FROM TrInfo AS X LEFT OUTER JOIN State AS S ON (X.from_state=S.eid)'''
+     '''SELECT S.cw_name
+FROM cw_TrInfo AS X LEFT OUTER JOIN cw_State AS S ON (X.cw_from_state=S.cw_eid)'''
     ),
 
     ('Any A,N WHERE N? inline1 A',
-     '''SELECT A.eid, N.eid
-FROM Affaire AS A LEFT OUTER JOIN Note AS N ON (N.inline1=A.eid)'''
+     '''SELECT A.cw_eid, N.cw_eid
+FROM cw_Affaire AS A LEFT OUTER JOIN cw_Note AS N ON (N.cw_inline1=A.cw_eid)'''
     ),
 
     ('Any A,B,C,D,E,F,G WHERE A eid 12,A creation_date B,A modification_date C,A comment D,A from_state E?,A to_state F?,A wf_info_for G?',
-    '''SELECT A.eid, A.creation_date, A.modification_date, A.comment, A.from_state, A.to_state, A.wf_info_for
-FROM TrInfo AS A
-WHERE A.eid=12'''),
+    '''SELECT A.cw_eid, A.cw_creation_date, A.cw_modification_date, A.cw_comment, A.cw_from_state, A.cw_to_state, A.cw_wf_info_for
+FROM cw_TrInfo AS A
+WHERE A.cw_eid=12'''),
 
     ('Any FS,TS,C,D,U ORDERBY D DESC WHERE WF wf_info_for X,WF from_state FS?, WF to_state TS, WF comment C,WF creation_date D, WF owned_by U, X eid 1',
-     '''SELECT WF.from_state, WF.to_state, WF.comment, WF.creation_date, rel_owned_by0.eid_to
-FROM TrInfo AS WF, owned_by_relation AS rel_owned_by0
-WHERE WF.wf_info_for=1 AND WF.to_state IS NOT NULL AND rel_owned_by0.eid_from=WF.eid
+     '''SELECT WF.cw_from_state, WF.cw_to_state, WF.cw_comment, WF.cw_creation_date, rel_owned_by0.eid_to
+FROM cw_TrInfo AS WF, owned_by_relation AS rel_owned_by0
+WHERE WF.cw_wf_info_for=1 AND WF.cw_to_state IS NOT NULL AND rel_owned_by0.eid_from=WF.cw_eid
 ORDER BY 4 DESC'''),
 
     ('Any X WHERE X is Affaire, S is Societe, EXISTS(X owned_by U OR (X concerne S?, S owned_by U))',
-     '''SELECT X.eid
-FROM Affaire AS X
-WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, EUser AS U, Affaire AS A LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_from=A.eid) LEFT OUTER JOIN Societe AS S ON (rel_concerne1.eid_to=S.eid), owned_by_relation AS rel_owned_by2 WHERE ((rel_owned_by0.eid_from=A.eid AND rel_owned_by0.eid_to=U.eid) OR (rel_owned_by2.eid_from=S.eid AND rel_owned_by2.eid_to=U.eid)) AND X.eid=A.eid)'''),
+     '''SELECT X.cw_eid
+FROM cw_Affaire AS X
+WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_CWUser AS U, cw_Affaire AS A LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_from=A.cw_eid) LEFT OUTER JOIN cw_Societe AS S ON (rel_concerne1.eid_to=S.cw_eid), owned_by_relation AS rel_owned_by2 WHERE ((rel_owned_by0.eid_from=A.cw_eid AND rel_owned_by0.eid_to=U.cw_eid) OR (rel_owned_by2.eid_from=S.cw_eid AND rel_owned_by2.eid_to=U.cw_eid)) AND X.cw_eid=A.cw_eid)'''),
 
     ('Any C,M WHERE C travaille G?, G evaluee M?, G is Societe',
-     '''SELECT C.eid, rel_evaluee1.eid_to
-FROM Personne AS C LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=C.eid) LEFT OUTER JOIN Societe AS G ON (rel_travaille0.eid_to=G.eid) LEFT OUTER JOIN evaluee_relation AS rel_evaluee1 ON (rel_evaluee1.eid_from=G.eid)'''
-#SELECT C.eid, M.eid
-#FROM Personne AS C LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=C.eid) LEFT OUTER JOIN Societe AS G ON (rel_travaille0.eid_to=G.eid) LEFT OUTER JOIN evaluee_relation AS rel_evaluee1 ON (rel_evaluee1.eid_from=G.eid) LEFT OUTER JOIN Note AS M ON (rel_evaluee1.eid_to=M.eid)'''
+     '''SELECT C.cw_eid, rel_evaluee1.eid_to
+FROM cw_Personne AS C LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=C.cw_eid) LEFT OUTER JOIN cw_Societe AS G ON (rel_travaille0.eid_to=G.cw_eid) LEFT OUTER JOIN evaluee_relation AS rel_evaluee1 ON (rel_evaluee1.eid_from=G.cw_eid)'''
+#SELECT C.cw_eid, M.cw_eid
+#FROM cw_Personne AS C LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=C.cw_eid) LEFT OUTER JOIN cw_Societe AS G ON (rel_travaille0.eid_to=G.cw_eid) LEFT OUTER JOIN evaluee_relation AS rel_evaluee1 ON (rel_evaluee1.eid_from=G.cw_eid) LEFT OUTER JOIN cw_Note AS M ON (rel_evaluee1.eid_to=M.cw_eid)'''
      ),
 
     ('Any A,C WHERE A documented_by C?, (C is NULL) OR (EXISTS(C require_permission F, '
      'F name "read", F require_group E, U in_group E)), U eid 1',
-     '''SELECT A.eid, rel_documented_by0.eid_to
-FROM Affaire AS A LEFT OUTER JOIN documented_by_relation AS rel_documented_by0 ON (rel_documented_by0.eid_from=A.eid)
-WHERE ((rel_documented_by0.eid_to IS NULL) OR (EXISTS(SELECT 1 FROM require_permission_relation AS rel_require_permission1, EPermission AS F, require_group_relation AS rel_require_group2, in_group_relation AS rel_in_group3 WHERE rel_documented_by0.eid_to=rel_require_permission1.eid_from AND rel_require_permission1.eid_to=F.eid AND F.name=read AND rel_require_group2.eid_from=F.eid AND rel_in_group3.eid_to=rel_require_group2.eid_to AND rel_in_group3.eid_from=1)))'''),
+     '''SELECT A.cw_eid, rel_documented_by0.eid_to
+FROM cw_Affaire AS A LEFT OUTER JOIN documented_by_relation AS rel_documented_by0 ON (rel_documented_by0.eid_from=A.cw_eid)
+WHERE ((rel_documented_by0.eid_to IS NULL) OR (EXISTS(SELECT 1 FROM require_permission_relation AS rel_require_permission1, cw_CWPermission AS F, require_group_relation AS rel_require_group2, in_group_relation AS rel_in_group3 WHERE rel_documented_by0.eid_to=rel_require_permission1.eid_from AND rel_require_permission1.eid_to=F.cw_eid AND F.cw_name=read AND rel_require_group2.eid_from=F.cw_eid AND rel_in_group3.eid_to=rel_require_group2.eid_to AND rel_in_group3.eid_from=1)))'''),
 
     ("Any X WHERE X eid 12, P? connait X",
-     '''SELECT X.eid
-FROM Personne AS X LEFT OUTER JOIN connait_relation AS rel_connait0 ON (rel_connait0.eid_to=12)
-WHERE X.eid=12'''
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X LEFT OUTER JOIN connait_relation AS rel_connait0 ON (rel_connait0.eid_to=12)
+WHERE X.cw_eid=12'''
 #SELECT 12
-#FROM Personne AS X LEFT OUTER JOIN connait_relation AS rel_connait0 ON (rel_connait0.eid_to=12) LEFT OUTER JOIN Personne AS P ON (rel_connait0.eid_from=P.eid)
-#WHERE X.eid=12'''
+#FROM cw_Personne AS X LEFT OUTER JOIN connait_relation AS rel_connait0 ON (rel_connait0.eid_to=12) LEFT OUTER JOIN Personne AS P ON (rel_connait0.eid_from=P.cw_eid)
+#WHERE X.cw_eid=12'''
     ),
 
     ('Any GN, TN ORDERBY GN WHERE T tags G?, T name TN, G name GN',
-    '''SELECT _T0.C1, T.name
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN (SELECT G.eid AS C0, G.name AS C1
-FROM EGroup AS G
+    '''
+SELECT _T0.C1, T.cw_name
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN (SELECT G.cw_eid AS C0, G.cw_name AS C1
+FROM cw_CWGroup AS G
 UNION ALL
-SELECT G.eid AS C0, G.name AS C1
-FROM State AS G
+SELECT G.cw_eid AS C0, G.cw_name AS C1
+FROM cw_State AS G
 UNION ALL
-SELECT G.eid AS C0, G.name AS C1
-FROM Tag AS G) AS _T0 ON (rel_tags0.eid_to=_T0.C0)
+SELECT G.cw_eid AS C0, G.cw_name AS C1
+FROM cw_Tag AS G) AS _T0 ON (rel_tags0.eid_to=_T0.C0)
 ORDER BY 1'''),
 
 
     # optional variable with additional restriction
-    ('Any T,G WHERE T tags G?, G name "hop", G is EGroup',
-     '''SELECT T.eid, G.eid
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN EGroup AS G ON (rel_tags0.eid_to=G.eid AND G.name=hop)'''),
+    ('Any T,G WHERE T tags G?, G name "hop", G is CWGroup',
+     '''SELECT T.cw_eid, G.cw_eid
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN cw_CWGroup AS G ON (rel_tags0.eid_to=G.cw_eid AND G.cw_name=hop)'''),
 
     # optional variable with additional invariant restriction
     ('Any T,G WHERE T tags G?, G eid 12',
-     '''SELECT T.eid, rel_tags0.eid_to
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid AND rel_tags0.eid_to=12)'''),
+     '''SELECT T.cw_eid, rel_tags0.eid_to
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=12)'''),
 
     # optional variable with additional restriction appearing before the relation
-    ('Any T,G WHERE G name "hop", T tags G?, G is EGroup',
-     '''SELECT T.eid, G.eid
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN EGroup AS G ON (rel_tags0.eid_to=G.eid AND G.name=hop)'''),
+    ('Any T,G WHERE G name "hop", T tags G?, G is CWGroup',
+     '''SELECT T.cw_eid, G.cw_eid
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN cw_CWGroup AS G ON (rel_tags0.eid_to=G.cw_eid AND G.cw_name=hop)'''),
 
     # optional variable with additional restriction on inlined relation
     # XXX the expected result should be as the query below. So what, raise BadRQLQuery ?
-    ('Any T,G,S WHERE T tags G?, G in_state S, S name "hop", G is EUser',
-     '''SELECT T.eid, G.eid, S.eid
-FROM State AS S, Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN EUser AS G ON (rel_tags0.eid_to=G.eid)
-WHERE G.in_state=S.eid AND S.name=hop
+    ('Any T,G,S WHERE T tags G?, G in_state S, S name "hop", G is CWUser',
+     '''SELECT T.cw_eid, G.cw_eid, S.cw_eid
+FROM cw_State AS S, cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN cw_CWUser AS G ON (rel_tags0.eid_to=G.cw_eid)
+WHERE G.cw_in_state=S.cw_eid AND S.cw_name=hop
 '''),
 
     # optional variable with additional invariant restriction on an inlined relation
-    ('Any T,G,S WHERE T tags G, G in_state S?, S eid 1, G is EUser',
-     '''SELECT rel_tags0.eid_from, G.eid, G.in_state
-FROM EUser AS G, tags_relation AS rel_tags0
-WHERE rel_tags0.eid_to=G.eid AND (G.in_state=1 OR G.in_state IS NULL)'''),
+    ('Any T,G,S WHERE T tags G, G in_state S?, S eid 1, G is CWUser',
+     '''SELECT rel_tags0.eid_from, G.cw_eid, G.cw_in_state
+FROM cw_CWUser AS G, tags_relation AS rel_tags0
+WHERE rel_tags0.eid_to=G.cw_eid AND (G.cw_in_state=1 OR G.cw_in_state IS NULL)'''),
 
     # two optional variables with additional invariant restriction on an inlined relation
-    ('Any T,G,S WHERE T tags G?, G in_state S?, S eid 1, G is EUser',
-     '''SELECT T.eid, G.eid, G.in_state
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN EUser AS G ON (rel_tags0.eid_to=G.eid AND (G.in_state=1 OR G.in_state IS NULL))'''),
+    ('Any T,G,S WHERE T tags G?, G in_state S?, S eid 1, G is CWUser',
+     '''SELECT T.cw_eid, G.cw_eid, G.cw_in_state
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN cw_CWUser AS G ON (rel_tags0.eid_to=G.cw_eid AND (G.cw_in_state=1 OR G.cw_in_state IS NULL))'''),
 
     # two optional variables with additional restriction on an inlined relation
-    ('Any T,G,S WHERE T tags G?, G in_state S?, S name "hop", G is EUser',
-     '''SELECT T.eid, G.eid, S.eid
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN EUser AS G ON (rel_tags0.eid_to=G.eid) LEFT OUTER JOIN State AS S ON (G.in_state=S.eid AND S.name=hop)'''),
-    
+    ('Any T,G,S WHERE T tags G?, G in_state S?, S name "hop", G is CWUser',
+     '''SELECT T.cw_eid, G.cw_eid, S.cw_eid
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN cw_CWUser AS G ON (rel_tags0.eid_to=G.cw_eid) LEFT OUTER JOIN cw_State AS S ON (G.cw_in_state=S.cw_eid AND S.cw_name=hop)'''),
+
     # two optional variables with additional restriction on an ambigous inlined relation
     ('Any T,G,S WHERE T tags G?, G in_state S?, S name "hop"',
-     '''SELECT T.eid, _T0.C0, _T0.C1
-FROM Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.eid) LEFT OUTER JOIN (SELECT G.eid AS C0, S.eid AS C1
-FROM Affaire AS G LEFT OUTER JOIN State AS S ON (G.in_state=S.eid AND S.name=hop) 
+     '''
+SELECT T.cw_eid, _T0.C0, _T0.C1
+FROM cw_Tag AS T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_from=T.cw_eid) LEFT OUTER JOIN (SELECT G.cw_eid AS C0, S.cw_eid AS C1
+FROM cw_Affaire AS G LEFT OUTER JOIN cw_State AS S ON (G.cw_in_state=S.cw_eid AND S.cw_name=hop) 
 UNION ALL
-SELECT G.eid AS C0, S.eid AS C1
-FROM EUser AS G LEFT OUTER JOIN State AS S ON (G.in_state=S.eid AND S.name=hop) 
+SELECT G.cw_eid AS C0, S.cw_eid AS C1
+FROM cw_CWUser AS G LEFT OUTER JOIN cw_State AS S ON (G.cw_in_state=S.cw_eid AND S.cw_name=hop) 
 UNION ALL
-SELECT G.eid AS C0, S.eid AS C1
-FROM Note AS G LEFT OUTER JOIN State AS S ON (G.in_state=S.eid AND S.name=hop) ) AS _T0 ON (rel_tags0.eid_to=_T0.C0)'''),
+SELECT G.cw_eid AS C0, S.cw_eid AS C1
+FROM cw_Note AS G LEFT OUTER JOIN cw_State AS S ON (G.cw_in_state=S.cw_eid AND S.cw_name=hop) ) AS _T0 ON (rel_tags0.eid_to=_T0.C0)'''),
 
     ]
 
 VIRTUAL_VARS = [
     ("Personne P WHERE P travaille S, S tel T, S fax T, S is Societe;",
      '''SELECT rel_travaille0.eid_from
-FROM Societe AS S, travaille_relation AS rel_travaille0
-WHERE rel_travaille0.eid_to=S.eid AND S.fax=S.tel'''),
-    
+FROM cw_Societe AS S, travaille_relation AS rel_travaille0
+WHERE rel_travaille0.eid_to=S.cw_eid AND S.cw_fax=S.cw_tel'''),
+
     ("Personne P where X eid 0, X creation_date D, P datenaiss < D, X is Affaire",
-     '''SELECT P.eid
-FROM Affaire AS X, Personne AS P
-WHERE X.eid=0 AND P.datenaiss<X.creation_date'''),
+     '''SELECT P.cw_eid
+FROM cw_Affaire AS X, cw_Personne AS P
+WHERE X.cw_eid=0 AND P.cw_datenaiss<X.cw_creation_date'''),
 
     ("Any N,T WHERE N is Note, N type T;",
-     '''SELECT N.eid, N.type
-FROM Note AS N'''),
+     '''SELECT N.cw_eid, N.cw_type
+FROM cw_Note AS N'''),
 
     ("Personne P where X is Personne, X tel T, X fax F, P fax T+F",
-     '''SELECT P.eid
-FROM Personne AS P, Personne AS X
-WHERE P.fax=(X.tel + X.fax)'''),
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P, cw_Personne AS X
+WHERE P.cw_fax=(X.cw_tel + X.cw_fax)'''),
 
     ("Personne P where X tel T, X fax F, P fax IN (T,F)",
-     '''SELECT P.eid
-FROM Division AS X, Personne AS P
-WHERE P.fax IN(X.tel, X.fax)
+     '''SELECT P.cw_eid
+FROM cw_Division AS X, cw_Personne AS P
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, Personne AS X
-WHERE P.fax IN(X.tel, X.fax)
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_Personne AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, Societe AS X
-WHERE P.fax IN(X.tel, X.fax)
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_Societe AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, SubDivision AS X
-WHERE P.fax IN(X.tel, X.fax)'''),
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_SubDivision AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax)'''),
 
     ("Personne P where X tel T, X fax F, P fax IN (T,F,0832542332)",
-     '''SELECT P.eid
-FROM Division AS X, Personne AS P
-WHERE P.fax IN(X.tel, X.fax, 832542332)
+     '''SELECT P.cw_eid
+FROM cw_Division AS X, cw_Personne AS P
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax, 832542332)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, Personne AS X
-WHERE P.fax IN(X.tel, X.fax, 832542332)
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_Personne AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax, 832542332)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, Societe AS X
-WHERE P.fax IN(X.tel, X.fax, 832542332)
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_Societe AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax, 832542332)
 UNION ALL
-SELECT P.eid
-FROM Personne AS P, SubDivision AS X
-WHERE P.fax IN(X.tel, X.fax, 832542332)'''),
+SELECT P.cw_eid
+FROM cw_Personne AS P, cw_SubDivision AS X
+WHERE P.cw_fax IN(X.cw_tel, X.cw_fax, 832542332)'''),
     ]
 
 FUNCS = [
     ("Any COUNT(P) WHERE P is Personne",
-     '''SELECT COUNT(P.eid)
-FROM Personne AS P'''),
+     '''SELECT COUNT(P.cw_eid)
+FROM cw_Personne AS P'''),
 ##     ("Personne X where X nom upper('TOTO')",
-##      '''SELECT X.eid\nFROM Personne AS X\nWHERE UPPER(X.nom) = TOTO'''),
+##      '''SELECT X.cw_eid\nFROM cw_Personne AS X\nWHERE UPPER(X.cw_nom) = TOTO'''),
 ##     ("Personne X where X nom Y, UPPER(X) prenom upper(Y)",
-##      '''SELECT X.eid\nFROM Personne AS X\nWHERE UPPER(X.prenom) = UPPER(X.nom)'''),
+##      '''SELECT X.cw_eid\nFROM cw_Personne AS X\nWHERE UPPER(X.cw_prenom) = UPPER(X.cw_nom)'''),
     ]
 
 SYMETRIC = [
     ('Any P WHERE X eid 0, X connait P',
-     '''SELECT DISTINCT P.eid
-FROM Personne AS P, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=P.eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=P.eid)'''
+     '''SELECT DISTINCT P.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS P
+WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=P.cw_eid)'''
 #      '''SELECT rel_connait0.eid_to
 # FROM connait_relation AS rel_connait0
 # WHERE rel_connait0.eid_from=0
@@ -960,150 +961,150 @@
 # FROM connait_relation AS rel_connait0
 # WHERE rel_connait0.eid_to=0'''
      ),
-    
+
     ('Any P WHERE X connait P',
-    '''SELECT DISTINCT P.eid
-FROM Personne AS P, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_to=P.eid OR rel_connait0.eid_from=P.eid)'''
+    '''SELECT DISTINCT P.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS P
+WHERE (rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_from=P.cw_eid)'''
     ),
-    
+
     ('Any X WHERE X connait P',
-    '''SELECT DISTINCT X.eid
-FROM Personne AS X, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_from=X.eid OR rel_connait0.eid_to=X.eid)'''
+    '''SELECT DISTINCT X.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS X
+WHERE (rel_connait0.eid_from=X.cw_eid OR rel_connait0.eid_to=X.cw_eid)'''
      ),
-    
+
     ('Any P WHERE X eid 0, NOT X connait P',
-     '''SELECT P.eid
-FROM Personne AS P
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=P.eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=P.eid))'''),
-    
+     '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=P.cw_eid))'''),
+
     ('Any P WHERE NOT X connait P',
-    '''SELECT P.eid
-FROM Personne AS P
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_to=P.eid OR rel_connait0.eid_from=P.eid))'''),
-    
+    '''SELECT P.cw_eid
+FROM cw_Personne AS P
+WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_from=P.cw_eid))'''),
+
     ('Any X WHERE NOT X connait P',
-    '''SELECT X.eid
-FROM Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=X.eid OR rel_connait0.eid_to=X.eid))'''),
+    '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM connait_relation AS rel_connait0 WHERE (rel_connait0.eid_from=X.cw_eid OR rel_connait0.eid_to=X.cw_eid))'''),
 
     ('Any P WHERE X connait P, P nom "nom"',
-     '''SELECT DISTINCT P.eid
-FROM Personne AS P, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_to=P.eid OR rel_connait0.eid_from=P.eid) AND P.nom=nom'''),
-    
+     '''SELECT DISTINCT P.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS P
+WHERE (rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_from=P.cw_eid) AND P.cw_nom=nom'''),
+
     ('Any X WHERE X connait P, P nom "nom"',
-     '''SELECT DISTINCT X.eid
-FROM Personne AS P, Personne AS X, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_from=X.eid AND rel_connait0.eid_to=P.eid OR rel_connait0.eid_to=X.eid AND rel_connait0.eid_from=P.eid) AND P.nom=nom'''
+     '''SELECT DISTINCT X.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS P, cw_Personne AS X
+WHERE (rel_connait0.eid_from=X.cw_eid AND rel_connait0.eid_to=P.cw_eid OR rel_connait0.eid_to=X.cw_eid AND rel_connait0.eid_from=P.cw_eid) AND P.cw_nom=nom'''
     ),
 
     ('Any X ORDERBY X DESC LIMIT 9 WHERE E eid 0, E connait X',
-    '''SELECT DISTINCT X.eid
-FROM Personne AS X, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=X.eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=X.eid)
+    '''SELECT DISTINCT X.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS X
+WHERE (rel_connait0.eid_from=0 AND rel_connait0.eid_to=X.cw_eid OR rel_connait0.eid_to=0 AND rel_connait0.eid_from=X.cw_eid)
 ORDER BY 1 DESC
 LIMIT 9'''
      ),
 
     ('DISTINCT Any P WHERE P connait S OR S connait P, S nom "chouette"',
-     '''SELECT DISTINCT P.eid
-FROM Personne AS P, Personne AS S, connait_relation AS rel_connait0
-WHERE (rel_connait0.eid_from=P.eid AND rel_connait0.eid_to=S.eid OR rel_connait0.eid_to=P.eid AND rel_connait0.eid_from=S.eid) AND S.nom=chouette'''
+     '''SELECT DISTINCT P.cw_eid
+FROM connait_relation AS rel_connait0, cw_Personne AS P, cw_Personne AS S
+WHERE (rel_connait0.eid_from=P.cw_eid AND rel_connait0.eid_to=S.cw_eid OR rel_connait0.eid_to=P.cw_eid AND rel_connait0.eid_from=S.cw_eid) AND S.cw_nom=chouette'''
      )
     ]
 
 INLINE = [
     ('Any P, L WHERE N ecrit_par P, P nom L, N eid 0',
-     '''SELECT P.eid, P.nom
-FROM Note AS N, Personne AS P
-WHERE N.ecrit_par=P.eid AND N.eid=0'''),
-    
+     '''SELECT P.cw_eid, P.cw_nom
+FROM cw_Note AS N, cw_Personne AS P
+WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
+
     ('Any N WHERE NOT N ecrit_par P, P nom "toto"',
-     '''SELECT DISTINCT N.eid
-FROM Note AS N, Personne AS P
-WHERE (N.ecrit_par IS NULL OR N.ecrit_par!=P.eid) AND P.nom=toto'''),
-    
+     '''SELECT DISTINCT N.cw_eid
+FROM cw_Note AS N, cw_Personne AS P
+WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND P.cw_nom=toto'''),
+
     ('Any P WHERE N ecrit_par P, N eid 0',
-    '''SELECT N.ecrit_par
-FROM Note AS N
-WHERE N.ecrit_par IS NOT NULL AND N.eid=0'''),
+    '''SELECT N.cw_ecrit_par
+FROM cw_Note AS N
+WHERE N.cw_ecrit_par IS NOT NULL AND N.cw_eid=0'''),
 
     ('Any P WHERE N ecrit_par P, P is Personne, N eid 0',
-    '''SELECT P.eid
-FROM Note AS N, Personne AS P
-WHERE N.ecrit_par=P.eid AND N.eid=0'''),
+    '''SELECT P.cw_eid
+FROM cw_Note AS N, cw_Personne AS P
+WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
 
     ('Any P WHERE NOT N ecrit_par P, P is Personne, N eid 512',
-     '''SELECT DISTINCT P.eid
-FROM Note AS N, Personne AS P
-WHERE (N.ecrit_par IS NULL OR N.ecrit_par!=P.eid) AND N.eid=512'''),
+     '''SELECT DISTINCT P.cw_eid
+FROM cw_Note AS N, cw_Personne AS P
+WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND N.cw_eid=512'''),
 
-    ('Any S,ES,T WHERE S state_of ET, ET name "EUser", ES allowed_transition T, T destination_state S',
-     '''SELECT T.destination_state, rel_allowed_transition1.eid_from, T.eid
-FROM EEType AS ET, Transition AS T, allowed_transition_relation AS rel_allowed_transition1, state_of_relation AS rel_state_of0
-WHERE T.destination_state=rel_state_of0.eid_from AND rel_state_of0.eid_to=ET.eid AND ET.name=EUser AND rel_allowed_transition1.eid_to=T.eid'''),
+    ('Any S,ES,T WHERE S state_of ET, ET name "CWUser", ES allowed_transition T, T destination_state S',
+     '''SELECT T.cw_destination_state, rel_allowed_transition1.eid_from, T.cw_eid
+FROM allowed_transition_relation AS rel_allowed_transition1, cw_CWEType AS ET, cw_Transition AS T, state_of_relation AS rel_state_of0
+WHERE T.cw_destination_state=rel_state_of0.eid_from AND rel_state_of0.eid_to=ET.cw_eid AND ET.cw_name=CWUser AND rel_allowed_transition1.eid_to=T.cw_eid'''),
     ('Any O WHERE S eid 0, S in_state O',
-     '''SELECT S.in_state
-FROM Affaire AS S
-WHERE S.eid=0 AND S.in_state IS NOT NULL
+     '''SELECT S.cw_in_state
+FROM cw_Affaire AS S
+WHERE S.cw_eid=0 AND S.cw_in_state IS NOT NULL
 UNION ALL
-SELECT S.in_state
-FROM EUser AS S
-WHERE S.eid=0 AND S.in_state IS NOT NULL
+SELECT S.cw_in_state
+FROM cw_CWUser AS S
+WHERE S.cw_eid=0 AND S.cw_in_state IS NOT NULL
 UNION ALL
-SELECT S.in_state
-FROM Note AS S
-WHERE S.eid=0 AND S.in_state IS NOT NULL''')
-    
+SELECT S.cw_in_state
+FROM cw_Note AS S
+WHERE S.cw_eid=0 AND S.cw_in_state IS NOT NULL''')
+
     ]
 
 INTERSECT = [
     ('Any SN WHERE NOT X in_state S, S name SN',
-     '''SELECT DISTINCT S.name
-FROM Affaire AS X, State AS S
-WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+     '''SELECT DISTINCT S.cw_name
+FROM cw_Affaire AS X, cw_State AS S
+WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.name
-FROM EUser AS X, State AS S
-WHERE (X.in_state IS NULL OR X.in_state!=S.eid)
+SELECT DISTINCT S.cw_name
+FROM cw_CWUser AS X, cw_State AS S
+WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.name
-FROM Note AS X, State AS S
-WHERE (X.in_state IS NULL OR X.in_state!=S.eid)'''),
+SELECT DISTINCT S.cw_name
+FROM cw_Note AS X, cw_State AS S
+WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
 
     ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
-     '''SELECT X.nom
-FROM Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Division AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)
+     '''SELECT X.cw_nom
+FROM cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Division AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)
 INTERSECT ALL
-SELECT X.nom
-FROM Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,Societe AS S WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid)'''),
-    
+SELECT X.cw_nom
+FROM cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Societe AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)'''),
+
     ('Any PN WHERE NOT X travaille S, S nom PN, S is IN(Division, Societe)',
-     '''SELECT S.nom
-FROM Division AS S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)
+     '''SELECT S.cw_nom
+FROM cw_Division AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.cw_eid)
 UNION ALL
-SELECT S.nom
-FROM Societe AS S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.eid)'''),
-    
+SELECT S.cw_nom
+FROM cw_Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_to=S.cw_eid)'''),
+
     ('Personne X WHERE NOT X travaille S, S nom "chouette"',
-     '''SELECT X.eid
-FROM Division AS S, Personne AS X
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+     '''SELECT X.cw_eid
+FROM cw_Division AS S, cw_Personne AS X
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid) AND S.cw_nom=chouette
 UNION ALL
-SELECT X.eid
-FROM Personne AS X, Societe AS S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette
+SELECT X.cw_eid
+FROM cw_Personne AS X, cw_Societe AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid) AND S.cw_nom=chouette
 UNION ALL
-SELECT X.eid
-FROM Personne AS X, SubDivision AS S
-WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.eid AND rel_travaille0.eid_to=S.eid) AND S.nom=chouette'''),
-    
+SELECT X.cw_eid
+FROM cw_Personne AS X, cw_SubDivision AS S
+WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0 WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid) AND S.cw_nom=chouette'''),
+
     ('Any X WHERE X is ET, ET eid 2',
      '''SELECT rel_is0.eid_from
 FROM is_relation AS rel_is0
@@ -1111,14 +1112,14 @@
 
     ]
 from logilab.common.adbh import ADV_FUNC_HELPER_DIRECTORY
-    
+
 class PostgresSQLGeneratorTC(RQLGeneratorTC):
     schema = schema
-    
+
     #capture = True
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('postgres', 'utf8')        
+        indexer = get_indexer('postgres', 'utf8')
         dbms_helper = ADV_FUNC_HELPER_DIRECTORY['postgres']
         dbms_helper.fti_uid_attr = indexer.uid_attr
         dbms_helper.fti_table = indexer.table
@@ -1128,7 +1129,7 @@
 
     def _norm_sql(self, sql):
         return sql.strip()
-    
+
     def _check(self, rql, sql, varmap=None):
         try:
             union = self._prepare(rql)
@@ -1142,11 +1143,11 @@
                 print '!='
                 print sql.strip()
             raise
-    
+
     def _parse(self, rqls):
         for rql, sql in rqls:
             yield self._check, rql, sql
- 
+
     def _checkall(self, rql, sql):
         try:
             rqlst = self._prepare(rql)
@@ -1171,13 +1172,13 @@
 
     def test1(self):
         self._checkall('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s',
-                       ("""SELECT COUNT(T1.C0) FROM (SELECT RDEF.eid AS C0
-FROM EFRDef AS RDEF
-WHERE RDEF.relation_type=%(x)s
+                       ("""SELECT COUNT(T1.C0) FROM (SELECT RDEF.cw_eid AS C0
+FROM cw_CWAttribute AS RDEF
+WHERE RDEF.cw_relation_type=%(x)s
 UNION ALL
-SELECT RDEF.eid AS C0
-FROM ENFRDef AS RDEF
-WHERE RDEF.relation_type=%(x)s) AS T1""", {}),
+SELECT RDEF.cw_eid AS C0
+FROM cw_CWRelation AS RDEF
+WHERE RDEF.cw_relation_type=%(x)s) AS T1""", {}),
                        )
 
     def test2(self):
@@ -1192,28 +1193,28 @@
                     '''SELECT rel_in_basket0.eid_from
 FROM in_basket_relation AS rel_in_basket0
 WHERE rel_in_basket0.eid_to=12''')
-        
+
         self._check('Any X WHERE X in_basket B, B eid 12',
                     '''SELECT rel_in_basket0.eid_from
 FROM in_basket_relation AS rel_in_basket0
 WHERE rel_in_basket0.eid_to=12''')
 
     def test_varmap(self):
-        self._check('Any X,L WHERE X is EUser, X in_group G, X login L, G name "users"',
+        self._check('Any X,L WHERE X is CWUser, X in_group G, X login L, G name "users"',
                     '''SELECT T00.x, T00.l
-FROM EGroup AS G, T00, in_group_relation AS rel_in_group0
-WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.eid AND G.name=users''',
+FROM T00, cw_CWGroup AS G, in_group_relation AS rel_in_group0
+WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid AND G.cw_name=users''',
                     varmap={'X': 'T00.x', 'X.login': 'T00.l'})
-        self._check('Any X,L,GN WHERE X is EUser, X in_group G, X login L, G name GN',
-                    '''SELECT T00.x, T00.l, G.name
-FROM EGroup AS G, T00, in_group_relation AS rel_in_group0
-WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.eid''',
+        self._check('Any X,L,GN WHERE X is CWUser, X in_group G, X login L, G name GN',
+                    '''SELECT T00.x, T00.l, G.cw_name
+FROM T00, cw_CWGroup AS G, in_group_relation AS rel_in_group0
+WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid''',
                     varmap={'X': 'T00.x', 'X.login': 'T00.l'})
 
     def test_parser_parse(self):
         for t in self._parse(PARSER):
             yield t
-            
+
     def test_basic_parse(self):
         for t in self._parse(BASIC):
             yield t
@@ -1233,15 +1234,15 @@
     def test_multiple_sel_parse(self):
         for t in self._parse(MULTIPLE_SEL):
             yield t
-        
+
     def test_functions(self):
         for t in self._parse(FUNCS):
             yield t
-        
+
     def test_negation(self):
         for t in self._parse(NEGATIONS):
             yield t
-        
+
     def test_intersection(self):
         for t in self._parse(INTERSECT):
             yield t
@@ -1251,16 +1252,16 @@
             ('(Any N ORDERBY 1 WHERE X name N, X is State)'
              ' UNION '
              '(Any NN ORDERBY 1 WHERE XX name NN, XX is Transition)',
-             '''(SELECT X.name
-FROM State AS X
+             '''(SELECT X.cw_name
+FROM cw_State AS X
 ORDER BY 1)
 UNION ALL
-(SELECT XX.name
-FROM Transition AS XX
+(SELECT XX.cw_name
+FROM cw_Transition AS XX
 ORDER BY 1)'''),
             )):
             yield t
-            
+
     def test_subquery(self):
         for t in self._parse((
 
@@ -1269,56 +1270,56 @@
              ' UNION '
              '(Any NN WHERE XX name NN, XX is Transition))',
              '''SELECT _T0.C0
-FROM ((SELECT X.name AS C0
-FROM State AS X)
+FROM ((SELECT X.cw_name AS C0
+FROM cw_State AS X)
 UNION ALL
-(SELECT XX.name AS C0
-FROM Transition AS XX)) AS _T0
+(SELECT XX.cw_name AS C0
+FROM cw_Transition AS XX)) AS _T0
 ORDER BY 1'''),
-            
+
             ('Any N,NX ORDERBY NX WITH N,NX BEING '
              '((Any N,COUNT(X) GROUPBY N WHERE X name N, X is State HAVING COUNT(X)>1)'
              ' UNION '
              '(Any N,COUNT(X) GROUPBY N WHERE X name N, X is Transition HAVING COUNT(X)>1))',
              '''SELECT _T0.C0, _T0.C1
-FROM ((SELECT X.name AS C0, COUNT(X.eid) AS C1
-FROM State AS X
-GROUP BY X.name
-HAVING COUNT(X.eid)>1)
+FROM ((SELECT X.cw_name AS C0, COUNT(X.cw_eid) AS C1
+FROM cw_State AS X
+GROUP BY X.cw_name
+HAVING COUNT(X.cw_eid)>1)
 UNION ALL
-(SELECT X.name AS C0, COUNT(X.eid) AS C1
-FROM Transition AS X
-GROUP BY X.name
-HAVING COUNT(X.eid)>1)) AS _T0
-ORDER BY 2'''),            
+(SELECT X.cw_name AS C0, COUNT(X.cw_eid) AS C1
+FROM cw_Transition AS X
+GROUP BY X.cw_name
+HAVING COUNT(X.cw_eid)>1)) AS _T0
+ORDER BY 2'''),
 
             ('Any N,COUNT(X) GROUPBY N HAVING COUNT(X)>1 '
              'WITH X, N BEING ((Any X, N WHERE X name N, X is State) UNION '
              '                 (Any X, N WHERE X name N, X is Transition))',
              '''SELECT _T0.C1, COUNT(_T0.C0)
-FROM ((SELECT X.eid AS C0, X.name AS C1
-FROM State AS X)
+FROM ((SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_State AS X)
 UNION ALL
-(SELECT X.eid AS C0, X.name AS C1
-FROM Transition AS X)) AS _T0
+(SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Transition AS X)) AS _T0
 GROUP BY _T0.C1
 HAVING COUNT(_T0.C0)>1'''),
 
             ('Any ETN,COUNT(X) GROUPBY ETN WHERE X is ET, ET name ETN '
              'WITH X BEING ((Any X WHERE X is Societe) UNION (Any X WHERE X is Affaire, (EXISTS(X owned_by 1)) OR ((EXISTS(D concerne B?, B owned_by 1, X identity D, B is Note)) OR (EXISTS(F concerne E?, E owned_by 1, E is Societe, X identity F)))))',
-             '''SELECT ET.name, COUNT(_T0.C0)
-FROM ((SELECT X.eid AS C0
-FROM Societe AS X)
+             '''SELECT ET.cw_name, COUNT(_T0.C0)
+FROM ((SELECT X.cw_eid AS C0
+FROM cw_Societe AS X)
 UNION ALL
-(SELECT X.eid AS C0
-FROM Affaire AS X
-WHERE ((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_from=X.eid AND rel_owned_by0.eid_to=1)) OR (((EXISTS(SELECT 1 FROM Affaire AS D LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_from=D.eid) LEFT OUTER JOIN Note AS B ON (rel_concerne1.eid_to=B.eid), owned_by_relation AS rel_owned_by2 WHERE rel_owned_by2.eid_from=B.eid AND rel_owned_by2.eid_to=1 AND X.eid=D.eid)) OR (EXISTS(SELECT 1 FROM Affaire AS F LEFT OUTER JOIN concerne_relation AS rel_concerne3 ON (rel_concerne3.eid_from=F.eid) LEFT OUTER JOIN Societe AS E ON (rel_concerne3.eid_to=E.eid), owned_by_relation AS rel_owned_by4 WHERE rel_owned_by4.eid_from=E.eid AND rel_owned_by4.eid_to=1 AND X.eid=F.eid))))))) AS _T0, EEType AS ET, is_relation AS rel_is0
-WHERE rel_is0.eid_from=_T0.C0 AND rel_is0.eid_to=ET.eid
-GROUP BY ET.name'''),
+(SELECT X.cw_eid AS C0
+FROM cw_Affaire AS X
+WHERE ((EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_from=X.cw_eid AND rel_owned_by0.eid_to=1)) OR (((EXISTS(SELECT 1 FROM cw_Affaire AS D LEFT OUTER JOIN concerne_relation AS rel_concerne1 ON (rel_concerne1.eid_from=D.cw_eid) LEFT OUTER JOIN cw_Note AS B ON (rel_concerne1.eid_to=B.cw_eid), owned_by_relation AS rel_owned_by2 WHERE rel_owned_by2.eid_from=B.cw_eid AND rel_owned_by2.eid_to=1 AND X.cw_eid=D.cw_eid)) OR (EXISTS(SELECT 1 FROM cw_Affaire AS F LEFT OUTER JOIN concerne_relation AS rel_concerne3 ON (rel_concerne3.eid_from=F.cw_eid) LEFT OUTER JOIN cw_Societe AS E ON (rel_concerne3.eid_to=E.cw_eid), owned_by_relation AS rel_owned_by4 WHERE rel_owned_by4.eid_from=E.cw_eid AND rel_owned_by4.eid_to=1 AND X.cw_eid=F.cw_eid))))))) AS _T0, cw_CWEType AS ET, is_relation AS rel_is0
+WHERE rel_is0.eid_from=_T0.C0 AND rel_is0.eid_to=ET.cw_eid
+GROUP BY ET.cw_name'''),
             )):
             yield t
 
-            
+
     def test_subquery_error(self):
         rql = ('Any N WHERE X name N WITH X BEING '
                '((Any X WHERE X is State)'
@@ -1326,60 +1327,60 @@
                ' (Any X WHERE X is Transition))')
         rqlst = self._prepare(rql)
         self.assertRaises(BadRQLQuery, self.o.generate, rqlst)
-            
+
     def test_symetric(self):
         for t in self._parse(SYMETRIC):
             yield t
-        
+
     def test_inline(self):
         for t in self._parse(INLINE):
             yield t
-            
+
     def test_has_text(self):
         for t in self._parse((
             ('Any X WHERE X has_text "toto tata"',
              """SELECT appears0.uid
 FROM appears AS appears0
 WHERE appears0.words @@ to_tsquery('default', 'toto&tata')"""),
-            
+
             ('Personne X WHERE X has_text "toto tata"',
              """SELECT X.eid
 FROM appears AS appears0, entities AS X
 WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.type='Personne'"""),
-            
+
             ('Personne X WHERE X has_text %(text)s',
              """SELECT X.eid
 FROM appears AS appears0, entities AS X
 WHERE appears0.words @@ to_tsquery('default', 'hip&hop&momo') AND appears0.uid=X.eid AND X.type='Personne'"""),
-            
+
             ('Any X WHERE X has_text "toto tata", X name "tutu"',
-             """SELECT X.eid
-FROM Basket AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+             """SELECT X.cw_eid
+FROM appears AS appears0, cw_Basket AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM File AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_File AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Folder AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Folder AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Image AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Image AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM State AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_State AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Tag AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Tag AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Transition AS X, appears AS appears0
-WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.eid AND X.name=tutu"""),
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Transition AS X
+WHERE appears0.words @@ to_tsquery('default', 'toto&tata') AND appears0.uid=X.cw_eid AND X.cw_name=tutu"""),
 
             ('Personne X where X has_text %(text)s, X travaille S, S has_text %(text)s',
              """SELECT X.eid
@@ -1390,9 +1391,9 @@
 
 
     def test_from_clause_needed(self):
-        queries = [("Any 1 WHERE EXISTS(T is EGroup, T name 'managers')",
+        queries = [("Any 1 WHERE EXISTS(T is CWGroup, T name 'managers')",
                     '''SELECT 1
-WHERE EXISTS(SELECT 1 FROM EGroup AS T WHERE T.name=managers)'''),
+WHERE EXISTS(SELECT 1 FROM cw_CWGroup AS T WHERE T.cw_name=managers)'''),
                    ('Any X,Y WHERE NOT X created_by Y, X eid 5, Y eid 6',
                     '''SELECT 5, 6
 WHERE NOT EXISTS(SELECT 1 FROM created_by_relation AS rel_created_by0 WHERE rel_created_by0.eid_from=5 AND rel_created_by0.eid_to=6)'''),
@@ -1403,14 +1404,14 @@
     def test_ambigous_exists_no_from_clause(self):
         self._check('Any COUNT(U) WHERE U eid 1, EXISTS (P owned_by U, P is IN (Note, Affaire))',
                     '''SELECT COUNT(1)
-WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, Affaire AS P WHERE rel_owned_by0.eid_from=P.eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, Note AS P WHERE rel_owned_by1.eid_from=P.eid AND rel_owned_by1.eid_to=1)''')
+WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''')
 
 
 class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):
-    
+
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('sqlite', 'utf8')        
+        indexer = get_indexer('sqlite', 'utf8')
         dbms_helper = ADV_FUNC_HELPER_DIRECTORY['sqlite']
         dbms_helper.fti_uid_attr = indexer.uid_attr
         dbms_helper.fti_table = indexer.table
@@ -1426,16 +1427,16 @@
             ('(Any N ORDERBY 1 WHERE X name N, X is State)'
              ' UNION '
              '(Any NN ORDERBY 1 WHERE XX name NN, XX is Transition)',
-             '''SELECT X.name
-FROM State AS X
+             '''SELECT X.cw_name
+FROM cw_State AS X
 ORDER BY 1
 UNION ALL
-SELECT XX.name
-FROM Transition AS XX
+SELECT XX.cw_name
+FROM cw_Transition AS XX
 ORDER BY 1'''),
             )):
             yield t
-            
+
 
     def test_subquery(self):
         # NOTE: no paren around UNION with sqlitebackend
@@ -1446,88 +1447,88 @@
              ' UNION '
              '(Any NN WHERE XX name NN, XX is Transition))',
              '''SELECT _T0.C0
-FROM (SELECT X.name AS C0
-FROM State AS X
+FROM (SELECT X.cw_name AS C0
+FROM cw_State AS X
 UNION ALL
-SELECT XX.name AS C0
-FROM Transition AS XX) AS _T0
+SELECT XX.cw_name AS C0
+FROM cw_Transition AS XX) AS _T0
 ORDER BY 1'''),
-            
+
             ('Any N,NX ORDERBY NX WITH N,NX BEING '
              '((Any N,COUNT(X) GROUPBY N WHERE X name N, X is State HAVING COUNT(X)>1)'
              ' UNION '
              '(Any N,COUNT(X) GROUPBY N WHERE X name N, X is Transition HAVING COUNT(X)>1))',
              '''SELECT _T0.C0, _T0.C1
-FROM (SELECT X.name AS C0, COUNT(X.eid) AS C1
-FROM State AS X
-GROUP BY X.name
-HAVING COUNT(X.eid)>1
+FROM (SELECT X.cw_name AS C0, COUNT(X.cw_eid) AS C1
+FROM cw_State AS X
+GROUP BY X.cw_name
+HAVING COUNT(X.cw_eid)>1
 UNION ALL
-SELECT X.name AS C0, COUNT(X.eid) AS C1
-FROM Transition AS X
-GROUP BY X.name
-HAVING COUNT(X.eid)>1) AS _T0
-ORDER BY 2'''),            
+SELECT X.cw_name AS C0, COUNT(X.cw_eid) AS C1
+FROM cw_Transition AS X
+GROUP BY X.cw_name
+HAVING COUNT(X.cw_eid)>1) AS _T0
+ORDER BY 2'''),
 
             ('Any N,COUNT(X) GROUPBY N HAVING COUNT(X)>1 '
              'WITH X, N BEING ((Any X, N WHERE X name N, X is State) UNION '
              '                 (Any X, N WHERE X name N, X is Transition))',
              '''SELECT _T0.C1, COUNT(_T0.C0)
-FROM (SELECT X.eid AS C0, X.name AS C1
-FROM State AS X
+FROM (SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_State AS X
 UNION ALL
-SELECT X.eid AS C0, X.name AS C1
-FROM Transition AS X) AS _T0
+SELECT X.cw_eid AS C0, X.cw_name AS C1
+FROM cw_Transition AS X) AS _T0
 GROUP BY _T0.C1
 HAVING COUNT(_T0.C0)>1'''),
             )):
             yield t
-        
+
     def test_has_text(self):
         for t in self._parse((
             ('Any X WHERE X has_text "toto tata"',
              """SELECT appears0.uid
 FROM appears AS appears0
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata'))"""),
-            
+
             ('Any X WHERE X has_text %(text)s',
              """SELECT appears0.uid
 FROM appears AS appears0
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('hip', 'hop', 'momo'))"""),
-            
+
             ('Personne X WHERE X has_text "toto tata"',
              """SELECT X.eid
 FROM appears AS appears0, entities AS X
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.type='Personne'"""),
-            
+
             ('Any X WHERE X has_text "toto tata", X name "tutu"',
-             """SELECT X.eid
-FROM Basket AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+             """SELECT X.cw_eid
+FROM appears AS appears0, cw_Basket AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM File AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_File AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Folder AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Folder AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Image AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Image AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM State AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_State AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Tag AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Tag AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Transition AS X, appears AS appears0
-WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.eid AND X.name=tutu"""),
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Transition AS X
+WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=X.cw_eid AND X.cw_name=tutu"""),
             )):
             yield t
 
@@ -1537,7 +1538,7 @@
 
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('mysql', 'utf8')        
+        indexer = get_indexer('mysql', 'utf8')
         dbms_helper = ADV_FUNC_HELPER_DIRECTORY['mysql']
         dbms_helper.fti_uid_attr = indexer.uid_attr
         dbms_helper.fti_table = indexer.table
@@ -1546,13 +1547,13 @@
         self.o = SQLGenerator(schema, dbms_helper)
 
     def _norm_sql(self, sql):
-        return sql.strip().replace(' ILIKE ', ' LIKE ')
+        return sql.strip().replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0')
 
     def test_from_clause_needed(self):
-        queries = [("Any 1 WHERE EXISTS(T is EGroup, T name 'managers')",
+        queries = [("Any 1 WHERE EXISTS(T is CWGroup, T name 'managers')",
                     '''SELECT 1
 FROM (SELECT 1) AS _T
-WHERE EXISTS(SELECT 1 FROM EGroup AS T WHERE T.name=managers)'''),
+WHERE EXISTS(SELECT 1 FROM cw_CWGroup AS T WHERE T.cw_name=managers)'''),
                    ('Any X,Y WHERE NOT X created_by Y, X eid 5, Y eid 6',
                     '''SELECT 5, 6
 FROM (SELECT 1) AS _T
@@ -1577,46 +1578,46 @@
 FROM appears AS appears0, entities AS X
 WHERE MATCH (appears0.words) AGAINST ('hip hop momo' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.type='Personne'"""),
             ('Any X WHERE X has_text "toto tata", X name "tutu"',
-             """SELECT X.eid
-FROM Basket AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+             """SELECT X.cw_eid
+FROM appears AS appears0, cw_Basket AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM File AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_File AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Folder AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Folder AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Image AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Image AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM State AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_State AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Tag AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Tag AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu
 UNION ALL
-SELECT X.eid
-FROM Transition AS X, appears AS appears0
-WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.eid AND X.name=tutu""")
+SELECT X.cw_eid
+FROM appears AS appears0, cw_Transition AS X
+WHERE MATCH (appears0.words) AGAINST ('toto tata' IN BOOLEAN MODE) AND appears0.uid=X.cw_eid AND X.cw_name=tutu""")
             ]
         for t in self._parse(queries):
             yield t
-                             
+
 
     def test_ambigous_exists_no_from_clause(self):
         self._check('Any COUNT(U) WHERE U eid 1, EXISTS (P owned_by U, P is IN (Note, Affaire))',
                     '''SELECT COUNT(1)
 FROM (SELECT 1) AS _T
-WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, Affaire AS P WHERE rel_owned_by0.eid_from=P.eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, Note AS P WHERE rel_owned_by1.eid_from=P.eid AND rel_owned_by1.eid_to=1)''') 
-           
+WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''')
 
-        
+
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_rqlannotation.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_rqlannotation.py	Thu May 14 12:50:34 2009 +0200
@@ -9,15 +9,15 @@
 
 class SQLGenAnnotatorTC(BaseQuerierTC):
     repo = repo
-    
+
     def get_max_eid(self):
         # no need for cleanup here
         return None
     def cleanup(self):
         # no need for cleanup here
         pass
-                
-    def test_0_1(self):        
+
+    def test_0_1(self):
         rqlst = self._prepare('Any SEN,RN,OEN WHERE X from_entity SE, SE eid 44, X relation_type R, R eid 139, X to_entity OE, OE eid 42, R name RN, SE name SEN, OE name OEN')
         self.assertEquals(rqlst.defined_vars['SE']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['OE']._q_invariant, False)
@@ -25,43 +25,43 @@
         self.assertEquals(rqlst.defined_vars['SE'].stinfo['attrvar'], None)
         self.assertEquals(rqlst.defined_vars['OE'].stinfo['attrvar'], None)
         self.assertEquals(rqlst.defined_vars['R'].stinfo['attrvar'], None)
-        
-    def test_0_2(self):        
+
+    def test_0_2(self):
         rqlst = self._prepare('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P')
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['O'].stinfo['attrvar'], None)
 
-    def test_0_4(self):        
+    def test_0_4(self):
         rqlst = self._prepare('Any A,B,C WHERE A eid 12,A comment B, A ?wf_info_for C')
         self.assertEquals(rqlst.defined_vars['A']._q_invariant, False)
         self.assert_(rqlst.defined_vars['B'].stinfo['attrvar'])
         self.assertEquals(rqlst.defined_vars['C']._q_invariant, False)
         self.assertEquals(rqlst.solutions, [{'A': 'TrInfo', 'B': 'String', 'C': 'Affaire'},
-                                      {'A': 'TrInfo', 'B': 'String', 'C': 'EUser'},
+                                      {'A': 'TrInfo', 'B': 'String', 'C': 'CWUser'},
                                       {'A': 'TrInfo', 'B': 'String', 'C': 'Note'}])
 
-    def test_0_5(self):        
+    def test_0_5(self):
         rqlst = self._prepare('Any P WHERE N ecrit_par P, N eid 0')
         self.assertEquals(rqlst.defined_vars['N']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, True)
 
-    def test_0_6(self):        
+    def test_0_6(self):
         rqlst = self._prepare('Any P WHERE NOT N ecrit_par P, N eid 512')
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, False)
 
-    def test_0_7(self):        
+    def test_0_7(self):
         rqlst = self._prepare('Personne X,Y where X nom NX, Y nom NX, X eid XE, not Y eid XE')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
         self.assert_(rqlst.defined_vars['XE'].stinfo['attrvar'])
 
-    def test_0_8(self):        
+    def test_0_8(self):
         rqlst = self._prepare('Any P WHERE X eid 0, NOT X connait P')
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, False)
         #self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
         self.assertEquals(len(rqlst.solutions), 1, rqlst.solutions)
 
-    def test_0_10(self):        
+    def test_0_10(self):
         rqlst = self._prepare('Any X WHERE X concerne Y, Y is Note')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
@@ -71,78 +71,78 @@
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
 
-    def test_0_12(self):        
+    def test_0_12(self):
         rqlst = self._prepare('Personne P WHERE P concerne A, A concerne S, S nom "Logilab"')
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['A']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['S']._q_invariant, False)
-        
+
     def test_1_0(self):
         rqlst = self._prepare('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid 6')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
-                
+
     def test_1_1(self):
         rqlst = self._prepare('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid IN (6,7)')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
-                
+
     def test_2(self):
         rqlst = self._prepare('Any X WHERE X identity Y, Y eid 1')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
-        
+
     def test_7(self):
         rqlst = self._prepare('Personne X,Y where X nom NX, Y nom NX, X eid XE, not Y eid XE')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
-        
+
     def test_optional_inlined(self):
         rqlst = self._prepare('Any X,S where X from_state S?')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['S']._q_invariant, True)
-                
+
     def test_optional_inlined_2(self):
         rqlst = self._prepare('Any N,A WHERE N? inline1 A')
         self.assertEquals(rqlst.defined_vars['N']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['A']._q_invariant, False)
-        
+
     def test_optional_1(self):
         rqlst = self._prepare('Any X,S WHERE X travaille S?')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['S']._q_invariant, True)
-        
+
     def test_greater_eid(self):
         rqlst = self._prepare('Any X WHERE X eid > 5')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_greater_eid_typed(self):
         rqlst = self._prepare('Any X WHERE X eid > 5, X is Note')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_max_eid(self):
         rqlst = self._prepare('Any MAX(X)')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_max_eid_typed(self):
         rqlst = self._prepare('Any MAX(X) WHERE X is Note')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
-        
+
     def test_all_entities(self):
         rqlst = self._prepare('Any X')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_all_typed_entity(self):
         rqlst = self._prepare('Any X WHERE X is Note')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
-        
+
     def test_has_text_1(self):
         rqlst = self._prepare('Any X WHERE X has_text "toto tata"')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['X'].stinfo['principal'].r_type, 'has_text')
-        
+
     def test_has_text_2(self):
         rqlst = self._prepare('Any X WHERE X is Personne, X has_text "coucou"')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['X'].stinfo['principal'].r_type, 'has_text')
-        
+
     def test_not_relation_1(self):
         # P can't be invariant since deambiguification caused by "NOT X require_permission P"
         # is not considered by generated sql (NOT EXISTS(...))
@@ -150,33 +150,33 @@
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['G']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_not_relation_2(self):
         rqlst = self._prepare('TrInfo X WHERE X eid 2, NOT X from_state Y, Y is State')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
-                
+
     def test_not_relation_3(self):
         rqlst = self._prepare('Any X, Y WHERE X eid 1, Y eid in (2, 3)')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
-                
+
     def test_not_relation_4_1(self):
         rqlst = self._prepare('Note X WHERE NOT Y evaluee X')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
-        
+
     def test_not_relation_4_2(self):
         rqlst = self._prepare('Any X WHERE NOT Y evaluee X')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
-        
+
     def test_not_relation_4_3(self):
         rqlst = self._prepare('Any Y WHERE NOT Y evaluee X')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
-        
+
     def test_not_relation_4_4(self):
-        rqlst = self._prepare('Any X WHERE NOT Y evaluee X, Y is EUser')
+        rqlst = self._prepare('Any X WHERE NOT Y evaluee X, Y is CWUser')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
 
@@ -184,14 +184,14 @@
         rqlst = self._prepare('Any X WHERE NOT Y evaluee X, Y eid %s, X is Note' % self.ueid)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.solutions, [{'X': 'Note'}])
-        
+
     def test_not_relation_5_1(self):
-        rqlst = self._prepare('Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+        rqlst = self._prepare('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
 
     def test_not_relation_5_2(self):
-        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
 
@@ -201,64 +201,64 @@
         self.assertEquals(rqlst.defined_vars['A']._q_invariant, True)
 
     def test_not_relation_7(self):
-        rqlst = self._prepare('Any K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U') 
+        rqlst = self._prepare('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U')
         self.assertEquals(rqlst.defined_vars['P']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, True)
-       
-    def test_exists_1(self):        
+
+    def test_exists_1(self):
         rqlst = self._prepare('Any U WHERE U eid IN (1,2), EXISTS(X owned_by U)')
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-       
-    def test_exists_2(self):        
+
+    def test_exists_2(self):
         rqlst = self._prepare('Any U WHERE EXISTS(U eid IN (1,2), X owned_by U)')
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
 
-    def test_exists_3(self):        
+    def test_exists_3(self):
         rqlst = self._prepare('Any U WHERE EXISTS(X owned_by U, X bookmarked_by U)')
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
+
     def test_exists_4(self):
-        rqlst = self._prepare('Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)')
+        rqlst = self._prepare('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
-        
+
     def test_exists_5(self):
-        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)')
+        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True)
 
-    def test_not_exists_1(self):        
+    def test_not_exists_1(self):
         rqlst = self._prepare('Any U WHERE NOT EXISTS(X owned_by U, X bookmarked_by U)')
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, False)
-        self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)        
+        self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
 
-    def test_not_exists_2(self):        
-        rqlst = self._prepare('Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)')
+    def test_not_exists_2(self):
+        rqlst = self._prepare('Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
 
-    def test_not_exists_distinct_1(self):        
-        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "EGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)')
+    def test_not_exists_distinct_1(self):
+        rqlst = self._prepare('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT EXISTS(X read_permission Y)')
         self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False)
-        
-    def test_or_1(self):        
+
+    def test_or_1(self):
         rqlst = self._prepare('Any X WHERE X concerne B OR C concerne X, B eid 12, C eid 13')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, False)
 
-    def test_or_2(self):        
+    def test_or_2(self):
         rqlst = self._prepare('Any X WHERE X created_by U, X concerne B OR C concerne X, B eid 12, C eid 13')
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        self.assertEquals(rqlst.defined_vars['U']._q_invariant, True) 
+        self.assertEquals(rqlst.defined_vars['U']._q_invariant, True)
         self.assertEquals(rqlst.defined_vars['X'].stinfo['principal'].r_type, 'created_by')
 
-    def test_or_3(self):        
+    def test_or_3(self):
         rqlst = self._prepare('Any N WHERE A evaluee N or EXISTS(N todo_by U)')
         self.assertEquals(rqlst.defined_vars['N']._q_invariant, False)
-        self.assertEquals(rqlst.defined_vars['A']._q_invariant, True) 
-        self.assertEquals(rqlst.defined_vars['U']._q_invariant, True) 
-        
+        self.assertEquals(rqlst.defined_vars['A']._q_invariant, True)
+        self.assertEquals(rqlst.defined_vars['U']._q_invariant, True)
+
     def test_or_exists_1(self):
         # query generated by security rewriting
         rqlst = self._prepare('DISTINCT Any A,S WHERE A is Affaire, S nom "chouette", S is IN(Division, Societe, SubDivision),'
@@ -270,13 +270,13 @@
         self.assertEquals(rqlst.defined_vars['A']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['S']._q_invariant, False)
 
-    def test_or_exists_2(self):        
+    def test_or_exists_2(self):
         rqlst = self._prepare('Any U WHERE EXISTS(U in_group G, G name "managers") OR EXISTS(X owned_by U, X bookmarked_by U)')
         self.assertEquals(rqlst.defined_vars['U']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['G']._q_invariant, False)
         self.assertEquals(rqlst.defined_vars['X']._q_invariant, True)
-        
-    def test_or_exists_3(self):        
+
+    def test_or_exists_3(self):
         rqlst = self._prepare('Any COUNT(S),CS GROUPBY CS ORDERBY 1 DESC LIMIT 10 '
                               'WHERE C is Societe, S concerne C, C nom CS, '
                               '(EXISTS(S owned_by 1)) OR (EXISTS(S documented_by N, N title "published"))')
@@ -286,7 +286,7 @@
                               '(EXISTS(S owned_by 1)) OR (EXISTS(S documented_by N, N title "published"))')
         self.assertEquals(rqlst.defined_vars['S']._q_invariant, True)
 
-    def test_nonregr_ambiguity(self):        
+    def test_nonregr_ambiguity(self):
         rqlst = self._prepare('Note N WHERE N attachment F')
         # N may be an image as well, not invariant
         self.assertEquals(rqlst.defined_vars['N']._q_invariant, False)
--- a/server/test/unittest_rqlrewrite.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_rqlrewrite.py	Thu May 14 12:50:34 2009 +0200
@@ -11,7 +11,7 @@
 config.bootstrap_cubes()
 schema = config.load_schema()
 schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*'))
-                        
+
 rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
                                                  'has_text': 'fti'})
 
@@ -20,9 +20,9 @@
 
 def teardown_module(*args):
     repotest.undo_monkey_patch()
-    
+
 def eid_func_map(eid):
-    return {1: 'EUser',
+    return {1: 'CWUser',
             2: 'Card'}[eid]
 
 def rewrite(rqlst, snippets_map, kwargs):
@@ -41,7 +41,7 @@
     rewriter = RQLRewriter(FakeQuerier, mock_object(user=(mock_object(eid=1))))
     for v, snippets in snippets_map.items():
         snippets_map[v] = [mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0],
-                                       expression='Any X WHERE '+snippet) 
+                                       expression='Any X WHERE '+snippet)
                            for snippet in snippets]
     rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs)
     solutions = rqlst.children[0].solutions
@@ -62,10 +62,10 @@
 
     * optimisation: detecter les relations utilisees dans les rqlexpressions qui
       sont presentes dans la requete de depart pour les reutiliser si possible
-      
+
     * "has_<ACTION>_permission" ?
     """
-    
+
     def test_base_var(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
@@ -74,8 +74,8 @@
         self.failUnlessEqual(rqlst.as_string(),
                              u"Any C WHERE C is Card, B eid %(D)s, "
                              "EXISTS(C in_state A, B in_group E, F require_state A, "
-                             "F name 'read', F require_group E, A is State, E is EGroup, F is EPermission)")
-        
+                             "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)")
+
     def test_multiple_var(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
@@ -87,11 +87,11 @@
         self.assertTextEquals(rqlst.as_string(),
                              "Any S WHERE S documented_by C, C eid %(u)s, B eid %(D)s, "
                              "EXISTS(C in_state A, B in_group E, F require_state A, "
-                             "F name 'read', F require_group E, A is State, E is EGroup, F is EPermission), "
-                             "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is EGroup)), "
+                             "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission), "
+                             "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is CWGroup)), "
                              "S is Affaire")
         self.failUnless('D' in kwargs)
-        
+
     def test_or(self):
         constraint = '(X identity U) OR (X in_state ST, CL identity U, CL in_state ST, ST name "subscribed")'
         rqlst = parse('Any S WHERE S owned_by C, C eid %(u)s')
@@ -99,8 +99,8 @@
         self.failUnlessEqual(rqlst.as_string(),
                              "Any S WHERE S owned_by C, C eid %(u)s, A eid %(B)s, "
                              "EXISTS((C identity A) OR (C in_state D, E identity A, "
-                             "E in_state D, D name 'subscribed'), D is State, E is EUser), "
-                             "S is IN(Affaire, Basket, Bookmark, Card, Comment, Division, ECache, EConstraint, EConstraintType, EEType, EFRDef, EGroup, ENFRDef, EPermission, EProperty, ERType, EUser, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)")
+                             "E in_state D, D name 'subscribed'), D is State, E is CWUser), "
+                             "S is IN(Affaire, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)")
 
     def test_simplified_rqlst(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
@@ -110,7 +110,7 @@
         self.failUnlessEqual(rqlst.as_string(),
                              u"Any 2 WHERE B eid %(C)s, "
                              "EXISTS(2 in_state A, B in_group D, E require_state A, "
-                             "E name 'read', E require_group D, A is State, D is EGroup, E is EPermission)")
+                             "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)")
 
     def test_optional_var(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
@@ -129,7 +129,7 @@
                              "WITH C,T BEING "
                              "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', "
                              "G require_group F, C title T, D eid %(A)s, C is Card)")
-        
+
     def test_relation_optimization(self):
         # since Card in_state State as monovalued cardinality, the in_state
         # relation used in the rql expression can be ignored and S replaced by
@@ -141,22 +141,22 @@
         self.failUnlessEqual(rqlst.as_string(),
                              u"Any C WHERE C in_state STATE, C is Card, A eid %(B)s, "
                              "EXISTS(A in_group D, E require_state STATE, "
-                             "E name 'read', E require_group D, D is EGroup, E is EPermission), "
+                             "E name 'read', E require_group D, D is CWGroup, E is CWPermission), "
                              "STATE is State")
 
     def test_unsupported_constraint_1(self):
-        # EUser doesn't have require_permission
+        # CWUser doesn't have require_permission
         trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"')
-        rqlst = parse('Any U,T WHERE U is EUser, T wf_info_for U')
+        rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U')
         self.assertRaises(Unauthorized, rewrite, rqlst, {'T': (trinfo_constraint,)}, {})
-        
+
     def test_unsupported_constraint_2(self):
         trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"')
-        rqlst = parse('Any U,T WHERE U is EUser, T wf_info_for U')
+        rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U')
         rewrite(rqlst, {'T': (trinfo_constraint, 'X wf_info_for Y, Y in_group G, G name "managers"')}, {})
         self.failUnlessEqual(rqlst.as_string(),
-                             u"Any U,T WHERE U is EUser, T wf_info_for U, "
-                             "EXISTS(U in_group B, B name 'managers', B is EGroup), T is TrInfo")
+                             u"Any U,T WHERE U is CWUser, T wf_info_for U, "
+                             "EXISTS(U in_group B, B name 'managers', B is CWGroup), T is TrInfo")
 
     def test_unsupported_constraint_3(self):
         self.skip('raise unauthorized for now')
@@ -165,23 +165,23 @@
         rewrite(rqlst, {'T': (trinfo_constraint, 'X in_group G, G name "managers"')}, {})
         self.failUnlessEqual(rqlst.as_string(),
                              u'XXX dunno what should be generated')
-        
+
     def test_add_ambiguity_exists(self):
         constraint = ('X concerne Y')
         rqlst = parse('Affaire X')
         rewrite(rqlst, {'X': (constraint,)}, {})
         self.failUnlessEqual(rqlst.as_string(),
                              u"Any X WHERE X is Affaire, (((EXISTS(X concerne A, A is Division)) OR (EXISTS(X concerne D, D is SubDivision))) OR (EXISTS(X concerne C, C is Societe))) OR (EXISTS(X concerne B, B is Note))")
-        
+
     def test_add_ambiguity_outerjoin(self):
         constraint = ('X concerne Y')
         rqlst = parse('Any X,C WHERE X? documented_by C')
         rewrite(rqlst, {'X': (constraint,)}, {})
         # ambiguity are kept in the sub-query, no need to be resolved using OR
         self.failUnlessEqual(rqlst.as_string(),
-                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)") 
-       
-        
-        
+                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)")
+
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_schemaserial.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_schemaserial.py	Thu May 14 12:50:34 2009 +0200
@@ -16,22 +16,22 @@
 schema = loader.load(config)
 
 from cubicweb.server.schemaserial import *
-    
+
 class Schema2RQLTC(TestCase):
-        
+
     def test_eschema2rql1(self):
-        self.assertListEquals(list(eschema2rql(schema.eschema('EFRDef'))),
+        self.assertListEquals(list(eschema2rql(schema.eschema('CWAttribute'))),
                               [
-            ('INSERT EEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
+            ('INSERT CWEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
              {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the application schema',
-              'meta': True, 'name': u'EFRDef', 'final': False})
+              'meta': True, 'name': u'CWAttribute', 'final': False})
             ])
-        
+
     def test_eschema2rql2(self):
         self.assertListEquals(list(eschema2rql(schema.eschema('String'))), [
-                ('INSERT EEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
+                ('INSERT CWEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
                  {'description': u'', 'final': True, 'meta': True, 'name': u'String'})])
-    
+
     def test_eschema2rql_specialization(self):
         self.assertListEquals(list(specialize2rql(schema)),
                               [
@@ -39,86 +39,88 @@
                  {'x': 'Division', 'et': 'Societe'}),
                 ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
                  {'x': 'SubDivision', 'et': 'Division'})])
-        
+
     def test_rschema2rql1(self):
         self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'))),
                              [
-            ('INSERT ERType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
+            ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
              {'description': u'link a relation definition to its relation type', 'meta': True, 'symetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'ERType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'EFRDef'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is ENFRDef',
-             {'rt': 'relation_type', 'oe': 'ERType', 'ctname': u'RQLConstraint', 'se': 'EFRDef', 'value': u'O final TRUE'}),
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'ERType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'ENFRDef'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is ENFRDef',
-             {'rt': 'relation_type', 'oe': 'ERType', 'ctname': u'RQLConstraint', 'se': 'ENFRDef', 'value': u'O final FALSE'}),
+
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
+              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
+             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u'O final FALSE'}),
+
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
+              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWAttribute'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
+             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u'O final TRUE'}),
             ])
-        
+
     def test_rschema2rql2(self):
-        expected = [
-            ('INSERT ERType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'meta': True, 'symetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'EEType'}),
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'ERType'}),
-            
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'EGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'EEType'}),
-            ('INSERT ENFRDef X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'EGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'ERType'}),
-            ]
-        for i, (rql, args) in enumerate(rschema2rql(schema.rschema('add_permission'))):
-            yield self.assertEquals, (rql, args), expected[i]
-        
+        self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
+                              [
+            ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'meta': True, 'symetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
+
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWEType'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWRType'}),
+
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWEType'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWRType'}),
+            ])
+
     def test_rschema2rql3(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))), 
+        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))),
                              [
-            ('INSERT ERType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
+            ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
              {'description': u'', 'meta': False, 'symetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
 
-            ('INSERT EFRDef X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'EFRDef'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is EFRDef',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'EFRDef', 'value': u'max=2'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is EFRDef',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'EFRDef', 'value': u"u'?1', u'11', u'??', u'1?'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWRelation'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWRelation', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWRelation', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
 
-            ('INSERT EFRDef X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'ENFRDef'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is EFRDef',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'ENFRDef', 'value': u'max=2'}),
-            ('INSERT EConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is EFRDef',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'ENFRDef', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWAttribute'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11', u'??', u'1?'"}),
             ])
-        
+
 
     def test_updateeschema2rql1(self):
-        self.assertListEquals(list(updateeschema2rql(schema.eschema('EFRDef'))),
-                              [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is EEType, X name %(et)s',
-                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the application schema', 'meta': True, 'et': 'EFRDef', 'final': False, 'name': u'EFRDef'}),
+        self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'))),
+                              [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
+                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the application schema', 'meta': True, 'et': 'CWAttribute', 'final': False, 'name': u'CWAttribute'}),
                                ])
-        
+
     def test_updateeschema2rql2(self):
         self.assertListEquals(list(updateeschema2rql(schema.eschema('String'))),
-                              [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is EEType, X name %(et)s',
+                              [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
                                 {'description': u'', 'meta': True, 'et': 'String', 'final': True, 'name': u'String'})
                                ])
-        
+
     def test_updaterschema2rql1(self):
         self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'))),
                              [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is ERType, X name %(rt)s',
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
              {'rt': 'relation_type', 'symetric': False,
               'description': u'link a relation definition to its relation type',
               'meta': True, 'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
             ])
-        
+
     def test_updaterschema2rql2(self):
         expected = [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is ERType, X name %(rt)s',
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
              {'rt': 'add_permission', 'symetric': False,
               'description': u'core relation giving to a group the permission to add an entity or relation type',
               'meta': True, 'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'})
@@ -133,44 +135,44 @@
         'guests': 2,
         'owners': 3,
         }
-    
+
     def test_eperms2rql1(self):
-        self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.eschema('EEType'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is EEType, X name "EEType", Y eid 2',
-                               'SET X read_permission Y WHERE X is EEType, X name "EEType", Y eid 0',
-                               'SET X read_permission Y WHERE X is EEType, X name "EEType", Y eid 1',
-                               'SET X add_permission Y WHERE X is EEType, X name "EEType", Y eid 0',
-                               'SET X update_permission Y WHERE X is EEType, X name "EEType", Y eid 0',
-                               'SET X update_permission Y WHERE X is EEType, X name "EEType", Y eid 3',
-                               'SET X delete_permission Y WHERE X is EEType, X name "EEType", Y eid 0',
+        self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
+                              ['SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 2',
+                               'SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
+                               'SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 1',
+                               'SET X add_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
+                               'SET X update_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
+                               'SET X update_permission Y WHERE X is CWEType, X name "CWEType", Y eid 3',
+                               'SET X delete_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
                                ])
-        
+
     def test_rperms2rql2(self):
         self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('read_permission'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is ERType, X name "read_permission", Y eid 2',
-                               'SET X read_permission Y WHERE X is ERType, X name "read_permission", Y eid 0',
-                               'SET X read_permission Y WHERE X is ERType, X name "read_permission", Y eid 1',
-                               'SET X add_permission Y WHERE X is ERType, X name "read_permission", Y eid 0',
-                               'SET X delete_permission Y WHERE X is ERType, X name "read_permission", Y eid 0',
+                              ['SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 2',
+                               'SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
+                               'SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 1',
+                               'SET X add_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
+                               'SET X delete_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
                                ])
-        
+
     def test_rperms2rql3(self):
         self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('name'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is ERType, X name "name", Y eid 2',
-                               'SET X read_permission Y WHERE X is ERType, X name "name", Y eid 0',
-                               'SET X read_permission Y WHERE X is ERType, X name "name", Y eid 1',
-                               'SET X add_permission Y WHERE X is ERType, X name "name", Y eid 2',
-                               'SET X add_permission Y WHERE X is ERType, X name "name", Y eid 0',
-                               'SET X add_permission Y WHERE X is ERType, X name "name", Y eid 1',
-                               'SET X delete_permission Y WHERE X is ERType, X name "name", Y eid 2',
-                               'SET X delete_permission Y WHERE X is ERType, X name "name", Y eid 0',
-                               'SET X delete_permission Y WHERE X is ERType, X name "name", Y eid 1',
+                              ['SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 2',
+                               'SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 0',
+                               'SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 1',
+                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 2',
+                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 0',
+                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 1',
+                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 2',
+                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 0',
+                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 1',
                                ])
-        
+
     #def test_perms2rql(self):
     #    self.assertListEquals(perms2rql(schema, self.GROUP_MAPPING),
-    #                         ['INSERT EEType X: X name 'Societe', X final FALSE'])
-        
+    #                         ['INSERT CWEType X: X name 'Societe', X final FALSE'])
+
 
 
 if __name__ == '__main__':
--- a/server/test/unittest_security.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_security.py	Thu May 14 12:50:34 2009 +0200
@@ -15,15 +15,15 @@
         self.create_user('iaminusersgrouponly')
         self.readoriggroups = self.schema['Personne'].get_groups('read')
         self.addoriggroups = self.schema['Personne'].get_groups('add')
-        
+
     def tearDown(self):
         RepositoryBasedTC.tearDown(self)
         self.schema['Personne'].set_groups('read', self.readoriggroups)
         self.schema['Personne'].set_groups('add', self.addoriggroups)
 
-        
+
 class LowLevelSecurityFunctionTC(BaseSecurityTC):
-    
+
     def test_check_read_access(self):
         rql = u'Personne U where U nom "managers"'
         rqlst = self.repo.querier._rqlhelper.parse(rql).children[0]
@@ -38,26 +38,26 @@
                           check_read_access,
                           self.schema, cnx.user(self.current_session()), rqlst, solution)
         self.assertRaises(Unauthorized, cu.execute, rql)
-            
+
     def test_upassword_not_selectable(self):
         self.assertRaises(Unauthorized,
-                          self.execute, 'Any X,P WHERE X is EUser, X upassword P')
+                          self.execute, 'Any X,P WHERE X is CWUser, X upassword P')
         self.rollback()
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
         self.assertRaises(Unauthorized,
-                          cu.execute, 'Any X,P WHERE X is EUser, X upassword P')
-        
-    
+                          cu.execute, 'Any X,P WHERE X is CWUser, X upassword P')
+
+
 class SecurityTC(BaseSecurityTC):
-    
+
     def setUp(self):
         BaseSecurityTC.setUp(self)
         # implicitly test manager can add some entities
         self.execute("INSERT Affaire X: X sujet 'cool'")
         self.execute("INSERT Societe X: X nom 'logilab'")
         self.execute("INSERT Personne X: X nom 'bidule'")
-        self.execute('INSERT EGroup X: X name "staff"')
+        self.execute('INSERT CWGroup X: X name "staff"')
         self.commit()
 
     def test_insert_security(self):
@@ -66,7 +66,7 @@
         cu.execute("INSERT Personne X: X nom 'bidule'")
         self.assertRaises(Unauthorized, cnx.commit)
         self.assertEquals(cu.execute('Personne X').rowcount, 1)
-        
+
     def test_insert_rql_permission(self):
         # test user can only add une affaire related to a societe he owns
         cnx = self.login('iaminusersgrouponly')
@@ -82,7 +82,7 @@
         cu.execute("INSERT Societe X: X nom 'chouette'")
         cu.execute("SET A concerne S WHERE A sujet 'cool', S nom 'chouette'")
         cnx.commit()
-        
+
     def test_update_security_1(self):
         cnx = self.login('anon')
         cu = cnx.cursor()
@@ -91,7 +91,7 @@
         self.assertRaises(Unauthorized, cnx.commit)
         self.restore_connection()
         self.assertEquals(self.execute('Personne X WHERE X nom "bidulechouette"').rowcount, 0)
-        
+
     def test_update_security_2(self):
         cnx = self.login('anon')
         cu = cnx.cursor()
@@ -109,7 +109,7 @@
         cu.execute("INSERT Personne X: X nom 'biduuule'")
         cu.execute("INSERT Societe X: X nom 'looogilab'")
         cu.execute("SET X travaille S WHERE X nom 'biduuule', S nom 'looogilab'")
-        
+
     def test_update_rql_permission(self):
         self.execute("SET A concerne S WHERE A is Affaire, S is Societe")
         self.commit()
@@ -121,32 +121,32 @@
         cnx.commit()
         # to actually get Unauthorized exception, try to update an entity we can read
         cu.execute("SET X nom 'toto' WHERE X is Societe")
-        self.assertRaises(Unauthorized, cnx.commit)        
+        self.assertRaises(Unauthorized, cnx.commit)
         cu.execute("INSERT Affaire X: X sujet 'pascool'")
         cu.execute("INSERT Societe X: X nom 'chouette'")
         cu.execute("SET A concerne S WHERE A sujet 'pascool', S nom 'chouette'")
         cu.execute("SET X sujet 'habahsicestcool' WHERE X sujet 'pascool'")
         cnx.commit()
-    
+
     def test_delete_security(self):
         # FIXME: sample below fails because we don't detect "owner" can't delete
         # user anyway, and since no user with login == 'bidule' exists, no
         # exception is raised
         #user._groups = {'guests':1}
         #self.assertRaises(Unauthorized,
-        #                  self.o.execute, user, "DELETE EUser X WHERE X login 'bidule'")
+        #                  self.o.execute, user, "DELETE CWUser X WHERE X login 'bidule'")
         # check local security
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
-        self.assertRaises(Unauthorized, cu.execute, "DELETE EGroup Y WHERE Y name 'staff'")
-        
+        self.assertRaises(Unauthorized, cu.execute, "DELETE CWGroup Y WHERE Y name 'staff'")
+
     def test_delete_rql_permission(self):
         self.execute("SET A concerne S WHERE A is Affaire, S is Societe")
         self.commit()
         # test user can only dele une affaire related to a societe he owns
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
-        # this won't actually do anything since the selection query won't return anything        
+        # this won't actually do anything since the selection query won't return anything
         cu.execute("DELETE Affaire X")
         cnx.commit()
         # to actually get Unauthorized exception, try to delete an entity we can read
@@ -227,7 +227,7 @@
         self.assertRaises(Unauthorized, cnx.commit)
 
     # read security test
-    
+
     def test_read_base(self):
         self.schema['Personne'].set_groups('read', ('users', 'managers'))
         cnx = self.login('anon')
@@ -256,7 +256,7 @@
         self.assertEquals(rset.rows, [[aff2]])
         rset = cu.execute('Affaire X WHERE NOT X eid %(x)s', {'x': aff2}, 'x')
         self.assertEquals(rset.rows, [])
-        
+
     def test_read_erqlexpr_has_text1(self):
         aff1 = self.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
         card1 = self.execute("INSERT Card X: X title 'cool'")[0][0]
@@ -286,7 +286,7 @@
         rset = cu.execute('Any N WHERE N has_text "bidule"')
         self.assertEquals(len(rset.rows), 1, rset.rows)
         rset = cu.execute('Any N WITH N BEING (Any N WHERE N has_text "bidule")')
-        self.assertEquals(len(rset.rows), 1, rset.rows)        
+        self.assertEquals(len(rset.rows), 1, rset.rows)
 
     def test_read_erqlexpr_optional_rel(self):
         self.execute("INSERT Personne X: X nom 'bidule'")
@@ -304,7 +304,7 @@
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
         rset = cu.execute('Any COUNT(X) WHERE X is Affaire')
-        self.assertEquals(rset.rows, [[0]])        
+        self.assertEquals(rset.rows, [[0]])
         aff2 = cu.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
         soc1 = cu.execute("INSERT Societe X: X nom 'chouette'")[0][0]
         cu.execute("SET A concerne S WHERE A is Affaire, S is Societe")
@@ -320,7 +320,7 @@
         values = dict(rset)
         self.assertEquals(values['Affaire'], 1)
         self.assertEquals(values['Societe'], 2)
-        
+
 
     def test_attribute_security(self):
         # only managers should be able to edit the 'test' attribute of Personne entities
@@ -343,7 +343,7 @@
         cu.execute('SET X web "http://www.logilab.org" WHERE X eid %(x)s', {'x': eid}, 'x')
         cnx.commit()
         cnx.close()
-        
+
     def test_attribute_security_rqlexpr(self):
         # Note.para attribute editable by managers or if the note is in "todo" state
         eid = self.execute("INSERT Note X: X para 'bidule', X in_state S WHERE S name 'done'")[0][0]
@@ -369,11 +369,11 @@
 
     def test_attribute_read_security(self):
         # anon not allowed to see users'login, but they can see users
-        self.repo.schema['EUser'].set_groups('read', ('guests', 'users', 'managers'))
+        self.repo.schema['CWUser'].set_groups('read', ('guests', 'users', 'managers'))
         self.repo.schema['login'].set_groups('read', ('users', 'managers'))
         cnx = self.login('anon')
         cu = cnx.cursor()
-        rset = cu.execute('EUser X')
+        rset = cu.execute('CWUser X')
         self.failUnless(rset)
         x = rset.get_entity(0, 0)
         self.assertEquals(x.login, None)
@@ -384,10 +384,10 @@
         self.failUnless(x.creation_date)
         cnx.rollback()
 
-        
+
 class BaseSchemaSecurityTC(BaseSecurityTC):
     """tests related to the base schema permission configuration"""
-        
+
     def test_user_can_delete_object_he_created(self):
         # even if some other user have changed object'state
         cnx = self.login('iaminusersgrouponly')
@@ -400,7 +400,7 @@
         self.execute('SET X in_state S WHERE X ref "ARCT01", S name "ben non"')
         self.commit()
         self.assertEquals(len(self.execute('TrInfo X WHERE X wf_info_for A, A ref "ARCT01"')),
-                          2) 
+                          2)
         self.assertEquals(len(self.execute('TrInfo X WHERE X wf_info_for A, A ref "ARCT01",'
                                            'X owned_by U, U login "admin"')),
                           1) # TrInfo at the above state change
@@ -420,25 +420,25 @@
         # anonymous user can only read itself
         rset = cu.execute('Any L WHERE X owned_by U, U login L')
         self.assertEquals(rset.rows, [['anon']])
-        rset = cu.execute('EUser X')
+        rset = cu.execute('CWUser X')
         self.assertEquals(rset.rows, [[anon.eid]])
         # anonymous user can read groups (necessary to check allowed transitions for instance)
-        self.assert_(cu.execute('EGroup X'))
+        self.assert_(cu.execute('CWGroup X'))
         # should only be able to read the anonymous user, not another one
         origuser = self.session.user
-        self.assertRaises(Unauthorized, 
-                          cu.execute, 'EUser X WHERE X eid %(x)s', {'x': origuser.eid}, 'x')
+        self.assertRaises(Unauthorized,
+                          cu.execute, 'CWUser X WHERE X eid %(x)s', {'x': origuser.eid}, 'x')
         # nothing selected, nothing updated, no exception raised
         #self.assertRaises(Unauthorized,
         #                  cu.execute, 'SET X login "toto" WHERE X eid %(x)s',
         #                  {'x': self.user.eid})
-        
-        rset = cu.execute('EUser X WHERE X eid %(x)s', {'x': anon.eid}, 'x')
+
+        rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid}, 'x')
         self.assertEquals(rset.rows, [[anon.eid]])
         # but can't modify it
         cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
         self.assertRaises(Unauthorized, cnx.commit)
-    
+
     def test_in_group_relation(self):
         cnx = self.login('iaminusersgrouponly')
         cu = cnx.cursor()
@@ -454,7 +454,7 @@
         cu = cnx.cursor()
         rql = u"SET X owned_by U WHERE U login 'iaminusersgrouponly', X is Personne"
         self.assertRaises(Unauthorized, cu.execute, rql)
-        
+
     def test_bookmarked_by_guests_security(self):
         beid1 = self.execute('INSERT Bookmark B: B path "?vid=manage", B title "manage"')[0][0]
         beid2 = self.execute('INSERT Bookmark B: B path "?vid=index", B title "index", B bookmarked_by U WHERE U login "anon"')[0][0]
@@ -475,7 +475,7 @@
         self.assertRaises(Unauthorized,
                           cu.execute, 'SET B bookmarked_by U WHERE U eid %(x)s, B eid %(b)s',
                           {'x': anoneid, 'b': beid1}, 'x')
-        
+
 
     def test_ambigous_ordered(self):
         cnx = self.login('anon')
@@ -494,7 +494,7 @@
         # needed to avoid check_perm error
         session.set_pool()
         # needed to remove rql expr granting update perm to the user
-        self.schema['Affaire'].set_rqlexprs('update', ()) 
+        self.schema['Affaire'].set_rqlexprs('update', ())
         self.assertRaises(Unauthorized,
                           self.schema['Affaire'].check_perm, session, 'update', eid)
         cu = cnx.cursor()
@@ -506,6 +506,6 @@
         # the best would probably ValidationError if the transition doesn't exist
         # from the current state but Unauthorized if it exists but user can't pass it
         self.assertRaises(ValidationError, cu.execute, rql, {'x': cnx.user(self.current_session()).eid}, 'x')
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_session.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_session.py	Thu May 14 12:50:34 2009 +0200
@@ -6,12 +6,12 @@
     def __init__(self, name):
         self.name = name
         self.children = []
-        
+
     def get_type(self, solution, args=None):
         return solution[self.name]
     def as_string(self):
         return self.name
-    
+
 class Function:
     def __init__(self, name, varname):
         self.name = name
@@ -21,9 +21,9 @@
 
 class MakeDescriptionTC(TestCase):
     def test_known_values(self):
-        solution = {'A': 'Int', 'B': 'EUser'}
+        solution = {'A': 'Int', 'B': 'CWUser'}
         self.assertEquals(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
-                          ['Int','EUser'])
+                          ['Int','CWUser'])
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_sqlutils.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_sqlutils.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,6 @@
 """
 
 import sys
-from mx.DateTime import now
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -21,12 +20,12 @@
     def test_init(self):
         o = SQLAdapterMixIn(BASE_CONFIG)
         self.assertEquals(o.encoding, 'UTF-8')
-        
+
     def test_init_encoding(self):
         config = BASE_CONFIG.copy()
         config['db-encoding'] = 'ISO-8859-1'
         o = SQLAdapterMixIn(config)
         self.assertEquals(o.encoding, 'ISO-8859-1')
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_ssplanner.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_ssplanner.py	Thu May 14 12:50:34 2009 +0200
@@ -8,7 +8,7 @@
 class SSPlannerTC(BasePlannerTC):
     repo = repo
     _test = test_plan
-    
+
     def setUp(self):
         BasePlannerTC.setUp(self)
         self.planner = SSPlanner(self.o.schema, self.o._rqlhelper)
@@ -21,40 +21,40 @@
         self._test('Any XN ORDERBY XN WHERE X name XN',
                    [('OneFetchStep', [('Any XN ORDERBY XN WHERE X name XN',
                                        [{'X': 'Basket', 'XN': 'String'},
-                                        {'X': 'ECache', 'XN': 'String'},
-                                        {'X': 'EConstraintType', 'XN': 'String'},
-                                        {'X': 'EEType', 'XN': 'String'},
-                                        {'X': 'EGroup', 'XN': 'String'},
-                                        {'X': 'EPermission', 'XN': 'String'},
-                                        {'X': 'ERType', 'XN': 'String'},
+                                        {'X': 'CWCache', 'XN': 'String'},
+                                        {'X': 'CWConstraintType', 'XN': 'String'},
+                                        {'X': 'CWEType', 'XN': 'String'},
+                                        {'X': 'CWGroup', 'XN': 'String'},
+                                        {'X': 'CWPermission', 'XN': 'String'},
+                                        {'X': 'CWRType', 'XN': 'String'},
                                         {'X': 'File', 'XN': 'String'},
                                         {'X': 'Folder', 'XN': 'String'},
                                         {'X': 'Image', 'XN': 'String'},
                                         {'X': 'State', 'XN': 'String'},
                                         {'X': 'Tag', u'XN': 'String'},
                                         {'X': 'Transition', 'XN': 'String'}])],
-                     None, None, 
+                     None, None,
                      [self.system], None, [])])
-    
+
     def test_groupeded_ambigous_sol(self):
         self._test('Any XN,COUNT(X) GROUPBY XN WHERE X name XN',
                    [('OneFetchStep', [('Any XN,COUNT(X) GROUPBY XN WHERE X name XN',
                                        [{'X': 'Basket', 'XN': 'String'},
-                                        {'X': 'ECache', 'XN': 'String'},
-                                        {'X': 'EConstraintType', 'XN': 'String'},
-                                        {'X': 'EEType', 'XN': 'String'},
-                                        {'X': 'EGroup', 'XN': 'String'},
-                                        {'X': 'EPermission', 'XN': 'String'},
-                                        {'X': 'ERType', 'XN': 'String'},
+                                        {'X': 'CWCache', 'XN': 'String'},
+                                        {'X': 'CWConstraintType', 'XN': 'String'},
+                                        {'X': 'CWEType', 'XN': 'String'},
+                                        {'X': 'CWGroup', 'XN': 'String'},
+                                        {'X': 'CWPermission', 'XN': 'String'},
+                                        {'X': 'CWRType', 'XN': 'String'},
                                         {'X': 'File', 'XN': 'String'},
                                         {'X': 'Folder', 'XN': 'String'},
                                         {'X': 'Image', 'XN': 'String'},
                                         {'X': 'State', 'XN': 'String'},
                                         {'X': 'Tag', u'XN': 'String'},
                                         {'X': 'Transition', 'XN': 'String'}])],
-                     None, None, 
+                     None, None,
                      [self.system], None, [])])
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/test/unittest_tools.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/test/unittest_tools.py	Thu May 14 12:50:34 2009 +0200
@@ -6,6 +6,6 @@
         import cubicweb.server.server
         import cubicweb.server.checkintegrity
         import cubicweb.server.serverctl
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/utils.py	Thu May 14 12:50:14 2009 +0200
+++ b/server/utils.py	Thu May 14 12:50:34 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"
@@ -46,7 +46,7 @@
             for item in seqin[0]:
                 newcomb = comb + [item]     # add next item to current combination
                 # call rloop w/ remaining seqs, newcomb
-                for item in rloop(seqin[1:], newcomb):   
+                for item in rloop(seqin[1:], newcomb):
                     yield item          # seqs and newcomb
         else:                           # processing last sequence
             yield comb                  # comb finished, add to list
@@ -95,7 +95,7 @@
                 self.start()
         self.func = auto_restart_func
         self.name = func.__name__
-        
+
     def start(self):
         self._t = Timer(self.interval, self.func)
         self._t.start()
@@ -117,10 +117,10 @@
                 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__
-        
+
     def start(self):
         self.running_threads.append(self)
         Thread.start(self)
@@ -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/setup.py	Thu May 14 12:50:14 2009 +0200
+++ b/setup.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # pylint: disable-msg=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# Copyright (c) 2003 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This program is free software; you can redistribute it and/or modify it under
@@ -56,7 +56,7 @@
 
 BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog')
 IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc')
-    
+
 
 def ensure_scripts(linux_scripts):
     """
@@ -152,7 +152,7 @@
             for directory in include_dirs:
                 dest = join(self.install_dir, base, directory)
                 export(directory, dest)
-        
+
 def install(**kwargs):
     """setup entry point"""
     if subpackage_of:
@@ -177,6 +177,6 @@
                  cmdclass={'install_lib': MyInstallLib},
                  **kwargs
                  )
-            
+
 if __name__ == '__main__' :
     install()
--- a/skeleton/MANIFEST.in	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/MANIFEST.in	Thu May 14 12:50:34 2009 +0200
@@ -2,4 +2,4 @@
 
 recursive-include data external_resources *.gif *.png *.css *.ico *.js
 recursive-include i18n *.pot *.po
-recursive-include migration *.sql *.py depends.map
+recursive-include migration *.py
--- a/skeleton/__pkginfo__.py.tmpl	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/__pkginfo__.py.tmpl	Thu May 14 12:50:34 2009 +0200
@@ -31,7 +31,8 @@
 def listdir(dirpath):
     return [join(dirpath, fname) for fname in _listdir(dirpath)
             if fname[0] != '.' and not fname.endswith('.pyc')
-            and not fname.endswith('~')]
+            and not fname.endswith('~')
+            and not isdir(join(dirpath, fname))]¶
 
 from glob import glob
 try:
--- a/skeleton/debian/rules.tmpl	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/debian/rules.tmpl	Thu May 14 12:50:34 2009 +0200
@@ -5,12 +5,12 @@
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 build: build-stamp
-build-stamp: 
+build-stamp:
 	dh_testdir
 	python setup.py -q build
 	touch build-stamp
 
-clean: 
+clean:
 	dh_testdir
 	dh_testroot
 	rm -f build-stamp configure-stamp
@@ -24,6 +24,8 @@
 	dh_clean -k
 	dh_installdirs -i
 	python setup.py -q install --no-compile --prefix=debian/%(distname)s/usr/
+	# remove generated .egg-info file
+	rm -rf debian/%(distname)s/usr/lib/python*
 
 
 # Build architecture-independent files here.
@@ -39,13 +41,13 @@
 	dh_compress -i -X.py -X.ini -X.xml -Xtest
 	dh_fixperms -i
 	dh_installdeb -i
-	dh_gencontrol -i 
+	dh_gencontrol -i
 	dh_md5sums -i
 	dh_builddeb -i
 
 
 # Build architecture-dependent files here.
-binary-arch: 
+binary-arch:
 
-binary: binary-indep 
+binary: binary-indep
 .PHONY: build clean binary-arch binary-indep binary
--- a/skeleton/migration/precreate.py	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/migration/precreate.py	Thu May 14 12:50:34 2009 +0200
@@ -1,3 +1,3 @@
 # Instructions here will be read before reading the schema
 # You could create your own groups here, like in :
-#   add_entity('EGroup', name=u'mygroup')
+#   add_entity('CWGroup', name=u'mygroup')
--- a/skeleton/setup.py	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/setup.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # pylint: disable-msg=W0404,W0622,W0704,W0613,W0152
-# Copyright (c) 2003-2004 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This program is free software; you can redistribute it and/or modify it under
@@ -45,6 +45,6 @@
                  url=web,
                  data_files=data_files,
                  **kwargs)
-            
+
 if __name__ == '__main__' :
     install()
--- a/skeleton/test/realdb_test_CUBENAME.py	Thu May 14 12:50:14 2009 +0200
+++ b/skeleton/test/realdb_test_CUBENAME.py	Thu May 14 12:50:34 2009 +0200
@@ -18,7 +18,7 @@
     def test_all_primaries(self):
         for rset in self.iter_individual_rsets(limit=50):
             yield self.view, 'primary', rset, rset.req.reset_headers()
-    
+
     ## startup views
     def test_startup_views(self):
         for vid in self.list_startup_views():
--- a/sobjects/email.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/email.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """hooks to ensure use_email / primary_email relations consistency
 
 :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,50 +14,51 @@
     already setting the relation
     """
     rtype = 'use_email'
+    fromeid = toeid = None # make pylint happy
 
     def condition(self):
         """check entity has use_email set for the email address"""
         return not self.session.unsafe_execute(
             'Any X WHERE X eid %(x)s, X use_email Y, Y eid %(y)s',
             {'x': self.fromeid, 'y': self.toeid}, 'x')
-    
+
     def precommit_event(self):
         session = self.session
         if self.condition():
             session.unsafe_execute(
                 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
                 {'x': self.fromeid, 'y': self.toeid}, 'x')
-    
+
 class SetPrimaryEmailRelationOp(SetUseEmailRelationOp):
     rtype = 'primary_email'
-    
+
     def condition(self):
         """check entity has no primary_email set"""
         return not self.session.unsafe_execute(
             'Any X WHERE X eid %(x)s, X primary_email Y',
             {'x': self.fromeid}, 'x')
 
-    
+
 class SetPrimaryEmailHook(Hook):
     """notify when a bug or story or version has its state modified"""
     events = ('after_add_relation',)
     accepts = ('use_email',)
-    
+
     def call(self, session, fromeid, rtype, toeid):
         subjtype = session.describe(fromeid)[0]
         eschema = self.vreg.schema[subjtype]
         if 'primary_email' in eschema.subject_relations():
-            SetPrimaryEmailRelationOp(session, vreg=self.vreg, 
+            SetPrimaryEmailRelationOp(session, vreg=self.vreg,
                                       fromeid=fromeid, toeid=toeid)
 
 class SetUseEmailHook(Hook):
     """notify when a bug or story or version has its state modified"""
     events = ('after_add_relation',)
     accepts = ('primary_email',)
-    
+
     def call(self, session, fromeid, rtype, toeid):
         subjtype = session.describe(fromeid)[0]
         eschema = self.vreg.schema[subjtype]
         if 'use_email' in eschema.subject_relations():
-            SetUseEmailRelationOp(session, vreg=self.vreg, 
+            SetUseEmailRelationOp(session, vreg=self.vreg,
                                   fromeid=fromeid, toeid=toeid)
--- a/sobjects/hooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/hooks.py	Thu May 14 12:50:34 2009 +0200
@@ -1,25 +1,28 @@
 """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):
+
+class AddUpdateCWUserHook(Hook):
     """ensure user logins are stripped"""
     events = ('before_add_entity', 'before_update_entity',)
-    accepts = ('EUser',)
-    
+    accepts = ('CWUser',)
+
     def call(self, session, entity):
         if 'login' in entity and entity['login']:
             entity['login'] = entity['login'].strip()
 
 
 class AutoDeleteBookmark(PreCommitOperation):
+    beid = None # make pylint happy
     def precommit_event(self):
         session = self.session
         if not self.beid in session.query_data('pendingeids', ()):
@@ -27,11 +30,33 @@
                                           {'x': self.beid}, 'x'):
                 session.unsafe_execute('DELETE Bookmark X WHERE X eid %(x)s',
                                        {'x': self.beid}, 'x')
-        
+
 class DelBookmarkedByHook(Hook):
     """ensure user logins are stripped"""
     events = ('after_delete_relation',)
     accepts = ('bookmarked_by',)
-    
+
     def call(self, session, subj, rtype, obj):
         AutoDeleteBookmark(session, beid=subj)
+
+
+class TidyHtmlFields(Hook):
+    """tidy HTML in rich text strings"""
+    events = ('before_add_entity', 'before_update_entity')
+    accepts = ('Any',)
+
+    def call(self, session, entity):
+        metaattrs = entity.e_schema.meta_attributes()
+        for metaattr, (metadata, attr) in metaattrs.iteritems():
+            if metadata == 'format':
+                try:
+                    value = entity[attr]
+                except KeyError:
+                    continue # no text to tidy
+                if isinstance(value, unicode): # filter out None and Binary
+                    if self.event == 'before_add_entity':
+                        fmt = entity.get(metaattr)
+                    else:
+                        fmt = entity.get_value(metaattr)
+                    if fmt == 'text/html':
+                        entity[attr] = soup2xhtml(value, session.encoding)
--- a/sobjects/notification.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/notification.py	Thu May 14 12:50:34 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,12 +36,10 @@
     email addresses specified in the configuration are used
     """
     id = 'recipients_finder'
-    __registerer__ = accepts_registerer
-    __selectors__ = (accept,)
-    accepts = ('Any',)
-    user_rql = ('Any X,E,A WHERE X is EUser, X in_state S, S name "activated",'
+    __select__ = yes()
+    user_rql = ('Any X,E,A WHERE X is CWUser, X in_state S, S name "activated",'
                 'X primary_email E, E address A')
-    
+
     def recipients(self):
         mode = self.config['default-recipients-mode']
         if mode == 'users':
@@ -58,7 +55,7 @@
             dests = []
         return dests
 
-    
+
 # hooks #######################################################################
 
 class RenderAndSendNotificationView(PreCommitOperation):
@@ -67,12 +64,12 @@
         if self.view.rset[0][0] in self.session.query_data('pendingeids', ()):
             return # entity added and deleted in the same transaction
         self.view.render_and_send(**getattr(self, 'viewargs', {}))
-        
+
 class StatusChangeHook(Hook):
     """notify when a workflowable entity has its state modified"""
     events = ('after_add_entity',)
     accepts = ('TrInfo',)
-    
+
     def call(self, session, entity):
         if not entity.from_state: # not a transition
             return
@@ -135,15 +132,12 @@
     * 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):
         entity = self.entity(0, 0)
         subject = self.req._(self.message)
@@ -156,7 +150,7 @@
         # req is actually a session (we are on the server side), and we have to
         # prevent nested internal session
         return self.req.actual_session().user.login
-    
+
     def context(self, **kwargs):
         entity = self.entity(0, 0)
         for key, val in kwargs.iteritems():
@@ -168,7 +162,7 @@
                        'url': entity.absolute_url(),
                        'title': entity.dc_long_title(),})
         return kwargs
-    
+
     def cell_call(self, row, col=0, **kwargs):
         self.w(self.req._(self.content) % self.context(**kwargs))
 
@@ -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
@@ -205,8 +198,9 @@
             # since the same view (eg self) may be called multiple time and we
             # need a fresh stream at each iteration, reset it explicitly
             self.w = None
-            # call dispatch before subject to set .row/.col attributes on the view :/
-            content = self.dispatch(row=0, col=0, **kwargs)
+            # XXX call render before subject to set .row/.col attributes on the
+            #     view
+            content = self.render(row=0, col=0, **kwargs)
             subject = self.subject()
             msg = format_mail(userdata, [emailaddr], content, subject,
                               config=self.config, msgid=msgid, references=refs)
@@ -244,7 +238,7 @@
     if appid != fromappid or host != gethostname():
         return None
     return values
-    
+
 
 class StatusChangeMixIn(object):
     id = 'notif_status_change'
@@ -260,10 +254,17 @@
 """)
 
 
-class ContentAddedMixIn(object):
-    """define emailcontent view for entity types for which you want to be notified
-    """
-    id = 'notif_after_add_entity' 
+###############################################################################
+# 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')
     content = """
@@ -274,32 +275,17 @@
 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())
 
-
-class CardAddedView(NormalizedTextView):
-    """get notified from new cards"""
-    accepts = ('Card',)
-    content_attr = 'synopsis'
-    
-
+NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
--- a/sobjects/supervising.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/supervising.py	Thu May 14 12:50:34 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
@@ -18,7 +19,7 @@
     events = ('before_add_relation', 'before_delete_relation',
               'after_add_entity', 'before_update_entity')
     accepts = ('Any',)
-    
+
     def call(self, session, *args):
         dest = self.config['supervising-addrs']
         if not dest: # no supervisors, don't do this for nothing...
@@ -26,24 +27,24 @@
         self.session = session
         if self._call(*args):
             SupervisionMailOp(session)
-        
+
     def _call(self, *args):
-        if self._event() == 'update_entity' and args[0].e_schema == 'EUser':
+        if self._event() == 'update_entity' and args[0].e_schema == 'CWUser':
             updated = set(args[0].iterkeys())
             if not (updated - frozenset(('eid', 'modification_date', 'last_login_time'))):
-                # don't record last_login_time update which are done 
+                # don't record last_login_time update which are done
                 # automatically at login time
                 return False
         self.session.add_query_data('pendingchanges', (self._event(), args))
         return True
-        
+
     def _event(self):
         return self.event.split('_', 1)[1]
 
 
 class EntityDeleteHook(SomethingChangedHook):
     events = ('before_delete_entity',)
-    
+
     def _call(self, eid):
         entity = self.session.entity(eid)
         try:
@@ -79,19 +80,19 @@
                 changes.remove(change)
                 if entity.from_state:
                     try:
-                        changes.remove( ('delete_relation', 
-                                         (entity.wf_info_for[0].eid, 'in_state', 
+                        changes.remove( ('delete_relation',
+                                         (entity.wf_info_for[0].eid, 'in_state',
                                           entity.from_state[0].eid)) )
                     except ValueError:
                         pass
                     try:
-                        changes.remove( ('add_relation', 
-                                         (entity.wf_info_for[0].eid, 'in_state', 
+                        changes.remove( ('add_relation',
+                                         (entity.wf_info_for[0].eid, 'in_state',
                                           entity.to_state[0].eid)) )
                     except ValueError:
                         pass
                     event = 'change_state'
-                    change = (event, 
+                    change = (event,
                               (entity.wf_info_for[0],
                                entity.from_state[0], entity.to_state[0]))
                     changes.append(change)
@@ -114,7 +115,7 @@
                 #     at entity creation time
                 elif changedescr[1] == 'in_state' and changedescr[0] in added:
                     index['add_relation'].remove(change)
-                    
+
         except KeyError:
             break
     for eid in deleted:
@@ -137,17 +138,18 @@
             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):
         return self.config['supervising-addrs']
-        
+
     def subject(self):
         return self.req._('[%s supervision] changes summary') % self.config.appid
-    
+
     def call(self, changes):
         user = self.req.actual_session().user
         self.w(self.req._('user %s has made the following change(s):\n\n')
@@ -161,30 +163,30 @@
         return {'eid': entity.eid,
                 'etype': entity.dc_type().lower(),
                 'title': entity.dc_title()}
-    
+
     def add_entity(self, entity):
         msg = self.req._('added %(etype)s #%(eid)s (%(title)s)')
         self.w(u'%s\n' % (msg % self._entity_context(entity)))
         self.w(u'  %s' % entity.absolute_url())
-            
+
     def update_entity(self, entity):
         msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)')
         self.w(u'%s\n' % (msg % self._entity_context(entity)))
         # XXX print changes
         self.w(u'  %s' % entity.absolute_url())
-            
+
     def delete_entity(self, eid, etype, title):
         msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)')
         etype = display_name(self.req, etype).lower()
         self.w(msg % locals())
-        
+
     def change_state(self, entity, fromstate, tostate):
         msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)')
         self.w(u'%s\n' % (msg % self._entity_context(entity)))
-        self.w(_('  from state %(fromstate)s to state %(tostate)s\n' % 
+        self.w(_('  from state %(fromstate)s to state %(tostate)s\n' %
                  {'fromstate': _(fromstate.name), 'tostate': _(tostate.name)}))
         self.w(u'  %s' % entity.absolute_url())
-        
+
     def _relation_context(self, fromeid, rtype, toeid):
         _ = self.req._
         session = self.req.actual_session()
@@ -200,7 +202,7 @@
                 'frometype': describe(fromeid),
                 'toeid': toeid,
                 'toetype': describe(toeid)}
-        
+
     def add_relation(self, fromeid, rtype, toeid):
         msg = self.req._('added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
         self.w(msg % self._relation_context(fromeid, rtype, toeid))
@@ -208,8 +210,8 @@
     def delete_relation(self, fromeid, rtype, toeid):
         msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
         self.w(msg % self._relation_context(fromeid, rtype, toeid))
-        
-                
+
+
 class SupervisionMailOp(SendMailOp):
     """special send email operation which should be done only once for a bunch
     of changes
@@ -217,14 +219,14 @@
     def _get_view(self):
         return self.session.vreg.select_component('supervision_notif',
                                                   self.session, None)
-        
+
     def _prepare_email(self):
         session = self.session
         config = session.vreg.config
         uinfo = {'email': config['sender-addr'],
                  'name': config['sender-name']}
         view = self._get_view()
-        content = view.dispatch(changes=session.query_data('pendingchanges'))
+        content = view.render(changes=session.query_data('pendingchanges'))
         recipients = view.recipients()
         msg = format_mail(uinfo, recipients, content, view.subject(), config=config)
         self.to_send = [(msg, recipients)]
--- a/sobjects/test/data/bootstrap_cubes	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/data/bootstrap_cubes	Thu May 14 12:50:34 2009 +0200
@@ -1,1 +1,1 @@
-comment
+card,comment
--- a/sobjects/test/data/sobjects/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/data/sobjects/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,4 @@
 from cubicweb.sobjects.notification import StatusChangeMixIn, NotificationView
 
 class UserStatusChangeView(StatusChangeMixIn, NotificationView):
-    accepts = ('EUser',)
-    
-    
+    accepts = ('CWUser',)
--- a/sobjects/test/unittest_email.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/unittest_email.py	Thu May 14 12:50:34 2009 +0200
@@ -22,7 +22,7 @@
         self.commit()
         self.assertEquals(self.execute('Any A WHERE U use_email X, U login "admin", X address A')[0][0],
                           'admin@logilab.fr')
-        
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/sobjects/test/unittest_hooks.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/unittest_hooks.py	Thu May 14 12:50:34 2009 +0200
@@ -13,7 +13,7 @@
                              {'e': u.eid})[0][0]
         self.assertEquals(tname, 'jijoe')
 
-    
+
     def test_auto_delete_bookmarks(self):
         beid = self.execute('INSERT Bookmark X: X title "hop", X path "view", X bookmarked_by U '
                             'WHERE U login "admin"')[0][0]
@@ -25,6 +25,6 @@
         self.execute('DELETE X bookmarked_by U WHERE U login "anon"')
         self.commit()
         self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': beid}, 'x'))
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/sobjects/test/unittest_notification.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/unittest_notification.py	Thu May 14 12:50:34 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):
@@ -24,7 +22,7 @@
         self.failUnlessEqual(values['eid'], '21')
         self.failUnless('timestamp' in values)
         self.failUnlessEqual(parse_message_id(msgid1[1:-1], 'anotherapp'), None)
-        
+
     def test_notimestamp(self):
         msgid1 = construct_message_id('testapp', 21, False)
         msgid2 = construct_message_id('testapp', 21, False)
@@ -41,14 +39,14 @@
         for eid in (1, 12, 123, 1234):
             msgid1 = construct_message_id('testapp', eid, 12)
             self.assertNotEquals(msgid1, '<@testapp.%s>' % gethostname())
-        
+
 
 class RecipientsFinderTC(EnvBasedTC):
     def test(self):
-        urset = self.execute('EUser X WHERE X login "admin"')
+        urset = self.execute('CWUser X WHERE X login "admin"')
         self.execute('INSERT EmailAddress X: X address "admin@logilab.fr", U primary_email X '
                      'WHERE U eid %(x)s', {'x': urset[0][0]})
-        self.execute('INSERT EProperty X: X pkey "ui.language", X value "fr", X for_user U '
+        self.execute('INSERT CWProperty X: X pkey "ui.language", X value "fr", X for_user U '
                      'WHERE U eid %(x)s', {'x': urset[0][0]})
         self.commit() # commit so that admin get its properties updated
         finder = self.vreg.select_component('recipients_finder', self.request(), urset)
@@ -59,19 +57,19 @@
         self.set_option('default-recipients-mode', 'default-dest-addrs')
         self.set_option('default-dest-addrs', 'abcd@logilab.fr, efgh@logilab.fr')
         self.assertEquals(finder.recipients(), [('abcd@logilab.fr', 'en'), ('efgh@logilab.fr', 'en')])
-        
+
 
 class StatusChangeViewsTC(EnvBasedTC):
-        
+
     def test_status_change_view(self):
         req = self.session()
         u = self.create_user('toto', req=req)
         assert u.req
         self.execute('SET X in_state S WHERE X eid %s, S name "deactivated"' % u.eid)
         v = self.vreg.select_view('notif_status_change', req, u.rset, row=0)
-        content = v.dispatch(row=0, comment='yeah',
-                             previous_state='activated',
-                             current_state='deactivated')
+        content = v.render(row=0, comment='yeah',
+                           previous_state='activated',
+                           current_state='deactivated')
         # remove date
         self.assertEquals(content,
                           '''
--- a/sobjects/test/unittest_supervising.py	Thu May 14 12:50:14 2009 +0200
+++ b/sobjects/test/unittest_supervising.py	Thu May 14 12:50:34 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
 
 
@@ -19,12 +17,12 @@
         self.execute('SET C comments B WHERE B title "une autre news !", C content "Yo !"')
         self.vreg.config.global_set_option('supervising-addrs', 'test@logilab.fr')
 
-        
+
     def test_supervision(self):
         session = self.session()
         # do some modification
-        ueid = self.execute('INSERT EUser X: X login "toto", X upassword "sosafe", X in_group G, X in_state S '
-                            'WHERE G name "users", S name "activated"')[0][0]        
+        ueid = self.execute('INSERT CWUser X: X login "toto", X upassword "sosafe", X in_group G, X in_state S '
+                            'WHERE G name "users", S name "activated"')[0][0]
         self.execute('SET X last_login_time NOW WHERE X eid %(x)s', {'x': ueid}, 'x')
         self.execute('SET X in_state S WHERE X login "anon", S name "deactivated"')
         self.execute('DELETE Card B WHERE B title "une news !"')
@@ -40,7 +38,7 @@
         view = sentops[0]._get_view()
         self.assertEquals(view.recipients(), ['test@logilab.fr'])
         self.assertEquals(view.subject(), '[data supervision] changes summary')
-        data = view.dispatch(changes=session.query_data('pendingchanges')).strip()
+        data = view.render(changes=session.query_data('pendingchanges')).strip()
         data = re.sub('#\d+', '#EID', data)
         data = re.sub('/\d+', '/EID', data)
         self.assertTextEquals('''user admin has made the following change(s):
@@ -65,9 +63,9 @@
                               data)
         # check prepared email
         op._prepare_email()
-        self.assertEquals(len(op.to_send), 1) 
+        self.assertEquals(len(op.to_send), 1)
         self.assert_(op.to_send[0][0])
-        self.assertEquals(op.to_send[0][1], ['test@logilab.fr']) 
+        self.assertEquals(op.to_send[0][1], ['test@logilab.fr'])
 
     def test_nonregr1(self):
         session = self.session()
@@ -75,6 +73,6 @@
         self.execute('SET X last_login_time NOW WHERE X eid %(x)s', {'x': session.user.eid}, 'x')
         self.commit() # no crash
 
-        
+
 if __name__ == '__main__':
     unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/bootstrap_cubes	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+card, file, tag
--- a/test/data/bootstrap_packages	Thu May 14 12:50:14 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/cubes/file/__pkginfo__.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,54 @@
+# pylint: disable-msg=W0622
+"""cubicweb-file packaging information"""
+
+distname = "cubicweb-file"
+modname = distname.split('-', 1)[1]
+
+numversion = (1, 4, 3)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+copyright = '''Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
+
+author = "Logilab"
+author_email = "contact@logilab.fr"
+web = ''
+
+short_desc = "Raw file support for the CubicWeb framework"
+long_desc = """CubicWeb is a entities / relations bases knowledge management system
+developped at Logilab.
+.
+This package provides schema and views to store files and images in cubicweb
+applications.
+.
+"""
+
+from os import listdir
+from os.path import join
+
+CUBES_DIR = join('share', 'cubicweb', 'cubes')
+try:
+    data_files = [
+        [join(CUBES_DIR, 'file'),
+         [fname for fname in listdir('.')
+          if fname.endswith('.py') and fname != 'setup.py']],
+        [join(CUBES_DIR, 'file', 'data'),
+         [join('data', fname) for fname in listdir('data')]],
+        [join(CUBES_DIR, 'file', 'wdoc'),
+         [join('wdoc', fname) for fname in listdir('wdoc')]],
+        [join(CUBES_DIR, 'file', 'views'),
+         [join('views', fname) for fname in listdir('views') if fname.endswith('.py')]],
+        [join(CUBES_DIR, 'file', 'i18n'),
+         [join('i18n', fname) for fname in listdir('i18n')]],
+        [join(CUBES_DIR, 'file', 'migration'),
+         [join('migration', fname) for fname in listdir('migration')]],
+        ]
+except OSError:
+    # we are in an installed directory
+    pass
+
+
+cube_eid = 20320
+# used packages
+__use__ = ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/mycube/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+"""mycube's __init__"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/mycube/__pkginfo__.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,1 @@
+distname = 'cubicweb-mycube'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/entities.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,15 @@
+from cubicweb.entities import AnyEntity, fetch_config
+
+class Societe(AnyEntity):
+    id = 'Societe'
+    fetch_attrs = ('nom',)
+
+class Personne(Societe):
+    """customized class forne Person entities"""
+    id = 'Personne'
+    fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
+    rest_attr = 'nom'
+
+
+class Note(AnyEntity):
+    id = 'Note'
--- a/test/data/erqlexpr_on_ertype.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/data/erqlexpr_on_ertype.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
         'delete': ('managers',),
         }
     toto = SubjectRelation('TuTu')
-    
+
 class TuTu(EntityType):
     permissions = {
         'read': ('managers',),
--- a/test/data/rqlexpr_on_ertype_read.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/data/rqlexpr_on_ertype_read.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
         'delete': ('managers',),
         }
     toto = SubjectRelation('TuTu')
-    
+
 class TuTu(EntityType):
     permissions = {
         'read': ('managers',),
--- a/test/data/rrqlexpr_on_attr.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/data/rrqlexpr_on_attr.py	Thu May 14 12:50:34 2009 +0200
@@ -6,7 +6,7 @@
         'delete': ('managers',),
         }
     attr = String()
-    
+
 class attr(RelationType):
     permissions = {
         'read': ('managers', ),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/schema.py	Thu May 14 12:50:34 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 = 'CWUser'
+    object = 'Note'
--- a/test/unittest_cwconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_cwconfig.py	Thu May 14 12:50:34 2009 +0200
@@ -11,15 +11,18 @@
 def unabsolutize(path):
     parts = path.split(os.sep)
     for i, part in reversed(tuple(enumerate(parts))):
-        if part in ('cubicweb', 'cubes', 'cubes'):
+        if part.startswith('cubicweb') or part == 'cubes':
             return '/'.join(parts[i+1:])
     raise Exception('duh? %s' % path)
-    
+
 class CubicWebConfigurationTC(TestCase):
     def setUp(self):
         self.config = ApptestConfiguration('data')
         self.config._cubes = ('email', 'file')
 
+    def tearDown(self):
+        os.environ.pop('CW_CUBES_PATH', None)
+
     def test_reorder_cubes(self):
         # jpl depends on email and file and comment
         # email depends on file
@@ -35,7 +38,7 @@
                           ('jpl', 'email', 'file'))
         self.assertEquals(self.config.reorder_cubes(('jpl', 'email', 'file')),
                           ('jpl', 'email', 'file'))
-        
+
     def test_reorder_cubes_recommends(self):
         from cubes.comment import __pkginfo__ as comment_pkginfo
         comment_pkginfo.__recommend__ = ('file',)
@@ -52,8 +55,8 @@
                               ('jpl', 'email', 'comment', 'file'))
         finally:
             comment_pkginfo.__use__ = ()
-            
-        
+
+
 #     def test_vc_config(self):
 #         vcconf = self.config.vc_config()
 #         self.assertIsInstance(vcconf['EEMAIL'], Version)
@@ -61,7 +64,7 @@
 #         self.assertEquals(vcconf['CW'], (2, 31, 2))
 #         self.assertRaises(KeyError, vcconf.__getitem__, 'CW_VERSION')
 #         self.assertRaises(KeyError, vcconf.__getitem__, 'CRM')
-        
+
     def test_expand_cubes(self):
         self.assertEquals(self.config.expand_cubes(('email', 'eblog')),
                           ['email', 'eblog', 'file'])
@@ -70,7 +73,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
@@ -100,7 +104,7 @@
         del cubes.file
         from cubes import file
         self.assertEquals(file.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'file'))])
-                                       
-                          
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_cwctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_cwctl.py	Thu May 14 12:50:34 2009 +0200
@@ -19,10 +19,10 @@
         sys.stdout = self.stream
     def tearDown(self):
         sys.stdout = sys.__stdout__
-        
+
     def test_list(self):
         from cubicweb.cwctl import ListCommand
         ListCommand().run([])
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_dbapi.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_dbapi.py	Thu May 14 12:50:34 2009 +0200
@@ -29,7 +29,7 @@
     def test_api(self):
         cnx = self.cnx
         self.assertEquals(cnx.user(None).login, 'anon')
-        self.assertEquals(cnx.describe(1), (u'EGroup', u'system', None))
+        self.assertEquals(cnx.describe(1), (u'CWGroup', u'system', None))
         self.restore_connection() # proper way to close cnx
         self.assertRaises(ConnectionError, cnx.user, None)
         self.assertRaises(ConnectionError, cnx.describe, 1)
@@ -73,7 +73,7 @@
 
 #     def test_api(self):
 #         cu = self.cursor
-#         self.assertEquals(cu.describe(1), (u'EGroup', u'system', None))
+#         self.assertEquals(cu.describe(1), (u'CWGroup', u'system', None))
 #         #cu.close()
 #         #self.assertRaises(ConnectionError, cu.describe, 1)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_entity.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,389 @@
+# -*- 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('CWUser')
+        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('CWUser')
+        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 CWUser 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 CWUser 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 CWUser 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')
+        related = [r.eid for r in e.tags]
+        self.failUnlessEqual(related, [])
+        unrelated = [r[0] for r in e.unrelated('tags', 'Personne', '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 = [r[0] for r in e.unrelated('tags', 'Personne', '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')
+        self.assertEquals(len(e.unrelated('tags', 'Personne', 'subject', limit=1)),
+                          1)
+
+    def test_new_entity_unrelated(self):
+        e = self.etype_instance('CWUser')
+        unrelated = [r[0] for r in e.unrelated('in_group', 'CWGroup', 'subject')]
+        # should be default groups but owners, i.e. managers, users, guests
+        self.assertEquals(len(unrelated), 3)
+
+    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/cwgroup/managers">*ReST*</a></p>\n')
+        e['content'] = 'du <em>html</em> <ref rql="CWUser 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=CWUser%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('CWUser 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	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_rset.py	Thu May 14 12:50:34 2009 +0200
@@ -1,30 +1,33 @@
 # 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
 
 from cubicweb.rset import NotAnEntity, ResultSet, attr_desc_iterator
 
-        
+
 def pprelcachedict(d):
     res = {}
     for k, (rset, related) in d.items():
         res[k] = sorted(v.eid for v in related)
     return sorted(res.items())
-        
+
 
 class AttrDescIteratorTC(TestCase):
     """TestCase for cubicweb.rset.attr_desc_iterator"""
-    
+
     def test_relations_description(self):
         """tests relations_description() function"""
         queries = {
-            'Any U,L,M where U is EUser, U login L, U mail M' : [(1, 'login', 'subject'), (2, 'mail', 'subject')],
-            'Any U,L,M where U is EUser, L is Foo, U mail M' : [(2, 'mail', 'subject')],
+            'Any U,L,M where U is CWUser, U login L, U mail M' : [(1, 'login', 'subject'), (2, 'mail', 'subject')],
+            'Any U,L,M where U is CWUser, L is Foo, U mail M' : [(2, 'mail', 'subject')],
             'Any C,P where C is Company, C employs P' : [(1, 'employs', 'subject')],
             'Any C,P where C is Company, P employed_by P' : [],
             'Any C where C is Company, C employs P' : [],
@@ -32,11 +35,11 @@
         for rql, relations in queries.items():
             result = list(attr_desc_iterator(parse(rql).children[0]))
             self.assertEquals((rql, result), (rql, relations))
-            
+
     def test_relations_description_indexed(self):
         """tests relations_description() function"""
         queries = {
-            'Any C,U,P,L,M where C is Company, C employs P, U is EUser, U login L, U mail M' :
+            'Any C,U,P,L,M where C is Company, C employs P, U is CWUser, U login L, U mail M' :
             {0: [(2,'employs', 'subject')], 1: [(3,'login', 'subject'), (4,'mail', 'subject')]},
             }
         for rql, results in queries.items():
@@ -46,13 +49,13 @@
 
 
 
-class ResultSetTC(EnvBasedTC):    
+class ResultSetTC(EnvBasedTC):
 
     def setUp(self):
         super(ResultSetTC, self).setUp()
         self.rset = ResultSet([[12, 'adim'], [13, 'syt']],
-                              'Any U,L where U is EUser, U login L',
-                              description=[['EUser', 'String'], ['Bar', 'String']])
+                              'Any U,L where U is CWUser, U login L',
+                              description=[['CWUser', 'String'], ['Bar', 'String']])
         self.rset.vreg = self.vreg
 
     def compare_urls(self, url1, url2):
@@ -64,7 +67,7 @@
             params2 = dict(pair.split('=') for pair in info1[3].split('&'))
             self.assertDictEquals(params1, params2)
 
-        
+
     def test_build_url(self):
         req = self.request()
         baseurl = req.base_url()
@@ -76,20 +79,20 @@
         #                  '%stask/title/go' % baseurl)
         # empty _restpath should not crash
         self.compare_urls(req.build_url('view', _restpath=''), baseurl)
-                
-        
+
+
     def test_resultset_build(self):
         """test basic build of a ResultSet"""
-        rs = ResultSet([1,2,3], 'EGroup X', description=['EGroup', 'EGroup', 'EGroup'])
+        rs = ResultSet([1,2,3], 'CWGroup X', description=['CWGroup', 'CWGroup', 'CWGroup'])
         self.assertEquals(rs.rowcount, 3)
         self.assertEquals(rs.rows, [1,2,3])
-        self.assertEquals(rs.description, ['EGroup', 'EGroup', 'EGroup'])
+        self.assertEquals(rs.description, ['CWGroup', 'CWGroup', 'CWGroup'])
 
 
     def test_resultset_limit(self):
         rs = ResultSet([[12000, 'adim'], [13000, 'syt'], [14000, 'nico']],
-                       'Any U,L where U is EUser, U login L',
-                       description=[['EUser', 'String']] * 3)
+                       'Any U,L where U is CWUser, U login L',
+                       description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
         rs.vreg = self.env.vreg
 
@@ -99,25 +102,25 @@
         self.assertEquals(rs2.get_entity(0, 0).row, 0)
         self.assertEquals(rs.limit(2, offset=2).rows, [[14000, 'nico']])
         self.assertEquals(rs.limit(2, offset=3).rows, [])
-        
+
 
     def test_resultset_filter(self):
         rs = ResultSet([[12000, 'adim'], [13000, 'syt'], [14000, 'nico']],
-                       'Any U,L where U is EUser, U login L',
-                       description=[['EUser', 'String']] * 3)
+                       'Any U,L where U is CWUser, U login L',
+                       description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
         rs.vreg = self.env.vreg
         def test_filter(entity):
             return entity.login != 'nico'
-        
+
         rs2 = rs.filtered_rset(test_filter)
         self.assertEquals(len(rs2), 2)
         self.assertEquals([login for _, login in rs2], ['adim', 'syt'])
-        
+
     def test_resultset_transform(self):
         rs = ResultSet([[12, 'adim'], [13, 'syt'], [14, 'nico']],
-                       'Any U,L where U is EUser, U login L',
-                       description=[['EUser', 'String']] * 3)
+                       'Any U,L where U is CWUser, U login L',
+                       description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
         def test_transform(row, desc):
             return row[1:], desc[1:]
@@ -125,20 +128,20 @@
 
         self.assertEquals(len(rs2), 3)
         self.assertEquals(list(rs2), [['adim'],['syt'],['nico']])
-        
+
     def test_resultset_sort(self):
         rs = ResultSet([[12000, 'adim'], [13000, 'syt'], [14000, 'nico']],
-                       'Any U,L where U is EUser, U login L',
-                       description=[['EUser', 'String']] * 3)
+                       'Any U,L where U is CWUser, U login L',
+                       description=[['CWUser', 'String']] * 3)
         rs.req = self.request()
         rs.vreg = self.env.vreg
-        
+
         rs2 = rs.sorted_rset(lambda e:e['login'])
         self.assertEquals(len(rs2), 3)
         self.assertEquals([login for _, login in rs2], ['adim', 'nico', 'syt'])
         # make sure rs is unchanged
         self.assertEquals([login for _, login in rs], ['adim', 'syt', 'nico'])
-        
+
         rs2 = rs.sorted_rset(lambda e:e['login'], reverse=True)
         self.assertEquals(len(rs2), 3)
         self.assertEquals([login for _, login in rs2], ['syt', 'nico', 'adim'])
@@ -157,12 +160,12 @@
                         [13000, 'syt',  u'Le carrelage en 42 leçons'],
                         [14000, 'nico', u'La tarte tatin en 15 minutes'],
                         [14000, 'nico', u"L'épluchage du castor commun"]],
-                       'Any U, L, T WHERE U is EUser, U login L,'\
+                       'Any U, L, T WHERE U is CWUser, U login L,'\
                        'D created_by U, D title T',
-                       description=[['EUser', 'String', 'String']] * 5)
+                       description=[['CWUser', 'String', 'String']] * 5)
         rs.req = self.request()
         rs.vreg = self.env.vreg
-        
+
         rsets = rs.split_rset(lambda e:e['login'])
         self.assertEquals(len(rsets), 3)
         self.assertEquals([login for _, login,_ in rsets[0]], ['adim', 'adim'])
@@ -170,7 +173,7 @@
         self.assertEquals([login for _, login,_ in rsets[2]], ['nico', 'nico'])
         # make sure rs is unchanged
         self.assertEquals([login for _, login,_ in rs], ['adim', 'adim', 'syt', 'nico', 'nico'])
-        
+
         rsets = rs.split_rset(lambda e:e['login'], return_dict=True)
         self.assertEquals(len(rsets), 3)
         self.assertEquals([login for _, login,_ in rsets['nico']], ['nico', 'nico'])
@@ -195,7 +198,7 @@
                            u'Le carrelage en 42 leçons',
                            u'La tarte tatin en 15 minutes',
                            u"L'épluchage du castor commun"])
-        
+
     def test_cached_syntax_tree(self):
         """make sure syntax tree is cached"""
         rqlst1 = self.rset.syntax_tree()
@@ -203,7 +206,7 @@
         self.assert_(rqlst1 is rqlst2)
 
     def test_get_entity_simple(self):
-        self.add_entity('EUser', login=u'adim', upassword='adim',
+        self.add_entity('CWUser', login=u'adim', upassword='adim',
                         surname=u'di mascio', firstname=u'adrien')
         e = self.entity('Any X,T WHERE X login "adim", X surname T')
         self.assertEquals(e['surname'], 'di mascio')
@@ -213,12 +216,12 @@
         e.complete()
         self.assertEquals(e['firstname'], 'adrien')
         self.assertEquals(pprelcachedict(e._related_cache), [])
-        
+
     def test_get_entity_advanced(self):
         self.add_entity('Bookmark', title=u'zou', path=u'/view')
         self.execute('SET X bookmarked_by Y WHERE X is Bookmark, Y login "anon"')
         rset = self.execute('Any X,Y,XT,YN WHERE X bookmarked_by Y, X title XT, Y login YN')
-        
+
         e = rset.get_entity(0, 0)
         self.assertEquals(e.row, 0)
         self.assertEquals(e.col, 0)
@@ -226,7 +229,7 @@
         self.assertRaises(KeyError, e.__getitem__, 'path')
         self.assertEquals(e.view('text'), 'zou')
         self.assertEquals(pprelcachedict(e._related_cache), [])
-        
+
         e = rset.get_entity(0, 1)
         self.assertEquals(e.row, 0)
         self.assertEquals(e.col, 1)
@@ -239,7 +242,7 @@
         self.assertEquals(e.view('text'), 'anon')
         self.assertEquals(pprelcachedict(e._related_cache),
                           [])
-        
+
         self.assertRaises(NotAnEntity, rset.get_entity, 0, 2)
         self.assertRaises(NotAnEntity, rset.get_entity, 0, 3)
 
@@ -247,7 +250,7 @@
         rset = self.execute('Any X,S WHERE X in_state S, X login "anon"')
         e = rset.get_entity(0, 0)
         seid = self.execute('State X WHERE X name "activated"')[0][0]
-        # for_user / in_group are prefetched in EUser __init__, in_state should
+        # for_user / in_group are prefetched in CWUser __init__, in_state should
         # be filed from our query rset
         self.assertEquals(pprelcachedict(e._related_cache),
                           [('in_state_subject', [seid])])
@@ -270,9 +273,9 @@
         self.assertEquals(s['name'], 'activated')
         self.assertRaises(KeyError, s.__getitem__, 'description')
 
-        
+
     def test_get_entity_cache_with_left_outer_join(self):
-        eid = self.execute('INSERT EUser E: E login "joe", E upassword "joe", E in_group G '
+        eid = self.execute('INSERT CWUser E: E login "joe", E upassword "joe", E in_group G '
                            'WHERE G name "users"')[0][0]
         rset = self.execute('Any X,E WHERE X eid %(x)s, X primary_email E?', {'x': eid})
         e = rset.get_entity(0, 0)
@@ -283,63 +286,63 @@
         cached = e.related_cache('primary_email', 'subject', False)
         self.assertIsInstance(cached, ResultSet)
         self.assertEquals(cached.rowcount, 0)
-        
+
 
     def test_get_entity_union(self):
         e = self.add_entity('Bookmark', title=u'manger', path=u'path')
         rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
                             '((Any X,N WHERE X is Bookmark, X title N)'
                             ' UNION '
-                            ' (Any X,N WHERE X is EGroup, X name N))')
-        expected = (('EGroup', 'guests'), ('EGroup', 'managers'),
-                    ('Bookmark', 'manger'), ('EGroup', 'owners'),
-                    ('EGroup', 'users'))
+                            ' (Any X,N WHERE X is CWGroup, X name N))')
+        expected = (('CWGroup', 'guests'), ('CWGroup', 'managers'),
+                    ('Bookmark', 'manger'), ('CWGroup', 'owners'),
+                    ('CWGroup', 'users'))
         for entity in rset.entities(): # test get_entity for each row actually
             etype, n = expected[entity.row]
             self.assertEquals(entity.id, etype)
             attr = etype == 'Bookmark' and 'title' or 'name'
             self.assertEquals(entity[attr], n)
-        
+
     def test_related_entity_optional(self):
         e = self.add_entity('Bookmark', title=u'aaaa', path=u'path')
         rset = self.execute('Any B,U,L WHERE B bookmarked_by U?, U login L')
         entity, rtype = rset.related_entity(0, 2)
         self.assertEquals(entity, None)
         self.assertEquals(rtype, None)
-        
+
     def test_related_entity_union_subquery(self):
         e = self.add_entity('Bookmark', title=u'aaaa', path=u'path')
         rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
-                            '((Any X,N WHERE X is EGroup, X name N)'
+                            '((Any X,N WHERE X is CWGroup, X name N)'
                             ' UNION '
                             ' (Any X,N WHERE X is Bookmark, X title N))')
         entity, rtype = rset.related_entity(0, 1)
         self.assertEquals(entity.eid, e.eid)
         self.assertEquals(rtype, 'title')
         entity, rtype = rset.related_entity(1, 1)
-        self.assertEquals(entity.id, 'EGroup')
+        self.assertEquals(entity.id, 'CWGroup')
         self.assertEquals(rtype, 'name')
         rset = self.execute('Any X,N ORDERBY N WHERE X is Bookmark WITH X,N BEING '
-                            '((Any X,N WHERE X is EGroup, X name N)'
+                            '((Any X,N WHERE X is CWGroup, X name N)'
                             ' UNION '
                             ' (Any X,N WHERE X is Bookmark, X title N))')
         entity, rtype = rset.related_entity(0, 1)
         self.assertEquals(entity.eid, e.eid)
         self.assertEquals(rtype, 'title')
-        
+
     def test_entities(self):
         rset = self.execute('Any U,G WHERE U in_group G')
         # make sure we have at least one element
         self.failUnless(rset)
         self.assertEquals(set(e.e_schema.type for e in rset.entities(0)),
-                          set(['EUser',]))
+                          set(['CWUser',]))
         self.assertEquals(set(e.e_schema.type for e in rset.entities(1)),
-                          set(['EGroup',]))
+                          set(['CWGroup',]))
 
-    def test_printable_rql(self):        
-        rset = self.execute(u'EEType X WHERE X final FALSE, X meta FALSE')
+    def test_printable_rql(self):
+        rset = self.execute(u'CWEType X WHERE X final FALSE, X meta FALSE')
         self.assertEquals(rset.printable_rql(),
-                          'Any X WHERE X final FALSE, X meta FALSE, X is EEType')
+                          'Any X WHERE X final FALSE, X meta FALSE, X is CWEType')
 
 
     def test_searched_text(self):
@@ -347,7 +350,7 @@
         self.assertEquals(rset.searched_text(), 'foobar')
         rset = self.execute(u'Any X WHERE X has_text %(text)s', {'text' : 'foo'})
         self.assertEquals(rset.searched_text(), 'foo')
-        
-   
+
+
 if __name__ == '__main__':
     unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_rtags.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,57 @@
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.rtags import RelationTags, RelationTagsSet
+
+class RelationTagsTC(TestCase):
+
+    def test_rtags_expansion(self):
+        rtags = RelationTags()
+        rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
+        rtags.tag_subject_of(('*', 'evaluee', '*'), 'secondary')
+        rtags.tag_object_of(('*', 'tags', '*'), 'generated')
+        self.assertEquals(rtags.get('Note', 'evaluee', '*', 'subject'),
+                          'secondary')
+        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+                          'primary')
+        self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
+                          None)
+        self.assertEquals(rtags.get('Note', 'tags', '*', 'subject'),
+                          None)
+        self.assertEquals(rtags.get('*', 'tags', 'Note', 'object'),
+                          'generated')
+        self.assertEquals(rtags.get('Tag', 'tags', '*', 'object'),
+                          'generated')
+
+#         self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
+#         self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
+#         self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+#         self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+#         self.assertEquals(rtags.rtag('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.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
+#         self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
+#         self.assertEquals(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+#         self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+
+
+    def test_rtagset_expansion(self):
+        rtags = RelationTagsSet()
+        rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
+        rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary')
+        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+                          set(('primary', 'secondary')))
+        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+                          set(('primary', 'secondary')))
+        self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
+                          set(('secondary',)))
+        self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'),
+                          set())
+
+if __name__ == '__main__':
+    unittest_main()
--- a/test/unittest_schema.py	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_schema.py	Thu May 14 12:50:34 2009 +0200
@@ -96,7 +96,7 @@
                          ['Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s'])
         eperson.set_groups('read', ('managers',))
         self.assertEqual(eperson.get_groups('read'), set(('managers',)))
-        
+
     def test_relation_perms(self):
         rconcerne = schema.rschema('concerne')
         rconcerne.set_default_groups()
@@ -112,19 +112,19 @@
         self.assertRaises(RQLSyntaxError, ERQLExpression, '1')
         expr = ERQLExpression('X travaille S, S owned_by U')
         self.assertEquals(str(expr), 'Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s')
-        
+
     def test_rrqlexpression(self):
         self.assertRaises(Exception, RRQLExpression, '1')
         self.assertRaises(RQLSyntaxError, RRQLExpression, 'O X Y')
         expr = RRQLExpression('U has_update_permission O')
         self.assertEquals(str(expr), 'Any O WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
-        
+
 
 loader = CubicWebSchemaLoader()
 config = TestConfiguration('data')
 config.bootstrap_cubes()
 loader.lib_directory = config.schemas_lib_dir()
-    
+
 class SQLSchemaReaderClassTest(TestCase):
 
     def test_knownValues_include_schema_files(self):
@@ -134,21 +134,20 @@
         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')
         entities = [str(e) for e in schema.entities()]
         entities.sort()
-        expected_entities = ['Bookmark', 'Boolean', 'Bytes', 'Card', 
+        expected_entities = ['Bookmark', 'Boolean', 'Bytes', 'Card',
                              'Date', 'Datetime', 'Decimal',
-                             'ECache', 'EConstraint', 'EConstraintType', 'EEType',
-                             'EFRDef', 'EGroup', 'EmailAddress', 'ENFRDef',
-                             'EPermission', 'EProperty', 'ERType', 'EUser',
-                             'Float', 'Int', 'Interval', 
-                             'Password', 
-                             'RQLExpression', 
-                             'State', 'String', 'Time', 
+                             'CWCache', 'CWConstraint', 'CWConstraintType', 'CWEType',
+                             'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation',
+                             'CWPermission', 'CWProperty', 'CWRType', 'CWUser',
+                             'File', 'Float', 'Image', 'Int', 'Interval', 'Note',
+                             'Password', 'Personne',
+                             'RQLExpression',
+                             'Societe', 'State', 'String', 'SubNote', 'Tag', 'Time',
                              'Transition', 'TrInfo']
         self.assertListEquals(entities, sorted(expected_entities))
         relations = [str(r) for r in schema.relations()]
@@ -156,19 +155,19 @@
         expected_relations = ['add_permission', 'address', 'alias',
                               'allowed_transition', 'bookmarked_by', 'canonical',
 
-                              'cardinality', 'comment', 'comment_format', 
-                              'composite', 'condition', 'constrained_by', 'content',
+                              'cardinality', 'comment', 'comment_format',
+                              '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',
 
-                              'has_text', 
+                              'has_text',
                               'identical_to', 'identity', 'in_group', 'in_state', 'indexed',
                               'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of',
 
@@ -176,30 +175,30 @@
 
                               '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',
 
-                              'value', 
+                              'value',
 
                               'wf_info_for', 'wikiid']
-    
+
         self.assertListEquals(relations, expected_relations)
 
-        eschema = schema.eschema('EUser')
+        eschema = schema.eschema('CWUser')
         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',
@@ -209,7 +208,7 @@
         self.assertListEquals(rels, ['bookmarked_by', 'created_by', 'for_user',
                                      'identity', 'owned_by', 'wf_info_for'])
         rschema = schema.rschema('relation_type')
-        properties = rschema.rproperties('EFRDef', 'ERType')
+        properties = rschema.rproperties('CWAttribute', 'CWRType')
         self.assertEquals(properties['cardinality'], '1*')
         constraints = properties['constraints']
         self.failUnlessEqual(len(constraints), 1, constraints)
@@ -219,7 +218,7 @@
 
     def test_fulltext_container(self):
         schema = loader.load(config)
-        self.failUnless('has_text' in schema['EUser'].subject_relations())
+        self.failUnless('has_text' in schema['CWUser'].subject_relations())
         self.failIf('has_text' in schema['EmailAddress'].subject_relations())
 
 
@@ -235,16 +234,16 @@
         ex = self.assertRaises(BadSchemaDefinition,
                                self.loader._build_schema, 'toto', False)
         self.assertEquals(str(ex), msg)
-        
+
     def test_rrqlexpr_on_etype(self):
         self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on an entity type, use an ERQLExpression (ToTo)")
-        
+
     def test_erqlexpr_on_rtype(self):
         self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on a relation type, use a RRQLExpression (toto)")
-        
+
     def test_rqlexpr_on_rtype_read(self):
         self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of a relation type (toto)")
-        
+
     def test_rrqlexpr_on_attr(self):
         self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on a final relation type (eg attribute relation), use an ERQLExpression (attr)")
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_selectors.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,136 @@
+"""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.devtools.testlib import EnvBasedTC
+from cubicweb.vregistry import Selector, AndSelector, OrSelector
+from cubicweb.selectors import implements, match_user_groups
+from cubicweb.interfaces import IDownloadable
+from cubicweb.web import action
+
+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)
+
+
+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))
+
+    def test_etype_inheritance_no_yams_inheritance(self):
+        cls = self.vreg.etype_class('Personne')
+        self.failIf(implements('Societe').score_class(cls, self.request()))
+
+
+class MatchUserGroupsTC(EnvBasedTC):
+    def test_owners_group(self):
+        """tests usage of 'owners' group with match_user_group"""
+        class SomeAction(action.Action):
+            id = 'yo'
+            category = 'foo'
+            __select__ = match_user_groups('owners')
+        self.vreg._loadedmods[__name__] = {}
+        self.vreg.register_vobject_class(SomeAction)
+        self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
+        try:
+            # login as a simple user
+            self.create_user('john')
+            self.login('john')
+            # it should not be possible to use SomeAction not owned objects
+            rset, req = self.env.get_rset_and_req('Any G WHERE G is CWGroup, G name "managers"')
+            self.failIf('yo' in dict(self.pactions(req, rset)))
+            # insert a new card, and check that we can use SomeAction on our object
+            self.execute('INSERT Card C: C title "zoubidou"')
+            self.commit()
+            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+            self.failUnless('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset))
+            # make sure even managers can't use the action
+            self.restore_connection()
+            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+            self.failIf('yo' in dict(self.pactions(req, rset)))
+        finally:
+            del self.vreg[SomeAction.__registry__][SomeAction.id]
+
+if __name__ == '__main__':
+    unittest_main()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_utils.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/test/unittest_vregistry.py	Thu May 14 12:50:34 2009 +0200
@@ -5,45 +5,62 @@
 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.interfaces import IMileStone
+
+from cubes.card.entities import Card
 
 class YesSchema:
     def __contains__(self, something):
         return True
-    
+
+WEBVIEWSDIR = join(BASE, 'web', 'views')
+
 class VRegistryTC(TestCase):
 
     def setUp(self):
-        config = CubicWebConfiguration('data')
+        config = TestServerConfiguration('data')
         self.vreg = CubicWebRegistry(config)
-        self.vreg.schema = YesSchema()
-
-    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]
-        fpv = fpvc(None, None)
-        # don't want a TypeError due to super call
-        self.assertRaises(AttributeError, fpv.render_entity_attributes, None, None)
+        config.bootstrap_cubes()
+        self.vreg.schema = config.load_schema()
 
     def test_load_interface_based_vojects(self):
-        self.vreg.load_file(join(BASE, 'web', 'views'), 'idownloadable.py')
-        self.vreg.load_file(join(BASE, 'web', 'views'), 'baseviews.py')
-        # check loading baseviews after idownloadable isn't kicking interface based views
+        self.vreg.init_registration([WEBVIEWSDIR])
+        self.vreg.load_file(join(BASE, 'entities', '__init__.py'), 'cubicweb.entities.__init__')
+        self.vreg.load_file(join(WEBVIEWSDIR, 'idownloadable.py'), 'cubicweb.web.views.idownloadable')
+        self.vreg.load_file(join(WEBVIEWSDIR, 'primary.py'), 'cubicweb.web.views.primary')
         self.assertEquals(len(self.vreg['views']['primary']), 2)
-                              
-    def test_autoselectors(self):
+        self.vreg.initialization_completed()
+        self.assertEquals(len(self.vreg['views']['primary']), 1)
+
+    def test___selectors__compat(self):
         myselector1 = lambda *args: 1
         myselector2 = lambda *args: 1
         class AnAppObject(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._loadedmods[__name__] = {}
+        self.vreg.register_vobject_class(MyCard)
+        self.vreg.register_objects([join(BASE, 'entities', '__init__.py'),
+                                    join(BASE, 'web', 'views', 'iprogress.py')])
+        # check progressbar isn't kicked
+        self.assertEquals(len(self.vreg['views']['progressbar']), 1)
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/toolsutils.py	Thu May 14 12:50:14 2009 +0200
+++ b/toolsutils.py	Thu May 14 12:50:34 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
@@ -39,7 +39,7 @@
         if ex.errno != errno.EEXIST:
             raise
         print 'directory %s already exists' % directory
-                
+
 def create_symlink(source, target):
     """create a symbolic link"""
     if exists(target):
@@ -51,7 +51,7 @@
     import shutil
     print '[copy] %s <-- %s' % (target, source)
     shutil.copy2(source, target)
-    
+
 def rm(whatever):
     import shutil
     shutil.rmtree(whatever)
@@ -66,7 +66,7 @@
     diffs = p_output.read()
     if diffs:
         if askconfirm:
-            print 
+            print
             print diffs
             action = raw_input('replace (N/y/q) ? ').lower()
         else:
@@ -114,13 +114,13 @@
                 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)
             else:
                 shutil.copyfile(fpath, tfpath)
-                
+
 def fill_templated_file(fpath, tfpath, context):
     fobj = file(tfpath, 'w')
     templated = file(fpath).read()
@@ -158,7 +158,7 @@
     :param config_file: path to the configuration file
 
     :rtype: dict
-    :return: a dictionary with specified values associated to option names 
+    :return: a dictionary with specified values associated to option names
     """
     from logilab.common.fileutils import lines
     config = current = {}
@@ -194,10 +194,10 @@
 
     :type default: str
     :param default: default value if the environment variable is not defined
-    
+
     :type name: str
     :param name: the informal name of the path, used for error message
-    
+
     :rtype: str
     :return: the value of the environment variable or the default value
 
@@ -243,12 +243,12 @@
             msg = 'No helper for command %s using %s configuration' % (
                 cmdname, config.name)
             raise ConfigurationError(msg)
-        
+
     def fail(self, reason):
         print "command failed:", reason
         sys.exit(1)
-    
-                    
+
+
 def main_run(args, doc):
     """command line tool"""
     try:
@@ -289,4 +289,4 @@
     if not password:
         password = getpass('password: ')
     return connect(user=user, password=password, host=optconfig.host, database=appid)
-    
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/utils.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,320 @@
+"""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
+from calendar import monthrange
+
+# 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), repr(somedate)
+    return somedate
+
+def todatetime(somedate):
+    """return a date from a date (leaving unchanged) or a datetime"""
+    # take care, datetime is a subclass of date
+    if isinstance(somedate, datetime):
+        return somedate
+    assert isinstance(somedate, date), repr(somedate)
+    return datetime(somedate.year, somedate.month, somedate.day)
+
+ONEDAY = timedelta(days=1)
+ONEWEEK = timedelta(days=7)
+
+def days_in_month(date_):
+    return monthrange(date_.year, date_.month)[1]
+
+def previous_month(date_, nbmonth=1):
+    while nbmonth:
+        date_ = first_day(date_) - ONEDAY
+        nbmonth -= 1
+    return date_
+
+def next_month(date_, nbmonth=1):
+    while nbmonth:
+        date_ = last_day(date_) + ONEDAY
+        nbmonth -= 1
+    return date_
+
+def first_day(date_):
+    return date(date_.year, date_.month, 1)
+
+def last_day(date_):
+    return date(date_.year, date_.month, days_in_month(date_))
+
+def date_range(begin, end, incday=None, incmonth=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.
+    """
+    assert not (incday and incmonth)
+    begin = todate(begin)
+    end = todate(end)
+    if incmonth:
+        while begin < end:
+            begin = next_month(begin, incmonth)
+            yield begin
+    else:
+        if not incday:
+            incr = ONEDAY
+        else:
+            incr = timedelta(incday)
+        while begin <= end:
+           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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,484 @@
+"""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
+
+_ = 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:fname             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' % CW_XHTML_EXTENSIONS
+TRANSITIONAL_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\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' % CW_XHTML_EXTENSIONS
+STRICT_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\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'
+    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):
+        return self.req.html_content_type()
+
+    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 render(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()
+
+    dispatch = obsolete('.dispatch is deprecated, use .render')(render)
+
+    # 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>')
+
+
+
+# concrete views base classes #################################################
+
+class EntityView(View):
+    """base class for views applying on an entity (i.e. uniform result set)"""
+    __select__ = non_final_entity()
+    registered = accepts_compat(View.registered)
+
+    category = 'entityview'
+
+
+class StartupView(View):
+    """base class for views which doesn't need a particular result set to be
+    displayed (so they can always be displayed !)
+    """
+    __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
+    """
+    registered = require_group_compat(View.registered)
+
+    @property
+    def doctype(self):
+        if self.req.xhtml_browser():
+            return STRICT_DOCTYPE
+        return STRICT_DOCTYPE_NOEXT
+
+    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 = 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'
+    __select__ = yes()
+    property_defs = {}
+
+    def div_class(self):
+        return '%s %s' % (self.propval('htmlclass'), self.id)
+
+    def div_id(self):
+        return '%sComponent' % self.id
--- a/vregistry.py	Thu May 14 12:50:14 2009 +0200
+++ b/vregistry.py	Thu May 14 12:50:34 2009 +0200
@@ -6,12 +6,7 @@
 
 * to interact with the vregistry, object should inherit from the
   VObject abstract class
-  
-* the registration procedure is delegated to a registerer. Each
-  registerable vobject must defines its registerer class using the
-  __registerer__ attribute.  A registerer is instantianted at
-  registration time after what the instance is lost
-  
+
 * the selection procedure has been generalized by delegating to a
   selector, which is responsible to score the vobject according to the
   current state (req, rset, row, col). At the end of the selection, if
@@ -27,124 +22,53 @@
 
 import sys
 from os import listdir, stat
-from os.path import dirname, join, realpath, split, isdir
+from os.path import dirname, join, realpath, split, isdir, exists
 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 _toload_info(path, _toload=None):
+    """return a dictionary of <modname>: <modpath> and an ordered list of
+    (file, module name) to load
     """
-
-    def __init__(self, registry, vobject):
-        self.registry = registry
-        self.vobject = vobject
-        self.config = registry.config
-        self.schema = registry.schema
-
-
-class registerer(vobject_helper):
-    """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
-    same id).
-
-    The wrapped class may be skipped, some previously selected object
-    may be kicked out... After whatever works needed, if the object or
-    a transformed object is returned, it will be added to previously
-    registered objects.
-    """
-
-    def __init__(self, registry, vobject):
-        super(registerer, self).__init__(registry, vobject)
-        self.kicked = set()
-    
-    def do_it_yourself(self, registered):
-        raise NotImplementedError(str(self.vobject))
-        
-    def kick(self, registered, kicked):
-        self.debug('kicking vobject %s', kicked)
-        registered.remove(kicked)
-        self.kicked.add(kicked.classid())
-        
-    def skip(self):
-        self.debug('no schema compat, skipping %s', self.vobject)
-
+    from logilab.common.modutils import modpath_from_file
+    if _toload is None:
+        _toload = {}, []
+    for fileordir in path:
+        if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
+            subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
+            _toload_info(subfiles, _toload)
+        elif fileordir[-3:] == '.py':
+            modname = '.'.join(modpath_from_file(fileordir))
+            _toload[0][modname] = fileordir
+            _toload[1].append((fileordir, modname))
+    return _toload
 
-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))
-
-    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
     registry.
 
     The following attributes should be set on concret vobject subclasses:
-    
+
     :__registry__:
       name of the registry for this object (string like 'views',
       'templates'...)
     :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
     __select__ = None
 
     @classmethod
@@ -155,12 +79,13 @@
         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
     def selected(cls, *args, **kwargs):
         """called by the registry when the vobject has been selected.
-        
+
         It must return the  object that will be actually returned by the
         .select method (this may be the right hook to create an
         instance for example). By default the selected object is
@@ -173,13 +98,36 @@
         """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
     elements used to build the web interface. Currently, we have templates,
     views, actions and components.
     """
-    
+
     def __init__(self, config):#, cache_size=1000):
         self.config = config
         # dictionnary of registry (themself dictionnary) by name
@@ -204,45 +152,290 @@
 
     def __contains__(self, key):
         return key in self._registries
-        
-    def register_vobject_class(self, cls, _kicked=set()):
+
+    def registry(self, name):
+        """return the registry (dictionary of class objects) associated to
+        this name
+        """
+        try:
+            return self._registries[name]
+        except KeyError:
+            raise RegistryNotFound(name), None, sys.exc_info()[-1]
+
+    def registry_objects(self, name, oid=None):
+        """returns objects registered with the given oid in the given registry.
+        If no oid is given, return all objects in this registry
+        """
+        registry = self.registry(name)
+        if oid:
+            try:
+                return registry[oid]
+            except KeyError:
+                raise ObjectNotFound(oid), None, sys.exc_info()[-1]
+        else:
+            result = []
+            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, vobject
+        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._loadedmods[obj.__module__]['%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
+
+        raise NoSelectableObject if not object apply
+        """
+        score, winners = 0, []
+        for vobject in vobjects:
+            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]))
+        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]))
+        winner = winners[0]
+        # return the result of the .selected method of the vobject
+        return winner.selected(*args, **kwargs)
+
+    def possible_objects(self, registry, *args, **kwargs):
+        """return an iterator on possible objects in a registry for this result set
+
+        actions returned are classes, not instances
+        """
+        for vobjects in self.registry(registry).values():
+            try:
+                yield self.select(vobjects, *args, **kwargs)
+            except NoSelectableObject:
+                continue
+
+    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)
+
+    # intialization methods ###################################################
+
+    def init_registration(self, path):
+        # compute list of all modules that have to be loaded
+        self._toloadmods, filemods = _toload_info(path)
+        self._loadedmods = {}
+        return filemods
+
+    def register_objects(self, path, force_reload=None):
+        if force_reload is None:
+            force_reload = self.config.mode == 'dev'
+        elif not force_reload:
+            # force_reload == False usually mean modules have been reloaded
+            # by another connection, so we want to update the registry
+            # content even if there has been no module content modification
+            self.reset()
+        # need to clean sys.path this to avoid import confusion pb (i.e.
+        # having the same module loaded as 'cubicweb.web.views' subpackage and
+        # as views'  or 'web.views' subpackage
+        # this is mainly for testing purpose, we should'nt need this in
+        # production environment
+        for webdir in (join(dirname(realpath(__file__)), 'web'),
+                       join(dirname(__file__), 'web')):
+            if webdir in sys.path:
+                sys.path.remove(webdir)
+        if CW_SOFTWARE_ROOT in sys.path:
+            sys.path.remove(CW_SOFTWARE_ROOT)
+        # load views from each directory in the application's path
+        filemods = self.init_registration(path)
+        change = False
+        for filepath, modname in filemods:
+            if self.load_file(filepath, modname, force_reload):
+                change = True
+        return change
+
+    def load_file(self, filepath, modname, force_reload=False):
+        """load visual objects from a python file"""
+        from logilab.common.modutils import load_module_from_name
+        if modname in self._loadedmods:
+            return
+        self._loadedmods[modname] = {}
+        try:
+            modified_on = stat(filepath)[-2]
+        except OSError:
+            # this typically happens on emacs backup files (.#foo.py)
+            self.warning('Unable to load %s. It is likely to be a backup file',
+                         filepath)
+            return False
+        if filepath in self._lastmodifs:
+            # only load file if it was modified
+            if modified_on <= self._lastmodifs[filepath]:
+                return
+            # if it was modified, unregister all exisiting objects
+            # from this module, and keep track of what was unregistered
+            unregistered = self.unregister_module_vobjects(modname)
+        else:
+            unregistered = None
+        # load the module
+        module = load_module_from_name(modname, use_sys=not force_reload)
+        self.load_module(module)
+        # if something was unregistered, we need to update places where it was
+        # referenced
+        if unregistered:
+            # oldnew_mapping = {}
+            registered = self._loadedmods[modname]
+            oldnew_mapping = dict((unregistered[name], registered[name])
+                                  for name in unregistered if name in registered)
+            self.update_registered_subclasses(oldnew_mapping)
+        self._lastmodifs[filepath] = modified_on
+        return True
+
+    def load_module(self, module):
+        self.info('loading %s', module)
+        if hasattr(module, 'registration_callback'):
+            module.registration_callback(self)
+        else:
+            for objname, obj in vars(module).items():
+                if objname.startswith('_'):
+                    continue
+                self._load_ancestors_then_object(module.__name__, obj)
+        self.debug('loaded %s', module)
+
+    def _load_ancestors_then_object(self, modname, obj):
+        # imported classes
+        objmodname = getattr(obj, '__module__', None)
+        if objmodname != modname:
+            if objmodname in self._toloadmods:
+                self.load_file(self._toloadmods[objmodname], objmodname)
+            return
+        # skip non registerable object
+        try:
+            if not issubclass(obj, VObject):
+                return
+        except TypeError:
+            return
+        objname = '%s.%s' % (modname, obj.__name__)
+        if objname in self._loadedmods[modname]:
+            return
+        self._loadedmods[modname][objname] = obj
+        for parent in obj.__bases__:
+            self._load_ancestors_then_object(modname, parent)
+        self.load_object(obj)
+
+    def load_object(self, obj):
+        try:
+            self.register_vobject_class(obj)
+        except Exception, ex:
+            if self.config.mode in ('test', 'dev'):
+                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
-        # 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)
-            
+        regname = cls.__registry__
+        if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
+            return
+        self.register(cls)
+
     def unregister_module_vobjects(self, modname):
         """removes registered objects coming from a given module
 
@@ -279,7 +472,6 @@
                     del objdict[oid]
         return unregistered
 
-
     def update_registered_subclasses(self, oldnew_mapping):
         """updates subclasses of re-registered vobjects
 
@@ -306,240 +498,189 @@
                                   obj.__module__, obj.__name__)
                         obj.__bases__ = newbases
 
-    def registry(self, name):
-        """return the registry (dictionary of class objects) associated to
-        this name
-        """
-        try:
-            return self._registries[name]
-        except KeyError:
-            raise RegistryNotFound(name), None, sys.exc_info()[-1]
-
-    def registry_objects(self, name, oid=None):
-        """returns objects registered with the given oid in the given registry.
-        If no oid is given, return all objects in this registry
-        """
-        registry = self.registry(name)
-        if oid:
-            try:
-                return registry[oid]
-            except KeyError:
-                raise ObjectNotFound(oid), None, sys.exc_info()[-1]
-        else:
-            result = []
-            for objs in registry.values():
-                result += objs
-            return result
-        
-    def select(self, vobjects, *args, **kwargs):
-        """return an instance of the most specific object according
-        to parameters
-
-        raise NoSelectableObject if not object apply
-        """
-        score, winners = 0, []
-        for vobject in vobjects:
-            vobjectscore = vobject.__select__(*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]))
-        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]))
-        winner = winners[0]
-        # return the result of the .selected method of the vobject
-        return winner.selected(*args, **kwargs)
-    
-    def possible_objects(self, registry, *args, **kwargs):
-        """return an iterator on possible objects in a registry for this result set
-
-        actions returned are classes, not instances
-        """
-        for vobjects in self.registry(registry).values():
-            try:
-                yield self.select(vobjects, *args, **kwargs)
-            except NoSelectableObject:
-                continue
-
-    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:
-            force_reload = self.config.mode == 'dev'
-        elif not force_reload:
-            # force_reload == False usually mean modules have been reloaded
-            # by another connection, so we want to update the registry
-            # content even if there has been no module content modification
-            self.reset()
-        # need to clean sys.path this to avoid import confusion pb (i.e.
-        # having the same module loaded as 'cubicweb.web.views' subpackage and
-        # as views'  or 'web.views' subpackage
-        # this is mainly for testing purpose, we should'nt need this in
-        # production environment
-        for webdir in (join(dirname(realpath(__file__)), 'web'),
-                       join(dirname(__file__), 'web')):
-            if webdir in sys.path:
-                sys.path.remove(webdir)
-        if CW_SOFTWARE_ROOT in sys.path:
-            sys.path.remove(CW_SOFTWARE_ROOT)        
-        # load views from each directory in the application's path
-        change = False
-        for fileordirectory in path:
-            if isdir(fileordirectory):
-                if self.read_directory(fileordirectory, force_reload):
-                    change = True
-            else:
-                directory, filename = split(fileordirectory)
-                if self.load_file(directory, filename, force_reload):
-                    change = True
-        if change:
-            for registry, objects in self.items():
-                self.debug('available in registry %s: %s', registry,
-                           sorted(objects))
-        return change
-    
-    def read_directory(self, directory, force_reload=False):
-        """read a directory and register available views"""
-        modified_on = stat(realpath(directory))[-2]
-        # only read directory if it was modified
-        _lastmodifs = self._lastmodifs
-        if directory in _lastmodifs and modified_on <= _lastmodifs[directory]:
-            return False
-        self.info('loading directory %s', directory)
-        for filename in listdir(directory):
-            if filename[-3:] == '.py':
-                try:
-                    self.load_file(directory, filename, force_reload)
-                except OSError:
-                    # this typically happens on emacs backup files (.#foo.py)
-                    self.warning('Unable to load file %s. It is likely to be a backup file',
-                                 filename)
-                except Exception, ex:
-                    if self.config.mode in ('dev', 'test'):
-                        raise
-                    self.exception('%r while loading file %s', ex, filename)
-        _lastmodifs[directory] = modified_on
-        return True
-
-    def load_file(self, directory, filename, force_reload=False):
-        """load visual objects from a python file"""
-        from logilab.common.modutils import load_module_from_modpath, modpath_from_file
-        filepath = join(directory, filename)
-        modified_on = stat(filepath)[-2]
-        modpath = modpath_from_file(join(directory, filename))
-        modname = '.'.join(modpath)
-        unregistered = {}
-        _lastmodifs = self._lastmodifs
-        if filepath in _lastmodifs:
-            # only load file if it was modified
-            if modified_on <= _lastmodifs[filepath]:
-                return
-            else:
-                # if it was modified, unregister all exisiting objects
-                # from this module, and keep track of what was unregistered
-                unregistered = self.unregister_module_vobjects(modname)
-        # load the module
-        module = load_module_from_modpath(modpath, use_sys=not force_reload)
-        registered = self.load_module(module)
-        # if something was unregistered, we need to update places where it was
-        # referenced 
-        if unregistered:
-            # oldnew_mapping = {}
-            oldnew_mapping = dict((unregistered[name], registered[name])
-                                  for name in unregistered if name in registered)
-            self.update_registered_subclasses(oldnew_mapping)
-        _lastmodifs[filepath] = modified_on
-        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
-    
-    def load_ancestors_then_object(self, modname, registered, obj):
-        # skip imported classes
-        if getattr(obj, '__module__', None) != modname:
-            return
-        # skip non registerable object
-        try:
-            if not issubclass(obj, VObject):
-                return
-        except TypeError:
-            return
-        objname = '%s.%s' % (modname, obj.__name__)
-        if objname in registered:
-            return
-        registered[objname] = obj
-        for parent in obj.__bases__:
-            self.load_ancestors_then_object(modname, registered, parent)
-        self.load_object(obj)
-            
-    def load_object(self, obj):
-        try:
-            self.register_vobject_class(obj)
-        except Exception, ex:
-            if self.config.mode in ('test', 'dev'):
-                raise
-            self.exception('vobject %s registration failed: %s', obj, ex)
-        
-# init logging 
+# init logging
 set_log_methods(VObject, getLogger('cubicweb'))
 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
-set_log_methods(registerer, getLogger('cubicweb.registration'))
+
+
+# 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
 
 
-# advanced selector building functions ########################################
+    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))
+
+    @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 chainall(*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
+
+    """
+    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 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
-    def selector(cls, *args, **kwargs):
-        score = 0
-        for selector in selectors:
-            partscore = selector(cls, *args, **kwargs)
-            if not partscore:
-                return 0
-            score += partscore
-        return score
+    # 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -3,14 +3,21 @@
 
 
 :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"
+_ = unicode
 
-from cubicweb.web._exceptions import *    
+from decimal import Decimal
+from datetime import datetime, date, timedelta
+from simplejson import dumps
 
-_ = unicode
+from logilab.common.deprecation import obsolete
+
+from cubicweb.common.uilib import urlquote
+from cubicweb.web._exceptions import *
+
 
 INTERNAL_FIELD_VALUE = '__cubicweb_internal_field__'
 
@@ -35,12 +42,47 @@
 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'),
-##     ))
+
+def json_dumps(value):
+    if isinstance(value, Decimal):
+        value = float(value)
+    elif isinstance(value, (date, datetime)):
+        value = value.strftime('%Y-%m-%d %H:%M')
+    elif isinstance(value, timedelta):
+        value = (value.days * 24*60*60) + value.seconds
+    try:
+        return dumps(value)
+    except TypeError:
+        return dumps(repr(value))
+
+def jsonize(function):
+    def newfunc(*args, **kwargs):
+        return json_dumps(function(*args, **kwargs))
+    return newfunc
+
+@obsolete('use req.build_ajax_replace_url() instead')
+def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
+    """builds a replacePageChunk-like url
+    >>> ajax_replace_url('foo', 'Person P')
+    "javascript: replacePageChunk('foo', 'Person%20P');"
+    >>> ajax_replace_url('foo', 'Person P', 'oneline')
+    "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');"
+    >>> ajax_replace_url('foo', 'Person P', 'oneline', name='bar', age=12)
+    "javascript: replacePageChunk('foo', 'Person%20P', 'oneline', {'age':12, 'name':'bar'});"
+    >>> ajax_replace_url('foo', 'Person P', name='bar', age=12)
+    "javascript: replacePageChunk('foo', 'Person%20P', 'null', {'age':12, 'name':'bar'});"
+    """
+    params = [repr(nodeid), repr(urlquote(rql))]
+    if extraparams and not vid:
+        params.append("'null'")
+    elif vid:
+        params.append(repr(vid))
+    if extraparams:
+        params.append(json_dumps(extraparams))
+    if swap:
+        params.append('true')
+    return "javascript: replacePageChunk(%s);" % ', '.join(params)
--- a/web/_exceptions.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/_exceptions.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 """exceptions used in the core of the CubicWeb web application
 
 :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,13 +11,13 @@
 
 class PublishException(CubicWebException):
     """base class for publishing related exception"""
-    
+
 class RequestError(PublishException):
     """raised when a request can't be served because of a bad input"""
 
 class NothingToEdit(RequestError):
     """raised when an edit request doesn't specify any eid to edit"""
-    
+
 class NotFound(RequestError):
     """raised when a 404 error should be returned"""
 
@@ -34,7 +34,7 @@
     def __init__(self, status, content=''):
         self.status = int(status)
         self.content = content
-    
+
 class ExplicitLogin(AuthenticationError):
     """raised when a bad connection id is given or when an attempt to establish
     a connection failed"""
@@ -55,4 +55,3 @@
     def dumps(self):
         import simplejson
         return simplejson.dumps({'reason': self.reason})
-        
--- a/web/action.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/action.py	Thu May 14 12:50:34 2009 +0200
@@ -1,29 +1,27 @@
 """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
+
 _ = unicode
 
 
 class Action(AppRsetObject):
     """abstract action. Handle the .search_states attribute to match
-    request search state. 
+    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',) 
+    __select__ = match_search_state('normal')
+
     property_defs = {
         'visible':  dict(type='Boolean', default=True,
                          help=_('display the action or not')),
@@ -36,186 +34,61 @@
     }
     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
-    
+
     def html_class(self):
         if self.req.selected(self.url()):
             return 'selected'
         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.
     """
     category = None
     id = None
-    
+
     def __init__(self, req, rset, title, path, **kwargs):
         Action.__init__(self, req, rset)
         self.title = req._(title)
         self._path = path
         self.__dict__.update(kwargs)
-        
+
     def url(self):
         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 EntityAction(Action):
+    """DEPRECATED / BACKWARD COMPAT
+    """
+    registered = deprecate(condition_compat(accepts_compat(Action.registered)),
+                           msg='EntityAction is deprecated, use Action with '
+                           'appropriate selectors')
 
-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
-    """
-    __selectors__ = (searchstate_accept_one_but_etype,)
-    
--- a/web/application.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/application.py	Thu May 14 12:50:34 2009 +0200
@@ -14,20 +14,19 @@
 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'
-    
+
     def __init__(self):
         self.session_time = self.vreg.config['http-session-time'] or None
         assert self.session_time is None or self.session_time > 0
@@ -40,7 +39,7 @@
             assert self.cleanup_anon_session_time < self.session_time
         self.authmanager = self.vreg.select_component('authmanager')
         assert self.authmanager, 'no authentication manager found'
-        
+
     def clean_sessions(self):
         """cleanup sessions which has not been unused since a given amount of
         time. Return the number of sessions which have been closed.
@@ -58,28 +57,28 @@
                 self.close_session(session)
                 closed += 1
         return closed, total - closed
-    
+
     def has_expired(self, session):
         """return True if the web session associated to the session is expired
         """
         return not (self.session_time is None or
                     time() < session.last_usage_time + self.session_time)
-                
+
     def current_sessions(self):
         """return currently open sessions"""
         raise NotImplementedError()
-            
+
     def get_session(self, req, sessionid):
         """return existing session for the given session identifier"""
         raise NotImplementedError()
 
     def open_session(self, req):
         """open and return a new session for the given request
-        
+
         :raise ExplicitLogin: if authentication is required
         """
         raise NotImplementedError()
-    
+
     def close_session(self, session):
         """close session on logout or on invalid session detected (expired out,
         corrupted...)
@@ -87,19 +86,19 @@
         raise NotImplementedError()
 
 
-class AbstractAuthenticationManager(SingletonComponent):
+class AbstractAuthenticationManager(Component):
     """authenticate user associated to a request and check session validity"""
     id = 'authmanager'
 
     def authenticate(self, req):
         """authenticate user and return corresponding user object
-        
+
         :raise ExplicitLogin: if authentication is required (no authentication
         info found or wrong user/password)
         """
         raise NotImplementedError()
 
-    
+
 class CookieSessionHandler(object):
     """a session handler using a cookie to store the session identifier
 
@@ -108,7 +107,7 @@
       identifier
     """
     SESSION_VAR = '__session'
-    
+
     def __init__(self, appli):
         self.session_manager = appli.vreg.select_component('sessionmanager')
         assert self.session_manager, 'no session manager found'
@@ -122,7 +121,7 @@
         time
         """
         self.session_manager.clean_sessions()
-        
+
     def set_session(self, req):
         """associate a session to the request
 
@@ -133,7 +132,7 @@
         if no session id is found, open a new session for the connected user
         or request authentification as needed
 
-        :raise Redirect: if authentication has occured and succeed        
+        :raise Redirect: if authentication has occured and succeed
         """
         assert req.cnx is None # at this point no cnx should be set on the request
         cookie = req.get_cookie()
@@ -155,7 +154,7 @@
 
     def get_session(self, req, sessionid):
         return self.session_manager.get_session(req, sessionid)
-    
+
     def open_session(self, req):
         session = self.session_manager.open_session(req)
         cookie = req.get_cookie()
@@ -178,7 +177,7 @@
         except:
             req.cnx.rollback()
             raise
-        
+
     def _postlogin(self, req):
         """postlogin: the user has been authenticated, redirect to the original
         page (index by default) with a welcome message
@@ -198,7 +197,7 @@
         if path == 'login':
             path = 'view'
         raise Redirect(req.build_url(path, **args))
-    
+
     def logout(self, req):
         """logout from the application by cleaning the session and raising
         `AuthenticationError`
@@ -212,7 +211,7 @@
     """Central registry for the web application. This is one of the central
     object in the web application, coupling dynamically loaded objects with
     the application's schema and the application's configuration objects.
-    
+
     It specializes the VRegistry by adding some convenience methods to
     access to stored objects. Currently we have the following registries
     of objects known by the web application (library may use some others
@@ -224,7 +223,7 @@
     * components
     * actions
     """
-    
+
     def __init__(self, config, debug=None,
                  session_handler_fact=CookieSessionHandler,
                  vreg=None):
@@ -244,14 +243,14 @@
             from threading import Lock
             self._query_log = open(config['query-log-file'], 'a')
             self.publish = self.log_publish
-            self._logfile_lock = Lock()            
+            self._logfile_lock = Lock()
         else:
             self._query_log = None
             self.publish = self.main_publish
         # instantiate session and url resolving helpers
         self.session_handler = session_handler_fact(self)
         self.url_resolver = vreg.select_component('urlpublisher')
-    
+
     def connect(self, req):
         """return a connection for a logged user object according to existing
         sessions (i.e. a new connection may be created or an already existing
@@ -267,9 +266,9 @@
                                req=req, appli=self)
         except NoSelectableObject:
             raise Unauthorized(req._('not authorized'))
-            
+
     # publish methods #########################################################
-        
+
     def log_publish(self, path, req):
         """wrapper around _publish to log all queries executed for a given
         accessed path
@@ -294,13 +293,13 @@
 
     def main_publish(self, path, req):
         """method called by the main publisher to process <path>
-        
+
         should return a string containing the resulting page or raise a
         `NotFound` exception
 
         :type path: str
         :param path: the path part of the url to publish
-        
+
         :type req: `web.Request`
         :param req: the request object
 
@@ -371,7 +370,7 @@
             req.set_session_data(req.form['__errorurl'], forminfo)
             raise Redirect(req.form['__errorurl'])
         self.error_handler(req, ex, tb=False)
-        
+
     def error_handler(self, req, ex, tb=False):
         excinfo = sys.exc_info()
         self.exception(repr(ex))
@@ -384,21 +383,30 @@
             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):
         return self.vreg.main_template(req, 'login')
-    
+
     def loggedout_content(self, req):
         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)
--- a/web/box.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/box.py	Thu May 14 12:50:34 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,23 +22,24 @@
 _ = 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
     rendering.
-    
+
     You may override on of the formatting callbacks is this is not necessary
     for your custom box.
-    
+
     Classes inheriting from this class usually only have to override call
     to fetch desired actions, and then to do something like  ::
 
         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 = {
         _('visible'): dict(type='Boolean', default=True,
@@ -80,9 +76,9 @@
         if escape:
             title = html_escape(title)
         return self.box_action(self._action(title, path, **kwargs))
-    
+
     def _action(self, title, path, **kwargs):
-        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)        
+        return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
 
     # formating callbacks
 
@@ -95,25 +91,24 @@
         cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass
         return BoxLink(action.url(), self.req._(action.title),
                        cls, self.boxitem_link_tooltip(action))
-        
+
 
 class RQLBoxTemplate(BoxTemplate):
     """abstract box for boxes displaying the content of a rql query not
     related to the current result set.
-    
+
     It rely on etype, rtype (both optional, usable to control registration
     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
-    
+
     def to_display_rql(self):
         assert self.rql is not None, self.id
         return (self.rql,)
-    
+
     def call(self, **kwargs):
         try:
             rset = self.req.execute(*self.to_display_rql())
@@ -128,7 +123,7 @@
             box.append(self.mk_action(tname, entity.absolute_url()))
         box.render(w=self.w)
 
-        
+
 class UserRQLBoxTemplate(RQLBoxTemplate):
     """same as rql box template but the rql is build using the eid of the
     request's user
@@ -137,34 +132,22 @@
     def to_display_rql(self):
         assert self.rql is not None, self.id
         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"""
         self.cell_call(row, col, **kwargs)
 
 
 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
@@ -182,37 +165,32 @@
     subclasses should define at least id, rtype and target
     class attributes.
     """
-    
-    def cell_call(self, row, col, view=None):
+
+    def cell_call(self, row, col, view=None, **kwargs):
         self.req.add_js('cubicweb.ajax.js')
         entity = self.entity(row, col)
         box = SideBoxWidget(display_name(self.req, self.rtype), self.id)
         count = self.w_related(box, entity)
         if count:
             box.append(BoxSeparator())
-        self.w_unrelated(box, entity)
+        if not self.w_unrelated(box, entity):
+            del box.items[-1] # remove useless separator
         box.render(self.w)
 
     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,
                                                etarget.view('incontext'))
         return RawBoxItem(label, liclass=u'invisible')
-    
+
     def w_related(self, box, entity):
         """appends existing relations to the `box`"""
         rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
@@ -220,12 +198,15 @@
         for etarget in related:
             box.append(self.box_item(entity, etarget, rql, u'-'))
         return len(related)
-    
+
     def w_unrelated(self, box, entity):
         """appends unrelated entities to the `box`"""
         rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+        i = 0
         for etarget in self.unrelated_entities(entity):
             box.append(self.box_item(entity, etarget, rql, u'+'))
+            i += 1
+        return i
 
     def unrelated_entities(self, entity):
         """returns the list of unrelated entities
@@ -233,19 +214,20 @@
         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):
+        form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                       row=self.row or 0)
+        field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
+        for _, eid in form.form_field_vocabulary(field):
             if eid is not None:
                 rset = self.req.eid_rset(eid)
                 entities.append(rset.get_entity(0, 0))
         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	Thu May 14 12:50:14 2009 +0200
+++ b/web/component.py	Thu May 14 12:50:34 2009 +0200
@@ -1,71 +1,72 @@
 """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)
 
 _ = unicode
 
-
-class EntityVComponent(VComponent):
+class EntityVComponent(Component):
     """abstract base class for additinal components displayed in content
     headers and footer according to:
-    
+
     * the displayed entity's type
     * a context (currently 'header' or 'footer')
 
     it should be configured using .accepts, .etype, .rtype, .target and
     .context class attributes
     """
-    
+
     __registry__ = 'contentnavigation'
-    __registerer__ = action_registerer    
-    __selectors__ = (one_line_rset, primary_view,
-                     match_context_prop, etype_rtype_selector,
-                     has_relation, accept,
-                     rql_condition)
-    
+    __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,
                             help=_('display the box or not')),
         _('order'):    dict(type='Int', default=99,
                             help=_('display order of the component')),
         _('context'):  dict(type='String', default='header',
-                            vocabulary=(_('navtop'), _('navbottom'), 
+                            vocabulary=(_('navtop'), _('navbottom'),
                                         _('navcontenttop'), _('navcontentbottom')),
                             #vocabulary=(_('header'), _('incontext'), _('footer')),
                             help=_('context where this component should be displayed')),
         _('htmlclass'):dict(type='String', default='mainRelated',
                             help=_('html class of the component')),
     }
-    
-    accepts = ('Any',)
+
     context = 'navcontentbottom' # 'footer' | 'header' | 'incontext'
-    condition = None
-    
-    def call(self, view):
-        return self.cell_call(0, 0, view)
 
-    def cell_call(self, row, col, view):
+    def call(self, view=None):
+        return self.cell_call(0, 0, view=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()
+
+    property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the component or not')),
+        }
+
     page_size_property = 'navigation.page-size'
     start_param = '__start'
     stop_param = '__stop'
@@ -74,21 +75,8 @@
     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)
+    def __init__(self, req, rset, **kwargs):
+        super(NavigationComponent, self).__init__(req, rset, **kwargs)
         self.starting_from = 0
         self.total = rset.rowcount
 
@@ -96,14 +84,20 @@
         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
-        
+
     page_size = property(get_page_size, set_page_size)
-    
+
     def page_boundaries(self):
         try:
             stop = int(self.req.form[self.stop_param]) + 1
@@ -112,7 +106,7 @@
             start, stop = 0, self.page_size
         self.starting_from = start
         return start, stop
-        
+
     def clean_params(self, params):
         if self.start_param in params:
             del params[self.start_param]
@@ -151,25 +145,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 +166,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/controller.py	Thu May 14 12:50:34 2009 +0200
@@ -2,17 +2,17 @@
 
 
 :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
+import datetime
 
 from cubicweb import typed_eid
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.selectors import match_user_group
-from cubicweb.common.appobject import AppObject
+from cubicweb.utils import strptime, todate, todatetime
+from cubicweb.selectors import yes, require_group_compat
+from cubicweb.appobject import AppObject
 from cubicweb.web import LOGGER, Redirect, RequestError
 
 
@@ -21,7 +21,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
@@ -47,7 +47,7 @@
         for subj in subjs.split('_'):
             for obj in objs.split('_'):
                 yield typed_eid(subj), rtype, typed_eid(obj)
-        
+
 def append_url_params(url, params):
     """append raw parameters to the url. Given parameters, if any, are expected
     to be already url-quoted.
@@ -67,16 +67,15 @@
     and another linked by forms to edit objects ("edit").
     """
     __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)
         # attributes use to control after edition redirection
         self._after_deletion_path = None
         self._edited_entity = None
-        
+
     def publish(self, rset=None):
         """publish the current request, with an option input rql string
         (already processed if necessary)
@@ -84,7 +83,18 @@
         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
         dictionary
@@ -96,7 +106,7 @@
         if missing:
             raise RequestError('missing required parameter(s): %s'
                                % ','.join(missing))
-    
+
     def parse_datetime(self, value, etype='Datetime'):
         """get a datetime or time from a string (according to etype)
         Datetime formatted as Date are accepted
@@ -106,21 +116,24 @@
         if etype == 'Datetime':
             format = self.req.property_value('ui.datetime-format')
             try:
-                return strptime(value, format)
-            except MxDTError:
+                return todatetime(strptime(value, format))
+            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 datetime.time(date.hour, date.minute, date.second)
+            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:
+            dt = strptime(value, format)
+            if etype == 'Datetime':
+                return todatetime(dt)
+            return todate(dt)
+        except:
             raise ValueError('can\'t parse %r (expected %s)' % (value, format))
 
 
@@ -131,7 +144,7 @@
         #       relation) that might not be satisfied yet (in case of creations)
         if not self._edited_entity:
             self._edited_entity = entity
-        
+
     def delete_entities(self, eidtypes):
         """delete entities from the repository"""
         redirect_info = set()
@@ -150,7 +163,7 @@
             self.req.set_message(self.req._('entities deleted'))
         else:
             self.req.set_message(self.req._('entity deleted'))
-        
+
     def delete_relations(self, rdefs):
         """delete relations from the repository"""
         # FIXME convert to using the syntax subject:relation:eids
@@ -159,7 +172,7 @@
             rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
             execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
         self.req.set_message(self.req._('relations deleted'))
-    
+
     def insert_relations(self, rdefs):
         """insert relations into the repository"""
         execute = self.req.execute
@@ -167,7 +180,7 @@
             rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
             execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
 
-    
+
     def reset(self):
         """reset form parameters and redirect to a view determinated by given
         parameters
@@ -211,7 +224,7 @@
         url = self.build_url(path, **newparams)
         url = append_url_params(url, self.req.form.get('__redirectparams'))
         raise Redirect(url)
-    
+
 
     def _return_to_edition_view(self, newparams):
         """apply-button case"""
--- a/web/data/cubicweb.acl.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.acl.css	Thu May 14 12:50:34 2009 +0200
@@ -9,19 +9,90 @@
 /* security edition form (views/management.py)                                */
 /******************************************************************************/
 
+h2.schema{
+ background : #ff7700;
+ color: #fff;
+ font-weight: bold;
+ padding : 0.1em 0.3em;
+}
+
+
+h3.schema{ 
+ font-weight: bold;
+}
+
+h4 a,
+h4 a:link,
+h4 a:visited{ 
+ color:#000;
+ }
+
 table.schemaInfo {
-  margin: 1ex 1em;
+  margin: 1em 0em;
   text-align: left;
   border: 1px solid black;
   border-collapse: collapse;
+  width:100%;
 }
 
 table.schemaInfo th,
 table.schemaInfo td {
-  padding: 0em 1em;
-  border: 1px solid black;
+  padding: .3em .5em;
+  border: 1px solid grey;
+  width:33%; 
+}
+
+
+table.schemaInfo tr th {   
+ padding: 0.2em 0px 0.2em 5px;
+ background-image:none;
+ background-color:#dfdfdf;
+}
+
+table.schemaInfo thead tr {
+  border: 1px solid #dfdfdf;
+} 
+
+table.schemaInfo td {
+  padding: 3px 10px 3px 5px; 
+
 }
 
+.users{ 
+ color : #00CC33;
+ font-weight: bold }
+
+.guests{ 
+ color :  #ff7700;
+ font-weight: bold;
+}
+
+.staff{  
+ color : #0083ab;
+ font-weight: bold;
+}
+
+.owners{ 
+ color : #8b0000;
+ font-weight: bold;
+}
+
+.discret,
+a.grey{ 
+ color:#666;
+}
+
+a.grey:hover{ 
+ color:#000;
+}
+
+.red{ 
+ color :  #ff7700;
+ }
+
+div#schema_security{ 
+ width:780px;
+ }
 /******************************************************************************/
 /* user groups edition form (views/euser.py)                                  */
 /******************************************************************************/
--- a/web/data/cubicweb.ajax.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 /*
  *  :organization: Logilab
- *  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ *  :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
@@ -9,6 +9,36 @@
 
 var JSON_BASE_URL = baseuri() + 'json?';
 
+function _loadAjaxHtmlHead(node, head, tag, srcattr) {
+    var loaded = [];
+    jQuery('head ' + tag).each(function(i) {
+	loaded.push(this.getAttribute(srcattr));
+    });
+    node.find(tag).each(function(i) {
+	if (!loaded.contains(this.getAttribute(srcattr))) {
+	    jQuery(this).appendTo(head);
+	}
+    });
+    node.find(tag).remove();
+}
+
+/*
+ * 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) {
+    var head = jQuery('head');
+    node = jQuery(node).find('div.ajaxHtmlHead');
+    _loadAjaxHtmlHead(node, head, 'script', 'src');
+    _loadAjaxHtmlHead(node, head, 'link', 'href');
+    node.find('*').appendTo(head);
+}
+
+function preprocessAjaxLoad(node, newdomnode) {
+    loadAjaxHtmlHead(newdomnode);
+}
+
 function postAjaxLoad(node) {
     // find sortable tables if there are some
     if (typeof(Sortable) != 'undefined') {
@@ -27,11 +57,12 @@
     if (typeof roundedCornersOnLoad != 'undefined') {
 	roundedCornersOnLoad();
     }
+    loadDynamicFragments(node);
+    jQuery(CubicWeb).trigger('ajax-loaded');
 }
 
 // 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,16 +72,16 @@
     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) {
 	callback = data.callback;
 	delete data.callback;
     }
-    var node = this.get(0); // only consider the first element
-    ajax = jQuery.post;
     ajax(url, data, function(response) {
 	var domnode = getDomFromResponse(response);
+	preprocessAjaxLoad(node, domnode);
 	if (mode == 'swap') {
 	    var origId = node.id;
 	    node = swapDOM(node, domnode);
@@ -74,8 +105,12 @@
 /* finds each dynamic fragment in the page and executes the
  * the associated RQL to build them (Async call)
  */
-function loadDynamicFragments() {
-    var fragments = jQuery('div.dynamicFragment');
+function loadDynamicFragments(node) {
+    if (node) {
+	var fragments = jQuery(node).find('div.dynamicFragment');
+    } else {
+	var fragments = jQuery('div.dynamicFragment');
+    }
     if (fragments.length == 0) {
 	return;
     }
@@ -97,26 +132,10 @@
     }
 }
 
-jQuery(document).ready(loadDynamicFragments);
+jQuery(document).ready(function() {loadDynamicFragments();});
 
 //============= base AJAX functions to make remote calls =====================//
 
-
-/*
- * This function will call **synchronously** a remote method on the cubicweb server
- * @param fname: the function name to call (as exposed by the JSONController)
- * @param args: the list of arguments to pass the function
- */
-function remote_exec(fname) {
-    setProgressCursor();
-    var props = {'mode' : "remote", 'fname' : fname, 'pageid' : pageid,
-     		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
-    var result  = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText;
-    result = evalJSON(result);
-    resetCursor();
-    return result;
-}
-
 function remoteCallFailed(err, req) {
     if (req.status == 500) {
 	updateMessage(err);
@@ -125,92 +144,64 @@
     }
 }
 
+
 /*
- * This function is the equivalent of MochiKit's loadJSONDoc but
- * uses POST instead of GET
+ * This function will call **synchronously** a remote method on the cubicweb server
+ * @param fname: the function name to call (as exposed by the JSONController)
+ *
+ * additional arguments will be directly passed to the specified function
+ *
+ * It looks at http headers to guess the response type.
  */
-function loadJSONDocUsingPOST(url, queryargs, mode) {
-    mode = mode || 'remote';
+function remoteExec(fname /* ... */) {
     setProgressCursor();
-    var dataType = (mode == 'remote') ? "json":null;
-    var deferred = loadJSON(url, queryargs, 'POST', dataType);
+    var props = {'fname' : fname, 'pageid' : pageid,
+     		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+    var result  = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText;
+    if (result) {
+	result = evalJSON(result);
+    }
+    resetCursor();
+    return result;
+}
+
+/*
+ * This function will call **asynchronously** a remote method on the json
+ * controller of the cubicweb http server
+ *
+ * @param fname: the function name to call (as exposed by the JSONController)
+ *
+ * additional arguments will be directly passed to the specified function
+ *
+ * It looks at http headers to guess the response type.
+ */
+
+function asyncRemoteExec(fname /* ... */) {
+    setProgressCursor();
+    var props = {'fname' : fname, 'pageid' : pageid,
+     		 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+    var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
     deferred = deferred.addErrback(remoteCallFailed);
-//     if (mode == 'remote') {
-// 	deferred = deferred.addCallbacks(evalJSONRequest);
-//     }
+    deferred = deferred.addErrback(resetCursor);
     deferred = deferred.addCallback(resetCursor);
     return deferred;
 }
 
 
-function _buildRemoteArgs(fname) {
-    return  {'mode' : "remote", 'fname' : fname, 'pageid' : pageid,
-     	     'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
-}
-
-/*
- * This function will call **asynchronously** a remote method on the cubicweb server
- * This function is a low level one. You should use `async_remote_exec` or
- * `async_rawremote_exec` instead.
- *
- * @param fname: the function name to call (as exposed by the JSONController)
- * @param funcargs: the function's arguments
- * @param mode: rawremote or remote
- */
-function _async_exec(fname, funcargs, mode) {
-    var props = {'mode' : mode, 'fname' : fname, 'pageid' : pageid};
-    var args = map(urlEncode, map(jQuery.toJSON, funcargs));
-    args.unshift(''); // this is to be able to use join() directly
-    var queryargs = as_url(props) + args.join('&arg=');
-    return loadJSONDocUsingPOST(JSON_BASE_URL, queryargs, mode);
-}
-
-/*
- * This function will call **asynchronously** a remote method on the cubicweb server
- * @param fname: the function name to call (as exposed by the JSONController)
- * additional arguments will be directly passed to the specified function
- * Expected response type is Json.
- */
-function async_remote_exec(fname /* ... */) {
-    return _async_exec(fname, sliceList(arguments, 1), 'remote');
-}
-
-/*
- * This version of _async_exec doesn't expect a json response.
- * It looks at http headers to guess the response type.
- */
-function async_rawremote_exec(fname /* ... */) {
-    return _async_exec(fname, sliceList(arguments, 1), 'rawremote');
-}
-
-/*
- * This function will call **asynchronously** a remote method on the cubicweb server
- * @param fname: the function name to call (as exposed by the JSONController)
- * @param varargs: the list of arguments to pass to the function
- * This is an alternative form of `async_remote_exec` provided for convenience
- */
-function async_remote_exec_varargs(fname, varargs) {
-    return _async_exec(fname, varargs, 'remote');
-}
-
 /* emulation of gettext's _ shortcut
  */
 function _(message) {
-    return remote_exec('i18n', [message])[0];
-}
-
-function rqlexec(rql) {
-    return async_remote_exec('rql', rql);
+    return remoteExec('i18n', [message])[0];
 }
 
 function userCallback(cbname) {
-    async_remote_exec('user_callback', cbname);
+    asyncRemoteExec('user_callback', cbname);
 }
 
 function unloadPageData() {
     // NOTE: do not make async calls on unload if you want to avoid
     //       strange bugs
-    remote_exec('unload_page_data');
+    remoteExec('unload_page_data');
 }
 
 function openHash() {
@@ -228,7 +219,7 @@
     nodeid = nodeid || (compid + 'Component');
     extraargs = extraargs || {};
     var node = getNode(nodeid);
-    var d = async_rawremote_exec('component', compid, rql, registry, extraargs);
+    var d = asyncRemoteExec('component', compid, rql, registry, extraargs);
     d.addCallback(function(result, req) {
 	var domnode = getDomFromResponse(result);
 	if (node) {
@@ -251,7 +242,7 @@
 }
 
 function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) {
-    var d = async_remote_exec('user_callback', cbname);
+    var d = asyncRemoteExec('user_callback', cbname);
     d.addCallback(function() {
 	reloadComponent(compid, rql, registry, nodeid);
 	if (msg) { updateMessage(msg); }
@@ -265,7 +256,7 @@
 }
 
 function userCallbackThenReloadPage(cbname, msg) {
-    var d = async_remote_exec('user_callback', cbname);
+    var d = asyncRemoteExec('user_callback', cbname);
     d.addCallback(function() {
 	window.location.reload();
 	if (msg) { updateMessage(msg); }
@@ -283,7 +274,7 @@
  * while the page was generated.
  */
 function unregisterUserCallback(cbname) {
-    var d = async_remote_exec('unregister_user_callback', cbname);
+    var d = asyncRemoteExec('unregister_user_callback', cbname);
     d.addCallback(function() {resetCursor();});
     d.addErrback(function(xxx) {
 	updateMessage(_("an error occured"));
@@ -311,20 +302,33 @@
     var props = {};
     if (node) {
 	props['rql'] = rql;
+	props['fname'] = 'view';
 	props['pageid'] = pageid;
 	if (vid) { props['vid'] = vid; }
 	if (extraparams) { jQuery.extend(props, extraparams); }
-	// FIXME we need to do as_url(props) manually instead of
+	// FIXME we need to do asURL(props) manually instead of
 	// passing `props` directly to loadxml because replacePageChunk
 	// is sometimes called (abusively) with some extra parameters in `vid`
 	var mode = swap?'swap':'replace';
-	var url = JSON_BASE_URL + as_url(props);
+	var url = JSON_BASE_URL + asURL(props);
 	jQuery(node).loadxhtml(url, params, 'get', mode);
     } else {
 	log('Node', nodeId, 'not found');
     }
 }
 
+/*
+ * fetches `url` and replaces `nodeid`'s content with the result
+ * @param replacemode how the replacement should be done (default is 'replace')
+ *  Possible values are :
+ *    - 'replace' to replace the node's content with the generated HTML
+ *    - 'swap' to replace the node itself with the generated HTML
+ *    - 'append' to append the generated HTML to the node's content
+ */
+function loadxhtml(nodeid, url, /* ... */ replacemode) {
+    jQuery('#' + nodeid).loadxhtml(url, null, 'post', replacemode);
+}
+
 /* XXX: this function should go in edition.js but as for now, htmlReplace
  * references it.
  *
@@ -349,6 +353,22 @@
 jQuery(document).ready(buildWysiwygEditors);
 
 
+/*
+ * takes a list of DOM nodes and removes all empty text nodes
+ */
+function stripEmptyTextNodes(nodelist) {
+    var stripped = [];
+    for (var i=0; i < nodelist.length; i++) {
+	var node = nodelist[i];
+	if (isTextNode(node) && !node.textContent.strip()) {
+	    continue;
+	} else {
+	    stripped.push(node);
+	}
+    }
+    return stripped;
+}
+
 /* convenience function that returns a DOM node based on req's result. */
 function getDomFromResponse(response) {
     if (typeof(response) == 'string') {
@@ -360,6 +380,7 @@
 	// no child (error cases) => return the whole document
 	return doc.cloneNode(true);
     }
+    children = stripEmptyTextNodes(children);
     if (children.length == 1) {
 	// only one child => return it
 	return children[0].cloneNode(true);
--- a/web/data/cubicweb.bookmarks.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.bookmarks.js	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 CubicWeb.require('ajax.js');
 
 function removeBookmark(beid) {
-    d = async_remote_exec('delete_bookmark', beid);
+    d = asyncRemoteExec('delete_bookmark', beid);
     d.addCallback(function(boxcontent) {
 	    reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
   	document.location.hash = '#header';
--- a/web/data/cubicweb.calendar.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.calendar.js	Thu May 14 12:50:34 2009 +0200
@@ -235,7 +235,7 @@
     //      the only way understood by both IE and Mozilla. Otherwise,
     //      IE accepts innerText and mozilla accepts textContent
     var selectedDate = new Date(cal.year, cal.month, cell.innerHTML, 12);
-    var xxx = remote_exec("format_date", toISOTimestamp(selectedDate));
+    var xxx = remoteExec("format_date", toISOTimestamp(selectedDate));
     input.value = xxx;
     cal.hide();
 }
--- a/web/data/cubicweb.compat.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.compat.js	Thu May 14 12:50:34 2009 +0200
@@ -365,19 +365,26 @@
 };
 
 
-function loadJSON(url, data, type, dataType) {
+/*
+ * Asynchronously load an url and return a deferred
+ * whose callbacks args are decoded according to
+ * the Content-Type response header
+ */
+function loadRemote(url, data, reqtype) {
     var d = new Deferred();
     jQuery.ajax({
 	url: url,
-	type: type,
+	type: reqtype,
 	data: data,
-	dataType: dataType,
 
 	beforeSend: function(xhr) {
 	    d.req = xhr;
 	},
 
 	success: function(data, status) {
+            if (d.req.getResponseHeader("content-type") == 'application/json') {
+              data = evalJSON(data);
+            }
 	    d.success(data);
 	},
 
@@ -507,23 +514,7 @@
 var KEYS = {
     KEY_ESC: 27,
     KEY_ENTER: 13
-}
+};
 
-// XHR = null;
-// function test() {
-//     var d = loadJSON('http://crater:9876/json?mode=remote&fname=i18n&pageid=xxx&arg=' + jQuery.toJSON(['modify']));
-//     d = d.addCallback(function (result, xhr) {
-// 	XHR = xhr;
-// 	log('got ajax result 1' + result + xhr);
-// 	log('got ajax result 1' + xhr);
-// 	log('got ajax result 1' + xhr + 'arguments =', arguments.length);
-//     });
-//     d.addCallback(function (x, req, y, z) {
-// 	log('callback 2 x =' + x, ' req=', req, 'y =', y, 'z=',z);
-//     }, 12, 13)
-//     d.addErrback(function (error, xhr) {
-// 	XHR = xhr;
-// 	log('got err', error, ' code =', xhr.status, 'arguments length=', arguments.length);
-//     })
-// }
 
+
--- a/web/data/cubicweb.edition.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.edition.js	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 /*
  *  :organization: Logilab
- *  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ *  :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
@@ -20,10 +20,11 @@
  * @param tabindex the tabindex that should be set on the widget
  */
 function setPropValueWidget(varname, tabindex) {
-    var key = firstSelected(jQuery('#pkey:'+varname));
+    var key = firstSelected(document.getElementById('pkey:'+varname));
     if (key) {
-	var args = _buildRemoteArgs('prop_widget', key, varname, tabindex);
-	jQuery('#div:value:'+varname).loadxhtml(JSON_BASE_URL, args, 'post');
+	var args = {fname: 'prop_widget', pageid: pageid,
+     		    arg: map(jQuery.toJSON, [key, varname, tabindex])};
+	jqNode('div:value:'+varname).loadxhtml(JSON_BASE_URL, args, 'post');
     }
 }
 
@@ -51,40 +52,25 @@
     });
 }
 
+
 function showMatchingSelect(selectedValue, eid) {
     if (selectedValue) {
 	divId = 'div' + selectedValue + '_' + eid;
 	var divNode = jQuery('#' + divId);
 	if (!divNode.length) {
 	    var args = {vid: 'unrelateddivs', relation: selectedValue,
-			rql: rql_for_eid(eid), pageid: pageid,
-			'__notemplate': 1};
-	    jQuery.get(JSON_BASE_URL, args, function(response) {
-		// append generated HTML to the cell
-		jQuery('#unrelatedDivs_' + eid).append(getDomFromResponse(response));
-		_showMatchingSelect(eid, jQuery('#' + divId));
-	    });
-	    // deferred = doXHR(JSON_BASE_URL + queryString(args));
-	    // deferred.addCallback(_buildAndShowMatchingSelect, eid, divId);
+			rql: rql_for_eid(eid), '__notemplate': 1,
+			callback: function() {_showMatchingSelect(eid, jQuery('#' + divId))}};
+	    jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append');
 	} else {
 	    _showMatchingSelect(eid, divNode);
 	}
-    }
-    else {
+    } else {
 	_showMatchingSelect(eid, null);
     }
 }
 
 
-
-// @param divStr a HTML string returned by the server
-// function _buildAndShowMatchingSelect(eid, divId, req) {
-//     var tdNode = jQuery('#unrelatedDivs_' + eid);
-//     // append generated HTML to the cell
-//     tdNode.appendChild(getDomFromRequest(req));
-//     _showMatchingSelect(eid, jQuery('#' + divId));
-// }
-
 // @param divNode is a jQuery selection
 function _showMatchingSelect(eid, divNode) {
     // hide all divs, and then show the matching one
@@ -154,7 +140,7 @@
     // add hidden parameter
     var entityForm = jQuery('#entityForm');
     var oid = optionNode.id.substring(2); // option id is prefixed by "id"
-    remote_exec('add_pending_insert', oid.split(':'));
+    remoteExec('add_pending_inserts', [oid.split(':')]);
     var selectNode = optionNode.parentNode;
     // remove option node
     selectNode.removeChild(optionNode);
@@ -180,13 +166,15 @@
     if (comboId) {
 	// re-insert option in combobox if it was taken from there
 	var selectNode = getNode(comboId);
+        // XXX what on object relation
 	if (selectNode){
 	   var options = selectNode.options;
 	   var node_id = elementId.substring(0, elementId.indexOf(':'));
 	   options[options.length] = OPTION({'id' : elementId, 'value' : node_id}, entityView);
 	}
     }
-    remote_exec('remove_pending_insert', elementId.split(':'));
+    elementId = elementId.substring(2, elementId.length);
+    remoteExec('remove_pending_insert', elementId.split(':'));
 }
 
 // this function builds a Handle to cancel pending insertion
@@ -198,7 +186,7 @@
 
 // @param nodeId eid_from:r_type:eid_to
 function addPendingDelete(nodeId, eid) {
-    var d = async_remote_exec('add_pending_delete', nodeId.split(':'));
+    var d = asyncRemoteExec('add_pending_delete', nodeId.split(':'));
     d.addCallback(function () {
 	// and strike entity view
 	jqNode('span' + nodeId).addClass('pendingDelete');
@@ -209,7 +197,7 @@
 
 // @param nodeId eid_from:r_type:eid_to
 function cancelPendingDelete(nodeId, eid) {
-    var d = async_remote_exec('remove_pending_delete', nodeId.split(':'));
+    var d = asyncRemoteExec('remove_pending_delete', nodeId.split(':'));
     d.addCallback(function () {
 	// reset link's CSS class
 	jqNode('span' + nodeId).removeClass('pendingDelete');
@@ -232,11 +220,11 @@
 function selectForAssociation(tripletIdsString, originalEid) {
     var tripletlist = map(function (x) { return x.split(':'); },
 			  tripletIdsString.split('-'));
-    var d = async_remote_exec('add_pending_inserts', tripletlist);
+    var d = asyncRemoteExec('add_pending_inserts', tripletlist);
     d.addCallback(function () {
 	var args = {vid: 'edition', __mode: 'normal',
 		    rql: rql_for_eid(originalEid)};
-	document.location = 'view?' + as_url(args);
+	document.location = 'view?' + asURL(args);
     });
 
 }
@@ -246,36 +234,27 @@
     jQuery('#inline' + rtype + 'slot span.icounter').each(function (i) {
 	this.innerHTML = i+1;
     });
-    // var divnode = jQuery('#inline' + rtype + 'slot');
-    // var iforms = getElementsByTagAndClassName('span', 'icounter', divnode);
-    // for (var i=0; i<iforms.length; i++) {
-    //   iforms[i].innerHTML = i+1;
-    // }
 }
 
+
 /*
  * makes an AJAX request to get an inline-creation view's content
  * @param peid : the parent entity eid
- * @param ptype : the parent entity type
  * @param ttype : the target (inlined) entity type
  * @param rtype : the relation type between both entities
  */
-function addInlineCreationForm(peid, ptype, ttype, rtype, role) {
-    var d = async_rawremote_exec('inline_creation_form', peid, ptype, ttype, rtype, role);
+function addInlineCreationForm(peid, ttype, rtype, role) {
+    var d = asyncRemoteExec('inline_creation_form', peid, ttype, rtype, role);
     d.addCallback(function (response) {
 	var linknode = getNode('add' + rtype + ':' + peid + 'link');
         var dom = getDomFromResponse(response);
 	var form = jQuery(dom);
 	form.css('display', 'none');
 	form.insertBefore(linknode.parentNode).slideDown('fast');
-	// setStyle(form, {display: 'none'});
-	// insertSiblingNodesBefore(linknode.parentNode, form);
 	updateInlinedEntitiesCounters(rtype);
-	// slideDown(form, {'duration':0.6});
 	reorderTabindex();
 	form.trigger('inlinedform-added');
         postAjaxLoad(dom);
-	// MochiKit.Signal.signal(CubicWeb, 'inlinedform-added', form);
     });
     d.addErrback(function (xxx) {
 	log('xxx =', xxx);
@@ -301,15 +280,13 @@
  */
 function removeInlinedEntity(peid, rtype, eid) {
     var nodeid = ['rel', peid, rtype, eid].join('-');
-    var divid = ['div', peid, rtype, eid].join('-');
-    var noticeid = ['notice', peid, rtype, eid].join('-');
     var node = jqNode(nodeid);
     if (node && node.length) {
 	node.remove();
+	var divid = ['div', peid, rtype, eid].join('-');
 	jqNode(divid).fadeTo('fast', 0.5);
-	// setOpacity(divid, 0.4);
+	var noticeid = ['notice', peid, rtype, eid].join('-');
 	jqNode(noticeid).fadeIn('fast');
-	// appear(jQuery('#' + noticeid), {'duration': 0.5});
     }
 }
 
@@ -322,11 +299,8 @@
 	node = INPUT({type: 'hidden', id: nodeid,
 		      name: rtype+':'+peid, value: eid});
 	jqNode(['fs', peid, rtype, eid].join('-')).append(node);
-	// appendChildNodes(fs, node);
 	jqNode(divid).fadeTo('fast', 1);
-	// setOpacity(divid, 1);
 	jqNode(noticeid).hide();
-	// jQuery('#' + noticeid).hide();
     }
 }
 
@@ -434,8 +408,8 @@
 	var target = form.attr('cubicweb:target');
 	if (target) {
 	    form.attr('target', target);
-	    /* do not use display: none because some browser ignore iframe
-             *     with no display */
+	    /* do not use display: none because some browsers ignore iframe
+             * with no display */
 	    form.append(IFRAME({name: target, id: target,
 				src: 'javascript: void(0)',
 				width: '0px', height: '0px'}));
@@ -445,10 +419,6 @@
 
 $(document).ready(setFormsTarget);
 
-function _sendForm(formid, action) {
-    var zipped = formContents(formid);
-    return async_remote_exec('validate_form', action, zipped[0], zipped[1]);
-}
 
 /*
  * called on traditionnal form submission : the idea is to try
@@ -458,7 +428,8 @@
  */
 function validateForm(formid, action, onsuccess) {
     try {
-	var d = _sendForm(formid, action);
+	var zipped = formContents(formid);
+	var d = asyncRemoteExec('validate_form', action, zipped[0], zipped[1]);
     } catch (ex) {
 	log('got exception', ex);
 	return false;
@@ -466,19 +437,19 @@
     function _callback(result, req) {
 	handleFormValidationResponse(formid, onsuccess, result);
     }
-    // d.addCallback(handleFormValidationResponse, formid, onsuccess);
     d.addCallback(_callback);
     return false;
 }
 
+
 /*
- * called by live-edit forms to submit changes
+ * called by reledit forms to submit changes
  * @param formid : the dom id of the form used
  * @param rtype : the attribute being edited
  * @param eid : the eid of the entity being edited
  * @param reload: boolean to reload page if true (when changing URL dependant data)
  */
-function inlineValidateForm(formid, rtype, eid, divid, reload) {
+function inlineValidateAttributeForm(formid, rtype, eid, divid, reload, default_value) {
     try {
 	var form = getNode(formid);
 	if (typeof FCKeditorAPI != "undefined") {
@@ -490,7 +461,8 @@
 	    }
 	}
 	var zipped = formContents(form);
-	var d = async_remote_exec('edit_field', 'apply', zipped[0], zipped[1], rtype, eid);
+	var d = asyncRemoteExec('edit_field', 'apply', zipped[0], zipped[1],
+                                rtype, eid, default_value);
     } catch (ex) {
 	log('got exception', ex);
 	return false;
@@ -517,6 +489,37 @@
     return false;
 }
 
+function inlineValidateRelationForm(formid, rtype, role, eid, divid, vid, default_value) {
+    try {
+	var form = getNode(formid);
+        var relname = rtype + ':' + eid;
+        var newtarget = jQuery('[name=' + relname + ']').val();
+	var zipped = formContents(form);
+	var d = asyncRemoteExec('edit_relation', 'apply', zipped[0], zipped[1], rtype, role,
+                                eid, vid, default_value);
+    } catch (ex) {
+	log('got exception', ex);
+	return false;
+    }
+    d.addCallback(function (result, req) {
+        handleFormValidationResponse(formid, noop, result);
+	var fieldview = getNode(divid);
+        fieldview.innerHTML = result[2];
+	// switch inline form off only if no error
+	if (result[0]) {
+          // hide global error messages
+	  jQuery('div.errorMessage').remove();
+	  jQuery('#appMsg').hide();
+          var inputname = 'edit' + role[0] + '-' + relname;
+          jQuery('input[name=' + inputname + ']').val(newtarget);
+	  cancelInlineEdit(eid, rtype, divid);
+	}
+        return false;
+    });
+  return false;
+}
+
+
 /**** inline edition ****/
 function showInlineEditionForm(eid, rtype, divid) {
     jQuery('#' + divid).hide();
--- a/web/data/cubicweb.form.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.form.css	Thu May 14 12:50:34 2009 +0200
@@ -56,7 +56,8 @@
 table.attributeForm {
   border: 1px solid #E4EAD8;
   margin-bottom: 1em;
-  padding: 0.8em;
+  padding: 0.8em 1.2em;
+  width: 100%;
 }
 
 fieldset.subentity table td {
@@ -77,11 +78,12 @@
 
 table.attributeForm th,
 table.attributeForm td {
-  padding : 0px 2px;
+  padding : .7em 2px;
 }
 
 table.attributeForm th {
-  text-align: right;
+  text-align: left;
+  width: 12em;
 }
 
 table.attributeForm div#comfirmPsw {
@@ -100,7 +102,7 @@
 
 table.attributeForm label,
 .entityForm .label {
-  padding : 0.2em  10px 0.2em 0.4em;
+  padding : 0.2em  1em 0.2em 0;
 }
 
 table.attributeForm label.required {
--- a/web/data/cubicweb.formfilter.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.formfilter.js	Thu May 14 12:50:34 2009 +0200
@@ -43,7 +43,7 @@
     var zipped = facetFormContent(form);
     zipped[0].push('facetargs');
     zipped[1].push(vidargs);
-    var d = async_remote_exec('filter_build_rql', zipped[0], zipped[1]);
+    var d = asyncRemoteExec('filter_build_rql', zipped[0], zipped[1]);
     d.addCallback(function(result) {
 	var rql = result[0];
 	var $bkLink = jQuery('#facetBkLink');
@@ -80,7 +80,7 @@
 		reloadComponent('edit_box', rql, 'boxes', 'edit_box');
 	    }
 	}
-	var d = async_remote_exec('filter_select_content', toupdate, rql);
+	var d = asyncRemoteExec('filter_select_content', toupdate, rql);
 	d.addCallback(function(updateMap) {
 	    for (facetId in updateMap) {
 		var values = updateMap[facetId];
--- a/web/data/cubicweb.htmlhelpers.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js	Thu May 14 12:50:34 2009 +0200
@@ -111,13 +111,13 @@
 }
 
 /* builds an url from an object (used as a dictionnary)
- * Notable difference with MochiKit's queryString: as_url does not
+ * Notable difference with MochiKit's queryString: asURL does not
  * *url_quote* each value found in the dictionnary
  *
- * >>> as_url({'rql' : "RQL", 'x': [1, 2], 'itemvid' : "oneline"})
+ * >>> asURL({'rql' : "RQL", 'x': [1, 2], 'itemvid' : "oneline"})
  * rql=RQL&vid=list&itemvid=oneline&x=1&x=2
  */
-function as_url(props) {
+function asURL(props) {
     var chunks = [];
     for(key in props) {
 	var value = props[key];
--- a/web/data/cubicweb.login.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.login.css	Thu May 14 12:50:34 2009 +0200
@@ -9,7 +9,7 @@
   position: absolute;
   z-index: 400;
   right: 0px;
-  width: 23em;
+  width: 26em;
   padding: 0px 1px 1px;
   font-weight: bold;
   background: #E4EAD8; 
@@ -24,8 +24,8 @@
   position : absolute;
   top: 15%;
   left : 50%;
-  margin-left: -11em;
-  width: 24em;
+  margin-left: -14em;
+  width: 28em;
   background: #fff;
   border: 2px solid #cfceb7;
   padding-bottom: 0.5em;
--- a/web/data/cubicweb.mailform.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.mailform.css	Thu May 14 12:50:34 2009 +0200
@@ -5,7 +5,7 @@
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
-div#compose {
+form#sendmail {
   border: 1px solid #DBDCE3;
   background-color: #E9F5F7;
   font-family:Verdana,Tahoma,Arial,sans-serif;
@@ -16,7 +16,7 @@
   width: 100%;
 }
 
-div#compose td#buttonbar {
+form#sendmail td#buttonbar {
   padding: 0.5ex 0ex;
 }
 
@@ -36,12 +36,12 @@
   width: 47em; 
 }
 
-div#compose div#toolbar {
+form#sendmail div#toolbar {
   margin: 0.5em 0em;
   height: 29px;
 }
 
-div#compose div#toolbar ul {
+form#sendmail div#toolbar ul {
   list-style-image: none;
   list-style-position: outside;
   list-style-type:none;
@@ -50,13 +50,13 @@
   /* border: 1px solid #DBDCE3; */
 }
 
-div#compose div#toolbar li {
+form#sendmail div#toolbar li {
   background: none;
   padding-left: 1em;
   float: left;
 }
 
-div#compose div#toolbar li a {
+form#sendmail div#toolbar li a {
   font-family: Verdana,Tahoma,Arial,sans-serif;
   color: #444444;
 }
--- a/web/data/cubicweb.preferences.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.preferences.css	Thu May 14 12:50:34 2009 +0200
@@ -5,12 +5,46 @@
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
-.componentTitle{
+
+table.preferences td{ 
+ padding: 0 0.5em 1em;
+ }
+
+fieldset.preferences{  
+ border : 1px solid #CFCEB7;
+ margin:1em 0;
+ padding:0 1em 1em;
+}
+
+div.preffield {
+ margin-bottom: 0.8em ;
+}
+
+/*
+div.preffield label{ 
+ font-size:110%
+ }
+*/
+
+div.prefinput{ 
+ margin:.3em 0;
+}
+
+div.componentLink{ 
+ margin-top:0.3em;
+ }
+
+a.componentTitle{
  font-weight:bold;
- color: #ff7700;
- padding:0px 4px;
+ color: #000;
+ }
+
+a.componentTitle:visited{
+ color: #000;
 }
 
+
+
 h2.propertiesform a{
  display:block;
  margin: 10px 0px 6px 0px;
@@ -26,3 +60,56 @@
  background-color:#cfceb7;
  text-decoration:none;
 }
+
+div.prefinput select.changed,
+div.prefinput input.changed{ 
+ background:#eeedd9;
+ border: 1px solid #eeedd9;
+}
+
+div.prefinput select,
+div.prefinput input{ 
+ background:#fff;
+ border: 1px solid #CFCEB7;
+}
+
+.prefinput input.error {
+ background:transparent url(error.png) no-repeat scroll 100% 50% !important;
+}
+
+
+div.formsg{ 
+ font-weight:bold;
+ margin:0.5em 0px;
+ }
+
+
+div.formsg .critical{ 
+ color:red;
+ padding-left:20px;
+ background:#fff url(critical.png) no-repeat;
+ }
+
+div.formsg .message{ 
+ color : green;
+}
+
+.helper{
+  font-size: 96%;
+  color: #555544;
+  padding:0; 
+}
+
+div.prefinput .helper:hover {
+  color: #000;
+  cursor: default;
+}
+
+.error{ 
+ color:red;
+ padding-right:1em;
+ }
+
+div.openlink{ 
+ display:inline;
+ }
\ No newline at end of file
--- a/web/data/cubicweb.preferences.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.preferences.js	Thu May 14 12:50:34 2009 +0200
@@ -3,8 +3,176 @@
  * XXX whenever used outside of preferences, don't forget to
  *     move me in a more appropriate place
  */
-function toggle_and_remember_visibility(elemId, cookiename) {
+
+function toggleVisibility(elemId, cookiename) {
+    _clearPreviousMessages();
     jqNode(elemId).toggleClass('hidden');
-    async_remote_exec('set_cookie', cookiename,
+    asyncRemoteExec('set_cookie', cookiename,
                       jQuery('#' + elemId).attr('class'));
 }
+
+function closeFieldset(fieldsetid){
+    var linklabel = _('open all');
+    var linkhref = 'javascript:openFieldset("' +fieldsetid + '")'
+    _toggleFieldset(fieldsetid, 1, linklabel, linkhref)
+}
+
+function openFieldset(fieldsetid){
+    var linklabel = _('close all');
+    var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")'
+    _toggleFieldset(fieldsetid, 0, linklabel, linkhref)
+}
+
+
+function _toggleFieldset(fieldsetid, closeaction, linklabel, linkhref){
+    jQuery('#'+fieldsetid).find('div.openlink').each(function(){
+	    var link = A({'href' : "javascript:noop();",
+			  'onclick' : linkhref},
+			  linklabel)
+	    jQuery(this).empty().append(link);
+	});
+    jQuery('#'+fieldsetid).find('fieldset[id]').each(function(){
+	    var fieldset = jQuery(this);
+	    if(closeaction){
+		fieldset.addClass('hidden')
+	    }else{
+		fieldset.removeClass('hidden');
+		linkLabel = (_('open all'));
+	    }
+	});
+}
+
+function validatePrefsForm(formid){
+    var form = getNode(formid);
+    freezeFormButtons(formid);
+    try {
+	var d = _sendForm(formid, null);
+    } catch (ex) {
+	log('got exception', ex);
+	return false;
+    }
+    function _callback(result, req) {
+	_clearPreviousMessages();
+	_clearPreviousErrors(formid);
+	// success
+	if(result[0]){
+	    return submitSucces(formid)
+	}
+ 	// Failures
+	unfreezeFormButtons(formid);
+	var descr = result[1];
+        if (!isArrayLike(descr) || descr.length != 2) {
+	   log('got strange error :', descr);
+	   updateMessage(descr);
+	   return ;
+	}
+        _displayValidationerrors(formid, descr[0], descr[1]);
+	var dom = DIV({'class':'critical'},
+		      _("please correct errors below"));
+	jQuery(form).find('div.formsg').empty().append(dom);
+	updateMessage(_(""));
+	return false;
+    }
+    d.addCallback(_callback);
+    return false;
+}
+
+function submitSucces(formid){
+    var form = jQuery('#'+formid);
+    setCurrentValues(form);
+    var dom = DIV({'class':'message'},
+		  _("changes applied"));
+    jQuery(form).find('div.formsg').empty().append(dom);
+    jQuery(form).find('input').removeClass('changed');
+    checkValues(form, true);
+    return;
+}
+
+function _clearPreviousMessages() {
+    jQuery('div#appMsg').addClass('hidden');
+    jQuery('div.formsg').empty();
+}
+
+function _clearPreviousErrors(formid) {
+    jQuery('#' + formid + ' span.error').remove();
+}
+
+
+function checkValues(form, success){
+    var unfreezeButtons = false;
+    jQuery(form).find('select').each(function () { 
+	    unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
+	});
+    jQuery(form).find('[type=text]').each(function () {
+	    unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
+	});
+    jQuery(form).find('input[type=radio]').each(function () { 
+	    if (jQuery(this).attr('checked')){
+		unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
+	    }
+     }); 
+   
+    if (unfreezeButtons){
+	unfreezeFormButtons(form.attr('id'));
+    }else{
+	if (!success){
+	    _clearPreviousMessages();
+	}
+	_clearPreviousErrors(form.attr('id'));
+	freezeFormButtons(form.attr('id'));
+    }
+}
+
+function _checkValue(input, unfreezeButtons){
+     var currentValueInput = jQuery("input[id=current-" + input.attr('name') + "]");
+     if (currentValueInput.attr('value') != input.attr('value')){
+	 input.addClass('changed');
+	 unfreezeButtons = true;
+     }else{
+	 input.removeClass('changed');
+	 jQuery("span[id=err-" + input.attr('id') + "]").remove();
+     }	
+     input.removeClass('error');
+     return unfreezeButtons
+}
+
+
+function setCurrentValues(form){
+    jQuery(form).find('input[id^=current-value]').each(function () { 
+	    var currentValueInput = jQuery(this);
+	    var name = currentValueInput.attr('id').split('-')[1];
+	    jQuery(form).find("[name=" + name + "]").each(function (){
+		    var input = jQuery(this);
+		    if(input.attr('type')=='radio'){
+			if(input.attr('checked')){
+			    log(input.attr('value'));
+			    currentValueInput.attr('value', input.attr('value'));
+			}
+		    }else{
+			currentValueInput.attr('value', input.attr('value'));
+		    }
+		});
+    });
+}
+
+
+function initEvents(){
+  jQuery('form').each(function() { 
+	  var form = jQuery(this);
+	  freezeFormButtons(form.attr('id'));
+	  form.find('input[type=text]').keyup(function(){  
+		  checkValues(form);	   
+          });
+	  form.find('input[type=radio]').change(function(){  
+		  checkValues(form);	   
+          });
+	  form.find('select').change(function(){  
+		  checkValues(form);	 
+          });
+    });
+}
+
+$(document).ready(function() {
+	initEvents();
+});
+
--- a/web/data/cubicweb.tabs.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.tabs.js	Thu May 14 12:50:34 2009 +0200
@@ -1,6 +1,6 @@
 function set_tab(tabname, cookiename) {
     // set appropriate cookie
-    async_remote_exec('set_cookie', cookiename, tabname);
+    asyncRemoteExec('set_cookie', cookiename, tabname);
     // trigger show + tabname event
     trigger_load(tabname);
 }
--- a/web/data/cubicweb.timeline-bundle.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.timeline-bundle.js	Thu May 14 12:50:34 2009 +0200
@@ -1,3980 +1,10123 @@
-
-var SimileAjax = {
-    loaded:                 false,
-    loadingScriptsCount:    0,
-    error:                  null,
-    params:                 { bundle:"true" }
+var SimileAjax_urlPrefix = baseuri() + 'data/';
+var Timeline_urlPrefix = baseuri() + 'data/';
+
+/*==================================================
+ *  Simile Ajax API
+ *
+ *  Include this file in your HTML file as follows:
+ *
+ *    <script src="http://simile.mit.edu/ajax/api/simile-ajax-api.js" type="text/javascript"></script>
+ *
+ *==================================================
+ */
+
+if (typeof SimileAjax == "undefined") {
+    var SimileAjax = {
+        loaded:                 false,
+        loadingScriptsCount:    0,
+        error:                  null,
+        params:                 { bundle:"true" }
+    };
+
+    SimileAjax.Platform = new Object();
+        /*
+            HACK: We need these 2 things here because we cannot simply append
+            a <script> element containing code that accesses SimileAjax.Platform
+            to initialize it because IE executes that <script> code first
+            before it loads ajax.js and platform.js.
+        */
+
+    var getHead = function(doc) {
+        return doc.getElementsByTagName("head")[0];
+    };
+
+    SimileAjax.findScript = function(doc, substring) {
+        var heads = doc.documentElement.getElementsByTagName("head");
+        for (var h = 0; h < heads.length; h++) {
+            var node = heads[h].firstChild;
+            while (node != null) {
+                if (node.nodeType == 1 && node.tagName.toLowerCase() == "script") {
+                    var url = node.src;
+                    var i = url.indexOf(substring);
+                    if (i >= 0) {
+                        return url;
+                    }
+                }
+                node = node.nextSibling;
+            }
+        }
+        return null;
+    };
+    SimileAjax.includeJavascriptFile = function(doc, url, onerror, charset) {
+        onerror = onerror || "";
+        if (doc.body == null) {
+            try {
+                var q = "'" + onerror.replace( /'/g, '&apos' ) + "'"; // "
+                doc.write("<script src='" + url + "' onerror="+ q +
+                          (charset ? " charset='"+ charset +"'" : "") +
+                          " type='text/javascript'>"+ onerror + "</script>");
+                return;
+            } catch (e) {
+                // fall through
+            }
+        }
+
+        var script = doc.createElement("script");
+        if (onerror) {
+            try { script.innerHTML = onerror; } catch(e) {}
+            script.setAttribute("onerror", onerror);
+        }
+        if (charset) {
+            script.setAttribute("charset", charset);
+        }
+        script.type = "text/javascript";
+        script.language = "JavaScript";
+        script.src = url;
+        return getHead(doc).appendChild(script);
+    };
+    SimileAjax.includeJavascriptFiles = function(doc, urlPrefix, filenames) {
+        for (var i = 0; i < filenames.length; i++) {
+            SimileAjax.includeJavascriptFile(doc, urlPrefix + filenames[i]);
+        }
+        SimileAjax.loadingScriptsCount += filenames.length;
+        SimileAjax.includeJavascriptFile(doc, SimileAjax.urlPrefix + "scripts/signal.js?" + filenames.length);
+    };
+    SimileAjax.includeCssFile = function(doc, url) {
+        if (doc.body == null) {
+            try {
+                doc.write("<link rel='stylesheet' href='" + url + "' type='text/css'/>");
+                return;
+            } catch (e) {
+                // fall through
+            }
+        }
+
+        var link = doc.createElement("link");
+        link.setAttribute("rel", "stylesheet");
+        link.setAttribute("type", "text/css");
+        link.setAttribute("href", url);
+        getHead(doc).appendChild(link);
+    };
+    SimileAjax.includeCssFiles = function(doc, urlPrefix, filenames) {
+        for (var i = 0; i < filenames.length; i++) {
+            SimileAjax.includeCssFile(doc, urlPrefix + filenames[i]);
+        }
+    };
+
+    /**
+     * Append into urls each string in suffixes after prefixing it with urlPrefix.
+     * @param {Array} urls
+     * @param {String} urlPrefix
+     * @param {Array} suffixes
+     */
+    SimileAjax.prefixURLs = function(urls, urlPrefix, suffixes) {
+        for (var i = 0; i < suffixes.length; i++) {
+            urls.push(urlPrefix + suffixes[i]);
+        }
+    };
+
+    /**
+     * Parse out the query parameters from a URL
+     * @param {String} url    the url to parse, or location.href if undefined
+     * @param {Object} to     optional object to extend with the parameters
+     * @param {Object} types  optional object mapping keys to value types
+     *        (String, Number, Boolean or Array, String by default)
+     * @return a key/value Object whose keys are the query parameter names
+     * @type Object
+     */
+    SimileAjax.parseURLParameters = function(url, to, types) {
+        to = to || {};
+        types = types || {};
+
+        if (typeof url == "undefined") {
+            url = location.href;
+        }
+        var q = url.indexOf("?");
+        if (q < 0) {
+            return to;
+        }
+        url = (url+"#").slice(q+1, url.indexOf("#")); // toss the URL fragment
+
+        var params = url.split("&"), param, parsed = {};
+        var decode = window.decodeURIComponent || unescape;
+        for (var i = 0; param = params[i]; i++) {
+            var eq = param.indexOf("=");
+            var name = decode(param.slice(0,eq));
+            var old = parsed[name];
+            if (typeof old == "undefined") {
+                old = [];
+            } else if (!(old instanceof Array)) {
+                old = [old];
+            }
+            parsed[name] = old.concat(decode(param.slice(eq+1)));
+        }
+        for (var i in parsed) {
+            if (!parsed.hasOwnProperty(i)) continue;
+            var type = types[i] || String;
+            var data = parsed[i];
+            if (!(data instanceof Array)) {
+                data = [data];
+            }
+            if (type === Boolean && data[0] == "false") {
+                to[i] = false; // because Boolean("false") === true
+            } else {
+                to[i] = type.apply(this, data);
+            }
+        }
+        return to;
+    };
+
+    (function() {
+        var javascriptFiles = [
+            "jquery-1.2.6.js",
+            "platform.js",
+            "debug.js",
+            "xmlhttp.js",
+            "json.js",
+            "dom.js",
+            "graphics.js",
+            "date-time.js",
+            "string.js",
+            "html.js",
+            "data-structure.js",
+            "units.js",
+
+            "ajax.js",
+            "history.js",
+            "window-manager.js"
+        ];
+        var cssFiles = [
+            "graphics.css"
+        ];
+
+        if (typeof SimileAjax_urlPrefix == "string") {
+            SimileAjax.urlPrefix = SimileAjax_urlPrefix;
+        } else {
+            var url = SimileAjax.findScript(document, "simile-ajax-api.js");
+            if (url == null) {
+                SimileAjax.error = new Error("Failed to derive URL prefix for Simile Ajax API code files");
+                return;
+            }
+
+            SimileAjax.urlPrefix = url.substr(0, url.indexOf("simile-ajax-api.js"));
+        }
+
+        SimileAjax.parseURLParameters(url, SimileAjax.params, {bundle:Boolean});
+//         if (SimileAjax.params.bundle) {
+//             SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix, [ "simile-ajax-bundle.js" ]);
+//         } else {
+//             SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix + "scripts/", javascriptFiles);
+//         }
+        SimileAjax.includeCssFiles(document, SimileAjax.urlPrefix + "styles/", cssFiles);
+
+        SimileAjax.loaded = true;
+    })();
+}
+/*==================================================
+ *  Platform Utility Functions and Constants
+ *==================================================
+ */
+
+/*  This must be called after our jQuery has been loaded
+    but before control returns to user-code.
+*/
+SimileAjax.jQuery = jQuery;
+// SimileAjax.jQuery = jQuery.noConflict(true);
+if (typeof window["$"] == "undefined") {
+    window.$ = SimileAjax.jQuery;
+}
+
+SimileAjax.Platform.os = {
+    isMac:   false,
+    isWin:   false,
+    isWin32: false,
+    isUnix:  false
+};
+SimileAjax.Platform.browser = {
+    isIE:           false,
+    isNetscape:     false,
+    isMozilla:      false,
+    isFirefox:      false,
+    isOpera:        false,
+    isSafari:       false,
+
+    majorVersion:   0,
+    minorVersion:   0
+};
+
+(function() {
+    var an = navigator.appName.toLowerCase();
+	var ua = navigator.userAgent.toLowerCase();
+
+    /*
+     *  Operating system
+     */
+	SimileAjax.Platform.os.isMac = (ua.indexOf('mac') != -1);
+	SimileAjax.Platform.os.isWin = (ua.indexOf('win') != -1);
+	SimileAjax.Platform.os.isWin32 = SimileAjax.Platform.isWin && (
+        ua.indexOf('95') != -1 ||
+        ua.indexOf('98') != -1 ||
+        ua.indexOf('nt') != -1 ||
+        ua.indexOf('win32') != -1 ||
+        ua.indexOf('32bit') != -1
+    );
+	SimileAjax.Platform.os.isUnix = (ua.indexOf('x11') != -1);
+
+    /*
+     *  Browser
+     */
+    SimileAjax.Platform.browser.isIE = (an.indexOf("microsoft") != -1);
+    SimileAjax.Platform.browser.isNetscape = (an.indexOf("netscape") != -1);
+    SimileAjax.Platform.browser.isMozilla = (ua.indexOf("mozilla") != -1);
+    SimileAjax.Platform.browser.isFirefox = (ua.indexOf("firefox") != -1);
+    SimileAjax.Platform.browser.isOpera = (an.indexOf("opera") != -1);
+    SimileAjax.Platform.browser.isSafari = (an.indexOf("safari") != -1);
+
+    var parseVersionString = function(s) {
+        var a = s.split(".");
+        SimileAjax.Platform.browser.majorVersion = parseInt(a[0]);
+        SimileAjax.Platform.browser.minorVersion = parseInt(a[1]);
+    };
+    var indexOf = function(s, sub, start) {
+        var i = s.indexOf(sub, start);
+        return i >= 0 ? i : s.length;
+    };
+
+    if (SimileAjax.Platform.browser.isMozilla) {
+        var offset = ua.indexOf("mozilla/");
+        if (offset >= 0) {
+            parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset)));
+        }
+    }
+    if (SimileAjax.Platform.browser.isIE) {
+        var offset = ua.indexOf("msie ");
+        if (offset >= 0) {
+            parseVersionString(ua.substring(offset + 5, indexOf(ua, ";", offset)));
+        }
+    }
+    if (SimileAjax.Platform.browser.isNetscape) {
+        var offset = ua.indexOf("rv:");
+        if (offset >= 0) {
+            parseVersionString(ua.substring(offset + 3, indexOf(ua, ")", offset)));
+        }
+    }
+    if (SimileAjax.Platform.browser.isFirefox) {
+        var offset = ua.indexOf("firefox/");
+        if (offset >= 0) {
+            parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset)));
+        }
+    }
+
+    if (!("localeCompare" in String.prototype)) {
+        String.prototype.localeCompare = function (s) {
+            if (this < s) return -1;
+            else if (this > s) return 1;
+            else return 0;
+        };
+    }
+})();
+
+SimileAjax.Platform.getDefaultLocale = function() {
+    return SimileAjax.Platform.clientLocale;
+};/*==================================================
+ *  Debug Utility Functions
+ *==================================================
+ */
+
+SimileAjax.Debug = {
+    silent: false
+};
+
+SimileAjax.Debug.log = function(msg) {
+    var f;
+    if ("console" in window && "log" in window.console) { // FireBug installed
+        f = function(msg2) {
+            console.log(msg2);
+        }
+    } else {
+        f = function(msg2) {
+            if (!SimileAjax.Debug.silent) {
+                alert(msg2);
+            }
+        }
+    }
+    SimileAjax.Debug.log = f;
+    f(msg);
+};
+
+SimileAjax.Debug.warn = function(msg) {
+    var f;
+    if ("console" in window && "warn" in window.console) { // FireBug installed
+        f = function(msg2) {
+            console.warn(msg2);
+        }
+    } else {
+        f = function(msg2) {
+            if (!SimileAjax.Debug.silent) {
+                alert(msg2);
+            }
+        }
+    }
+    SimileAjax.Debug.warn = f;
+    f(msg);
+};
+
+SimileAjax.Debug.exception = function(e, msg) {
+    var f, params = SimileAjax.parseURLParameters();
+    if (params.errors == "throw" || SimileAjax.params.errors == "throw") {
+        f = function(e2, msg2) {
+            throw(e2); // do not hide from browser's native debugging features
+        };
+    } else if ("console" in window && "error" in window.console) { // FireBug installed
+        f = function(e2, msg2) {
+            if (msg2 != null) {
+                console.error(msg2 + " %o", e2);
+            } else {
+                console.error(e2);
+            }
+            throw(e2); // do not hide from browser's native debugging features
+        };
+    } else {
+        f = function(e2, msg2) {
+            if (!SimileAjax.Debug.silent) {
+                alert("Caught exception: " + msg2 + "\n\nDetails: " + ("description" in e2 ? e2.description : e2));
+            }
+            throw(e2); // do not hide from browser's native debugging features
+        };
+    }
+    SimileAjax.Debug.exception = f;
+    f(e, msg);
+};
+
+SimileAjax.Debug.objectToString = function(o) {
+    return SimileAjax.Debug._objectToString(o, "");
+};
+
+SimileAjax.Debug._objectToString = function(o, indent) {
+    var indent2 = indent + " ";
+    if (typeof o == "object") {
+        var s = "{";
+        for (n in o) {
+            s += indent2 + n + ": " + SimileAjax.Debug._objectToString(o[n], indent2) + "\n";
+        }
+        s += indent + "}";
+        return s;
+    } else if (typeof o == "array") {
+        var s = "[";
+        for (var n = 0; n < o.length; n++) {
+            s += SimileAjax.Debug._objectToString(o[n], indent2) + "\n";
+        }
+        s += indent + "]";
+        return s;
+    } else {
+        return o;
+    }
+};
+/**
+ * @fileOverview XmlHttp utility functions
+ * @name SimileAjax.XmlHttp
+ */
+
+SimileAjax.XmlHttp = new Object();
+
+/**
+ *  Callback for XMLHttp onRequestStateChange.
+ */
+SimileAjax.XmlHttp._onReadyStateChange = function(xmlhttp, fError, fDone) {
+    switch (xmlhttp.readyState) {
+    // 1: Request not yet made
+    // 2: Contact established with server but nothing downloaded yet
+    // 3: Called multiple while downloading in progress
+
+    // Download complete
+    case 4:
+        try {
+            if (xmlhttp.status == 0     // file:// urls, works on Firefox
+             || xmlhttp.status == 200   // http:// urls
+            ) {
+                if (fDone) {
+                    fDone(xmlhttp);
+                }
+            } else {
+                if (fError) {
+                    fError(
+                        xmlhttp.statusText,
+                        xmlhttp.status,
+                        xmlhttp
+                    );
+                }
+            }
+        } catch (e) {
+            SimileAjax.Debug.exception("XmlHttp: Error handling onReadyStateChange", e);
+        }
+        break;
+    }
+};
+
+/**
+ *  Creates an XMLHttpRequest object. On the first run, this
+ *  function creates a platform-specific function for
+ *  instantiating an XMLHttpRequest object and then replaces
+ *  itself with that function.
+ */
+SimileAjax.XmlHttp._createRequest = function() {
+    if (SimileAjax.Platform.browser.isIE) {
+        var programIDs = [
+        "Msxml2.XMLHTTP",
+        "Microsoft.XMLHTTP",
+        "Msxml2.XMLHTTP.4.0"
+        ];
+        for (var i = 0; i < programIDs.length; i++) {
+            try {
+                var programID = programIDs[i];
+                var f = function() {
+                    return new ActiveXObject(programID);
+                };
+                var o = f();
+
+                // We are replacing the SimileAjax._createXmlHttpRequest
+                // function with this inner function as we've
+                // found out that it works. This is so that we
+                // don't have to do all the testing over again
+                // on subsequent calls.
+                SimileAjax.XmlHttp._createRequest = f;
+
+                return o;
+            } catch (e) {
+                // silent
+            }
+        }
+        // fall through to try new XMLHttpRequest();
+    }
+
+    try {
+        var f = function() {
+            return new XMLHttpRequest();
+        };
+        var o = f();
+
+        // We are replacing the SimileAjax._createXmlHttpRequest
+        // function with this inner function as we've
+        // found out that it works. This is so that we
+        // don't have to do all the testing over again
+        // on subsequent calls.
+        SimileAjax.XmlHttp._createRequest = f;
+
+        return o;
+    } catch (e) {
+        throw new Error("Failed to create an XMLHttpRequest object");
+    }
+};
+
+/**
+ * Performs an asynchronous HTTP GET.
+ *
+ * @param {Function} fError a function of the form
+     function(statusText, statusCode, xmlhttp)
+ * @param {Function} fDone a function of the form function(xmlhttp)
+ */
+SimileAjax.XmlHttp.get = function(url, fError, fDone) {
+    var xmlhttp = SimileAjax.XmlHttp._createRequest();
+
+    xmlhttp.open("GET", url, true);
+    xmlhttp.onreadystatechange = function() {
+        SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone);
+    };
+    xmlhttp.send(null);
+};
+
+/**
+ * Performs an asynchronous HTTP POST.
+ *
+ * @param {Function} fError a function of the form
+     function(statusText, statusCode, xmlhttp)
+ * @param {Function} fDone a function of the form function(xmlhttp)
+ */
+SimileAjax.XmlHttp.post = function(url, body, fError, fDone) {
+    var xmlhttp = SimileAjax.XmlHttp._createRequest();
+
+    xmlhttp.open("POST", url, true);
+    xmlhttp.onreadystatechange = function() {
+        SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone);
+    };
+    xmlhttp.send(body);
+};
+
+SimileAjax.XmlHttp._forceXML = function(xmlhttp) {
+    try {
+        xmlhttp.overrideMimeType("text/xml");
+    } catch (e) {
+        xmlhttp.setrequestheader("Content-Type", "text/xml");
+    }
+};/*
+ *  Copied directly from http://www.json.org/json.js.
+ */
+
+/*
+    json.js
+    2006-04-28
+
+    This file adds these methods to JavaScript:
+
+        object.toJSONString()
+
+            This method produces a JSON text from an object. The
+            object must not contain any cyclical references.
+
+        array.toJSONString()
+
+            This method produces a JSON text from an array. The
+            array must not contain any cyclical references.
+
+        string.parseJSON()
+
+            This method parses a JSON text to produce an object or
+            array. It will return false if there is an error.
+*/
+
+SimileAjax.JSON = new Object();
+
+(function () {
+    var m = {
+        '\b': '\\b',
+        '\t': '\\t',
+        '\n': '\\n',
+        '\f': '\\f',
+        '\r': '\\r',
+        '"' : '\\"',
+        '\\': '\\\\'
+    };
+    var s = {
+        array: function (x) {
+            var a = ['['], b, f, i, l = x.length, v;
+            for (i = 0; i < l; i += 1) {
+                v = x[i];
+                f = s[typeof v];
+                if (f) {
+                    v = f(v);
+                    if (typeof v == 'string') {
+                        if (b) {
+                            a[a.length] = ',';
+                        }
+                        a[a.length] = v;
+                        b = true;
+                    }
+                }
+            }
+            a[a.length] = ']';
+            return a.join('');
+        },
+        'boolean': function (x) {
+            return String(x);
+        },
+        'null': function (x) {
+            return "null";
+        },
+        number: function (x) {
+            return isFinite(x) ? String(x) : 'null';
+        },
+        object: function (x) {
+            if (x) {
+                if (x instanceof Array) {
+                    return s.array(x);
+                }
+                var a = ['{'], b, f, i, v;
+                for (i in x) {
+                    v = x[i];
+                    f = s[typeof v];
+                    if (f) {
+                        v = f(v);
+                        if (typeof v == 'string') {
+                            if (b) {
+                                a[a.length] = ',';
+                            }
+                            a.push(s.string(i), ':', v);
+                            b = true;
+                        }
+                    }
+                }
+                a[a.length] = '}';
+                return a.join('');
+            }
+            return 'null';
+        },
+        string: function (x) {
+            if (/["\\\x00-\x1f]/.test(x)) {
+                x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+                    var c = m[b];
+                    if (c) {
+                        return c;
+                    }
+                    c = b.charCodeAt();
+                    return '\\u00' +
+                        Math.floor(c / 16).toString(16) +
+                        (c % 16).toString(16);
+                });
+            }
+            return '"' + x + '"';
+        }
+    };
+
+    SimileAjax.JSON.toJSONString = function(o) {
+        if (o instanceof Object) {
+            return s.object(o);
+        } else if (o instanceof Array) {
+            return s.array(o);
+        } else {
+            return o.toString();
+        }
+    };
+
+    SimileAjax.JSON.parseJSON = function () {
+        try {
+            return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
+                    this.replace(/"(\\.|[^"\\])*"/g, ''))) &&
+                eval('(' + this + ')');
+        } catch (e) {
+            return false;
+        }
+    };
+})();
+/*==================================================
+ *  DOM Utility Functions
+ *==================================================
+ */
+
+SimileAjax.DOM = new Object();
+
+SimileAjax.DOM.registerEventWithObject = function(elmt, eventName, obj, handlerName) {
+    SimileAjax.DOM.registerEvent(elmt, eventName, function(elmt2, evt, target) {
+        return obj[handlerName].call(obj, elmt2, evt, target);
+    });
+};
+
+SimileAjax.DOM.registerEvent = function(elmt, eventName, handler) {
+    var handler2 = function(evt) {
+        evt = (evt) ? evt : ((event) ? event : null);
+        if (evt) {
+            var target = (evt.target) ?
+                evt.target : ((evt.srcElement) ? evt.srcElement : null);
+            if (target) {
+                target = (target.nodeType == 1 || target.nodeType == 9) ?
+                    target : target.parentNode;
+            }
+
+            return handler(elmt, evt, target);
+        }
+        return true;
+    }
+
+    if (SimileAjax.Platform.browser.isIE) {
+        elmt.attachEvent("on" + eventName, handler2);
+    } else {
+        elmt.addEventListener(eventName, handler2, false);
+    }
+};
+
+SimileAjax.DOM.getPageCoordinates = function(elmt) {
+    var left = 0;
+    var top = 0;
+
+    if (elmt.nodeType != 1) {
+        elmt = elmt.parentNode;
+    }
+
+    var elmt2 = elmt;
+    while (elmt2 != null) {
+        left += elmt2.offsetLeft;
+        top += elmt2.offsetTop;
+        elmt2 = elmt2.offsetParent;
+    }
+
+    var body = document.body;
+    while (elmt != null && elmt != body) {
+        if ("scrollLeft" in elmt) {
+            left -= elmt.scrollLeft;
+            top -= elmt.scrollTop;
+        }
+        elmt = elmt.parentNode;
+    }
+
+    return { left: left, top: top };
+};
+
+SimileAjax.DOM.getSize = function(elmt) {
+	var w = this.getStyle(elmt,"width");
+	var h = this.getStyle(elmt,"height");
+	if (w.indexOf("px") > -1) w = w.replace("px","");
+	if (h.indexOf("px") > -1) h = h.replace("px","");
+	return {
+		w: w,
+		h: h
+	}
+}
+
+SimileAjax.DOM.getStyle = function(elmt, styleProp) {
+    if (elmt.currentStyle) { // IE
+        var style = elmt.currentStyle[styleProp];
+    } else if (window.getComputedStyle) { // standard DOM
+        var style = document.defaultView.getComputedStyle(elmt, null).getPropertyValue(styleProp);
+    } else {
+    	var style = "";
+    }
+    return style;
+}
+
+SimileAjax.DOM.getEventRelativeCoordinates = function(evt, elmt) {
+    if (SimileAjax.Platform.browser.isIE) {
+      if (evt.type == "mousewheel") {
+        var coords = SimileAjax.DOM.getPageCoordinates(elmt);
+        return {
+          x: evt.clientX - coords.left,
+          y: evt.clientY - coords.top
+        };
+      } else {
+        return {
+          x: evt.offsetX,
+          y: evt.offsetY
+        };
+      }
+    } else {
+        var coords = SimileAjax.DOM.getPageCoordinates(elmt);
+
+        if ((evt.type == "DOMMouseScroll") &&
+          SimileAjax.Platform.browser.isFirefox &&
+          (SimileAjax.Platform.browser.majorVersion == 2)) {
+          // Due to: https://bugzilla.mozilla.org/show_bug.cgi?id=352179
+
+          return {
+            x: evt.screenX - coords.left,
+            y: evt.screenY - coords.top
+          };
+        } else {
+          return {
+              x: evt.pageX - coords.left,
+              y: evt.pageY - coords.top
+          };
+        }
+    }
+};
+
+SimileAjax.DOM.getEventPageCoordinates = function(evt) {
+    if (SimileAjax.Platform.browser.isIE) {
+        return {
+            x: evt.clientX + document.body.scrollLeft,
+            y: evt.clientY + document.body.scrollTop
+        };
+    } else {
+        return {
+            x: evt.pageX,
+            y: evt.pageY
+        };
+    }
+};
+
+SimileAjax.DOM.hittest = function(x, y, except) {
+    return SimileAjax.DOM._hittest(document.body, x, y, except);
+};
+
+SimileAjax.DOM._hittest = function(elmt, x, y, except) {
+    var childNodes = elmt.childNodes;
+    outer: for (var i = 0; i < childNodes.length; i++) {
+        var childNode = childNodes[i];
+        for (var j = 0; j < except.length; j++) {
+            if (childNode == except[j]) {
+                continue outer;
+            }
+        }
+
+        if (childNode.offsetWidth == 0 && childNode.offsetHeight == 0) {
+            /*
+             *  Sometimes SPAN elements have zero width and height but
+             *  they have children like DIVs that cover non-zero areas.
+             */
+            var hitNode = SimileAjax.DOM._hittest(childNode, x, y, except);
+            if (hitNode != childNode) {
+                return hitNode;
+            }
+        } else {
+            var top = 0;
+            var left = 0;
+
+            var node = childNode;
+            while (node) {
+                top += node.offsetTop;
+                left += node.offsetLeft;
+                node = node.offsetParent;
+            }
+
+            if (left <= x && top <= y && (x - left) < childNode.offsetWidth && (y - top) < childNode.offsetHeight) {
+                return SimileAjax.DOM._hittest(childNode, x, y, except);
+            } else if (childNode.nodeType == 1 && childNode.tagName == "TR") {
+                /*
+                 *  Table row might have cells that span several rows.
+                 */
+                var childNode2 = SimileAjax.DOM._hittest(childNode, x, y, except);
+                if (childNode2 != childNode) {
+                    return childNode2;
+                }
+            }
+        }
+    }
+    return elmt;
+};
+
+SimileAjax.DOM.cancelEvent = function(evt) {
+    evt.returnValue = false;
+    evt.cancelBubble = true;
+    if ("preventDefault" in evt) {
+        evt.preventDefault();
+    }
+};
+
+SimileAjax.DOM.appendClassName = function(elmt, className) {
+    var classes = elmt.className.split(" ");
+    for (var i = 0; i < classes.length; i++) {
+        if (classes[i] == className) {
+            return;
+        }
+    }
+    classes.push(className);
+    elmt.className = classes.join(" ");
+};
+
+SimileAjax.DOM.createInputElement = function(type) {
+    var div = document.createElement("div");
+    div.innerHTML = "<input type='" + type + "' />";
+
+    return div.firstChild;
+};
+
+SimileAjax.DOM.createDOMFromTemplate = function(template) {
+    var result = {};
+    result.elmt = SimileAjax.DOM._createDOMFromTemplate(template, result, null);
+
+    return result;
+};
+
+SimileAjax.DOM._createDOMFromTemplate = function(templateNode, result, parentElmt) {
+    if (templateNode == null) {
+        /*
+        var node = doc.createTextNode("--null--");
+        if (parentElmt != null) {
+            parentElmt.appendChild(node);
+        }
+        return node;
+        */
+        return null;
+    } else if (typeof templateNode != "object") {
+        var node = document.createTextNode(templateNode);
+        if (parentElmt != null) {
+            parentElmt.appendChild(node);
+        }
+        return node;
+    } else {
+        var elmt = null;
+        if ("tag" in templateNode) {
+            var tag = templateNode.tag;
+            if (parentElmt != null) {
+                if (tag == "tr") {
+                    elmt = parentElmt.insertRow(parentElmt.rows.length);
+                } else if (tag == "td") {
+                    elmt = parentElmt.insertCell(parentElmt.cells.length);
+                }
+            }
+            if (elmt == null) {
+                elmt = tag == "input" ?
+                    SimileAjax.DOM.createInputElement(templateNode.type) :
+                    document.createElement(tag);
+
+                if (parentElmt != null) {
+                    parentElmt.appendChild(elmt);
+                }
+            }
+        } else {
+            elmt = templateNode.elmt;
+            if (parentElmt != null) {
+                parentElmt.appendChild(elmt);
+            }
+        }
+
+        for (var attribute in templateNode) {
+            var value = templateNode[attribute];
+
+            if (attribute == "field") {
+                result[value] = elmt;
+
+            } else if (attribute == "className") {
+                elmt.className = value;
+            } else if (attribute == "id") {
+                elmt.id = value;
+            } else if (attribute == "title") {
+                elmt.title = value;
+            } else if (attribute == "type" && elmt.tagName == "input") {
+                // do nothing
+            } else if (attribute == "style") {
+                for (n in value) {
+                    var v = value[n];
+                    if (n == "float") {
+                        n = SimileAjax.Platform.browser.isIE ? "styleFloat" : "cssFloat";
+                    }
+                    elmt.style[n] = v;
+                }
+            } else if (attribute == "children") {
+                for (var i = 0; i < value.length; i++) {
+                    SimileAjax.DOM._createDOMFromTemplate(value[i], result, elmt);
+                }
+            } else if (attribute != "tag" && attribute != "elmt") {
+                elmt.setAttribute(attribute, value);
+            }
+        }
+        return elmt;
+    }
+}
+
+SimileAjax.DOM._cachedParent = null;
+SimileAjax.DOM.createElementFromString = function(s) {
+    if (SimileAjax.DOM._cachedParent == null) {
+        SimileAjax.DOM._cachedParent = document.createElement("div");
+    }
+    SimileAjax.DOM._cachedParent.innerHTML = s;
+    return SimileAjax.DOM._cachedParent.firstChild;
+};
+
+SimileAjax.DOM.createDOMFromString = function(root, s, fieldElmts) {
+    var elmt = typeof root == "string" ? document.createElement(root) : root;
+    elmt.innerHTML = s;
+
+    var dom = { elmt: elmt };
+    SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts != null ? fieldElmts : {} );
+
+    return dom;
+};
+
+SimileAjax.DOM._processDOMConstructedFromString = function(dom, elmt, fieldElmts) {
+    var id = elmt.id;
+    if (id != null && id.length > 0) {
+        elmt.removeAttribute("id");
+        if (id in fieldElmts) {
+            var parentElmt = elmt.parentNode;
+            parentElmt.insertBefore(fieldElmts[id], elmt);
+            parentElmt.removeChild(elmt);
+
+            dom[id] = fieldElmts[id];
+            return;
+        } else {
+            dom[id] = elmt;
+        }
+    }
+
+    if (elmt.hasChildNodes()) {
+        SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts);
+    }
+};
+
+SimileAjax.DOM._processDOMChildrenConstructedFromString = function(dom, elmt, fieldElmts) {
+    var node = elmt.firstChild;
+    while (node != null) {
+        var node2 = node.nextSibling;
+        if (node.nodeType == 1) {
+            SimileAjax.DOM._processDOMConstructedFromString(dom, node, fieldElmts);
+        }
+        node = node2;
+    }
+};
+/**
+ * @fileOverview Graphics utility functions and constants
+ * @name SimileAjax.Graphics
+ */
+
+SimileAjax.Graphics = new Object();
+
+/**
+ * A boolean value indicating whether PNG translucency is supported on the
+ * user's browser or not.
+ *
+ * @type Boolean
+ */
+SimileAjax.Graphics.pngIsTranslucent = (!SimileAjax.Platform.browser.isIE) || (SimileAjax.Platform.browser.majorVersion > 6);
+if (!SimileAjax.Graphics.pngIsTranslucent) {
+    SimileAjax.includeCssFile(document, SimileAjax.urlPrefix + "styles/graphics-ie6.css");
+}
+
+/*==================================================
+ *  Opacity, translucency
+ *==================================================
+ */
+SimileAjax.Graphics._createTranslucentImage1 = function(url, verticalAlign) {
+    var elmt = document.createElement("img");
+    elmt.setAttribute("src", url);
+    if (verticalAlign != null) {
+        elmt.style.verticalAlign = verticalAlign;
+    }
+    return elmt;
+};
+SimileAjax.Graphics._createTranslucentImage2 = function(url, verticalAlign) {
+    var elmt = document.createElement("img");
+    elmt.style.width = "1px";  // just so that IE will calculate the size property
+    elmt.style.height = "1px";
+    elmt.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image')";
+    elmt.style.verticalAlign = (verticalAlign != null) ? verticalAlign : "middle";
+    return elmt;
+};
+
+/**
+ * Creates a DOM element for an <code>img</code> tag using the URL given. This
+ * is a convenience method that automatically includes the necessary CSS to
+ * allow for translucency, even on IE.
+ *
+ * @function
+ * @param {String} url the URL to the image
+ * @param {String} verticalAlign the CSS value for the image's vertical-align
+ * @return {Element} a DOM element containing the <code>img</code> tag
+ */
+SimileAjax.Graphics.createTranslucentImage = SimileAjax.Graphics.pngIsTranslucent ?
+    SimileAjax.Graphics._createTranslucentImage1 :
+    SimileAjax.Graphics._createTranslucentImage2;
+
+SimileAjax.Graphics._createTranslucentImageHTML1 = function(url, verticalAlign) {
+    return "<img src=\"" + url + "\"" +
+        (verticalAlign != null ? " style=\"vertical-align: " + verticalAlign + ";\"" : "") +
+        " />";
+};
+SimileAjax.Graphics._createTranslucentImageHTML2 = function(url, verticalAlign) {
+    var style =
+        "width: 1px; height: 1px; " +
+        "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image');" +
+        (verticalAlign != null ? " vertical-align: " + verticalAlign + ";" : "");
+
+    return "<img src='" + url + "' style=\"" + style + "\" />";
+};
+
+/**
+ * Creates an HTML string for an <code>img</code> tag using the URL given.
+ * This is a convenience method that automatically includes the necessary CSS
+ * to allow for translucency, even on IE.
+ *
+ * @function
+ * @param {String} url the URL to the image
+ * @param {String} verticalAlign the CSS value for the image's vertical-align
+ * @return {String} a string containing the <code>img</code> tag
+ */
+SimileAjax.Graphics.createTranslucentImageHTML = SimileAjax.Graphics.pngIsTranslucent ?
+    SimileAjax.Graphics._createTranslucentImageHTML1 :
+    SimileAjax.Graphics._createTranslucentImageHTML2;
+
+/**
+ * Sets the opacity on the given DOM element.
+ *
+ * @param {Element} elmt the DOM element to set the opacity on
+ * @param {Number} opacity an integer from 0 to 100 specifying the opacity
+ */
+SimileAjax.Graphics.setOpacity = function(elmt, opacity) {
+    if (SimileAjax.Platform.browser.isIE) {
+        elmt.style.filter = "progid:DXImageTransform.Microsoft.Alpha(Style=0,Opacity=" + opacity + ")";
+    } else {
+        var o = (opacity / 100).toString();
+        elmt.style.opacity = o;
+        elmt.style.MozOpacity = o;
+    }
+};
+
+/*==================================================
+ *  Bubble
+ *==================================================
+ */
+
+SimileAjax.Graphics.bubbleConfig = {
+    containerCSSClass:              "simileAjax-bubble-container",
+    innerContainerCSSClass:         "simileAjax-bubble-innerContainer",
+    contentContainerCSSClass:       "simileAjax-bubble-contentContainer",
+
+    borderGraphicSize:              50,
+    borderGraphicCSSClassPrefix:    "simileAjax-bubble-border-",
+
+    arrowGraphicTargetOffset:       33,  // from tip of arrow to the side of the graphic that touches the content of the bubble
+    arrowGraphicLength:             100, // dimension of arrow graphic along the direction that the arrow points
+    arrowGraphicWidth:              49,  // dimension of arrow graphic perpendicular to the direction that the arrow points
+    arrowGraphicCSSClassPrefix:     "simileAjax-bubble-arrow-",
+
+    closeGraphicCSSClass:           "simileAjax-bubble-close",
+
+    extraPadding:                   20
+};
+
+/**
+ * Creates a nice, rounded bubble popup with the given content in a div,
+ * page coordinates and a suggested width. The bubble will point to the
+ * location on the page as described by pageX and pageY.  All measurements
+ * should be given in pixels.
+ *
+ * @param {Element} the content div
+ * @param {Number} pageX the x coordinate of the point to point to
+ * @param {Number} pageY the y coordinate of the point to point to
+ * @param {Number} contentWidth a suggested width of the content
+ * @param {String} orientation a string ("top", "bottom", "left", or "right")
+ *   that describes the orientation of the arrow on the bubble
+ * @param {Number} maxHeight. Add a scrollbar div if bubble would be too tall.
+ *   Default of 0 or null means no maximum
+ */
+SimileAjax.Graphics.createBubbleForContentAndPoint = function(
+       div, pageX, pageY, contentWidth, orientation, maxHeight) {
+    if (typeof contentWidth != "number") {
+        contentWidth = 300;
+    }
+    if (typeof maxHeight != "number") {
+        maxHeight = 0;
+    }
+
+    div.style.position = "absolute";
+    div.style.left = "-5000px";
+    div.style.top = "0px";
+    div.style.width = contentWidth + "px";
+    document.body.appendChild(div);
+
+    window.setTimeout(function() {
+        var width = div.scrollWidth + 10;
+        var height = div.scrollHeight + 10;
+        var scrollDivW = 0; // width of the possible inner container when we want vertical scrolling
+        if (maxHeight > 0 && height > maxHeight) {
+          height = maxHeight;
+          scrollDivW = width - 25;
+        }
+
+        var bubble = SimileAjax.Graphics.createBubbleForPoint(pageX, pageY, width, height, orientation);
+
+        document.body.removeChild(div);
+        div.style.position = "static";
+        div.style.left = "";
+        div.style.top = "";
+
+        // create a scroll div if needed
+        if (scrollDivW > 0) {
+          var scrollDiv = document.createElement("div");
+          div.style.width = "";
+          scrollDiv.style.width = scrollDivW + "px";
+          scrollDiv.appendChild(div);
+          bubble.content.appendChild(scrollDiv);
+        } else {
+          div.style.width = width + "px";
+          bubble.content.appendChild(div);
+        }
+    }, 200);
+};
+
+/**
+ * Creates a nice, rounded bubble popup with the given page coordinates and
+ * content dimensions.  The bubble will point to the location on the page
+ * as described by pageX and pageY.  All measurements should be given in
+ * pixels.
+ *
+ * @param {Number} pageX the x coordinate of the point to point to
+ * @param {Number} pageY the y coordinate of the point to point to
+ * @param {Number} contentWidth the width of the content box in the bubble
+ * @param {Number} contentHeight the height of the content box in the bubble
+ * @param {String} orientation a string ("top", "bottom", "left", or "right")
+ *   that describes the orientation of the arrow on the bubble
+ * @return {Element} a DOM element for the newly created bubble
+ */
+SimileAjax.Graphics.createBubbleForPoint = function(pageX, pageY, contentWidth, contentHeight, orientation) {
+    contentWidth = parseInt(contentWidth, 10); // harden against bad input bugs
+    contentHeight = parseInt(contentHeight, 10); // getting numbers-as-strings
+
+    var bubbleConfig = SimileAjax.Graphics.bubbleConfig;
+    var pngTransparencyClassSuffix =
+        SimileAjax.Graphics.pngIsTranslucent ? "pngTranslucent" : "pngNotTranslucent";
+
+    var bubbleWidth = contentWidth + 2 * bubbleConfig.borderGraphicSize;
+    var bubbleHeight = contentHeight + 2 * bubbleConfig.borderGraphicSize;
+
+    var generatePngSensitiveClass = function(className) {
+        return className + " " + className + "-" + pngTransparencyClassSuffix;
+    };
+
+    /*
+     *  Render container divs
+     */
+    var div = document.createElement("div");
+    div.className = generatePngSensitiveClass(bubbleConfig.containerCSSClass);
+    div.style.width = contentWidth + "px";
+    div.style.height = contentHeight + "px";
+
+    var divInnerContainer = document.createElement("div");
+    divInnerContainer.className = generatePngSensitiveClass(bubbleConfig.innerContainerCSSClass);
+    div.appendChild(divInnerContainer);
+
+    /*
+     *  Create layer for bubble
+     */
+    var close = function() {
+        if (!bubble._closed) {
+            document.body.removeChild(bubble._div);
+            bubble._doc = null;
+            bubble._div = null;
+            bubble._content = null;
+            bubble._closed = true;
+        }
+    }
+    var bubble = { _closed: false };
+    var layer = SimileAjax.WindowManager.pushLayer(close, true, div);
+    bubble._div = div;
+    bubble.close = function() { SimileAjax.WindowManager.popLayer(layer); }
+
+    /*
+     *  Render border graphics
+     */
+    var createBorder = function(classNameSuffix) {
+        var divBorderGraphic = document.createElement("div");
+        divBorderGraphic.className = generatePngSensitiveClass(bubbleConfig.borderGraphicCSSClassPrefix + classNameSuffix);
+        divInnerContainer.appendChild(divBorderGraphic);
+    };
+    createBorder("top-left");
+    createBorder("top-right");
+    createBorder("bottom-left");
+    createBorder("bottom-right");
+    createBorder("left");
+    createBorder("right");
+    createBorder("top");
+    createBorder("bottom");
+
+    /*
+     *  Render content
+     */
+    var divContentContainer = document.createElement("div");
+    divContentContainer.className = generatePngSensitiveClass(bubbleConfig.contentContainerCSSClass);
+    divInnerContainer.appendChild(divContentContainer);
+    bubble.content = divContentContainer;
+
+    /*
+     *  Render close button
+     */
+    var divClose = document.createElement("div");
+    divClose.className = generatePngSensitiveClass(bubbleConfig.closeGraphicCSSClass);
+    divInnerContainer.appendChild(divClose);
+    SimileAjax.WindowManager.registerEventWithObject(divClose, "click", bubble, "close");
+
+    (function() {
+        var dims = SimileAjax.Graphics.getWindowDimensions();
+        var docWidth = dims.w;
+        var docHeight = dims.h;
+
+        var halfArrowGraphicWidth = Math.ceil(bubbleConfig.arrowGraphicWidth / 2);
+
+        var createArrow = function(classNameSuffix) {
+            var divArrowGraphic = document.createElement("div");
+            divArrowGraphic.className = generatePngSensitiveClass(bubbleConfig.arrowGraphicCSSClassPrefix + "point-" + classNameSuffix);
+            divInnerContainer.appendChild(divArrowGraphic);
+            return divArrowGraphic;
+        };
+
+        if (pageX - halfArrowGraphicWidth - bubbleConfig.borderGraphicSize - bubbleConfig.extraPadding > 0 &&
+            pageX + halfArrowGraphicWidth + bubbleConfig.borderGraphicSize + bubbleConfig.extraPadding < docWidth) {
+
+            /*
+             *  Bubble can be positioned above or below the target point.
+             */
+
+            var left = pageX - Math.round(contentWidth / 2);
+            left = pageX < (docWidth / 2) ?
+                Math.max(left, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) :
+                Math.min(left, docWidth - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentWidth);
+
+            if ((orientation && orientation == "top") ||
+                (!orientation &&
+                    (pageY
+                        - bubbleConfig.arrowGraphicTargetOffset
+                        - contentHeight
+                        - bubbleConfig.borderGraphicSize
+                        - bubbleConfig.extraPadding > 0))) {
+
+                /*
+                 *  Position bubble above the target point.
+                 */
+
+                var divArrow = createArrow("down");
+                divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px";
+
+                div.style.left = left + "px";
+                div.style.top = (pageY - bubbleConfig.arrowGraphicTargetOffset - contentHeight) + "px";
+
+                return;
+            } else if ((orientation && orientation == "bottom") ||
+                (!orientation &&
+                    (pageY
+                        + bubbleConfig.arrowGraphicTargetOffset
+                        + contentHeight
+                        + bubbleConfig.borderGraphicSize
+                        + bubbleConfig.extraPadding < docHeight))) {
+
+                /*
+                 *  Position bubble below the target point.
+                 */
+
+                var divArrow = createArrow("up");
+                divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px";
+
+                div.style.left = left + "px";
+                div.style.top = (pageY + bubbleConfig.arrowGraphicTargetOffset) + "px";
+
+                return;
+            }
+        }
+
+        var top = pageY - Math.round(contentHeight / 2);
+        top = pageY < (docHeight / 2) ?
+            Math.max(top, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) :
+            Math.min(top, docHeight - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentHeight);
+
+        if ((orientation && orientation == "left") ||
+            (!orientation &&
+                (pageX
+                    - bubbleConfig.arrowGraphicTargetOffset
+                    - contentWidth
+                    - bubbleConfig.borderGraphicSize
+                    - bubbleConfig.extraPadding > 0))) {
+
+            /*
+             *  Position bubble left of the target point.
+             */
+
+            var divArrow = createArrow("right");
+            divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px";
+
+            div.style.top = top + "px";
+            div.style.left = (pageX - bubbleConfig.arrowGraphicTargetOffset - contentWidth) + "px";
+        } else {
+
+            /*
+             *  Position bubble right of the target point, as the last resort.
+             */
+
+            var divArrow = createArrow("left");
+            divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px";
+
+            div.style.top = top + "px";
+            div.style.left = (pageX + bubbleConfig.arrowGraphicTargetOffset) + "px";
+        }
+    })();
+
+    document.body.appendChild(div);
+
+    return bubble;
+};
+
+SimileAjax.Graphics.getWindowDimensions = function() {
+    if (typeof window.innerHeight == 'number') {
+        return { w:window.innerWidth, h:window.innerHeight }; // Non-IE
+    } else if (document.documentElement && document.documentElement.clientHeight) {
+        return { // IE6+, in "standards compliant mode"
+            w:document.documentElement.clientWidth,
+            h:document.documentElement.clientHeight
+        };
+    } else if (document.body && document.body.clientHeight) {
+        return { // IE 4 compatible
+            w:document.body.clientWidth,
+            h:document.body.clientHeight
+        };
+    }
 };
 
 
 /**
- * Parse out the query parameters from a URL
- * @param {String} url    the url to parse, or location.href if undefined
- * @param {Object} to     optional object to extend with the parameters
- * @param {Object} types  optional object mapping keys to value types
- *        (String, Number, Boolean or Array, String by default)
- * @return a key/value Object whose keys are the query parameter names
- * @type Object
+ * Creates a floating, rounded message bubble in the center of the window for
+ * displaying modal information, e.g. "Loading..."
+ *
+ * @param {Document} doc the root document for the page to render on
+ * @param {Object} an object with two properties, contentDiv and containerDiv,
+ *   consisting of the newly created DOM elements
+ */
+SimileAjax.Graphics.createMessageBubble = function(doc) {
+    var containerDiv = doc.createElement("div");
+    if (SimileAjax.Graphics.pngIsTranslucent) {
+        var topDiv = doc.createElement("div");
+        topDiv.style.height = "33px";
+        topDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-left.png) top left no-repeat";
+        topDiv.style.paddingLeft = "44px";
+        containerDiv.appendChild(topDiv);
+
+        var topRightDiv = doc.createElement("div");
+        topRightDiv.style.height = "33px";
+        topRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-right.png) top right no-repeat";
+        topDiv.appendChild(topRightDiv);
+
+        var middleDiv = doc.createElement("div");
+        middleDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-left.png) top left repeat-y";
+        middleDiv.style.paddingLeft = "44px";
+        containerDiv.appendChild(middleDiv);
+
+        var middleRightDiv = doc.createElement("div");
+        middleRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-right.png) top right repeat-y";
+        middleRightDiv.style.paddingRight = "44px";
+        middleDiv.appendChild(middleRightDiv);
+
+        var contentDiv = doc.createElement("div");
+        middleRightDiv.appendChild(contentDiv);
+
+        var bottomDiv = doc.createElement("div");
+        bottomDiv.style.height = "55px";
+        bottomDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-left.png) bottom left no-repeat";
+        bottomDiv.style.paddingLeft = "44px";
+        containerDiv.appendChild(bottomDiv);
+
+        var bottomRightDiv = doc.createElement("div");
+        bottomRightDiv.style.height = "55px";
+        bottomRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-right.png) bottom right no-repeat";
+        bottomDiv.appendChild(bottomRightDiv);
+    } else {
+        containerDiv.style.border = "2px solid #7777AA";
+        containerDiv.style.padding = "20px";
+        containerDiv.style.background = "white";
+        SimileAjax.Graphics.setOpacity(containerDiv, 90);
+
+        var contentDiv = doc.createElement("div");
+        containerDiv.appendChild(contentDiv);
+    }
+
+    return {
+        containerDiv:   containerDiv,
+        contentDiv:     contentDiv
+    };
+};
+
+/*==================================================
+ *  Animation
+ *==================================================
+ */
+
+/**
+ * Creates an animation for a function, and an interval of values.  The word
+ * "animation" here is used in the sense of repeatedly calling a function with
+ * a current value from within an interval, and a delta value.
+ *
+ * @param {Function} f a function to be called every 50 milliseconds throughout
+ *   the animation duration, of the form f(current, delta), where current is
+ *   the current value within the range and delta is the current change.
+ * @param {Number} from a starting value
+ * @param {Number} to an ending value
+ * @param {Number} duration the duration of the animation in milliseconds
+ * @param {Function} [cont] an optional function that is called at the end of
+ *   the animation, i.e. a continuation.
+ * @return {SimileAjax.Graphics._Animation} a new animation object
+ */
+SimileAjax.Graphics.createAnimation = function(f, from, to, duration, cont) {
+    return new SimileAjax.Graphics._Animation(f, from, to, duration, cont);
+};
+
+SimileAjax.Graphics._Animation = function(f, from, to, duration, cont) {
+    this.f = f;
+    this.cont = (typeof cont == "function") ? cont : function() {};
+
+    this.from = from;
+    this.to = to;
+    this.current = from;
+
+    this.duration = duration;
+    this.start = new Date().getTime();
+    this.timePassed = 0;
+};
+
+/**
+ * Runs this animation.
+ */
+SimileAjax.Graphics._Animation.prototype.run = function() {
+    var a = this;
+    window.setTimeout(function() { a.step(); }, 50);
+};
+
+/**
+ * Increments this animation by one step, and then continues the animation with
+ * <code>run()</code>.
+ */
+SimileAjax.Graphics._Animation.prototype.step = function() {
+    this.timePassed += 50;
+
+    var timePassedFraction = this.timePassed / this.duration;
+    var parameterFraction = -Math.cos(timePassedFraction * Math.PI) / 2 + 0.5;
+    var current = parameterFraction * (this.to - this.from) + this.from;
+
+    try {
+        this.f(current, current - this.current);
+    } catch (e) {
+    }
+    this.current = current;
+
+    if (this.timePassed < this.duration) {
+        this.run();
+    } else {
+        this.f(this.to, 0);
+        this["cont"]();
+    }
+};
+
+/*==================================================
+ *  CopyPasteButton
+ *
+ *  Adapted from http://spaces.live.com/editorial/rayozzie/demo/liveclip/liveclipsample/techPreview.html.
+ *==================================================
+ */
+
+/**
+ * Creates a button and textarea for displaying structured data and copying it
+ * to the clipboard.  The data is dynamically generated by the given
+ * createDataFunction parameter.
+ *
+ * @param {String} image an image URL to use as the background for the
+ *   generated box
+ * @param {Number} width the width in pixels of the generated box
+ * @param {Number} height the height in pixels of the generated box
+ * @param {Function} createDataFunction a function that is called with no
+ *   arguments to generate the structured data
+ * @return a new DOM element
+ */
+SimileAjax.Graphics.createStructuredDataCopyButton = function(image, width, height, createDataFunction) {
+    var div = document.createElement("div");
+    div.style.position = "relative";
+    div.style.display = "inline";
+    div.style.width = width + "px";
+    div.style.height = height + "px";
+    div.style.overflow = "hidden";
+    div.style.margin = "2px";
+
+    if (SimileAjax.Graphics.pngIsTranslucent) {
+        div.style.background = "url(" + image + ") no-repeat";
+    } else {
+        div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + image +"', sizingMethod='image')";
+    }
+
+    var style;
+    if (SimileAjax.Platform.browser.isIE) {
+        style = "filter:alpha(opacity=0)";
+    } else {
+        style = "opacity: 0";
+    }
+    div.innerHTML = "<textarea rows='1' autocomplete='off' value='none' style='" + style + "' />";
+
+    var textarea = div.firstChild;
+    textarea.style.width = width + "px";
+    textarea.style.height = height + "px";
+    textarea.onmousedown = function(evt) {
+        evt = (evt) ? evt : ((event) ? event : null);
+        if (evt.button == 2) {
+            textarea.value = createDataFunction();
+            textarea.select();
+        }
+    };
+
+    return div;
+};
+
+/*==================================================
+ *  getWidthHeight
+ *==================================================
+ */
+SimileAjax.Graphics.getWidthHeight = function(el) {
+    // RETURNS hash {width:  w, height: h} in pixels
+
+    var w, h;
+    // offsetWidth rounds on FF, so doesn't work for us.
+    // See https://bugzilla.mozilla.org/show_bug.cgi?id=458617
+    if (el.getBoundingClientRect == null) {
+    	// use offsetWidth
+      w = el.offsetWidth;
+      h = el.offsetHeight;
+    } else {
+    	// use getBoundingClientRect
+      var rect = el.getBoundingClientRect();
+      w = Math.ceil(rect.right - rect.left);
+    	h = Math.ceil(rect.bottom - rect.top);
+    }
+    return {
+        width:  w,
+        height: h
+    };
+};
+
+
+/*==================================================
+ *  FontRenderingContext
+ *==================================================
  */
-SimileAjax.parseURLParameters = function(url, to, types) {
-    to = to || {};
-    types = types || {};
-
-    if (typeof url == "undefined") {
-        url = location.href;
-    }
-    var q = url.indexOf("?");
-    if (q < 0) {
-        return to;
-    }
-    url = (url+"#").slice(q+1, url.indexOf("#")); // toss the URL fragment
-
-    var params = url.split("&"), param, parsed = {};
-    var decode = window.decodeURIComponent || unescape;
-    for (var i = 0; param = params[i]; i++) {
-        var eq = param.indexOf("=");
-        var name = decode(param.slice(0,eq));
-        var old = parsed[name];
-        if (typeof old == "undefined") {
-            old = [];
-        } else if (!(old instanceof Array)) {
-            old = [old];
-        }
-        parsed[name] = old.concat(decode(param.slice(eq+1)));
-    }
-    for (var i in parsed) {
-        if (!parsed.hasOwnProperty(i)) continue;
-        var type = types[i] || String;
-        var data = parsed[i];
-        if (!(data instanceof Array)) {
-            data = [data];
-        }
-        if (type === Boolean && data[0] == "false") {
-            to[i] = false; // because Boolean("false") === true
+SimileAjax.Graphics.getFontRenderingContext = function(elmt, width) {
+    return new SimileAjax.Graphics._FontRenderingContext(elmt, width);
+};
+
+SimileAjax.Graphics._FontRenderingContext = function(elmt, width) {
+    this._elmt = elmt;
+    this._elmt.style.visibility = "hidden";
+    if (typeof width == "string") {
+        this._elmt.style.width = width;
+    } else if (typeof width == "number") {
+        this._elmt.style.width = width + "px";
+    }
+};
+
+SimileAjax.Graphics._FontRenderingContext.prototype.dispose = function() {
+    this._elmt = null;
+};
+
+SimileAjax.Graphics._FontRenderingContext.prototype.update = function() {
+    this._elmt.innerHTML = "A";
+    this._lineHeight = this._elmt.offsetHeight;
+};
+
+SimileAjax.Graphics._FontRenderingContext.prototype.computeSize = function(text, className) {
+    // className arg is optional
+    var el = this._elmt;
+    el.innerHTML = text;
+    el.className = className === undefined ? '' : className;
+    var wh = SimileAjax.Graphics.getWidthHeight(el);
+    el.className = ''; // reset for the next guy
+
+    return wh;
+};
+
+SimileAjax.Graphics._FontRenderingContext.prototype.getLineHeight = function() {
+    return this._lineHeight;
+};
+
+/**
+ * @fileOverview A collection of date/time utility functions
+ * @name SimileAjax.DateTime
+ */
+
+SimileAjax.DateTime = new Object();
+
+SimileAjax.DateTime.MILLISECOND    = 0;
+SimileAjax.DateTime.SECOND         = 1;
+SimileAjax.DateTime.MINUTE         = 2;
+SimileAjax.DateTime.HOUR           = 3;
+SimileAjax.DateTime.DAY            = 4;
+SimileAjax.DateTime.WEEK           = 5;
+SimileAjax.DateTime.MONTH          = 6;
+SimileAjax.DateTime.YEAR           = 7;
+SimileAjax.DateTime.DECADE         = 8;
+SimileAjax.DateTime.CENTURY        = 9;
+SimileAjax.DateTime.MILLENNIUM     = 10;
+
+SimileAjax.DateTime.EPOCH          = -1;
+SimileAjax.DateTime.ERA            = -2;
+
+/**
+ * An array of unit lengths, expressed in milliseconds, of various lengths of
+ * time.  The array indices are predefined and stored as properties of the
+ * SimileAjax.DateTime object, e.g. SimileAjax.DateTime.YEAR.
+ * @type Array
+ */
+SimileAjax.DateTime.gregorianUnitLengths = [];
+    (function() {
+        var d = SimileAjax.DateTime;
+        var a = d.gregorianUnitLengths;
+
+        a[d.MILLISECOND] = 1;
+        a[d.SECOND]      = 1000;
+        a[d.MINUTE]      = a[d.SECOND] * 60;
+        a[d.HOUR]        = a[d.MINUTE] * 60;
+        a[d.DAY]         = a[d.HOUR] * 24;
+        a[d.WEEK]        = a[d.DAY] * 7;
+        a[d.MONTH]       = a[d.DAY] * 31;
+        a[d.YEAR]        = a[d.DAY] * 365;
+        a[d.DECADE]      = a[d.YEAR] * 10;
+        a[d.CENTURY]     = a[d.YEAR] * 100;
+        a[d.MILLENNIUM]  = a[d.YEAR] * 1000;
+    })();
+
+SimileAjax.DateTime._dateRegexp = new RegExp(
+    "^(-?)([0-9]{4})(" + [
+        "(-?([0-9]{2})(-?([0-9]{2}))?)", // -month-dayOfMonth
+        "(-?([0-9]{3}))",                // -dayOfYear
+        "(-?W([0-9]{2})(-?([1-7]))?)"    // -Wweek-dayOfWeek
+    ].join("|") + ")?$"
+);
+SimileAjax.DateTime._timezoneRegexp = new RegExp(
+    "Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$"
+);
+SimileAjax.DateTime._timeRegexp = new RegExp(
+    "^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(\.([0-9]+))?)?)?$"
+);
+
+/**
+ * Takes a date object and a string containing an ISO 8601 date and sets the
+ * the date using information parsed from the string.  Note that this method
+ * does not parse any time information.
+ *
+ * @param {Date} dateObject the date object to modify
+ * @param {String} string an ISO 8601 string to parse
+ * @return {Date} the modified date object
+ */
+SimileAjax.DateTime.setIso8601Date = function(dateObject, string) {
+    /*
+     *  This function has been adapted from dojo.date, v.0.3.0
+     *  http://dojotoolkit.org/.
+     */
+
+    var d = string.match(SimileAjax.DateTime._dateRegexp);
+    if(!d) {
+        throw new Error("Invalid date string: " + string);
+    }
+
+    var sign = (d[1] == "-") ? -1 : 1; // BC or AD
+    var year = sign * d[2];
+    var month = d[5];
+    var date = d[7];
+    var dayofyear = d[9];
+    var week = d[11];
+    var dayofweek = (d[13]) ? d[13] : 1;
+
+    dateObject.setUTCFullYear(year);
+    if (dayofyear) {
+        dateObject.setUTCMonth(0);
+        dateObject.setUTCDate(Number(dayofyear));
+    } else if (week) {
+        dateObject.setUTCMonth(0);
+        dateObject.setUTCDate(1);
+        var gd = dateObject.getUTCDay();
+        var day =  (gd) ? gd : 7;
+        var offset = Number(dayofweek) + (7 * Number(week));
+
+        if (day <= 4) {
+            dateObject.setUTCDate(offset + 1 - day);
+        } else {
+            dateObject.setUTCDate(offset + 8 - day);
+        }
+    } else {
+        if (month) {
+            dateObject.setUTCDate(1);
+            dateObject.setUTCMonth(month - 1);
+        }
+        if (date) {
+            dateObject.setUTCDate(date);
+        }
+    }
+
+    return dateObject;
+};
+
+/**
+ * Takes a date object and a string containing an ISO 8601 time and sets the
+ * the time using information parsed from the string.  Note that this method
+ * does not parse any date information.
+ *
+ * @param {Date} dateObject the date object to modify
+ * @param {String} string an ISO 8601 string to parse
+ * @return {Date} the modified date object
+ */
+SimileAjax.DateTime.setIso8601Time = function (dateObject, string) {
+    /*
+     *  This function has been adapted from dojo.date, v.0.3.0
+     *  http://dojotoolkit.org/.
+     */
+
+    var d = string.match(SimileAjax.DateTime._timeRegexp);
+    if(!d) {
+        SimileAjax.Debug.warn("Invalid time string: " + string);
+        return false;
+    }
+    var hours = d[1];
+    var mins = Number((d[3]) ? d[3] : 0);
+    var secs = (d[5]) ? d[5] : 0;
+    var ms = d[7] ? (Number("0." + d[7]) * 1000) : 0;
+
+    dateObject.setUTCHours(hours);
+    dateObject.setUTCMinutes(mins);
+    dateObject.setUTCSeconds(secs);
+    dateObject.setUTCMilliseconds(ms);
+
+    return dateObject;
+};
+
+/**
+ * The timezone offset in minutes in the user's browser.
+ * @type Number
+ */
+SimileAjax.DateTime.timezoneOffset = new Date().getTimezoneOffset();
+
+/**
+ * Takes a date object and a string containing an ISO 8601 date and time and
+ * sets the date object using information parsed from the string.
+ *
+ * @param {Date} dateObject the date object to modify
+ * @param {String} string an ISO 8601 string to parse
+ * @return {Date} the modified date object
+ */
+SimileAjax.DateTime.setIso8601 = function (dateObject, string){
+    /*
+     *  This function has been adapted from dojo.date, v.0.3.0
+     *  http://dojotoolkit.org/.
+     */
+
+    var offset = null;
+    var comps = (string.indexOf("T") == -1) ? string.split(" ") : string.split("T");
+
+    SimileAjax.DateTime.setIso8601Date(dateObject, comps[0]);
+    if (comps.length == 2) {
+        // first strip timezone info from the end
+        var d = comps[1].match(SimileAjax.DateTime._timezoneRegexp);
+        if (d) {
+            if (d[0] == 'Z') {
+                offset = 0;
+            } else {
+                offset = (Number(d[3]) * 60) + Number(d[5]);
+                offset *= ((d[2] == '-') ? 1 : -1);
+            }
+            comps[1] = comps[1].substr(0, comps[1].length - d[0].length);
+        }
+
+        SimileAjax.DateTime.setIso8601Time(dateObject, comps[1]);
+    }
+    if (offset == null) {
+        offset = dateObject.getTimezoneOffset(); // local time zone if no tz info
+    }
+    dateObject.setTime(dateObject.getTime() + offset * 60000);
+
+    return dateObject;
+};
+
+/**
+ * Takes a string containing an ISO 8601 date and returns a newly instantiated
+ * date object with the parsed date and time information from the string.
+ *
+ * @param {String} string an ISO 8601 string to parse
+ * @return {Date} a new date object created from the string
+ */
+SimileAjax.DateTime.parseIso8601DateTime = function (string) {
+    try {
+        return SimileAjax.DateTime.setIso8601(new Date(0), string);
+    } catch (e) {
+        return null;
+    }
+};
+
+/**
+ * Takes a string containing a Gregorian date and time and returns a newly
+ * instantiated date object with the parsed date and time information from the
+ * string.  If the param is actually an instance of Date instead of a string,
+ * simply returns the given date instead.
+ *
+ * @param {Object} o an object, to either return or parse as a string
+ * @return {Date} the date object
+ */
+SimileAjax.DateTime.parseGregorianDateTime = function(o) {
+    if (o == null) {
+        return null;
+    } else if (o instanceof Date) {
+        return o;
+    }
+
+    var s = o.toString();
+    if (s.length > 0 && s.length < 8) {
+        var space = s.indexOf(" ");
+        if (space > 0) {
+            var year = parseInt(s.substr(0, space));
+            var suffix = s.substr(space + 1);
+            if (suffix.toLowerCase() == "bc") {
+                year = 1 - year;
+            }
         } else {
-            to[i] = type.apply(this, data);
-        }
-    }
-    return to;
-};
-
-
-SimileAjax.Platform = new Object();
-SimileAjax.urlPrefix = baseuri();
-
-window.Timeline = new Object();
-Timeline.urlPrefix = baseuri();
-window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility
-
-/* platform.js */
-SimileAjax.jQuery = jQuery;
-// SimileAjax.jQuery=jQuery.noConflict(true);
-if(typeof window["$"]=="undefined"){window.$=SimileAjax.jQuery;
-}SimileAjax.Platform.os={isMac:false,isWin:false,isWin32:false,isUnix:false};
-SimileAjax.Platform.browser={isIE:false,isNetscape:false,isMozilla:false,isFirefox:false,isOpera:false,isSafari:false,majorVersion:0,minorVersion:0};
-(function(){var C=navigator.appName.toLowerCase();
-var A=navigator.userAgent.toLowerCase();
-SimileAjax.Platform.os.isMac=(A.indexOf("mac")!=-1);
-SimileAjax.Platform.os.isWin=(A.indexOf("win")!=-1);
-SimileAjax.Platform.os.isWin32=SimileAjax.Platform.isWin&&(A.indexOf("95")!=-1||A.indexOf("98")!=-1||A.indexOf("nt")!=-1||A.indexOf("win32")!=-1||A.indexOf("32bit")!=-1);
-SimileAjax.Platform.os.isUnix=(A.indexOf("x11")!=-1);
-SimileAjax.Platform.browser.isIE=(C.indexOf("microsoft")!=-1);
-SimileAjax.Platform.browser.isNetscape=(C.indexOf("netscape")!=-1);
-SimileAjax.Platform.browser.isMozilla=(A.indexOf("mozilla")!=-1);
-SimileAjax.Platform.browser.isFirefox=(A.indexOf("firefox")!=-1);
-SimileAjax.Platform.browser.isOpera=(C.indexOf("opera")!=-1);
-SimileAjax.Platform.browser.isSafari=(C.indexOf("safari")!=-1);
-var E=function(G){var F=G.split(".");
-SimileAjax.Platform.browser.majorVersion=parseInt(F[0]);
-SimileAjax.Platform.browser.minorVersion=parseInt(F[1]);
-};
-var B=function(H,G,I){var F=H.indexOf(G,I);
-return F>=0?F:H.length;
-};
-if(SimileAjax.Platform.browser.isMozilla){var D=A.indexOf("mozilla/");
-if(D>=0){E(A.substring(D+8,B(A," ",D)));
-}}if(SimileAjax.Platform.browser.isIE){var D=A.indexOf("msie ");
-if(D>=0){E(A.substring(D+5,B(A,";",D)));
-}}if(SimileAjax.Platform.browser.isNetscape){var D=A.indexOf("rv:");
-if(D>=0){E(A.substring(D+3,B(A,")",D)));
-}}if(SimileAjax.Platform.browser.isFirefox){var D=A.indexOf("firefox/");
-if(D>=0){E(A.substring(D+8,B(A," ",D)));
-}}if(!("localeCompare" in String.prototype)){String.prototype.localeCompare=function(F){if(this<F){return -1;
-}else{if(this>F){return 1;
-}else{return 0;
-}}};
-}})();
-SimileAjax.Platform.getDefaultLocale=function(){return SimileAjax.Platform.clientLocale;
-};
-
-
-/* ajax.js */
-SimileAjax.ListenerQueue=function(A){this._listeners=[];
-this._wildcardHandlerName=A;
-};
-SimileAjax.ListenerQueue.prototype.add=function(A){this._listeners.push(A);
-};
-SimileAjax.ListenerQueue.prototype.remove=function(C){var B=this._listeners;
-for(var A=0;
-A<B.length;
-A++){if(B[A]==C){B.splice(A,1);
-break;
-}}};
-SimileAjax.ListenerQueue.prototype.fire=function(B,A){var D=[].concat(this._listeners);
-for(var C=0;
-C<D.length;
-C++){var E=D[C];
-if(B in E){try{E[B].apply(E,A);
-}catch(F){SimileAjax.Debug.exception("Error firing event of name "+B,F);
-}}else{if(this._wildcardHandlerName!=null&&this._wildcardHandlerName in E){try{E[this._wildcardHandlerName].apply(E,[B]);
-}catch(F){SimileAjax.Debug.exception("Error firing event of name "+B+" to wildcard handler",F);
-}}}}};
-
-
-/* data-structure.js */
-SimileAjax.Set=function(A){this._hash={};
-this._count=0;
-if(A instanceof Array){for(var B=0;
-B<A.length;
-B++){this.add(A[B]);
-}}else{if(A instanceof SimileAjax.Set){this.addSet(A);
-}}};
-SimileAjax.Set.prototype.add=function(A){if(!(A in this._hash)){this._hash[A]=true;
-this._count++;
-return true;
-}return false;
-};
-SimileAjax.Set.prototype.addSet=function(B){for(var A in B._hash){this.add(A);
-}};
-SimileAjax.Set.prototype.remove=function(A){if(A in this._hash){delete this._hash[A];
-this._count--;
-return true;
-}return false;
-};
-SimileAjax.Set.prototype.removeSet=function(B){for(var A in B._hash){this.remove(A);
-}};
-SimileAjax.Set.prototype.retainSet=function(B){for(var A in this._hash){if(!B.contains(A)){delete this._hash[A];
-this._count--;
-}}};
-SimileAjax.Set.prototype.contains=function(A){return(A in this._hash);
-};
-SimileAjax.Set.prototype.size=function(){return this._count;
-};
-SimileAjax.Set.prototype.toArray=function(){var A=[];
-for(var B in this._hash){A.push(B);
-}return A;
-};
-SimileAjax.Set.prototype.visit=function(A){for(var B in this._hash){if(A(B)==true){break;
-}}};
-SimileAjax.SortedArray=function(B,A){this._a=(A instanceof Array)?A:[];
-this._compare=B;
-};
-SimileAjax.SortedArray.prototype.add=function(C){var A=this;
-var B=this.find(function(D){return A._compare(D,C);
-});
-if(B<this._a.length){this._a.splice(B,0,C);
-}else{this._a.push(C);
-}};
-SimileAjax.SortedArray.prototype.remove=function(C){var A=this;
-var B=this.find(function(D){return A._compare(D,C);
-});
-while(B<this._a.length&&this._compare(this._a[B],C)==0){if(this._a[B]==C){this._a.splice(B,1);
-return true;
-}else{B++;
-}}return false;
-};
-SimileAjax.SortedArray.prototype.removeAll=function(){this._a=[];
-};
-SimileAjax.SortedArray.prototype.elementAt=function(A){return this._a[A];
-};
-SimileAjax.SortedArray.prototype.length=function(){return this._a.length;
-};
-SimileAjax.SortedArray.prototype.find=function(D){var B=0;
-var A=this._a.length;
-while(B<A){var C=Math.floor((B+A)/2);
-var E=D(this._a[C]);
-if(C==B){return E<0?B+1:B;
-}else{if(E<0){B=C;
-}else{A=C;
-}}}return B;
-};
-SimileAjax.SortedArray.prototype.getFirst=function(){return(this._a.length>0)?this._a[0]:null;
-};
-SimileAjax.SortedArray.prototype.getLast=function(){return(this._a.length>0)?this._a[this._a.length-1]:null;
-};
-SimileAjax.EventIndex=function(B){var A=this;
-this._unit=(B!=null)?B:SimileAjax.NativeDateUnit;
-this._events=new SimileAjax.SortedArray(function(D,C){return A._unit.compare(D.getStart(),C.getStart());
-});
-this._idToEvent={};
-this._indexed=true;
-};
-SimileAjax.EventIndex.prototype.getUnit=function(){return this._unit;
-};
-SimileAjax.EventIndex.prototype.getEvent=function(A){return this._idToEvent[A];
-};
-SimileAjax.EventIndex.prototype.add=function(A){this._events.add(A);
-this._idToEvent[A.getID()]=A;
-this._indexed=false;
-};
-SimileAjax.EventIndex.prototype.removeAll=function(){this._events.removeAll();
-this._idToEvent={};
-this._indexed=false;
-};
-SimileAjax.EventIndex.prototype.getCount=function(){return this._events.length();
-};
-SimileAjax.EventIndex.prototype.getIterator=function(A,B){if(!this._indexed){this._index();
-}return new SimileAjax.EventIndex._Iterator(this._events,A,B,this._unit);
-};
-SimileAjax.EventIndex.prototype.getReverseIterator=function(A,B){if(!this._indexed){this._index();
-}return new SimileAjax.EventIndex._ReverseIterator(this._events,A,B,this._unit);
-};
-SimileAjax.EventIndex.prototype.getAllIterator=function(){return new SimileAjax.EventIndex._AllIterator(this._events);
-};
-SimileAjax.EventIndex.prototype.getEarliestDate=function(){var A=this._events.getFirst();
-return(A==null)?null:A.getStart();
-};
-SimileAjax.EventIndex.prototype.getLatestDate=function(){var A=this._events.getLast();
-if(A==null){return null;
-}if(!this._indexed){this._index();
-}var C=A._earliestOverlapIndex;
-var B=this._events.elementAt(C).getEnd();
-for(var D=C+1;
-D<this._events.length();
-D++){B=this._unit.later(B,this._events.elementAt(D).getEnd());
-}return B;
-};
-SimileAjax.EventIndex.prototype._index=function(){var D=this._events.length();
-for(var E=0;
-E<D;
-E++){var C=this._events.elementAt(E);
-C._earliestOverlapIndex=E;
-}var G=1;
-for(var E=0;
-E<D;
-E++){var C=this._events.elementAt(E);
-var B=C.getEnd();
-G=Math.max(G,E+1);
-while(G<D){var A=this._events.elementAt(G);
-var F=A.getStart();
-if(this._unit.compare(F,B)<0){A._earliestOverlapIndex=E;
-G++;
-}else{break;
-}}}this._indexed=true;
-};
-SimileAjax.EventIndex._Iterator=function(B,A,D,C){this._events=B;
-this._startDate=A;
-this._endDate=D;
-this._unit=C;
-this._currentIndex=B.find(function(E){return C.compare(E.getStart(),A);
-});
-if(this._currentIndex-1>=0){this._currentIndex=this._events.elementAt(this._currentIndex-1)._earliestOverlapIndex;
-}this._currentIndex--;
-this._maxIndex=B.find(function(E){return C.compare(E.getStart(),D);
-});
-this._hasNext=false;
-this._next=null;
-this._findNext();
-};
-SimileAjax.EventIndex._Iterator.prototype={hasNext:function(){return this._hasNext;
-},next:function(){if(this._hasNext){var A=this._next;
-this._findNext();
-return A;
-}else{return null;
-}},_findNext:function(){var B=this._unit;
-while((++this._currentIndex)<this._maxIndex){var A=this._events.elementAt(this._currentIndex);
-if(B.compare(A.getStart(),this._endDate)<0&&B.compare(A.getEnd(),this._startDate)>0){this._next=A;
-this._hasNext=true;
-return ;
-}}this._next=null;
-this._hasNext=false;
-}};
-SimileAjax.EventIndex._ReverseIterator=function(B,A,D,C){this._events=B;
-this._startDate=A;
-this._endDate=D;
-this._unit=C;
-this._minIndex=B.find(function(E){return C.compare(E.getStart(),A);
-});
-if(this._minIndex-1>=0){this._minIndex=this._events.elementAt(this._minIndex-1)._earliestOverlapIndex;
-}this._maxIndex=B.find(function(E){return C.compare(E.getStart(),D);
-});
-this._currentIndex=this._maxIndex;
-this._hasNext=false;
-this._next=null;
-this._findNext();
-};
-SimileAjax.EventIndex._ReverseIterator.prototype={hasNext:function(){return this._hasNext;
-},next:function(){if(this._hasNext){var A=this._next;
-this._findNext();
-return A;
-}else{return null;
-}},_findNext:function(){var B=this._unit;
-while((--this._currentIndex)>=this._minIndex){var A=this._events.elementAt(this._currentIndex);
-if(B.compare(A.getStart(),this._endDate)<0&&B.compare(A.getEnd(),this._startDate)>0){this._next=A;
-this._hasNext=true;
-return ;
-}}this._next=null;
-this._hasNext=false;
-}};
-SimileAjax.EventIndex._AllIterator=function(A){this._events=A;
-this._index=0;
-};
-SimileAjax.EventIndex._AllIterator.prototype={hasNext:function(){return this._index<this._events.length();
-},next:function(){return this._index<this._events.length()?this._events.elementAt(this._index++):null;
-}};
-
-
-/* date-time.js */
-SimileAjax.DateTime=new Object();
-SimileAjax.DateTime.MILLISECOND=0;
-SimileAjax.DateTime.SECOND=1;
-SimileAjax.DateTime.MINUTE=2;
-SimileAjax.DateTime.HOUR=3;
-SimileAjax.DateTime.DAY=4;
-SimileAjax.DateTime.WEEK=5;
-SimileAjax.DateTime.MONTH=6;
-SimileAjax.DateTime.YEAR=7;
-SimileAjax.DateTime.DECADE=8;
-SimileAjax.DateTime.CENTURY=9;
-SimileAjax.DateTime.MILLENNIUM=10;
-SimileAjax.DateTime.EPOCH=-1;
-SimileAjax.DateTime.ERA=-2;
-SimileAjax.DateTime.gregorianUnitLengths=[];
-(function(){var B=SimileAjax.DateTime;
-var A=B.gregorianUnitLengths;
-A[B.MILLISECOND]=1;
-A[B.SECOND]=1000;
-A[B.MINUTE]=A[B.SECOND]*60;
-A[B.HOUR]=A[B.MINUTE]*60;
-A[B.DAY]=A[B.HOUR]*24;
-A[B.WEEK]=A[B.DAY]*7;
-A[B.MONTH]=A[B.DAY]*31;
-A[B.YEAR]=A[B.DAY]*365;
-A[B.DECADE]=A[B.YEAR]*10;
-A[B.CENTURY]=A[B.YEAR]*100;
-A[B.MILLENNIUM]=A[B.YEAR]*1000;
+            var year = parseInt(s);
+        }
+
+        var d = new Date(0);
+        d.setUTCFullYear(year);
+
+        return d;
+    }
+
+    try {
+        return new Date(Date.parse(s));
+    } catch (e) {
+        return null;
+    }
+};
+
+/**
+ * Rounds date objects down to the nearest interval or multiple of an interval.
+ * This method modifies the given date object, converting it to the given
+ * timezone if specified.
+ *
+ * @param {Date} date the date object to round
+ * @param {Number} intervalUnit a constant, integer index specifying an
+ *   interval, e.g. SimileAjax.DateTime.HOUR
+ * @param {Number} timeZone a timezone shift, given in hours
+ * @param {Number} multiple a multiple of the interval to round by
+ * @param {Number} firstDayOfWeek an integer specifying the first day of the
+ *   week, 0 corresponds to Sunday, 1 to Monday, etc.
+ */
+SimileAjax.DateTime.roundDownToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
+    var timeShift = timeZone *
+        SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
+
+    var date2 = new Date(date.getTime() + timeShift);
+    var clearInDay = function(d) {
+        d.setUTCMilliseconds(0);
+        d.setUTCSeconds(0);
+        d.setUTCMinutes(0);
+        d.setUTCHours(0);
+    };
+    var clearInYear = function(d) {
+        clearInDay(d);
+        d.setUTCDate(1);
+        d.setUTCMonth(0);
+    };
+
+    switch(intervalUnit) {
+    case SimileAjax.DateTime.MILLISECOND:
+        var x = date2.getUTCMilliseconds();
+        date2.setUTCMilliseconds(x - (x % multiple));
+        break;
+    case SimileAjax.DateTime.SECOND:
+        date2.setUTCMilliseconds(0);
+
+        var x = date2.getUTCSeconds();
+        date2.setUTCSeconds(x - (x % multiple));
+        break;
+    case SimileAjax.DateTime.MINUTE:
+        date2.setUTCMilliseconds(0);
+        date2.setUTCSeconds(0);
+
+        var x = date2.getUTCMinutes();
+        date2.setTime(date2.getTime() -
+            (x % multiple) * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
+        break;
+    case SimileAjax.DateTime.HOUR:
+        date2.setUTCMilliseconds(0);
+        date2.setUTCSeconds(0);
+        date2.setUTCMinutes(0);
+
+        var x = date2.getUTCHours();
+        date2.setUTCHours(x - (x % multiple));
+        break;
+    case SimileAjax.DateTime.DAY:
+        clearInDay(date2);
+        break;
+    case SimileAjax.DateTime.WEEK:
+        clearInDay(date2);
+        var d = (date2.getUTCDay() + 7 - firstDayOfWeek) % 7;
+        date2.setTime(date2.getTime() -
+            d * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]);
+        break;
+    case SimileAjax.DateTime.MONTH:
+        clearInDay(date2);
+        date2.setUTCDate(1);
+
+        var x = date2.getUTCMonth();
+        date2.setUTCMonth(x - (x % multiple));
+        break;
+    case SimileAjax.DateTime.YEAR:
+        clearInYear(date2);
+
+        var x = date2.getUTCFullYear();
+        date2.setUTCFullYear(x - (x % multiple));
+        break;
+    case SimileAjax.DateTime.DECADE:
+        clearInYear(date2);
+        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 10) * 10);
+        break;
+    case SimileAjax.DateTime.CENTURY:
+        clearInYear(date2);
+        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 100) * 100);
+        break;
+    case SimileAjax.DateTime.MILLENNIUM:
+        clearInYear(date2);
+        date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 1000) * 1000);
+        break;
+    }
+
+    date.setTime(date2.getTime() - timeShift);
+};
+
+/**
+ * Rounds date objects up to the nearest interval or multiple of an interval.
+ * This method modifies the given date object, converting it to the given
+ * timezone if specified.
+ *
+ * @param {Date} date the date object to round
+ * @param {Number} intervalUnit a constant, integer index specifying an
+ *   interval, e.g. SimileAjax.DateTime.HOUR
+ * @param {Number} timeZone a timezone shift, given in hours
+ * @param {Number} multiple a multiple of the interval to round by
+ * @param {Number} firstDayOfWeek an integer specifying the first day of the
+ *   week, 0 corresponds to Sunday, 1 to Monday, etc.
+ * @see SimileAjax.DateTime.roundDownToInterval
+ */
+SimileAjax.DateTime.roundUpToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) {
+    var originalTime = date.getTime();
+    SimileAjax.DateTime.roundDownToInterval(date, intervalUnit, timeZone, multiple, firstDayOfWeek);
+    if (date.getTime() < originalTime) {
+        date.setTime(date.getTime() +
+            SimileAjax.DateTime.gregorianUnitLengths[intervalUnit] * multiple);
+    }
+};
+
+/**
+ * Increments a date object by a specified interval, taking into
+ * consideration the timezone.
+ *
+ * @param {Date} date the date object to increment
+ * @param {Number} intervalUnit a constant, integer index specifying an
+ *   interval, e.g. SimileAjax.DateTime.HOUR
+ * @param {Number} timeZone the timezone offset in hours
+ */
+SimileAjax.DateTime.incrementByInterval = function(date, intervalUnit, timeZone) {
+    timeZone = (typeof timeZone == 'undefined') ? 0 : timeZone;
+
+    var timeShift = timeZone *
+        SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
+
+    var date2 = new Date(date.getTime() + timeShift);
+
+    switch(intervalUnit) {
+    case SimileAjax.DateTime.MILLISECOND:
+        date2.setTime(date2.getTime() + 1)
+        break;
+    case SimileAjax.DateTime.SECOND:
+        date2.setTime(date2.getTime() + 1000);
+        break;
+    case SimileAjax.DateTime.MINUTE:
+        date2.setTime(date2.getTime() +
+            SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
+        break;
+    case SimileAjax.DateTime.HOUR:
+        date2.setTime(date2.getTime() +
+            SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
+        break;
+    case SimileAjax.DateTime.DAY:
+        date2.setUTCDate(date2.getUTCDate() + 1);
+        break;
+    case SimileAjax.DateTime.WEEK:
+        date2.setUTCDate(date2.getUTCDate() + 7);
+        break;
+    case SimileAjax.DateTime.MONTH:
+        date2.setUTCMonth(date2.getUTCMonth() + 1);
+        break;
+    case SimileAjax.DateTime.YEAR:
+        date2.setUTCFullYear(date2.getUTCFullYear() + 1);
+        break;
+    case SimileAjax.DateTime.DECADE:
+        date2.setUTCFullYear(date2.getUTCFullYear() + 10);
+        break;
+    case SimileAjax.DateTime.CENTURY:
+        date2.setUTCFullYear(date2.getUTCFullYear() + 100);
+        break;
+    case SimileAjax.DateTime.MILLENNIUM:
+        date2.setUTCFullYear(date2.getUTCFullYear() + 1000);
+        break;
+    }
+
+    date.setTime(date2.getTime() - timeShift);
+};
+
+/**
+ * Returns a new date object with the given time offset removed.
+ *
+ * @param {Date} date the starting date
+ * @param {Number} timeZone a timezone specified in an hour offset to remove
+ * @return {Date} a new date object with the offset removed
+ */
+SimileAjax.DateTime.removeTimeZoneOffset = function(date, timeZone) {
+    return new Date(date.getTime() +
+        timeZone * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
+};
+
+/**
+ * Returns the timezone of the user's browser.
+ *
+ * @return {Number} the timezone in the user's locale in hours
+ */
+SimileAjax.DateTime.getTimezone = function() {
+    var d = new Date().getTimezoneOffset();
+    return d / -60;
+};
+/*==================================================
+ *  String Utility Functions and Constants
+ *==================================================
+ */
+
+String.prototype.trim = function() {
+    return this.replace(/^\s+|\s+$/g, '');
+};
+
+String.prototype.startsWith = function(prefix) {
+    return this.length >= prefix.length && this.substr(0, prefix.length) == prefix;
+};
+
+String.prototype.endsWith = function(suffix) {
+    return this.length >= suffix.length && this.substr(this.length - suffix.length) == suffix;
+};
+
+String.substitute = function(s, objects) {
+    var result = "";
+    var start = 0;
+    while (start < s.length - 1) {
+        var percent = s.indexOf("%", start);
+        if (percent < 0 || percent == s.length - 1) {
+            break;
+        } else if (percent > start && s.charAt(percent - 1) == "\\") {
+            result += s.substring(start, percent - 1) + "%";
+            start = percent + 1;
+        } else {
+            var n = parseInt(s.charAt(percent + 1));
+            if (isNaN(n) || n >= objects.length) {
+                result += s.substring(start, percent + 2);
+            } else {
+                result += s.substring(start, percent) + objects[n].toString();
+            }
+            start = percent + 2;
+        }
+    }
+
+    if (start < s.length) {
+        result += s.substring(start);
+    }
+    return result;
+};
+/*==================================================
+ *  HTML Utility Functions
+ *==================================================
+ */
+
+SimileAjax.HTML = new Object();
+
+SimileAjax.HTML._e2uHash = {};
+(function() {
+    var e2uHash = SimileAjax.HTML._e2uHash;
+    e2uHash['nbsp']= '\u00A0[space]';
+    e2uHash['iexcl']= '\u00A1';
+    e2uHash['cent']= '\u00A2';
+    e2uHash['pound']= '\u00A3';
+    e2uHash['curren']= '\u00A4';
+    e2uHash['yen']= '\u00A5';
+    e2uHash['brvbar']= '\u00A6';
+    e2uHash['sect']= '\u00A7';
+    e2uHash['uml']= '\u00A8';
+    e2uHash['copy']= '\u00A9';
+    e2uHash['ordf']= '\u00AA';
+    e2uHash['laquo']= '\u00AB';
+    e2uHash['not']= '\u00AC';
+    e2uHash['shy']= '\u00AD';
+    e2uHash['reg']= '\u00AE';
+    e2uHash['macr']= '\u00AF';
+    e2uHash['deg']= '\u00B0';
+    e2uHash['plusmn']= '\u00B1';
+    e2uHash['sup2']= '\u00B2';
+    e2uHash['sup3']= '\u00B3';
+    e2uHash['acute']= '\u00B4';
+    e2uHash['micro']= '\u00B5';
+    e2uHash['para']= '\u00B6';
+    e2uHash['middot']= '\u00B7';
+    e2uHash['cedil']= '\u00B8';
+    e2uHash['sup1']= '\u00B9';
+    e2uHash['ordm']= '\u00BA';
+    e2uHash['raquo']= '\u00BB';
+    e2uHash['frac14']= '\u00BC';
+    e2uHash['frac12']= '\u00BD';
+    e2uHash['frac34']= '\u00BE';
+    e2uHash['iquest']= '\u00BF';
+    e2uHash['Agrave']= '\u00C0';
+    e2uHash['Aacute']= '\u00C1';
+    e2uHash['Acirc']= '\u00C2';
+    e2uHash['Atilde']= '\u00C3';
+    e2uHash['Auml']= '\u00C4';
+    e2uHash['Aring']= '\u00C5';
+    e2uHash['AElig']= '\u00C6';
+    e2uHash['Ccedil']= '\u00C7';
+    e2uHash['Egrave']= '\u00C8';
+    e2uHash['Eacute']= '\u00C9';
+    e2uHash['Ecirc']= '\u00CA';
+    e2uHash['Euml']= '\u00CB';
+    e2uHash['Igrave']= '\u00CC';
+    e2uHash['Iacute']= '\u00CD';
+    e2uHash['Icirc']= '\u00CE';
+    e2uHash['Iuml']= '\u00CF';
+    e2uHash['ETH']= '\u00D0';
+    e2uHash['Ntilde']= '\u00D1';
+    e2uHash['Ograve']= '\u00D2';
+    e2uHash['Oacute']= '\u00D3';
+    e2uHash['Ocirc']= '\u00D4';
+    e2uHash['Otilde']= '\u00D5';
+    e2uHash['Ouml']= '\u00D6';
+    e2uHash['times']= '\u00D7';
+    e2uHash['Oslash']= '\u00D8';
+    e2uHash['Ugrave']= '\u00D9';
+    e2uHash['Uacute']= '\u00DA';
+    e2uHash['Ucirc']= '\u00DB';
+    e2uHash['Uuml']= '\u00DC';
+    e2uHash['Yacute']= '\u00DD';
+    e2uHash['THORN']= '\u00DE';
+    e2uHash['szlig']= '\u00DF';
+    e2uHash['agrave']= '\u00E0';
+    e2uHash['aacute']= '\u00E1';
+    e2uHash['acirc']= '\u00E2';
+    e2uHash['atilde']= '\u00E3';
+    e2uHash['auml']= '\u00E4';
+    e2uHash['aring']= '\u00E5';
+    e2uHash['aelig']= '\u00E6';
+    e2uHash['ccedil']= '\u00E7';
+    e2uHash['egrave']= '\u00E8';
+    e2uHash['eacute']= '\u00E9';
+    e2uHash['ecirc']= '\u00EA';
+    e2uHash['euml']= '\u00EB';
+    e2uHash['igrave']= '\u00EC';
+    e2uHash['iacute']= '\u00ED';
+    e2uHash['icirc']= '\u00EE';
+    e2uHash['iuml']= '\u00EF';
+    e2uHash['eth']= '\u00F0';
+    e2uHash['ntilde']= '\u00F1';
+    e2uHash['ograve']= '\u00F2';
+    e2uHash['oacute']= '\u00F3';
+    e2uHash['ocirc']= '\u00F4';
+    e2uHash['otilde']= '\u00F5';
+    e2uHash['ouml']= '\u00F6';
+    e2uHash['divide']= '\u00F7';
+    e2uHash['oslash']= '\u00F8';
+    e2uHash['ugrave']= '\u00F9';
+    e2uHash['uacute']= '\u00FA';
+    e2uHash['ucirc']= '\u00FB';
+    e2uHash['uuml']= '\u00FC';
+    e2uHash['yacute']= '\u00FD';
+    e2uHash['thorn']= '\u00FE';
+    e2uHash['yuml']= '\u00FF';
+    e2uHash['quot']= '\u0022';
+    e2uHash['amp']= '\u0026';
+    e2uHash['lt']= '\u003C';
+    e2uHash['gt']= '\u003E';
+    e2uHash['OElig']= '';
+    e2uHash['oelig']= '\u0153';
+    e2uHash['Scaron']= '\u0160';
+    e2uHash['scaron']= '\u0161';
+    e2uHash['Yuml']= '\u0178';
+    e2uHash['circ']= '\u02C6';
+    e2uHash['tilde']= '\u02DC';
+    e2uHash['ensp']= '\u2002';
+    e2uHash['emsp']= '\u2003';
+    e2uHash['thinsp']= '\u2009';
+    e2uHash['zwnj']= '\u200C';
+    e2uHash['zwj']= '\u200D';
+    e2uHash['lrm']= '\u200E';
+    e2uHash['rlm']= '\u200F';
+    e2uHash['ndash']= '\u2013';
+    e2uHash['mdash']= '\u2014';
+    e2uHash['lsquo']= '\u2018';
+    e2uHash['rsquo']= '\u2019';
+    e2uHash['sbquo']= '\u201A';
+    e2uHash['ldquo']= '\u201C';
+    e2uHash['rdquo']= '\u201D';
+    e2uHash['bdquo']= '\u201E';
+    e2uHash['dagger']= '\u2020';
+    e2uHash['Dagger']= '\u2021';
+    e2uHash['permil']= '\u2030';
+    e2uHash['lsaquo']= '\u2039';
+    e2uHash['rsaquo']= '\u203A';
+    e2uHash['euro']= '\u20AC';
+    e2uHash['fnof']= '\u0192';
+    e2uHash['Alpha']= '\u0391';
+    e2uHash['Beta']= '\u0392';
+    e2uHash['Gamma']= '\u0393';
+    e2uHash['Delta']= '\u0394';
+    e2uHash['Epsilon']= '\u0395';
+    e2uHash['Zeta']= '\u0396';
+    e2uHash['Eta']= '\u0397';
+    e2uHash['Theta']= '\u0398';
+    e2uHash['Iota']= '\u0399';
+    e2uHash['Kappa']= '\u039A';
+    e2uHash['Lambda']= '\u039B';
+    e2uHash['Mu']= '\u039C';
+    e2uHash['Nu']= '\u039D';
+    e2uHash['Xi']= '\u039E';
+    e2uHash['Omicron']= '\u039F';
+    e2uHash['Pi']= '\u03A0';
+    e2uHash['Rho']= '\u03A1';
+    e2uHash['Sigma']= '\u03A3';
+    e2uHash['Tau']= '\u03A4';
+    e2uHash['Upsilon']= '\u03A5';
+    e2uHash['Phi']= '\u03A6';
+    e2uHash['Chi']= '\u03A7';
+    e2uHash['Psi']= '\u03A8';
+    e2uHash['Omega']= '\u03A9';
+    e2uHash['alpha']= '\u03B1';
+    e2uHash['beta']= '\u03B2';
+    e2uHash['gamma']= '\u03B3';
+    e2uHash['delta']= '\u03B4';
+    e2uHash['epsilon']= '\u03B5';
+    e2uHash['zeta']= '\u03B6';
+    e2uHash['eta']= '\u03B7';
+    e2uHash['theta']= '\u03B8';
+    e2uHash['iota']= '\u03B9';
+    e2uHash['kappa']= '\u03BA';
+    e2uHash['lambda']= '\u03BB';
+    e2uHash['mu']= '\u03BC';
+    e2uHash['nu']= '\u03BD';
+    e2uHash['xi']= '\u03BE';
+    e2uHash['omicron']= '\u03BF';
+    e2uHash['pi']= '\u03C0';
+    e2uHash['rho']= '\u03C1';
+    e2uHash['sigmaf']= '\u03C2';
+    e2uHash['sigma']= '\u03C3';
+    e2uHash['tau']= '\u03C4';
+    e2uHash['upsilon']= '\u03C5';
+    e2uHash['phi']= '\u03C6';
+    e2uHash['chi']= '\u03C7';
+    e2uHash['psi']= '\u03C8';
+    e2uHash['omega']= '\u03C9';
+    e2uHash['thetasym']= '\u03D1';
+    e2uHash['upsih']= '\u03D2';
+    e2uHash['piv']= '\u03D6';
+    e2uHash['bull']= '\u2022';
+    e2uHash['hellip']= '\u2026';
+    e2uHash['prime']= '\u2032';
+    e2uHash['Prime']= '\u2033';
+    e2uHash['oline']= '\u203E';
+    e2uHash['frasl']= '\u2044';
+    e2uHash['weierp']= '\u2118';
+    e2uHash['image']= '\u2111';
+    e2uHash['real']= '\u211C';
+    e2uHash['trade']= '\u2122';
+    e2uHash['alefsym']= '\u2135';
+    e2uHash['larr']= '\u2190';
+    e2uHash['uarr']= '\u2191';
+    e2uHash['rarr']= '\u2192';
+    e2uHash['darr']= '\u2193';
+    e2uHash['harr']= '\u2194';
+    e2uHash['crarr']= '\u21B5';
+    e2uHash['lArr']= '\u21D0';
+    e2uHash['uArr']= '\u21D1';
+    e2uHash['rArr']= '\u21D2';
+    e2uHash['dArr']= '\u21D3';
+    e2uHash['hArr']= '\u21D4';
+    e2uHash['forall']= '\u2200';
+    e2uHash['part']= '\u2202';
+    e2uHash['exist']= '\u2203';
+    e2uHash['empty']= '\u2205';
+    e2uHash['nabla']= '\u2207';
+    e2uHash['isin']= '\u2208';
+    e2uHash['notin']= '\u2209';
+    e2uHash['ni']= '\u220B';
+    e2uHash['prod']= '\u220F';
+    e2uHash['sum']= '\u2211';
+    e2uHash['minus']= '\u2212';
+    e2uHash['lowast']= '\u2217';
+    e2uHash['radic']= '\u221A';
+    e2uHash['prop']= '\u221D';
+    e2uHash['infin']= '\u221E';
+    e2uHash['ang']= '\u2220';
+    e2uHash['and']= '\u2227';
+    e2uHash['or']= '\u2228';
+    e2uHash['cap']= '\u2229';
+    e2uHash['cup']= '\u222A';
+    e2uHash['int']= '\u222B';
+    e2uHash['there4']= '\u2234';
+    e2uHash['sim']= '\u223C';
+    e2uHash['cong']= '\u2245';
+    e2uHash['asymp']= '\u2248';
+    e2uHash['ne']= '\u2260';
+    e2uHash['equiv']= '\u2261';
+    e2uHash['le']= '\u2264';
+    e2uHash['ge']= '\u2265';
+    e2uHash['sub']= '\u2282';
+    e2uHash['sup']= '\u2283';
+    e2uHash['nsub']= '\u2284';
+    e2uHash['sube']= '\u2286';
+    e2uHash['supe']= '\u2287';
+    e2uHash['oplus']= '\u2295';
+    e2uHash['otimes']= '\u2297';
+    e2uHash['perp']= '\u22A5';
+    e2uHash['sdot']= '\u22C5';
+    e2uHash['lceil']= '\u2308';
+    e2uHash['rceil']= '\u2309';
+    e2uHash['lfloor']= '\u230A';
+    e2uHash['rfloor']= '\u230B';
+    e2uHash['lang']= '\u2329';
+    e2uHash['rang']= '\u232A';
+    e2uHash['loz']= '\u25CA';
+    e2uHash['spades']= '\u2660';
+    e2uHash['clubs']= '\u2663';
+    e2uHash['hearts']= '\u2665';
+    e2uHash['diams']= '\u2666';
 })();
-SimileAjax.DateTime._dateRegexp=new RegExp("^(-?)([0-9]{4})("+["(-?([0-9]{2})(-?([0-9]{2}))?)","(-?([0-9]{3}))","(-?W([0-9]{2})(-?([1-7]))?)"].join("|")+")?$");
-SimileAjax.DateTime._timezoneRegexp=new RegExp("Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$");
-SimileAjax.DateTime._timeRegexp=new RegExp("^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(.([0-9]+))?)?)?$");
-SimileAjax.DateTime.setIso8601Date=function(H,F){var I=F.match(SimileAjax.DateTime._dateRegexp);
-if(!I){throw new Error("Invalid date string: "+F);
-}var B=(I[1]=="-")?-1:1;
-var J=B*I[2];
-var G=I[5];
-var C=I[7];
-var E=I[9];
-var A=I[11];
-var M=(I[13])?I[13]:1;
-H.setUTCFullYear(J);
-if(E){H.setUTCMonth(0);
-H.setUTCDate(Number(E));
-}else{if(A){H.setUTCMonth(0);
-H.setUTCDate(1);
-var L=H.getUTCDay();
-var K=(L)?L:7;
-var D=Number(M)+(7*Number(A));
-if(K<=4){H.setUTCDate(D+1-K);
-}else{H.setUTCDate(D+8-K);
-}}else{if(G){H.setUTCDate(1);
-H.setUTCMonth(G-1);
-}if(C){H.setUTCDate(C);
-}}}return H;
-};
-SimileAjax.DateTime.setIso8601Time=function(F,C){var G=C.match(SimileAjax.DateTime._timeRegexp);
-if(!G){SimileAjax.Debug.warn("Invalid time string: "+C);
-return false;
-}var A=G[1];
-var E=Number((G[3])?G[3]:0);
-var D=(G[5])?G[5]:0;
-var B=G[7]?(Number("0."+G[7])*1000):0;
-F.setUTCHours(A);
-F.setUTCMinutes(E);
-F.setUTCSeconds(D);
-F.setUTCMilliseconds(B);
-return F;
-};
-SimileAjax.DateTime.timezoneOffset=new Date().getTimezoneOffset();
-SimileAjax.DateTime.setIso8601=function(B,A){var D=null;
-var E=(A.indexOf("T")==-1)?A.split(" "):A.split("T");
-SimileAjax.DateTime.setIso8601Date(B,E[0]);
-if(E.length==2){var C=E[1].match(SimileAjax.DateTime._timezoneRegexp);
-if(C){if(C[0]=="Z"){D=0;
-}else{D=(Number(C[3])*60)+Number(C[5]);
-D*=((C[2]=="-")?1:-1);
-}E[1]=E[1].substr(0,E[1].length-C[0].length);
-}SimileAjax.DateTime.setIso8601Time(B,E[1]);
-}if(D==null){D=B.getTimezoneOffset();
-}B.setTime(B.getTime()+D*60000);
-return B;
-};
-SimileAjax.DateTime.parseIso8601DateTime=function(A){try{return SimileAjax.DateTime.setIso8601(new Date(0),A);
-}catch(B){return null;
-}};
-SimileAjax.DateTime.parseGregorianDateTime=function(G){if(G==null){return null;
-}else{if(G instanceof Date){return G;
-}}var B=G.toString();
-if(B.length>0&&B.length<8){var C=B.indexOf(" ");
-if(C>0){var A=parseInt(B.substr(0,C));
-var E=B.substr(C+1);
-if(E.toLowerCase()=="bc"){A=1-A;
-}}else{var A=parseInt(B);
-}var F=new Date(0);
-F.setUTCFullYear(A);
-return F;
-}try{return new Date(Date.parse(B));
-}catch(D){return null;
-}};
-SimileAjax.DateTime.roundDownToInterval=function(B,G,J,K,A){var D=J*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
-var I=new Date(B.getTime()+D);
-var E=function(L){L.setUTCMilliseconds(0);
-L.setUTCSeconds(0);
-L.setUTCMinutes(0);
-L.setUTCHours(0);
-};
-var C=function(L){E(L);
-L.setUTCDate(1);
-L.setUTCMonth(0);
-};
-switch(G){case SimileAjax.DateTime.MILLISECOND:var H=I.getUTCMilliseconds();
-I.setUTCMilliseconds(H-(H%K));
-break;
-case SimileAjax.DateTime.SECOND:I.setUTCMilliseconds(0);
-var H=I.getUTCSeconds();
-I.setUTCSeconds(H-(H%K));
-break;
-case SimileAjax.DateTime.MINUTE:I.setUTCMilliseconds(0);
-I.setUTCSeconds(0);
-var H=I.getUTCMinutes();
-I.setTime(I.getTime()-(H%K)*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
-break;
-case SimileAjax.DateTime.HOUR:I.setUTCMilliseconds(0);
-I.setUTCSeconds(0);
-I.setUTCMinutes(0);
-var H=I.getUTCHours();
-I.setUTCHours(H-(H%K));
-break;
-case SimileAjax.DateTime.DAY:E(I);
-break;
-case SimileAjax.DateTime.WEEK:E(I);
-var F=(I.getUTCDay()+7-A)%7;
-I.setTime(I.getTime()-F*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]);
-break;
-case SimileAjax.DateTime.MONTH:E(I);
-I.setUTCDate(1);
-var H=I.getUTCMonth();
-I.setUTCMonth(H-(H%K));
-break;
-case SimileAjax.DateTime.YEAR:C(I);
-var H=I.getUTCFullYear();
-I.setUTCFullYear(H-(H%K));
-break;
-case SimileAjax.DateTime.DECADE:C(I);
-I.setUTCFullYear(Math.floor(I.getUTCFullYear()/10)*10);
-break;
-case SimileAjax.DateTime.CENTURY:C(I);
-I.setUTCFullYear(Math.floor(I.getUTCFullYear()/100)*100);
-break;
-case SimileAjax.DateTime.MILLENNIUM:C(I);
-I.setUTCFullYear(Math.floor(I.getUTCFullYear()/1000)*1000);
-break;
-}B.setTime(I.getTime()-D);
-};
-SimileAjax.DateTime.roundUpToInterval=function(D,F,C,A,B){var E=D.getTime();
-SimileAjax.DateTime.roundDownToInterval(D,F,C,A,B);
-if(D.getTime()<E){D.setTime(D.getTime()+SimileAjax.DateTime.gregorianUnitLengths[F]*A);
-}};
-SimileAjax.DateTime.incrementByInterval=function(B,E,A){A=(typeof A=="undefined")?0:A;
-var D=A*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
-var C=new Date(B.getTime()+D);
-switch(E){case SimileAjax.DateTime.MILLISECOND:C.setTime(C.getTime()+1);
-break;
-case SimileAjax.DateTime.SECOND:C.setTime(C.getTime()+1000);
-break;
-case SimileAjax.DateTime.MINUTE:C.setTime(C.getTime()+SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]);
-break;
-case SimileAjax.DateTime.HOUR:C.setTime(C.getTime()+SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
-break;
-case SimileAjax.DateTime.DAY:C.setUTCDate(C.getUTCDate()+1);
-break;
-case SimileAjax.DateTime.WEEK:C.setUTCDate(C.getUTCDate()+7);
-break;
-case SimileAjax.DateTime.MONTH:C.setUTCMonth(C.getUTCMonth()+1);
-break;
-case SimileAjax.DateTime.YEAR:C.setUTCFullYear(C.getUTCFullYear()+1);
-break;
-case SimileAjax.DateTime.DECADE:C.setUTCFullYear(C.getUTCFullYear()+10);
-break;
-case SimileAjax.DateTime.CENTURY:C.setUTCFullYear(C.getUTCFullYear()+100);
-break;
-case SimileAjax.DateTime.MILLENNIUM:C.setUTCFullYear(C.getUTCFullYear()+1000);
-break;
-}B.setTime(C.getTime()-D);
-};
-SimileAjax.DateTime.removeTimeZoneOffset=function(B,A){return new Date(B.getTime()+A*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]);
-};
-SimileAjax.DateTime.getTimezone=function(){var A=new Date().getTimezoneOffset();
-return A/-60;
-};
-
-
-/* debug.js */
-SimileAjax.Debug={silent:false};
-SimileAjax.Debug.log=function(B){var A;
-if("console" in window&&"log" in window.console){A=function(C){console.log(C);
-};
-}else{A=function(C){if(!SimileAjax.Debug.silent){alert(C);
-}};
-}SimileAjax.Debug.log=A;
-A(B);
-};
-SimileAjax.Debug.warn=function(B){var A;
-if("console" in window&&"warn" in window.console){A=function(C){console.warn(C);
-};
-}else{A=function(C){if(!SimileAjax.Debug.silent){alert(C);
-}};
-}SimileAjax.Debug.warn=A;
-A(B);
-};
-SimileAjax.Debug.exception=function(B,D){var A,C=SimileAjax.parseURLParameters();
-if(C.errors=="throw"||SimileAjax.params.errors=="throw"){A=function(F,E){throw (F);
-};
-}else{if("console" in window&&"error" in window.console){A=function(F,E){if(E!=null){console.error(E+" %o",F);
-}else{console.error(F);
-}throw (F);
-};
-}else{A=function(F,E){if(!SimileAjax.Debug.silent){alert("Caught exception: "+E+"\n\nDetails: "+("description" in F?F.description:F));
-}throw (F);
-};
-}}SimileAjax.Debug.exception=A;
-A(B,D);
-};
-SimileAjax.Debug.objectToString=function(A){return SimileAjax.Debug._objectToString(A,"");
-};
-SimileAjax.Debug._objectToString=function(D,A){var C=A+" ";
-if(typeof D=="object"){var B="{";
-for(E in D){B+=C+E+": "+SimileAjax.Debug._objectToString(D[E],C)+"\n";
-}B+=A+"}";
-return B;
-}else{if(typeof D=="array"){var B="[";
-for(var E=0;
-E<D.length;
-E++){B+=SimileAjax.Debug._objectToString(D[E],C)+"\n";
-}B+=A+"]";
-return B;
-}else{return D;
-}}};
-
-
-/* dom.js */
-SimileAjax.DOM=new Object();
-SimileAjax.DOM.registerEventWithObject=function(C,A,D,B){SimileAjax.DOM.registerEvent(C,A,function(F,E,G){return D[B].call(D,F,E,G);
-});
-};
-SimileAjax.DOM.registerEvent=function(C,B,D){var A=function(E){E=(E)?E:((event)?event:null);
-if(E){var F=(E.target)?E.target:((E.srcElement)?E.srcElement:null);
-if(F){F=(F.nodeType==1||F.nodeType==9)?F:F.parentNode;
-}return D(C,E,F);
-}return true;
-};
-if(SimileAjax.Platform.browser.isIE){C.attachEvent("on"+B,A);
-}else{C.addEventListener(B,A,false);
-}};
-SimileAjax.DOM.getPageCoordinates=function(B){var E=0;
-var D=0;
-if(B.nodeType!=1){B=B.parentNode;
-}var C=B;
-while(C!=null){E+=C.offsetLeft;
-D+=C.offsetTop;
-C=C.offsetParent;
-}var A=document.body;
-while(B!=null&&B!=A){if("scrollLeft" in B){E-=B.scrollLeft;
-D-=B.scrollTop;
-}B=B.parentNode;
-}return{left:E,top:D};
-};
-SimileAjax.DOM.getSize=function(B){var A=this.getStyle(B,"width");
-var C=this.getStyle(B,"height");
-if(A.indexOf("px")>-1){A=A.replace("px","");
-}if(C.indexOf("px")>-1){C=C.replace("px","");
-}return{w:A,h:C};
-};
-SimileAjax.DOM.getStyle=function(B,A){if(B.currentStyle){var C=B.currentStyle[A];
-}else{if(window.getComputedStyle){var C=document.defaultView.getComputedStyle(B,null).getPropertyValue(A);
-}else{var C="";
-}}return C;
-};
-SimileAjax.DOM.getEventRelativeCoordinates=function(A,B){if(SimileAjax.Platform.browser.isIE){if(A.type=="mousewheel"){var C=SimileAjax.DOM.getPageCoordinates(B);
-return{x:A.clientX-C.left,y:A.clientY-C.top};
-}else{return{x:A.offsetX,y:A.offsetY};
-}}else{var C=SimileAjax.DOM.getPageCoordinates(B);
-if((A.type=="DOMMouseScroll")&&SimileAjax.Platform.browser.isFirefox&&(SimileAjax.Platform.browser.majorVersion==2)){return{x:A.screenX-C.left,y:A.screenY-C.top};
-}else{return{x:A.pageX-C.left,y:A.pageY-C.top};
-}}};
-SimileAjax.DOM.getEventPageCoordinates=function(A){if(SimileAjax.Platform.browser.isIE){return{x:A.clientX+document.body.scrollLeft,y:A.clientY+document.body.scrollTop};
-}else{return{x:A.pageX,y:A.pageY};
-}};
-SimileAjax.DOM.hittest=function(A,C,B){return SimileAjax.DOM._hittest(document.body,A,C,B);
-};
-SimileAjax.DOM._hittest=function(C,L,K,H){var M=C.childNodes;
-outer:for(var G=0;
-G<M.length;
-G++){var A=M[G];
-for(var F=0;
-F<H.length;
-F++){if(A==H[F]){continue outer;
-}}if(A.offsetWidth==0&&A.offsetHeight==0){var B=SimileAjax.DOM._hittest(A,L,K,H);
-if(B!=A){return B;
-}}else{var J=0;
-var E=0;
-var D=A;
-while(D){J+=D.offsetTop;
-E+=D.offsetLeft;
-D=D.offsetParent;
-}if(E<=L&&J<=K&&(L-E)<A.offsetWidth&&(K-J)<A.offsetHeight){return SimileAjax.DOM._hittest(A,L,K,H);
-}else{if(A.nodeType==1&&A.tagName=="TR"){var I=SimileAjax.DOM._hittest(A,L,K,H);
-if(I!=A){return I;
-}}}}}return C;
-};
-SimileAjax.DOM.cancelEvent=function(A){A.returnValue=false;
-A.cancelBubble=true;
-if("preventDefault" in A){A.preventDefault();
-}};
-SimileAjax.DOM.appendClassName=function(C,D){var B=C.className.split(" ");
-for(var A=0;
-A<B.length;
-A++){if(B[A]==D){return ;
-}}B.push(D);
-C.className=B.join(" ");
-};
-SimileAjax.DOM.createInputElement=function(A){var B=document.createElement("div");
-B.innerHTML="<input type='"+A+"' />";
-return B.firstChild;
-};
-SimileAjax.DOM.createDOMFromTemplate=function(B){var A={};
-A.elmt=SimileAjax.DOM._createDOMFromTemplate(B,A,null);
-return A;
-};
-SimileAjax.DOM._createDOMFromTemplate=function(A,I,E){if(A==null){return null;
-}else{if(typeof A!="object"){var D=document.createTextNode(A);
-if(E!=null){E.appendChild(D);
-}return D;
-}else{var C=null;
-if("tag" in A){var J=A.tag;
-if(E!=null){if(J=="tr"){C=E.insertRow(E.rows.length);
-}else{if(J=="td"){C=E.insertCell(E.cells.length);
-}}}if(C==null){C=J=="input"?SimileAjax.DOM.createInputElement(A.type):document.createElement(J);
-if(E!=null){E.appendChild(C);
-}}}else{C=A.elmt;
-if(E!=null){E.appendChild(C);
-}}for(var B in A){var G=A[B];
-if(B=="field"){I[G]=C;
-}else{if(B=="className"){C.className=G;
-}else{if(B=="id"){C.id=G;
-}else{if(B=="title"){C.title=G;
-}else{if(B=="type"&&C.tagName=="input"){}else{if(B=="style"){for(n in G){var H=G[n];
-if(n=="float"){n=SimileAjax.Platform.browser.isIE?"styleFloat":"cssFloat";
-}C.style[n]=H;
-}}else{if(B=="children"){for(var F=0;
-F<G.length;
-F++){SimileAjax.DOM._createDOMFromTemplate(G[F],I,C);
-}}else{if(B!="tag"&&B!="elmt"){C.setAttribute(B,G);
-}}}}}}}}}return C;
-}}};
-SimileAjax.DOM._cachedParent=null;
-SimileAjax.DOM.createElementFromString=function(A){if(SimileAjax.DOM._cachedParent==null){SimileAjax.DOM._cachedParent=document.createElement("div");
-}SimileAjax.DOM._cachedParent.innerHTML=A;
-return SimileAjax.DOM._cachedParent.firstChild;
-};
-SimileAjax.DOM.createDOMFromString=function(A,C,D){var B=typeof A=="string"?document.createElement(A):A;
-B.innerHTML=C;
-var E={elmt:B};
-SimileAjax.DOM._processDOMChildrenConstructedFromString(E,B,D!=null?D:{});
-return E;
-};
-SimileAjax.DOM._processDOMConstructedFromString=function(D,A,B){var E=A.id;
-if(E!=null&&E.length>0){A.removeAttribute("id");
-if(E in B){var C=A.parentNode;
-C.insertBefore(B[E],A);
-C.removeChild(A);
-D[E]=B[E];
-return ;
-}else{D[E]=A;
-}}if(A.hasChildNodes()){SimileAjax.DOM._processDOMChildrenConstructedFromString(D,A,B);
-}};
-SimileAjax.DOM._processDOMChildrenConstructedFromString=function(E,B,D){var C=B.firstChild;
-while(C!=null){var A=C.nextSibling;
-if(C.nodeType==1){SimileAjax.DOM._processDOMConstructedFromString(E,C,D);
-}C=A;
-}};
-
-
-/* graphics.js */
-SimileAjax.Graphics=new Object();
-SimileAjax.Graphics.pngIsTranslucent=(!SimileAjax.Platform.browser.isIE)||(SimileAjax.Platform.browser.majorVersion>6);
-SimileAjax.Graphics._createTranslucentImage1=function(A,C){var B=document.createElement("img");
-B.setAttribute("src",A);
-if(C!=null){B.style.verticalAlign=C;
-}return B;
-};
-SimileAjax.Graphics._createTranslucentImage2=function(A,C){var B=document.createElement("img");
-B.style.width="1px";
-B.style.height="1px";
-B.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+A+"', sizingMethod='image')";
-B.style.verticalAlign=(C!=null)?C:"middle";
-return B;
-};
-SimileAjax.Graphics.createTranslucentImage=SimileAjax.Graphics.pngIsTranslucent?SimileAjax.Graphics._createTranslucentImage1:SimileAjax.Graphics._createTranslucentImage2;
-SimileAjax.Graphics._createTranslucentImageHTML1=function(A,B){return'<img src="'+A+'"'+(B!=null?' style="vertical-align: '+B+';"':"")+" />";
-};
-SimileAjax.Graphics._createTranslucentImageHTML2=function(A,C){var B="width: 1px; height: 1px; filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+A+"', sizingMethod='image');"+(C!=null?" vertical-align: "+C+";":"");
-return"<img src='"+A+"' style=\""+B+'" />';
-};
-SimileAjax.Graphics.createTranslucentImageHTML=SimileAjax.Graphics.pngIsTranslucent?SimileAjax.Graphics._createTranslucentImageHTML1:SimileAjax.Graphics._createTranslucentImageHTML2;
-SimileAjax.Graphics.setOpacity=function(B,A){if(SimileAjax.Platform.browser.isIE){B.style.filter="progid:DXImageTransform.Microsoft.Alpha(Style=0,Opacity="+A+")";
-}else{var C=(A/100).toString();
-B.style.opacity=C;
-B.style.MozOpacity=C;
-}};
-SimileAjax.Graphics._bubbleMargins={top:33,bottom:42,left:33,right:40};
-SimileAjax.Graphics._arrowOffsets={top:0,bottom:9,left:1,right:8};
-SimileAjax.Graphics._bubblePadding=15;
-SimileAjax.Graphics._bubblePointOffset=6;
-SimileAjax.Graphics._halfArrowWidth=18;
-SimileAjax.Graphics.createBubbleForContentAndPoint=function(F,D,C,A,B,E){if(typeof A!="number"){A=300;
-}if(typeof E!="number"){E=0;
-}F.style.position="absolute";
-F.style.left="-5000px";
-F.style.top="0px";
-F.style.width=A+"px";
-document.body.appendChild(F);
-window.setTimeout(function(){var J=F.scrollWidth+10;
-var G=F.scrollHeight+10;
-var I=0;
-if(E>0&&G>E){G=E;
-I=J-25;
-}var H=SimileAjax.Graphics.createBubbleForPoint(D,C,J,G,B);
-document.body.removeChild(F);
-F.style.position="static";
-F.style.left="";
-F.style.top="";
-if(I>0){var K=document.createElement("div");
-F.style.width="";
-K.style.width=I+"px";
-K.appendChild(F);
-H.content.appendChild(K);
-}else{F.style.width=J+"px";
-H.content.appendChild(F);
-}},200);
-};
-SimileAjax.Graphics.createBubbleForPoint=function(C,B,N,R,F){function T(){if(typeof window.innerHeight=="number"){return{w:window.innerWidth,h:window.innerHeight};
-}else{if(document.documentElement&&document.documentElement.clientHeight){return{w:document.documentElement.clientWidth,h:document.documentElement.clientHeight};
-}else{if(document.body&&document.body.clientHeight){return{w:document.body.clientWidth,h:document.body.clientHeight};
-}}}}var L=function(){if(!M._closed){document.body.removeChild(M._div);
-M._doc=null;
-M._div=null;
-M._content=null;
-M._closed=true;
-}};
-var M={_closed:false};
-var O=T();
-var H=O.w;
-var G=O.h;
-var D=SimileAjax.Graphics._bubbleMargins;
-N=parseInt(N,10);
-R=parseInt(R,10);
-var P=D.left+N+D.right;
-var U=D.top+R+D.bottom;
-var Q=SimileAjax.Graphics.pngIsTranslucent;
-var J=SimileAjax.urlPrefix;
-var A=function(Z,Y,a,X){Z.style.position="absolute";
-Z.style.width=a+"px";
-Z.style.height=X+"px";
-if(Q){Z.style.background="url("+Y+")";
-}else{Z.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+Y+"', sizingMethod='crop')";
-}};
-var K=document.createElement("div");
-K.style.width=P+"px";
-K.style.height=U+"px";
-K.style.position="absolute";
-K.style.zIndex=1000;
-var W=SimileAjax.WindowManager.pushLayer(L,true,K);
-M._div=K;
-M.close=function(){SimileAjax.WindowManager.popLayer(W);
-};
-var I=document.createElement("div");
-I.style.width="100%";
-I.style.height="100%";
-I.style.position="relative";
-K.appendChild(I);
-var S=function(Z,c,b,a,Y){var X=document.createElement("div");
-X.style.left=c+"px";
-X.style.top=b+"px";
-A(X,Z,a,Y);
-I.appendChild(X);
-};
-S(J+"data/timeline/bubble-top-left.png",0,0,D.left,D.top);
-S(J+"data/timeline/bubble-top.png",D.left,0,N,D.top);
-S(J+"data/timeline/bubble-top-right.png",D.left+N,0,D.right,D.top);
-S(J+"data/timeline/bubble-left.png",0,D.top,D.left,R);
-S(J+"data/timeline/bubble-right.png",D.left+N,D.top,D.right,R);
-S(J+"data/timeline/bubble-bottom-left.png",0,D.top+R,D.left,D.bottom);
-S(J+"data/timeline/bubble-bottom.png",D.left,D.top+R,N,D.bottom);
-S(J+"data/timeline/bubble-bottom-right.png",D.left+N,D.top+R,D.right,D.bottom);
-var V=document.createElement("div");
-V.style.left=(P-D.right+SimileAjax.Graphics._bubblePadding-16-2)+"px";
-V.style.top=(D.top-SimileAjax.Graphics._bubblePadding+1)+"px";
-V.style.cursor="pointer";
-A(V,J+"data/timeline/close-button.png",16,16);
-SimileAjax.WindowManager.registerEventWithObject(V,"click",M,"close");
-I.appendChild(V);
-var E=document.createElement("div");
-E.style.position="absolute";
-E.style.left=D.left+"px";
-E.style.top=D.top+"px";
-E.style.width=N+"px";
-E.style.height=R+"px";
-E.style.overflow="auto";
-E.style.background="white";
-I.appendChild(E);
-M.content=E;
-(function(){if(C-SimileAjax.Graphics._halfArrowWidth-SimileAjax.Graphics._bubblePadding>0&&C+SimileAjax.Graphics._halfArrowWidth+SimileAjax.Graphics._bubblePadding<H){var Z=C-Math.round(N/2)-D.left;
-Z=C<(H/2)?Math.max(Z,-(D.left-SimileAjax.Graphics._bubblePadding)):Math.min(Z,H+(D.right-SimileAjax.Graphics._bubblePadding)-P);
-if((F&&F=="top")||(!F&&(B-SimileAjax.Graphics._bubblePointOffset-U>0))){var X=document.createElement("div");
-X.style.left=(C-SimileAjax.Graphics._halfArrowWidth-Z)+"px";
-X.style.top=(D.top+R)+"px";
-A(X,J+"data/timeline/bubble-bottom-arrow.png",37,D.bottom);
-I.appendChild(X);
-K.style.left=Z+"px";
-K.style.top=(B-SimileAjax.Graphics._bubblePointOffset-U+SimileAjax.Graphics._arrowOffsets.bottom)+"px";
-return ;
-}else{if((F&&F=="bottom")||(!F&&(B+SimileAjax.Graphics._bubblePointOffset+U<G))){var X=document.createElement("div");
-X.style.left=(C-SimileAjax.Graphics._halfArrowWidth-Z)+"px";
-X.style.top="0px";
-A(X,J+"data/timeline/bubble-top-arrow.png",37,D.top);
-I.appendChild(X);
-K.style.left=Z+"px";
-K.style.top=(B+SimileAjax.Graphics._bubblePointOffset-SimileAjax.Graphics._arrowOffsets.top)+"px";
-return ;
-}}}var Y=B-Math.round(R/2)-D.top;
-Y=B<(G/2)?Math.max(Y,-(D.top-SimileAjax.Graphics._bubblePadding)):Math.min(Y,G+(D.bottom-SimileAjax.Graphics._bubblePadding)-U);
-if((F&&F=="left")||(!F&&(C-SimileAjax.Graphics._bubblePointOffset-P>0))){var X=document.createElement("div");
-X.style.left=(D.left+N)+"px";
-X.style.top=(B-SimileAjax.Graphics._halfArrowWidth-Y)+"px";
-A(X,J+"data/timeline/bubble-right-arrow.png",D.right,37);
-I.appendChild(X);
-K.style.left=(C-SimileAjax.Graphics._bubblePointOffset-P+SimileAjax.Graphics._arrowOffsets.right)+"px";
-K.style.top=Y+"px";
-}else{if((F&&F=="right")||(!F&&(C-SimileAjax.Graphics._bubblePointOffset-P<H))){var X=document.createElement("div");
-X.style.left="0px";
-X.style.top=(B-SimileAjax.Graphics._halfArrowWidth-Y)+"px";
-A(X,J+"data/timeline/bubble-left-arrow.png",D.left,37);
-I.appendChild(X);
-K.style.left=(C+SimileAjax.Graphics._bubblePointOffset-SimileAjax.Graphics._arrowOffsets.left)+"px";
-K.style.top=Y+"px";
-}}})();
-document.body.appendChild(K);
-return M;
-};
-SimileAjax.Graphics.createMessageBubble=function(H){var G=H.createElement("div");
-if(SimileAjax.Graphics.pngIsTranslucent){var I=H.createElement("div");
-I.style.height="33px";
-I.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-top-left.png) top left no-repeat";
-I.style.paddingLeft="44px";
-G.appendChild(I);
-var C=H.createElement("div");
-C.style.height="33px";
-C.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-top-right.png) top right no-repeat";
-I.appendChild(C);
-var F=H.createElement("div");
-F.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-left.png) top left repeat-y";
-F.style.paddingLeft="44px";
-G.appendChild(F);
-var A=H.createElement("div");
-A.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-right.png) top right repeat-y";
-A.style.paddingRight="44px";
-F.appendChild(A);
-var D=H.createElement("div");
-A.appendChild(D);
-var B=H.createElement("div");
-B.style.height="55px";
-B.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-bottom-left.png) bottom left no-repeat";
-B.style.paddingLeft="44px";
-G.appendChild(B);
-var E=H.createElement("div");
-E.style.height="55px";
-E.style.background="url("+SimileAjax.urlPrefix+"data/timeline/message-bottom-right.png) bottom right no-repeat";
-B.appendChild(E);
-}else{G.style.border="2px solid #7777AA";
-G.style.padding="20px";
-G.style.background="white";
-SimileAjax.Graphics.setOpacity(G,90);
-var D=H.createElement("div");
-G.appendChild(D);
-}return{containerDiv:G,contentDiv:D};
-};
-SimileAjax.Graphics.createAnimation=function(B,E,D,C,A){return new SimileAjax.Graphics._Animation(B,E,D,C,A);
-};
-SimileAjax.Graphics._Animation=function(B,E,D,C,A){this.f=B;
-this.cont=(typeof A=="function")?A:function(){};
-this.from=E;
-this.to=D;
-this.current=E;
-this.duration=C;
-this.start=new Date().getTime();
-this.timePassed=0;
-};
-SimileAjax.Graphics._Animation.prototype.run=function(){var A=this;
-window.setTimeout(function(){A.step();
-},50);
-};
-SimileAjax.Graphics._Animation.prototype.step=function(){this.timePassed+=50;
-var B=this.timePassed/this.duration;
-var A=-Math.cos(B*Math.PI)/2+0.5;
-var D=A*(this.to-this.from)+this.from;
-try{this.f(D,D-this.current);
-}catch(C){}this.current=D;
-if(this.timePassed<this.duration){this.run();
-}else{this.f(this.to,0);
-this["cont"]();
-}};
-SimileAjax.Graphics.createStructuredDataCopyButton=function(F,D,A,E){var G=document.createElement("div");
-G.style.position="relative";
-G.style.display="inline";
-G.style.width=D+"px";
-G.style.height=A+"px";
-G.style.overflow="hidden";
-G.style.margin="2px";
-if(SimileAjax.Graphics.pngIsTranslucent){G.style.background="url("+F+") no-repeat";
-}else{G.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+F+"', sizingMethod='image')";
-}var C;
-if(SimileAjax.Platform.browser.isIE){C="filter:alpha(opacity=0)";
-}else{C="opacity: 0";
-}G.innerHTML="<textarea rows='1' autocomplete='off' value='none' style='"+C+"' />";
-var B=G.firstChild;
-B.style.width=D+"px";
-B.style.height=A+"px";
-B.onmousedown=function(H){H=(H)?H:((event)?event:null);
-if(H.button==2){B.value=E();
-B.select();
-}};
-return G;
-};
-SimileAjax.Graphics.getWidthHeight=function(C){var A,B;
-if(C.getBoundingClientRect==null){A=C.offsetWidth;
-B=C.offsetHeight;
-}else{var D=C.getBoundingClientRect();
-A=Math.ceil(D.right-D.left);
-B=Math.ceil(D.bottom-D.top);
-}return{width:A,height:B};
-};
-SimileAjax.Graphics.getFontRenderingContext=function(A,B){return new SimileAjax.Graphics._FontRenderingContext(A,B);
-};
-SimileAjax.Graphics._FontRenderingContext=function(A,B){this._elmt=A;
-this._elmt.style.visibility="hidden";
-if(typeof B=="string"){this._elmt.style.width=B;
-}else{if(typeof B=="number"){this._elmt.style.width=B+"px";
-}}};
-SimileAjax.Graphics._FontRenderingContext.prototype.dispose=function(){this._elmt=null;
-};
-SimileAjax.Graphics._FontRenderingContext.prototype.update=function(){this._elmt.innerHTML="A";
-this._lineHeight=this._elmt.offsetHeight;
-};
-SimileAjax.Graphics._FontRenderingContext.prototype.computeSize=function(D,C){var B=this._elmt;
-B.innerHTML=D;
-B.className=C===undefined?"":C;
-var A=SimileAjax.Graphics.getWidthHeight(B);
-B.className="";
-return A;
-};
-SimileAjax.Graphics._FontRenderingContext.prototype.getLineHeight=function(){return this._lineHeight;
-};
-
-
-/* history.js */
-SimileAjax.History={maxHistoryLength:10,historyFile:"__history__.html",enabled:true,_initialized:false,_listeners:new SimileAjax.ListenerQueue(),_actions:[],_baseIndex:0,_currentIndex:0,_plainDocumentTitle:document.title};
-SimileAjax.History.formatHistoryEntryTitle=function(A){return SimileAjax.History._plainDocumentTitle+" {"+A+"}";
-};
-SimileAjax.History.initialize=function(){if(SimileAjax.History._initialized){return ;
-}if(SimileAjax.History.enabled){var A=document.createElement("iframe");
-A.id="simile-ajax-history";
-A.style.position="absolute";
-A.style.width="10px";
-A.style.height="10px";
-A.style.top="0px";
-A.style.left="0px";
-A.style.visibility="hidden";
-A.src=SimileAjax.History.historyFile+"?0";
-document.body.appendChild(A);
-SimileAjax.DOM.registerEvent(A,"load",SimileAjax.History._handleIFrameOnLoad);
-SimileAjax.History._iframe=A;
-}SimileAjax.History._initialized=true;
-};
-SimileAjax.History.addListener=function(A){SimileAjax.History.initialize();
-SimileAjax.History._listeners.add(A);
-};
-SimileAjax.History.removeListener=function(A){SimileAjax.History.initialize();
-SimileAjax.History._listeners.remove(A);
-};
-SimileAjax.History.addAction=function(A){SimileAjax.History.initialize();
-SimileAjax.History._listeners.fire("onBeforePerform",[A]);
-window.setTimeout(function(){try{A.perform();
-SimileAjax.History._listeners.fire("onAfterPerform",[A]);
-if(SimileAjax.History.enabled){SimileAjax.History._actions=SimileAjax.History._actions.slice(0,SimileAjax.History._currentIndex-SimileAjax.History._baseIndex);
-SimileAjax.History._actions.push(A);
-SimileAjax.History._currentIndex++;
-var C=SimileAjax.History._actions.length-SimileAjax.History.maxHistoryLength;
-if(C>0){SimileAjax.History._actions=SimileAjax.History._actions.slice(C);
-SimileAjax.History._baseIndex+=C;
-}try{SimileAjax.History._iframe.contentWindow.location.search="?"+SimileAjax.History._currentIndex;
-}catch(B){var D=SimileAjax.History.formatHistoryEntryTitle(A.label);
-document.title=D;
-}}}catch(B){SimileAjax.Debug.exception(B,"Error adding action {"+A.label+"} to history");
-}},0);
-};
-SimileAjax.History.addLengthyAction=function(C,A,B){SimileAjax.History.addAction({perform:C,undo:A,label:B,uiLayer:SimileAjax.WindowManager.getBaseLayer(),lengthy:true});
-};
-SimileAjax.History._handleIFrameOnLoad=function(){try{var B=SimileAjax.History._iframe.contentWindow.location.search;
-var F=(B.length==0)?0:Math.max(0,parseInt(B.substr(1)));
-var E=function(){var G=F-SimileAjax.History._currentIndex;
-SimileAjax.History._currentIndex+=G;
-SimileAjax.History._baseIndex+=G;
-SimileAjax.History._iframe.contentWindow.location.search="?"+F;
-};
-if(F<SimileAjax.History._currentIndex){SimileAjax.History._listeners.fire("onBeforeUndoSeveral",[]);
-window.setTimeout(function(){while(SimileAjax.History._currentIndex>F&&SimileAjax.History._currentIndex>SimileAjax.History._baseIndex){SimileAjax.History._currentIndex--;
-var G=SimileAjax.History._actions[SimileAjax.History._currentIndex-SimileAjax.History._baseIndex];
-try{G.undo();
-}catch(H){SimileAjax.Debug.exception(H,"History: Failed to undo action {"+G.label+"}");
-}}SimileAjax.History._listeners.fire("onAfterUndoSeveral",[]);
-E();
-},0);
-}else{if(F>SimileAjax.History._currentIndex){SimileAjax.History._listeners.fire("onBeforeRedoSeveral",[]);
-window.setTimeout(function(){while(SimileAjax.History._currentIndex<F&&SimileAjax.History._currentIndex-SimileAjax.History._baseIndex<SimileAjax.History._actions.length){var G=SimileAjax.History._actions[SimileAjax.History._currentIndex-SimileAjax.History._baseIndex];
-try{G.perform();
-}catch(H){SimileAjax.Debug.exception(H,"History: Failed to redo action {"+G.label+"}");
-}SimileAjax.History._currentIndex++;
-}SimileAjax.History._listeners.fire("onAfterRedoSeveral",[]);
-E();
-},0);
-}else{var A=SimileAjax.History._currentIndex-SimileAjax.History._baseIndex-1;
-var D=(A>=0&&A<SimileAjax.History._actions.length)?SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[A].label):SimileAjax.History._plainDocumentTitle;
-SimileAjax.History._iframe.contentWindow.document.title=D;
-document.title=D;
-}}}catch(C){}};
-SimileAjax.History.getNextUndoAction=function(){try{var A=SimileAjax.History._currentIndex-SimileAjax.History._baseIndex-1;
-return SimileAjax.History._actions[A];
-}catch(B){return null;
-}};
-SimileAjax.History.getNextRedoAction=function(){try{var A=SimileAjax.History._currentIndex-SimileAjax.History._baseIndex;
-return SimileAjax.History._actions[A];
-}catch(B){return null;
-}};
-
-
-/* html.js */
-SimileAjax.HTML=new Object();
-SimileAjax.HTML._e2uHash={};
-(function(){var A=SimileAjax.HTML._e2uHash;
-A["nbsp"]="\u00A0[space]";
-A["iexcl"]="\u00A1";
-A["cent"]="\u00A2";
-A["pound"]="\u00A3";
-A["curren"]="\u00A4";
-A["yen"]="\u00A5";
-A["brvbar"]="\u00A6";
-A["sect"]="\u00A7";
-A["uml"]="\u00A8";
-A["copy"]="\u00A9";
-A["ordf"]="\u00AA";
-A["laquo"]="\u00AB";
-A["not"]="\u00AC";
-A["shy"]="\u00AD";
-A["reg"]="\u00AE";
-A["macr"]="\u00AF";
-A["deg"]="\u00B0";
-A["plusmn"]="\u00B1";
-A["sup2"]="\u00B2";
-A["sup3"]="\u00B3";
-A["acute"]="\u00B4";
-A["micro"]="\u00B5";
-A["para"]="\u00B6";
-A["middot"]="\u00B7";
-A["cedil"]="\u00B8";
-A["sup1"]="\u00B9";
-A["ordm"]="\u00BA";
-A["raquo"]="\u00BB";
-A["frac14"]="\u00BC";
-A["frac12"]="\u00BD";
-A["frac34"]="\u00BE";
-A["iquest"]="\u00BF";
-A["Agrave"]="\u00C0";
-A["Aacute"]="\u00C1";
-A["Acirc"]="\u00C2";
-A["Atilde"]="\u00C3";
-A["Auml"]="\u00C4";
-A["Aring"]="\u00C5";
-A["AElig"]="\u00C6";
-A["Ccedil"]="\u00C7";
-A["Egrave"]="\u00C8";
-A["Eacute"]="\u00C9";
-A["Ecirc"]="\u00CA";
-A["Euml"]="\u00CB";
-A["Igrave"]="\u00CC";
-A["Iacute"]="\u00CD";
-A["Icirc"]="\u00CE";
-A["Iuml"]="\u00CF";
-A["ETH"]="\u00D0";
-A["Ntilde"]="\u00D1";
-A["Ograve"]="\u00D2";
-A["Oacute"]="\u00D3";
-A["Ocirc"]="\u00D4";
-A["Otilde"]="\u00D5";
-A["Ouml"]="\u00D6";
-A["times"]="\u00D7";
-A["Oslash"]="\u00D8";
-A["Ugrave"]="\u00D9";
-A["Uacute"]="\u00DA";
-A["Ucirc"]="\u00DB";
-A["Uuml"]="\u00DC";
-A["Yacute"]="\u00DD";
-A["THORN"]="\u00DE";
-A["szlig"]="\u00DF";
-A["agrave"]="\u00E0";
-A["aacute"]="\u00E1";
-A["acirc"]="\u00E2";
-A["atilde"]="\u00E3";
-A["auml"]="\u00E4";
-A["aring"]="\u00E5";
-A["aelig"]="\u00E6";
-A["ccedil"]="\u00E7";
-A["egrave"]="\u00E8";
-A["eacute"]="\u00E9";
-A["ecirc"]="\u00EA";
-A["euml"]="\u00EB";
-A["igrave"]="\u00EC";
-A["iacute"]="\u00ED";
-A["icirc"]="\u00EE";
-A["iuml"]="\u00EF";
-A["eth"]="\u00F0";
-A["ntilde"]="\u00F1";
-A["ograve"]="\u00F2";
-A["oacute"]="\u00F3";
-A["ocirc"]="\u00F4";
-A["otilde"]="\u00F5";
-A["ouml"]="\u00F6";
-A["divide"]="\u00F7";
-A["oslash"]="\u00F8";
-A["ugrave"]="\u00F9";
-A["uacute"]="\u00FA";
-A["ucirc"]="\u00FB";
-A["uuml"]="\u00FC";
-A["yacute"]="\u00FD";
-A["thorn"]="\u00FE";
-A["yuml"]="\u00FF";
-A["quot"]="\u0022";
-A["amp"]="\u0026";
-A["lt"]="\u003C";
-A["gt"]="\u003E";
-A["OElig"]="";
-A["oelig"]="\u0153";
-A["Scaron"]="\u0160";
-A["scaron"]="\u0161";
-A["Yuml"]="\u0178";
-A["circ"]="\u02C6";
-A["tilde"]="\u02DC";
-A["ensp"]="\u2002";
-A["emsp"]="\u2003";
-A["thinsp"]="\u2009";
-A["zwnj"]="\u200C";
-A["zwj"]="\u200D";
-A["lrm"]="\u200E";
-A["rlm"]="\u200F";
-A["ndash"]="\u2013";
-A["mdash"]="\u2014";
-A["lsquo"]="\u2018";
-A["rsquo"]="\u2019";
-A["sbquo"]="\u201A";
-A["ldquo"]="\u201C";
-A["rdquo"]="\u201D";
-A["bdquo"]="\u201E";
-A["dagger"]="\u2020";
-A["Dagger"]="\u2021";
-A["permil"]="\u2030";
-A["lsaquo"]="\u2039";
-A["rsaquo"]="\u203A";
-A["euro"]="\u20AC";
-A["fnof"]="\u0192";
-A["Alpha"]="\u0391";
-A["Beta"]="\u0392";
-A["Gamma"]="\u0393";
-A["Delta"]="\u0394";
-A["Epsilon"]="\u0395";
-A["Zeta"]="\u0396";
-A["Eta"]="\u0397";
-A["Theta"]="\u0398";
-A["Iota"]="\u0399";
-A["Kappa"]="\u039A";
-A["Lambda"]="\u039B";
-A["Mu"]="\u039C";
-A["Nu"]="\u039D";
-A["Xi"]="\u039E";
-A["Omicron"]="\u039F";
-A["Pi"]="\u03A0";
-A["Rho"]="\u03A1";
-A["Sigma"]="\u03A3";
-A["Tau"]="\u03A4";
-A["Upsilon"]="\u03A5";
-A["Phi"]="\u03A6";
-A["Chi"]="\u03A7";
-A["Psi"]="\u03A8";
-A["Omega"]="\u03A9";
-A["alpha"]="\u03B1";
-A["beta"]="\u03B2";
-A["gamma"]="\u03B3";
-A["delta"]="\u03B4";
-A["epsilon"]="\u03B5";
-A["zeta"]="\u03B6";
-A["eta"]="\u03B7";
-A["theta"]="\u03B8";
-A["iota"]="\u03B9";
-A["kappa"]="\u03BA";
-A["lambda"]="\u03BB";
-A["mu"]="\u03BC";
-A["nu"]="\u03BD";
-A["xi"]="\u03BE";
-A["omicron"]="\u03BF";
-A["pi"]="\u03C0";
-A["rho"]="\u03C1";
-A["sigmaf"]="\u03C2";
-A["sigma"]="\u03C3";
-A["tau"]="\u03C4";
-A["upsilon"]="\u03C5";
-A["phi"]="\u03C6";
-A["chi"]="\u03C7";
-A["psi"]="\u03C8";
-A["omega"]="\u03C9";
-A["thetasym"]="\u03D1";
-A["upsih"]="\u03D2";
-A["piv"]="\u03D6";
-A["bull"]="\u2022";
-A["hellip"]="\u2026";
-A["prime"]="\u2032";
-A["Prime"]="\u2033";
-A["oline"]="\u203E";
-A["frasl"]="\u2044";
-A["weierp"]="\u2118";
-A["image"]="\u2111";
-A["real"]="\u211C";
-A["trade"]="\u2122";
-A["alefsym"]="\u2135";
-A["larr"]="\u2190";
-A["uarr"]="\u2191";
-A["rarr"]="\u2192";
-A["darr"]="\u2193";
-A["harr"]="\u2194";
-A["crarr"]="\u21B5";
-A["lArr"]="\u21D0";
-A["uArr"]="\u21D1";
-A["rArr"]="\u21D2";
-A["dArr"]="\u21D3";
-A["hArr"]="\u21D4";
-A["forall"]="\u2200";
-A["part"]="\u2202";
-A["exist"]="\u2203";
-A["empty"]="\u2205";
-A["nabla"]="\u2207";
-A["isin"]="\u2208";
-A["notin"]="\u2209";
-A["ni"]="\u220B";
-A["prod"]="\u220F";
-A["sum"]="\u2211";
-A["minus"]="\u2212";
-A["lowast"]="\u2217";
-A["radic"]="\u221A";
-A["prop"]="\u221D";
-A["infin"]="\u221E";
-A["ang"]="\u2220";
-A["and"]="\u2227";
-A["or"]="\u2228";
-A["cap"]="\u2229";
-A["cup"]="\u222A";
-A["int"]="\u222B";
-A["there4"]="\u2234";
-A["sim"]="\u223C";
-A["cong"]="\u2245";
-A["asymp"]="\u2248";
-A["ne"]="\u2260";
-A["equiv"]="\u2261";
-A["le"]="\u2264";
-A["ge"]="\u2265";
-A["sub"]="\u2282";
-A["sup"]="\u2283";
-A["nsub"]="\u2284";
-A["sube"]="\u2286";
-A["supe"]="\u2287";
-A["oplus"]="\u2295";
-A["otimes"]="\u2297";
-A["perp"]="\u22A5";
-A["sdot"]="\u22C5";
-A["lceil"]="\u2308";
-A["rceil"]="\u2309";
-A["lfloor"]="\u230A";
-A["rfloor"]="\u230B";
-A["lang"]="\u2329";
-A["rang"]="\u232A";
-A["loz"]="\u25CA";
-A["spades"]="\u2660";
-A["clubs"]="\u2663";
-A["hearts"]="\u2665";
-A["diams"]="\u2666";
-})();
-SimileAjax.HTML.deEntify=function(C){var D=SimileAjax.HTML._e2uHash;
-var B=/&(\w+?);/;
-while(B.test(C)){var A=C.match(B);
-C=C.replace(B,D[A[1]]);
-}return C;
-};
-
-
-/* json.js */
-SimileAjax.JSON=new Object();
-(function(){var m={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};
-var s={array:function(x){var a=["["],b,f,i,l=x.length,v;
-for(i=0;
-i<l;
-i+=1){v=x[i];
-f=s[typeof v];
-if(f){v=f(v);
-if(typeof v=="string"){if(b){a[a.length]=",";
-}a[a.length]=v;
-b=true;
-}}}a[a.length]="]";
-return a.join("");
-},"boolean":function(x){return String(x);
-},"null":function(x){return"null";
-},number:function(x){return isFinite(x)?String(x):"null";
-},object:function(x){if(x){if(x instanceof Array){return s.array(x);
-}var a=["{"],b,f,i,v;
-for(i in x){v=x[i];
-f=s[typeof v];
-if(f){v=f(v);
-if(typeof v=="string"){if(b){a[a.length]=",";
-}a.push(s.string(i),":",v);
-b=true;
-}}}a[a.length]="}";
-return a.join("");
-}return"null";
-},string:function(x){if(/["\\\x00-\x1f]/.test(x)){x=x.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];
-if(c){return c;
-}c=b.charCodeAt();
-return"\\u00"+Math.floor(c/16).toString(16)+(c%16).toString(16);
-});
-}return'"'+x+'"';
-}};
-SimileAjax.JSON.toJSONString=function(o){if(o instanceof Object){return s.object(o);
-}else{if(o instanceof Array){return s.array(o);
-}else{return o.toString();
-}}};
-SimileAjax.JSON.parseJSON=function(){try{return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(this.replace(/"(\\.|[^"\\])*"/g,"")))&&eval("("+this+")");
-}catch(e){return false;
-}};
+
+SimileAjax.HTML.deEntify = function(s) {
+    var e2uHash = SimileAjax.HTML._e2uHash;
+
+    var re = /&(\w+?);/;
+    while (re.test(s)) {
+        var m = s.match(re);
+        s = s.replace(re, e2uHash[m[1]]);
+    }
+    return s;
+};/**
+ * A basic set (in the mathematical sense) data structure
+ *
+ * @constructor
+ * @param {Array or SimileAjax.Set} [a] an initial collection
+ */
+SimileAjax.Set = function(a) {
+    this._hash = {};
+    this._count = 0;
+
+    if (a instanceof Array) {
+        for (var i = 0; i < a.length; i++) {
+            this.add(a[i]);
+        }
+    } else if (a instanceof SimileAjax.Set) {
+        this.addSet(a);
+    }
+}
+
+/**
+ * Adds the given object to this set, assuming there it does not already exist
+ *
+ * @param {Object} o the object to add
+ * @return {Boolean} true if the object was added, false if not
+ */
+SimileAjax.Set.prototype.add = function(o) {
+    if (!(o in this._hash)) {
+        this._hash[o] = true;
+        this._count++;
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Adds each element in the given set to this set
+ *
+ * @param {SimileAjax.Set} set the set of elements to add
+ */
+SimileAjax.Set.prototype.addSet = function(set) {
+    for (var o in set._hash) {
+        this.add(o);
+    }
+}
+
+/**
+ * Removes the given element from this set
+ *
+ * @param {Object} o the object to remove
+ * @return {Boolean} true if the object was successfully removed,
+ *   false otherwise
+ */
+SimileAjax.Set.prototype.remove = function(o) {
+    if (o in this._hash) {
+        delete this._hash[o];
+        this._count--;
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Removes the elements in this set that correspond to the elements in the
+ * given set
+ *
+ * @param {SimileAjax.Set} set the set of elements to remove
+ */
+SimileAjax.Set.prototype.removeSet = function(set) {
+    for (var o in set._hash) {
+        this.remove(o);
+    }
+}
+
+/**
+ * Removes all elements in this set that are not present in the given set, i.e.
+ * modifies this set to the intersection of the two sets
+ *
+ * @param {SimileAjax.Set} set the set to intersect
+ */
+SimileAjax.Set.prototype.retainSet = function(set) {
+    for (var o in this._hash) {
+        if (!set.contains(o)) {
+            delete this._hash[o];
+            this._count--;
+        }
+    }
+}
+
+/**
+ * Returns whether or not the given element exists in this set
+ *
+ * @param {SimileAjax.Set} o the object to test for
+ * @return {Boolean} true if the object is present, false otherwise
+ */
+SimileAjax.Set.prototype.contains = function(o) {
+    return (o in this._hash);
+}
+
+/**
+ * Returns the number of elements in this set
+ *
+ * @return {Number} the number of elements in this set
+ */
+SimileAjax.Set.prototype.size = function() {
+    return this._count;
+}
+
+/**
+ * Returns the elements of this set as an array
+ *
+ * @return {Array} a new array containing the elements of this set
+ */
+SimileAjax.Set.prototype.toArray = function() {
+    var a = [];
+    for (var o in this._hash) {
+        a.push(o);
+    }
+    return a;
+}
+
+/**
+ * Iterates through the elements of this set, order unspecified, executing the
+ * given function on each element until the function returns true
+ *
+ * @param {Function} f a function of form f(element)
+ */
+SimileAjax.Set.prototype.visit = function(f) {
+    for (var o in this._hash) {
+        if (f(o) == true) {
+            break;
+        }
+    }
+}
+
+/**
+ * A sorted array data structure
+ *
+ * @constructor
+ */
+SimileAjax.SortedArray = function(compare, initialArray) {
+    this._a = (initialArray instanceof Array) ? initialArray : [];
+    this._compare = compare;
+};
+
+SimileAjax.SortedArray.prototype.add = function(elmt) {
+    var sa = this;
+    var index = this.find(function(elmt2) {
+        return sa._compare(elmt2, elmt);
+    });
+
+    if (index < this._a.length) {
+        this._a.splice(index, 0, elmt);
+    } else {
+        this._a.push(elmt);
+    }
+};
+
+SimileAjax.SortedArray.prototype.remove = function(elmt) {
+    var sa = this;
+    var index = this.find(function(elmt2) {
+        return sa._compare(elmt2, elmt);
+    });
+
+    while (index < this._a.length && this._compare(this._a[index], elmt) == 0) {
+        if (this._a[index] == elmt) {
+            this._a.splice(index, 1);
+            return true;
+        } else {
+            index++;
+        }
+    }
+    return false;
+};
+
+SimileAjax.SortedArray.prototype.removeAll = function() {
+    this._a = [];
+};
+
+SimileAjax.SortedArray.prototype.elementAt = function(index) {
+    return this._a[index];
+};
+
+SimileAjax.SortedArray.prototype.length = function() {
+    return this._a.length;
+};
+
+SimileAjax.SortedArray.prototype.find = function(compare) {
+    var a = 0;
+    var b = this._a.length;
+
+    while (a < b) {
+        var mid = Math.floor((a + b) / 2);
+        var c = compare(this._a[mid]);
+        if (mid == a) {
+            return c < 0 ? a+1 : a;
+        } else if (c < 0) {
+            a = mid;
+        } else {
+            b = mid;
+        }
+    }
+    return a;
+};
+
+SimileAjax.SortedArray.prototype.getFirst = function() {
+    return (this._a.length > 0) ? this._a[0] : null;
+};
+
+SimileAjax.SortedArray.prototype.getLast = function() {
+    return (this._a.length > 0) ? this._a[this._a.length - 1] : null;
+};
+
+/*==================================================
+ *  Event Index
+ *==================================================
+ */
+
+SimileAjax.EventIndex = function(unit) {
+    var eventIndex = this;
+
+    this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit;
+    this._events = new SimileAjax.SortedArray(
+        function(event1, event2) {
+            return eventIndex._unit.compare(event1.getStart(), event2.getStart());
+        }
+    );
+    this._idToEvent = {};
+    this._indexed = true;
+};
+
+SimileAjax.EventIndex.prototype.getUnit = function() {
+    return this._unit;
+};
+
+SimileAjax.EventIndex.prototype.getEvent = function(id) {
+    return this._idToEvent[id];
+};
+
+SimileAjax.EventIndex.prototype.add = function(evt) {
+    this._events.add(evt);
+    this._idToEvent[evt.getID()] = evt;
+    this._indexed = false;
+};
+
+SimileAjax.EventIndex.prototype.removeAll = function() {
+    this._events.removeAll();
+    this._idToEvent = {};
+    this._indexed = false;
+};
+
+SimileAjax.EventIndex.prototype.getCount = function() {
+    return this._events.length();
+};
+
+SimileAjax.EventIndex.prototype.getIterator = function(startDate, endDate) {
+    if (!this._indexed) {
+        this._index();
+    }
+    return new SimileAjax.EventIndex._Iterator(this._events, startDate, endDate, this._unit);
+};
+
+SimileAjax.EventIndex.prototype.getReverseIterator = function(startDate, endDate) {
+    if (!this._indexed) {
+        this._index();
+    }
+    return new SimileAjax.EventIndex._ReverseIterator(this._events, startDate, endDate, this._unit);
+};
+
+SimileAjax.EventIndex.prototype.getAllIterator = function() {
+    return new SimileAjax.EventIndex._AllIterator(this._events);
+};
+
+SimileAjax.EventIndex.prototype.getEarliestDate = function() {
+    var evt = this._events.getFirst();
+    return (evt == null) ? null : evt.getStart();
+};
+
+SimileAjax.EventIndex.prototype.getLatestDate = function() {
+    var evt = this._events.getLast();
+    if (evt == null) {
+        return null;
+    }
+
+    if (!this._indexed) {
+        this._index();
+    }
+
+    var index = evt._earliestOverlapIndex;
+    var date = this._events.elementAt(index).getEnd();
+    for (var i = index + 1; i < this._events.length(); i++) {
+        date = this._unit.later(date, this._events.elementAt(i).getEnd());
+    }
+
+    return date;
+};
+
+SimileAjax.EventIndex.prototype._index = function() {
+    /*
+     *  For each event, we want to find the earliest preceding
+     *  event that overlaps with it, if any.
+     */
+
+    var l = this._events.length();
+    for (var i = 0; i < l; i++) {
+        var evt = this._events.elementAt(i);
+        evt._earliestOverlapIndex = i;
+    }
+
+    var toIndex = 1;
+    for (var i = 0; i < l; i++) {
+        var evt = this._events.elementAt(i);
+        var end = evt.getEnd();
+
+        toIndex = Math.max(toIndex, i + 1);
+        while (toIndex < l) {
+            var evt2 = this._events.elementAt(toIndex);
+            var start2 = evt2.getStart();
+
+            if (this._unit.compare(start2, end) < 0) {
+                evt2._earliestOverlapIndex = i;
+                toIndex++;
+            } else {
+                break;
+            }
+        }
+    }
+    this._indexed = true;
+};
+
+SimileAjax.EventIndex._Iterator = function(events, startDate, endDate, unit) {
+    this._events = events;
+    this._startDate = startDate;
+    this._endDate = endDate;
+    this._unit = unit;
+
+    this._currentIndex = events.find(function(evt) {
+        return unit.compare(evt.getStart(), startDate);
+    });
+    if (this._currentIndex - 1 >= 0) {
+        this._currentIndex = this._events.elementAt(this._currentIndex - 1)._earliestOverlapIndex;
+    }
+    this._currentIndex--;
+
+    this._maxIndex = events.find(function(evt) {
+        return unit.compare(evt.getStart(), endDate);
+    });
+
+    this._hasNext = false;
+    this._next = null;
+    this._findNext();
+};
+
+SimileAjax.EventIndex._Iterator.prototype = {
+    hasNext: function() { return this._hasNext; },
+    next: function() {
+        if (this._hasNext) {
+            var next = this._next;
+            this._findNext();
+
+            return next;
+        } else {
+            return null;
+        }
+    },
+    _findNext: function() {
+        var unit = this._unit;
+        while ((++this._currentIndex) < this._maxIndex) {
+            var evt = this._events.elementAt(this._currentIndex);
+            if (unit.compare(evt.getStart(), this._endDate) < 0 &&
+                unit.compare(evt.getEnd(), this._startDate) > 0) {
+
+                this._next = evt;
+                this._hasNext = true;
+                return;
+            }
+        }
+        this._next = null;
+        this._hasNext = false;
+    }
+};
+
+SimileAjax.EventIndex._ReverseIterator = function(events, startDate, endDate, unit) {
+    this._events = events;
+    this._startDate = startDate;
+    this._endDate = endDate;
+    this._unit = unit;
+
+    this._minIndex = events.find(function(evt) {
+        return unit.compare(evt.getStart(), startDate);
+    });
+    if (this._minIndex - 1 >= 0) {
+        this._minIndex = this._events.elementAt(this._minIndex - 1)._earliestOverlapIndex;
+    }
+
+    this._maxIndex = events.find(function(evt) {
+        return unit.compare(evt.getStart(), endDate);
+    });
+
+    this._currentIndex = this._maxIndex;
+    this._hasNext = false;
+    this._next = null;
+    this._findNext();
+};
+
+SimileAjax.EventIndex._ReverseIterator.prototype = {
+    hasNext: function() { return this._hasNext; },
+    next: function() {
+        if (this._hasNext) {
+            var next = this._next;
+            this._findNext();
+
+            return next;
+        } else {
+            return null;
+        }
+    },
+    _findNext: function() {
+        var unit = this._unit;
+        while ((--this._currentIndex) >= this._minIndex) {
+            var evt = this._events.elementAt(this._currentIndex);
+            if (unit.compare(evt.getStart(), this._endDate) < 0 &&
+                unit.compare(evt.getEnd(), this._startDate) > 0) {
+
+                this._next = evt;
+                this._hasNext = true;
+                return;
+            }
+        }
+        this._next = null;
+        this._hasNext = false;
+    }
+};
+
+SimileAjax.EventIndex._AllIterator = function(events) {
+    this._events = events;
+    this._index = 0;
+};
+
+SimileAjax.EventIndex._AllIterator.prototype = {
+    hasNext: function() {
+        return this._index < this._events.length();
+    },
+    next: function() {
+        return this._index < this._events.length() ?
+            this._events.elementAt(this._index++) : null;
+    }
+};/*==================================================
+ *  Default Unit
+ *==================================================
+ */
+
+SimileAjax.NativeDateUnit = new Object();
+
+SimileAjax.NativeDateUnit.makeDefaultValue = function() {
+    return new Date();
+};
+
+SimileAjax.NativeDateUnit.cloneValue = function(v) {
+    return new Date(v.getTime());
+};
+
+SimileAjax.NativeDateUnit.getParser = function(format) {
+    if (typeof format == "string") {
+        format = format.toLowerCase();
+    }
+    return (format == "iso8601" || format == "iso 8601") ?
+        SimileAjax.DateTime.parseIso8601DateTime : 
+        SimileAjax.DateTime.parseGregorianDateTime;
+};
+
+SimileAjax.NativeDateUnit.parseFromObject = function(o) {
+    return SimileAjax.DateTime.parseGregorianDateTime(o);
+};
+
+SimileAjax.NativeDateUnit.toNumber = function(v) {
+    return v.getTime();
+};
+
+SimileAjax.NativeDateUnit.fromNumber = function(n) {
+    return new Date(n);
+};
+
+SimileAjax.NativeDateUnit.compare = function(v1, v2) {
+    var n1, n2;
+    if (typeof v1 == "object") {
+        n1 = v1.getTime();
+    } else {
+        n1 = Number(v1);
+    }
+    if (typeof v2 == "object") {
+        n2 = v2.getTime();
+    } else {
+        n2 = Number(v2);
+    }
+    
+    return n1 - n2;
+};
+
+SimileAjax.NativeDateUnit.earlier = function(v1, v2) {
+    return SimileAjax.NativeDateUnit.compare(v1, v2) < 0 ? v1 : v2;
+};
+
+SimileAjax.NativeDateUnit.later = function(v1, v2) {
+    return SimileAjax.NativeDateUnit.compare(v1, v2) > 0 ? v1 : v2;
+};
+
+SimileAjax.NativeDateUnit.change = function(v, n) {
+    return new Date(v.getTime() + n);
+};
+
+/*==================================================
+ *  General, miscellaneous SimileAjax stuff
+ *==================================================
+ */
+
+SimileAjax.ListenerQueue = function(wildcardHandlerName) {
+    this._listeners = [];
+    this._wildcardHandlerName = wildcardHandlerName;
+};
+
+SimileAjax.ListenerQueue.prototype.add = function(listener) {
+    this._listeners.push(listener);
+};
+
+SimileAjax.ListenerQueue.prototype.remove = function(listener) {
+    var listeners = this._listeners;
+    for (var i = 0; i < listeners.length; i++) {
+        if (listeners[i] == listener) {
+            listeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+SimileAjax.ListenerQueue.prototype.fire = function(handlerName, args) {
+    var listeners = [].concat(this._listeners);
+    for (var i = 0; i < listeners.length; i++) {
+        var listener = listeners[i];
+        if (handlerName in listener) {
+            try {
+                listener[handlerName].apply(listener, args);
+            } catch (e) {
+                SimileAjax.Debug.exception("Error firing event of name " + handlerName, e);
+            }
+        } else if (this._wildcardHandlerName != null &&
+            this._wildcardHandlerName in listener) {
+            try {
+                listener[this._wildcardHandlerName].apply(listener, [ handlerName ]);
+            } catch (e) {
+                SimileAjax.Debug.exception("Error firing event of name " + handlerName + " to wildcard handler", e);
+            }
+        }
+    }
+};
+
+/*======================================================================
+ *  History
+ *
+ *  This is a singleton that keeps track of undoable user actions and
+ *  performs undos and redos in response to the browser's Back and
+ *  Forward buttons.
+ *
+ *  Call addAction(action) to register an undoable user action. action
+ *  must have 4 fields:
+ *
+ *      perform: an argument-less function that carries out the action
+ *      undo:    an argument-less function that undos the action
+ *      label:   a short, user-friendly string describing the action
+ *      uiLayer: the UI layer on which the action takes place
+ *
+ *  By default, the history keeps track of upto 10 actions. You can
+ *  configure this behavior by setting
+ *      SimileAjax.History.maxHistoryLength
+ *  to a different number.
+ *
+ *  An iframe is inserted into the document's body element to track
+ *  onload events.
+ *======================================================================
+ */
+
+SimileAjax.History = {
+    maxHistoryLength:       10,
+    historyFile:            "__history__.html",
+    enabled:               true,
+
+    _initialized:           false,
+    _listeners:             new SimileAjax.ListenerQueue(),
+
+    _actions:               [],
+    _baseIndex:             0,
+    _currentIndex:          0,
+
+    _plainDocumentTitle:    document.title
+};
+
+SimileAjax.History.formatHistoryEntryTitle = function(actionLabel) {
+    return SimileAjax.History._plainDocumentTitle + " {" + actionLabel + "}";
+};
+
+SimileAjax.History.initialize = function() {
+    if (SimileAjax.History._initialized) {
+        return;
+    }
+
+    if (SimileAjax.History.enabled) {
+        var iframe = document.createElement("iframe");
+        iframe.id = "simile-ajax-history";
+        iframe.style.position = "absolute";
+        iframe.style.width = "10px";
+        iframe.style.height = "10px";
+        iframe.style.top = "0px";
+        iframe.style.left = "0px";
+        iframe.style.visibility = "hidden";
+        iframe.src = SimileAjax.History.historyFile + "?0";
+
+        document.body.appendChild(iframe);
+        SimileAjax.DOM.registerEvent(iframe, "load", SimileAjax.History._handleIFrameOnLoad);
+
+        SimileAjax.History._iframe = iframe;
+    }
+    SimileAjax.History._initialized = true;
+};
+
+SimileAjax.History.addListener = function(listener) {
+    SimileAjax.History.initialize();
+
+    SimileAjax.History._listeners.add(listener);
+};
+
+SimileAjax.History.removeListener = function(listener) {
+    SimileAjax.History.initialize();
+
+    SimileAjax.History._listeners.remove(listener);
+};
+
+SimileAjax.History.addAction = function(action) {
+    SimileAjax.History.initialize();
+
+    SimileAjax.History._listeners.fire("onBeforePerform", [ action ]);
+    window.setTimeout(function() {
+        try {
+            action.perform();
+            SimileAjax.History._listeners.fire("onAfterPerform", [ action ]);
+
+            if (SimileAjax.History.enabled) {
+                SimileAjax.History._actions = SimileAjax.History._actions.slice(
+                    0, SimileAjax.History._currentIndex - SimileAjax.History._baseIndex);
+
+                SimileAjax.History._actions.push(action);
+                SimileAjax.History._currentIndex++;
+
+                var diff = SimileAjax.History._actions.length - SimileAjax.History.maxHistoryLength;
+                if (diff > 0) {
+                    SimileAjax.History._actions = SimileAjax.History._actions.slice(diff);
+                    SimileAjax.History._baseIndex += diff;
+                }
+
+                try {
+                    SimileAjax.History._iframe.contentWindow.location.search =
+                        "?" + SimileAjax.History._currentIndex;
+                } catch (e) {
+                    /*
+                     *  We can't modify location.search most probably because it's a file:// url.
+                     *  We'll just going to modify the document's title.
+                     */
+                    var title = SimileAjax.History.formatHistoryEntryTitle(action.label);
+                    document.title = title;
+                }
+            }
+        } catch (e) {
+            SimileAjax.Debug.exception(e, "Error adding action {" + action.label + "} to history");
+        }
+    }, 0);
+};
+
+SimileAjax.History.addLengthyAction = function(perform, undo, label) {
+    SimileAjax.History.addAction({
+        perform:    perform,
+        undo:       undo,
+        label:      label,
+        uiLayer:    SimileAjax.WindowManager.getBaseLayer(),
+        lengthy:    true
+    });
+};
+
+SimileAjax.History._handleIFrameOnLoad = function() {
+    /*
+     *  This function is invoked when the user herself
+     *  navigates backward or forward. We need to adjust
+     *  the application's state accordingly.
+     */
+
+    try {
+        var q = SimileAjax.History._iframe.contentWindow.location.search;
+        var c = (q.length == 0) ? 0 : Math.max(0, parseInt(q.substr(1)));
+
+        var finishUp = function() {
+            var diff = c - SimileAjax.History._currentIndex;
+            SimileAjax.History._currentIndex += diff;
+            SimileAjax.History._baseIndex += diff;
+
+            SimileAjax.History._iframe.contentWindow.location.search = "?" + c;
+        };
+
+        if (c < SimileAjax.History._currentIndex) { // need to undo
+            SimileAjax.History._listeners.fire("onBeforeUndoSeveral", []);
+            window.setTimeout(function() {
+                while (SimileAjax.History._currentIndex > c &&
+                       SimileAjax.History._currentIndex > SimileAjax.History._baseIndex) {
+
+                    SimileAjax.History._currentIndex--;
+
+                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
+
+                    try {
+                        action.undo();
+                    } catch (e) {
+                        SimileAjax.Debug.exception(e, "History: Failed to undo action {" + action.label + "}");
+                    }
+                }
+
+                SimileAjax.History._listeners.fire("onAfterUndoSeveral", []);
+                finishUp();
+            }, 0);
+        } else if (c > SimileAjax.History._currentIndex) { // need to redo
+            SimileAjax.History._listeners.fire("onBeforeRedoSeveral", []);
+            window.setTimeout(function() {
+                while (SimileAjax.History._currentIndex < c &&
+                       SimileAjax.History._currentIndex - SimileAjax.History._baseIndex < SimileAjax.History._actions.length) {
+
+                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
+
+                    try {
+                        action.perform();
+                    } catch (e) {
+                        SimileAjax.Debug.exception(e, "History: Failed to redo action {" + action.label + "}");
+                    }
+
+                    SimileAjax.History._currentIndex++;
+                }
+
+                SimileAjax.History._listeners.fire("onAfterRedoSeveral", []);
+                finishUp();
+            }, 0);
+        } else {
+            var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
+            var title = (index >= 0 && index < SimileAjax.History._actions.length) ?
+                SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[index].label) :
+                SimileAjax.History._plainDocumentTitle;
+
+            SimileAjax.History._iframe.contentWindow.document.title = title;
+            document.title = title;
+        }
+    } catch (e) {
+        // silent
+    }
+};
+
+SimileAjax.History.getNextUndoAction = function() {
+    try {
+        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
+        return SimileAjax.History._actions[index];
+    } catch (e) {
+        return null;
+    }
+};
+
+SimileAjax.History.getNextRedoAction = function() {
+    try {
+        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex;
+        return SimileAjax.History._actions[index];
+    } catch (e) {
+        return null;
+    }
+};
+/**
+ * @fileOverview UI layers and window-wide dragging
+ * @name SimileAjax.WindowManager
+ */
+
+/**
+ *  This is a singleton that keeps track of UI layers (modal and
+ *  modeless) and enables/disables UI elements based on which layers
+ *  they belong to. It also provides window-wide dragging
+ *  implementation.
+ */
+SimileAjax.WindowManager = {
+    _initialized:       false,
+    _listeners:         [],
+
+    _draggedElement:                null,
+    _draggedElementCallback:        null,
+    _dropTargetHighlightElement:    null,
+    _lastCoords:                    null,
+    _ghostCoords:                   null,
+    _draggingMode:                  "",
+    _dragging:                      false,
+
+    _layers:            []
+};
+
+SimileAjax.WindowManager.initialize = function() {
+    if (SimileAjax.WindowManager._initialized) {
+        return;
+    }
+
+    SimileAjax.DOM.registerEvent(document.body, "mousedown", SimileAjax.WindowManager._onBodyMouseDown);
+    SimileAjax.DOM.registerEvent(document.body, "mousemove", SimileAjax.WindowManager._onBodyMouseMove);
+    SimileAjax.DOM.registerEvent(document.body, "mouseup",   SimileAjax.WindowManager._onBodyMouseUp);
+    SimileAjax.DOM.registerEvent(document, "keydown",       SimileAjax.WindowManager._onBodyKeyDown);
+    SimileAjax.DOM.registerEvent(document, "keyup",         SimileAjax.WindowManager._onBodyKeyUp);
+
+    SimileAjax.WindowManager._layers.push({index: 0});
+
+    SimileAjax.WindowManager._historyListener = {
+        onBeforeUndoSeveral:    function() {},
+        onAfterUndoSeveral:     function() {},
+        onBeforeUndo:           function() {},
+        onAfterUndo:            function() {},
+
+        onBeforeRedoSeveral:    function() {},
+        onAfterRedoSeveral:     function() {},
+        onBeforeRedo:           function() {},
+        onAfterRedo:            function() {}
+    };
+    SimileAjax.History.addListener(SimileAjax.WindowManager._historyListener);
+
+    SimileAjax.WindowManager._initialized = true;
+};
+
+SimileAjax.WindowManager.getBaseLayer = function() {
+    SimileAjax.WindowManager.initialize();
+    return SimileAjax.WindowManager._layers[0];
+};
+
+SimileAjax.WindowManager.getHighestLayer = function() {
+    SimileAjax.WindowManager.initialize();
+    return SimileAjax.WindowManager._layers[SimileAjax.WindowManager._layers.length - 1];
+};
+
+SimileAjax.WindowManager.registerEventWithObject = function(elmt, eventName, obj, handlerName, layer) {
+    SimileAjax.WindowManager.registerEvent(
+        elmt,
+        eventName,
+        function(elmt2, evt, target) {
+            return obj[handlerName].call(obj, elmt2, evt, target);
+        },
+        layer
+    );
+};
+
+SimileAjax.WindowManager.registerEvent = function(elmt, eventName, handler, layer) {
+    if (layer == null) {
+        layer = SimileAjax.WindowManager.getHighestLayer();
+    }
+
+    var handler2 = function(elmt, evt, target) {
+        if (SimileAjax.WindowManager._canProcessEventAtLayer(layer)) {
+            SimileAjax.WindowManager._popToLayer(layer.index);
+            try {
+                handler(elmt, evt, target);
+            } catch (e) {
+                SimileAjax.Debug.exception(e);
+            }
+        }
+        SimileAjax.DOM.cancelEvent(evt);
+        return false;
+    }
+
+    SimileAjax.DOM.registerEvent(elmt, eventName, handler2);
+};
+
+SimileAjax.WindowManager.pushLayer = function(f, ephemeral, elmt) {
+    var layer = { onPop: f, index: SimileAjax.WindowManager._layers.length, ephemeral: (ephemeral), elmt: elmt };
+    SimileAjax.WindowManager._layers.push(layer);
+
+    return layer;
+};
+
+SimileAjax.WindowManager.popLayer = function(layer) {
+    for (var i = 1; i < SimileAjax.WindowManager._layers.length; i++) {
+        if (SimileAjax.WindowManager._layers[i] == layer) {
+            SimileAjax.WindowManager._popToLayer(i - 1);
+            break;
+        }
+    }
+};
+
+SimileAjax.WindowManager.popAllLayers = function() {
+    SimileAjax.WindowManager._popToLayer(0);
+};
+
+SimileAjax.WindowManager.registerForDragging = function(elmt, callback, layer) {
+    SimileAjax.WindowManager.registerEvent(
+        elmt,
+        "mousedown",
+        function(elmt, evt, target) {
+            SimileAjax.WindowManager._handleMouseDown(elmt, evt, callback);
+        },
+        layer
+    );
+};
+
+SimileAjax.WindowManager._popToLayer = function(level) {
+    while (level+1 < SimileAjax.WindowManager._layers.length) {
+        try {
+            var layer = SimileAjax.WindowManager._layers.pop();
+            if (layer.onPop != null) {
+                layer.onPop();
+            }
+        } catch (e) {
+        }
+    }
+};
+
+SimileAjax.WindowManager._canProcessEventAtLayer = function(layer) {
+    if (layer.index == (SimileAjax.WindowManager._layers.length - 1)) {
+        return true;
+    }
+    for (var i = layer.index + 1; i < SimileAjax.WindowManager._layers.length; i++) {
+        if (!SimileAjax.WindowManager._layers[i].ephemeral) {
+            return false;
+        }
+    }
+    return true;
+};
+
+SimileAjax.WindowManager.cancelPopups = function(evt) {
+    var evtCoords = (evt) ? SimileAjax.DOM.getEventPageCoordinates(evt) : { x: -1, y: -1 };
+
+    var i = SimileAjax.WindowManager._layers.length - 1;
+    while (i > 0 && SimileAjax.WindowManager._layers[i].ephemeral) {
+        var layer = SimileAjax.WindowManager._layers[i];
+        if (layer.elmt != null) { // if event falls within main element of layer then don't cancel
+            var elmt = layer.elmt;
+            var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt);
+            if (evtCoords.x >= elmtCoords.left && evtCoords.x < (elmtCoords.left + elmt.offsetWidth) &&
+                evtCoords.y >= elmtCoords.top && evtCoords.y < (elmtCoords.top + elmt.offsetHeight)) {
+                break;
+            }
+        }
+        i--;
+    }
+    SimileAjax.WindowManager._popToLayer(i);
+};
+
+SimileAjax.WindowManager._onBodyMouseDown = function(elmt, evt, target) {
+    if (!("eventPhase" in evt) || evt.eventPhase == evt.BUBBLING_PHASE) {
+        SimileAjax.WindowManager.cancelPopups(evt);
+    }
+};
+
+SimileAjax.WindowManager._handleMouseDown = function(elmt, evt, callback) {
+    SimileAjax.WindowManager._draggedElement = elmt;
+    SimileAjax.WindowManager._draggedElementCallback = callback;
+    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
+
+    SimileAjax.DOM.cancelEvent(evt);
+    return false;
+};
+
+SimileAjax.WindowManager._onBodyKeyDown = function(elmt, evt, target) {
+    if (SimileAjax.WindowManager._dragging) {
+        if (evt.keyCode == 27) { // esc
+            SimileAjax.WindowManager._cancelDragging();
+        } else if ((evt.keyCode == 17 || evt.keyCode == 16) && SimileAjax.WindowManager._draggingMode != "copy") {
+            SimileAjax.WindowManager._draggingMode = "copy";
+
+            var img = SimileAjax.Graphics.createTranslucentImage(SimileAjax.urlPrefix + "images/copy.png");
+            img.style.position = "absolute";
+            img.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
+            img.style.top = (SimileAjax.WindowManager._ghostCoords.top) + "px";
+            document.body.appendChild(img);
+
+            SimileAjax.WindowManager._draggingModeIndicatorElmt = img;
+        }
+    }
+};
+
+SimileAjax.WindowManager._onBodyKeyUp = function(elmt, evt, target) {
+    if (SimileAjax.WindowManager._dragging) {
+        if (evt.keyCode == 17 || evt.keyCode == 16) {
+            SimileAjax.WindowManager._draggingMode = "";
+            if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
+                document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
+                SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
+            }
+        }
+    }
+};
+
+SimileAjax.WindowManager._onBodyMouseMove = function(elmt, evt, target) {
+    if (SimileAjax.WindowManager._draggedElement != null) {
+        var callback = SimileAjax.WindowManager._draggedElementCallback;
+
+        var lastCoords = SimileAjax.WindowManager._lastCoords;
+        var diffX = evt.clientX - lastCoords.x;
+        var diffY = evt.clientY - lastCoords.y;
+
+        if (!SimileAjax.WindowManager._dragging) {
+            if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {
+                try {
+                    if ("onDragStart" in callback) {
+                        callback.onDragStart();
+                    }
+
+                    if ("ghost" in callback && callback.ghost) {
+                        var draggedElmt = SimileAjax.WindowManager._draggedElement;
+
+                        SimileAjax.WindowManager._ghostCoords = SimileAjax.DOM.getPageCoordinates(draggedElmt);
+                        SimileAjax.WindowManager._ghostCoords.left += diffX;
+                        SimileAjax.WindowManager._ghostCoords.top += diffY;
+
+                        var ghostElmt = draggedElmt.cloneNode(true);
+                        ghostElmt.style.position = "absolute";
+                        ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
+                        ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
+                        ghostElmt.style.zIndex = 1000;
+                        SimileAjax.Graphics.setOpacity(ghostElmt, 50);
+
+                        document.body.appendChild(ghostElmt);
+                        callback._ghostElmt = ghostElmt;
+                    }
+
+                    SimileAjax.WindowManager._dragging = true;
+                    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
+
+                    document.body.focus();
+                } catch (e) {
+                    SimileAjax.Debug.exception("WindowManager: Error handling mouse down", e);
+                    SimileAjax.WindowManager._cancelDragging();
+                }
+            }
+        } else {
+            try {
+                SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
+
+                if ("onDragBy" in callback) {
+                    callback.onDragBy(diffX, diffY);
+                }
+
+                if ("_ghostElmt" in callback) {
+                    var ghostElmt = callback._ghostElmt;
+
+                    SimileAjax.WindowManager._ghostCoords.left += diffX;
+                    SimileAjax.WindowManager._ghostCoords.top += diffY;
+
+                    ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
+                    ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
+                    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
+                        var indicatorElmt = SimileAjax.WindowManager._draggingModeIndicatorElmt;
+
+                        indicatorElmt.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
+                        indicatorElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
+                    }
+
+                    if ("droppable" in callback && callback.droppable) {
+                        var coords = SimileAjax.DOM.getEventPageCoordinates(evt);
+                        var target = SimileAjax.DOM.hittest(
+                            coords.x, coords.y,
+                            [   SimileAjax.WindowManager._ghostElmt,
+                                SimileAjax.WindowManager._dropTargetHighlightElement
+                            ]
+                        );
+                        target = SimileAjax.WindowManager._findDropTarget(target);
+
+                        if (target != SimileAjax.WindowManager._potentialDropTarget) {
+                            if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
+                                document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
+
+                                SimileAjax.WindowManager._dropTargetHighlightElement = null;
+                                SimileAjax.WindowManager._potentialDropTarget = null;
+                            }
+
+                            var droppable = false;
+                            if (target != null) {
+                                if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
+                                    (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
+
+                                    droppable = true;
+                                }
+                            }
+
+                            if (droppable) {
+                                var border = 4;
+                                var targetCoords = SimileAjax.DOM.getPageCoordinates(target);
+                                var highlight = document.createElement("div");
+                                highlight.style.border = border + "px solid yellow";
+                                highlight.style.backgroundColor = "yellow";
+                                highlight.style.position = "absolute";
+                                highlight.style.left = targetCoords.left + "px";
+                                highlight.style.top = targetCoords.top + "px";
+                                highlight.style.width = (target.offsetWidth - border * 2) + "px";
+                                highlight.style.height = (target.offsetHeight - border * 2) + "px";
+                                SimileAjax.Graphics.setOpacity(highlight, 30);
+                                document.body.appendChild(highlight);
+
+                                SimileAjax.WindowManager._potentialDropTarget = target;
+                                SimileAjax.WindowManager._dropTargetHighlightElement = highlight;
+                            }
+                        }
+                    }
+                }
+            } catch (e) {
+                SimileAjax.Debug.exception("WindowManager: Error handling mouse move", e);
+                SimileAjax.WindowManager._cancelDragging();
+            }
+        }
+
+        SimileAjax.DOM.cancelEvent(evt);
+        return false;
+    }
+};
+
+SimileAjax.WindowManager._onBodyMouseUp = function(elmt, evt, target) {
+    if (SimileAjax.WindowManager._draggedElement != null) {
+        try {
+            if (SimileAjax.WindowManager._dragging) {
+                var callback = SimileAjax.WindowManager._draggedElementCallback;
+                if ("onDragEnd" in callback) {
+                    callback.onDragEnd();
+                }
+                if ("droppable" in callback && callback.droppable) {
+                    var dropped = false;
+
+                    var target = SimileAjax.WindowManager._potentialDropTarget;
+                    if (target != null) {
+                        if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
+                            (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
+
+                            if ("onDropOn" in callback) {
+                                callback.onDropOn(target);
+                            }
+                            target.ondrop(SimileAjax.WindowManager._draggedElement, SimileAjax.WindowManager._draggingMode);
+
+                            dropped = true;
+                        }
+                    }
+
+                    if (!dropped) {
+                        // TODO: do holywood explosion here
+                    }
+                }
+            }
+        } finally {
+            SimileAjax.WindowManager._cancelDragging();
+        }
+
+        SimileAjax.DOM.cancelEvent(evt);
+        return false;
+    }
+};
+
+SimileAjax.WindowManager._cancelDragging = function() {
+    var callback = SimileAjax.WindowManager._draggedElementCallback;
+    if ("_ghostElmt" in callback) {
+        var ghostElmt = callback._ghostElmt;
+        document.body.removeChild(ghostElmt);
+
+        delete callback._ghostElmt;
+    }
+    if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
+        document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
+        SimileAjax.WindowManager._dropTargetHighlightElement = null;
+    }
+    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
+        document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
+        SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
+    }
+
+    SimileAjax.WindowManager._draggedElement = null;
+    SimileAjax.WindowManager._draggedElementCallback = null;
+    SimileAjax.WindowManager._potentialDropTarget = null;
+    SimileAjax.WindowManager._dropTargetHighlightElement = null;
+    SimileAjax.WindowManager._lastCoords = null;
+    SimileAjax.WindowManager._ghostCoords = null;
+    SimileAjax.WindowManager._draggingMode = "";
+    SimileAjax.WindowManager._dragging = false;
+};
+
+SimileAjax.WindowManager._findDropTarget = function(elmt) {
+    while (elmt != null) {
+        if ("ondrop" in elmt && (typeof elmt.ondrop) == "function") {
+            break;
+        }
+        elmt = elmt.parentNode;
+    }
+    return elmt;
+};
+/*==================================================
+ *  Timeline API
+ *
+ *  This file will load all the Javascript files
+ *  necessary to make the standard timeline work.
+ *  It also detects the default locale.
+ *
+ *  To run from the MIT copy of Timeline:
+ *  Include this file in your HTML file as follows:
+ *
+ *    <script src="http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js"
+ *     type="text/javascript"></script>
+ *
+ *
+ * To host the Timeline files on your own server:
+ *   1) Install the Timeline and Simile-Ajax files onto your webserver using
+ *      timeline_libraries.zip or timeline_source.zip
+ *
+ *   2) Set global js variables used to send parameters to this script:
+ *        var Timeline_ajax_url -- url for simile-ajax-api.js
+ *        var Timeline_urlPrefix -- url for the *directory* that contains timeline-api.js
+ *            Include trailing slash
+ *        var Timeline_parameters='bundle=true'; // you must set bundle to true if you are using
+ *                                               // timeline_libraries.zip since only the
+ *                                               // bundled libraries are included
+ *
+ * eg your html page would include
+ *
+ *   <script>
+ *     var Timeline_ajax_url="http://YOUR_SERVER/javascripts/timeline/timeline_ajax/simile-ajax-api.js";
+ *     var Timeline_urlPrefix='http://YOUR_SERVER/javascripts/timeline/timeline_js/';
+ *     var Timeline_parameters='bundle=true';
+ *   </script>
+ *   <script src="http://YOUR_SERVER/javascripts/timeline/timeline_js/timeline-api.js"
+ *     type="text/javascript">
+ *   </script>
+ *
+ * SCRIPT PARAMETERS
+ * This script auto-magically figures out locale and has defaults for other parameters
+ * To set parameters explicity, set js global variable Timeline_parameters or include as
+ * parameters on the url using GET style. Eg the two next lines pass the same parameters:
+ *     Timeline_parameters='bundle=true';                    // pass parameter via js variable
+ *     <script src="http://....timeline-api.js?bundle=true"  // pass parameter via url
+ *
+ * Parameters
+ *   timeline-use-local-resources --
+ *   bundle -- true: use the single js bundle file; false: load individual files (for debugging)
+ *   locales --
+ *   defaultLocale --
+ *   forceLocale -- force locale to be a particular value--used for debugging. Normally locale is determined
+ *                  by browser's and server's locale settings.
+ *
+ * DEBUGGING
+ * If you have a problem with Timeline, the first step is to use the unbundled Javascript files. To do so:
+ * To use the unbundled Timeline and Ajax libraries
+ * Change
+ *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=true" type="text/javascript"></script>
+ * To
+ *   <script>var Timeline_ajax_url = "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js?bundle=false"</script>
+ *   <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=false" type="text/javascript"></script>
+ *
+ * Note that the Ajax version is usually NOT the same as the Timeline version.
+ * See variable simile_ajax_ver below for the current version
+ *
+ *==================================================
+ */
+
+(function() {
+
+    var simile_ajax_ver = "2.2.1"; // ===========>>>  current Simile-Ajax version
+
+    var useLocalResources = false;
+    if (document.location.search.length > 0) {
+        var params = document.location.search.substr(1).split("&");
+        for (var i = 0; i < params.length; i++) {
+            if (params[i] == "timeline-use-local-resources") {
+                useLocalResources = true;
+            }
+        }
+    };
+
+    var loadMe = function() {
+        if ("Timeline" in window) {
+            return;
+        }
+
+        window.Timeline = new Object();
+        window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility
+
+        var bundle = false;
+        var javascriptFiles = [
+            "timeline.js",
+            "band.js",
+            "themes.js",
+            "ethers.js",
+            "ether-painters.js",
+            "event-utils.js",
+            "labellers.js",
+            "sources.js",
+            "original-painter.js",
+            "detailed-painter.js",
+            "overview-painter.js",
+            "compact-painter.js",
+            "decorators.js",
+            "units.js"
+        ];
+        var cssFiles = [
+            "timeline.css",
+            "ethers.css",
+            "events.css"
+        ];
+
+        var localizedJavascriptFiles = [
+            "timeline.js",
+            "labellers.js"
+        ];
+        var localizedCssFiles = [
+        ];
+
+        // ISO-639 language codes, ISO-3166 country codes (2 characters)
+        var supportedLocales = [
+            "cs",       // Czech
+            "de",       // German
+            "en",       // English
+            "es",       // Spanish
+            "fr",       // French
+            "it",       // Italian
+            "nl",       // Dutch (The Netherlands)
+            "ru",       // Russian
+            "se",       // Swedish
+            "tr",       // Turkish
+            "vi",       // Vietnamese
+            "zh"        // Chinese
+        ];
+
+        try {
+            var desiredLocales = [ "en" ],
+                defaultServerLocale = "en",
+                forceLocale = null;
+
+            var parseURLParameters = function(parameters) {
+                var params = parameters.split("&");
+                for (var p = 0; p < params.length; p++) {
+                    var pair = params[p].split("=");
+                    if (pair[0] == "locales") {
+                        desiredLocales = desiredLocales.concat(pair[1].split(","));
+                    } else if (pair[0] == "defaultLocale") {
+                        defaultServerLocale = pair[1];
+                    } else if (pair[0] == "forceLocale") {
+                        forceLocale = pair[1];
+                        desiredLocales = desiredLocales.concat(pair[1].split(","));
+                    } else if (pair[0] == "bundle") {
+                        bundle = pair[1] != "false";
+                    }
+                }
+            };
+
+            (function() {
+                if (typeof Timeline_urlPrefix == "string") {
+                    Timeline.urlPrefix = Timeline_urlPrefix;
+                    if (typeof Timeline_parameters == "string") {
+                        parseURLParameters(Timeline_parameters);
+                    }
+                } else {
+                    var heads = document.documentElement.getElementsByTagName("head");
+                    for (var h = 0; h < heads.length; h++) {
+                        var scripts = heads[h].getElementsByTagName("script");
+                        for (var s = 0; s < scripts.length; s++) {
+                            var url = scripts[s].src;
+                            var i = url.indexOf("timeline-api.js");
+                            if (i >= 0) {
+                                Timeline.urlPrefix = url.substr(0, i);
+                                var q = url.indexOf("?");
+                                if (q > 0) {
+                                    parseURLParameters(url.substr(q + 1));
+                                }
+                                return;
+                            }
+                        }
+                    }
+                    throw new Error("Failed to derive URL prefix for Timeline API code files");
+                }
+            })();
+
+            var includeJavascriptFiles = function(urlPrefix, filenames) {
+                SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames);
+            }
+            var includeCssFiles = function(urlPrefix, filenames) {
+                SimileAjax.includeCssFiles(document, urlPrefix, filenames);
+            }
+
+            /*
+             *  Include non-localized files
+             */
+            if (bundle) {
+                includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]);
+                includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]);
+            } else {
+                includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
+                includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
+            }
+
+            /*
+             *  Include localized files
+             */
+            var loadLocale = [];
+            loadLocale[defaultServerLocale] = true;
+
+            var tryExactLocale = function(locale) {
+                for (var l = 0; l < supportedLocales.length; l++) {
+                    if (locale == supportedLocales[l]) {
+                        loadLocale[locale] = true;
+                        return true;
+                    }
+                }
+                return false;
+            }
+            var tryLocale = function(locale) {
+                if (tryExactLocale(locale)) {
+                    return locale;
+                }
+
+                var dash = locale.indexOf("-");
+                if (dash > 0 && tryExactLocale(locale.substr(0, dash))) {
+                    return locale.substr(0, dash);
+                }
+
+                return null;
+            }
+
+            for (var l = 0; l < desiredLocales.length; l++) {
+                tryLocale(desiredLocales[l]);
+            }
+
+            var defaultClientLocale = defaultServerLocale;
+            var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";");
+            for (var l = 0; l < defaultClientLocales.length; l++) {
+                var locale = tryLocale(defaultClientLocales[l]);
+                if (locale != null) {
+                    defaultClientLocale = locale;
+                    break;
+                }
+            }
+
+            for (var l = 0; l < supportedLocales.length; l++) {
+                var locale = supportedLocales[l];
+                if (loadLocale[locale]) {
+                    includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
+                    includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
+                }
+            }
+
+            if (forceLocale == null) {
+              Timeline.serverLocale = defaultServerLocale;
+              Timeline.clientLocale = defaultClientLocale;
+            } else {
+              Timeline.serverLocale = forceLocale;
+              Timeline.clientLocale = forceLocale;
+            }
+        } catch (e) {
+            alert(e);
+        }
+    };
+
+    /*
+     *  Load SimileAjax if it's not already loaded
+     */
+    if (typeof SimileAjax == "undefined") {
+        window.SimileAjax_onLoad = loadMe;
+
+        var url = useLocalResources ?
+            "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
+            "http://api.simile-widgets.org/ajax/" + simile_ajax_ver + "/simile-ajax-api.js";
+        if (typeof Timeline_ajax_url == "string") {
+           url = Timeline_ajax_url;
+        }
+        var createScriptElement = function() {
+            var script = document.createElement("script");
+            script.type = "text/javascript";
+            script.language = "JavaScript";
+            script.src = url;
+            document.getElementsByTagName("head")[0].appendChild(script);
+        }
+        if (document.body == null) {
+            try {
+                document.write("<script src='" + url + "' type='text/javascript'></script>");
+            } catch (e) {
+                createScriptElement();
+            }
+        } else {
+            createScriptElement();
+        }
+    } else {
+        loadMe();
+    }
 })();
-
-
-/* string.js */
-String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"");
-};
-String.prototype.startsWith=function(A){return this.length>=A.length&&this.substr(0,A.length)==A;
-};
-String.prototype.endsWith=function(A){return this.length>=A.length&&this.substr(this.length-A.length)==A;
-};
-String.substitute=function(B,D){var A="";
-var F=0;
-while(F<B.length-1){var C=B.indexOf("%",F);
-if(C<0||C==B.length-1){break;
-}else{if(C>F&&B.charAt(C-1)=="\\"){A+=B.substring(F,C-1)+"%";
-F=C+1;
-}else{var E=parseInt(B.charAt(C+1));
-if(isNaN(E)||E>=D.length){A+=B.substring(F,C+2);
-}else{A+=B.substring(F,C)+D[E].toString();
-}F=C+2;
-}}}if(F<B.length){A+=B.substring(F);
-}return A;
-};
-
-
-/* units.js */
-SimileAjax.NativeDateUnit=new Object();
-SimileAjax.NativeDateUnit.makeDefaultValue=function(){return new Date();
-};
-SimileAjax.NativeDateUnit.cloneValue=function(A){return new Date(A.getTime());
-};
-SimileAjax.NativeDateUnit.getParser=function(A){if(typeof A=="string"){A=A.toLowerCase();
-}return(A=="iso8601"||A=="iso 8601")?SimileAjax.DateTime.parseIso8601DateTime:SimileAjax.DateTime.parseGregorianDateTime;
-};
-SimileAjax.NativeDateUnit.parseFromObject=function(A){return SimileAjax.DateTime.parseGregorianDateTime(A);
-};
-SimileAjax.NativeDateUnit.toNumber=function(A){return A.getTime();
-};
-SimileAjax.NativeDateUnit.fromNumber=function(A){return new Date(A);
-};
-SimileAjax.NativeDateUnit.compare=function(D,C){var B,A;
-if(typeof D=="object"){B=D.getTime();
-}else{B=Number(D);
-}if(typeof C=="object"){A=C.getTime();
-}else{A=Number(C);
-}return B-A;
-};
-SimileAjax.NativeDateUnit.earlier=function(B,A){return SimileAjax.NativeDateUnit.compare(B,A)<0?B:A;
-};
-SimileAjax.NativeDateUnit.later=function(B,A){return SimileAjax.NativeDateUnit.compare(B,A)>0?B:A;
-};
-SimileAjax.NativeDateUnit.change=function(A,B){return new Date(A.getTime()+B);
-};
-
-
-/* window-manager.js */
-SimileAjax.WindowManager={_initialized:false,_listeners:[],_draggedElement:null,_draggedElementCallback:null,_dropTargetHighlightElement:null,_lastCoords:null,_ghostCoords:null,_draggingMode:"",_dragging:false,_layers:[]};
-SimileAjax.WindowManager.initialize=function(){if(SimileAjax.WindowManager._initialized){return ;
-}SimileAjax.DOM.registerEvent(document.body,"mousedown",SimileAjax.WindowManager._onBodyMouseDown);
-SimileAjax.DOM.registerEvent(document.body,"mousemove",SimileAjax.WindowManager._onBodyMouseMove);
-SimileAjax.DOM.registerEvent(document.body,"mouseup",SimileAjax.WindowManager._onBodyMouseUp);
-SimileAjax.DOM.registerEvent(document,"keydown",SimileAjax.WindowManager._onBodyKeyDown);
-SimileAjax.DOM.registerEvent(document,"keyup",SimileAjax.WindowManager._onBodyKeyUp);
-SimileAjax.WindowManager._layers.push({index:0});
-SimileAjax.WindowManager._historyListener={onBeforeUndoSeveral:function(){},onAfterUndoSeveral:function(){},onBeforeUndo:function(){},onAfterUndo:function(){},onBeforeRedoSeveral:function(){},onAfterRedoSeveral:function(){},onBeforeRedo:function(){},onAfterRedo:function(){}};
-SimileAjax.History.addListener(SimileAjax.WindowManager._historyListener);
-SimileAjax.WindowManager._initialized=true;
-};
-SimileAjax.WindowManager.getBaseLayer=function(){SimileAjax.WindowManager.initialize();
-return SimileAjax.WindowManager._layers[0];
-};
-SimileAjax.WindowManager.getHighestLayer=function(){SimileAjax.WindowManager.initialize();
-return SimileAjax.WindowManager._layers[SimileAjax.WindowManager._layers.length-1];
-};
-SimileAjax.WindowManager.registerEventWithObject=function(D,A,E,B,C){SimileAjax.WindowManager.registerEvent(D,A,function(G,F,H){return E[B].call(E,G,F,H);
-},C);
-};
-SimileAjax.WindowManager.registerEvent=function(D,B,E,C){if(C==null){C=SimileAjax.WindowManager.getHighestLayer();
-}var A=function(G,F,I){if(SimileAjax.WindowManager._canProcessEventAtLayer(C)){SimileAjax.WindowManager._popToLayer(C.index);
-try{E(G,F,I);
-}catch(H){SimileAjax.Debug.exception(H);
-}}SimileAjax.DOM.cancelEvent(F);
-return false;
-};
-SimileAjax.DOM.registerEvent(D,B,A);
-};
-SimileAjax.WindowManager.pushLayer=function(C,D,B){var A={onPop:C,index:SimileAjax.WindowManager._layers.length,ephemeral:(D),elmt:B};
-SimileAjax.WindowManager._layers.push(A);
-return A;
-};
-SimileAjax.WindowManager.popLayer=function(B){for(var A=1;
-A<SimileAjax.WindowManager._layers.length;
-A++){if(SimileAjax.WindowManager._layers[A]==B){SimileAjax.WindowManager._popToLayer(A-1);
-break;
-}}};
-SimileAjax.WindowManager.popAllLayers=function(){SimileAjax.WindowManager._popToLayer(0);
-};
-SimileAjax.WindowManager.registerForDragging=function(B,C,A){SimileAjax.WindowManager.registerEvent(B,"mousedown",function(E,D,F){SimileAjax.WindowManager._handleMouseDown(E,D,C);
-},A);
-};
-SimileAjax.WindowManager._popToLayer=function(C){while(C+1<SimileAjax.WindowManager._layers.length){try{var A=SimileAjax.WindowManager._layers.pop();
-if(A.onPop!=null){A.onPop();
-}}catch(B){}}};
-SimileAjax.WindowManager._canProcessEventAtLayer=function(B){if(B.index==(SimileAjax.WindowManager._layers.length-1)){return true;
-}for(var A=B.index+1;
-A<SimileAjax.WindowManager._layers.length;
-A++){if(!SimileAjax.WindowManager._layers[A].ephemeral){return false;
-}}return true;
-};
-SimileAjax.WindowManager.cancelPopups=function(A){var F=(A)?SimileAjax.DOM.getEventPageCoordinates(A):{x:-1,y:-1};
-var E=SimileAjax.WindowManager._layers.length-1;
-while(E>0&&SimileAjax.WindowManager._layers[E].ephemeral){var D=SimileAjax.WindowManager._layers[E];
-if(D.elmt!=null){var C=D.elmt;
-var B=SimileAjax.DOM.getPageCoordinates(C);
-if(F.x>=B.left&&F.x<(B.left+C.offsetWidth)&&F.y>=B.top&&F.y<(B.top+C.offsetHeight)){break;
-}}E--;
-}SimileAjax.WindowManager._popToLayer(E);
-};
-SimileAjax.WindowManager._onBodyMouseDown=function(B,A,C){if(!("eventPhase" in A)||A.eventPhase==A.BUBBLING_PHASE){SimileAjax.WindowManager.cancelPopups(A);
-}};
-SimileAjax.WindowManager._handleMouseDown=function(B,A,C){SimileAjax.WindowManager._draggedElement=B;
-SimileAjax.WindowManager._draggedElementCallback=C;
-SimileAjax.WindowManager._lastCoords={x:A.clientX,y:A.clientY};
-SimileAjax.DOM.cancelEvent(A);
-return false;
-};
-SimileAjax.WindowManager._onBodyKeyDown=function(C,A,D){if(SimileAjax.WindowManager._dragging){if(A.keyCode==27){SimileAjax.WindowManager._cancelDragging();
-}else{if((A.keyCode==17||A.keyCode==16)&&SimileAjax.WindowManager._draggingMode!="copy"){SimileAjax.WindowManager._draggingMode="copy";
-var B=SimileAjax.Graphics.createTranslucentImage(SimileAjax.urlPrefix+"data/timeline/copy.png");
-B.style.position="absolute";
-B.style.left=(SimileAjax.WindowManager._ghostCoords.left-16)+"px";
-B.style.top=(SimileAjax.WindowManager._ghostCoords.top)+"px";
-document.body.appendChild(B);
-SimileAjax.WindowManager._draggingModeIndicatorElmt=B;
-}}}};
-SimileAjax.WindowManager._onBodyKeyUp=function(B,A,C){if(SimileAjax.WindowManager._dragging){if(A.keyCode==17||A.keyCode==16){SimileAjax.WindowManager._draggingMode="";
-if(SimileAjax.WindowManager._draggingModeIndicatorElmt!=null){document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
-SimileAjax.WindowManager._draggingModeIndicatorElmt=null;
-}}}};
-SimileAjax.WindowManager._onBodyMouseMove=function(A,N,H){if(SimileAjax.WindowManager._draggedElement!=null){var P=SimileAjax.WindowManager._draggedElementCallback;
-var E=SimileAjax.WindowManager._lastCoords;
-var M=N.clientX-E.x;
-var J=N.clientY-E.y;
-if(!SimileAjax.WindowManager._dragging){if(Math.abs(M)>5||Math.abs(J)>5){try{if("onDragStart" in P){P.onDragStart();
-}if("ghost" in P&&P.ghost){var K=SimileAjax.WindowManager._draggedElement;
-SimileAjax.WindowManager._ghostCoords=SimileAjax.DOM.getPageCoordinates(K);
-SimileAjax.WindowManager._ghostCoords.left+=M;
-SimileAjax.WindowManager._ghostCoords.top+=J;
-var O=K.cloneNode(true);
-O.style.position="absolute";
-O.style.left=SimileAjax.WindowManager._ghostCoords.left+"px";
-O.style.top=SimileAjax.WindowManager._ghostCoords.top+"px";
-O.style.zIndex=1000;
-SimileAjax.Graphics.setOpacity(O,50);
-document.body.appendChild(O);
-P._ghostElmt=O;
-}SimileAjax.WindowManager._dragging=true;
-SimileAjax.WindowManager._lastCoords={x:N.clientX,y:N.clientY};
-document.body.focus();
-}catch(G){SimileAjax.Debug.exception("WindowManager: Error handling mouse down",G);
-SimileAjax.WindowManager._cancelDragging();
-}}}else{try{SimileAjax.WindowManager._lastCoords={x:N.clientX,y:N.clientY};
-if("onDragBy" in P){P.onDragBy(M,J);
-}if("_ghostElmt" in P){var O=P._ghostElmt;
-SimileAjax.WindowManager._ghostCoords.left+=M;
-SimileAjax.WindowManager._ghostCoords.top+=J;
-O.style.left=SimileAjax.WindowManager._ghostCoords.left+"px";
-O.style.top=SimileAjax.WindowManager._ghostCoords.top+"px";
-if(SimileAjax.WindowManager._draggingModeIndicatorElmt!=null){var I=SimileAjax.WindowManager._draggingModeIndicatorElmt;
-I.style.left=(SimileAjax.WindowManager._ghostCoords.left-16)+"px";
-I.style.top=SimileAjax.WindowManager._ghostCoords.top+"px";
-}if("droppable" in P&&P.droppable){var L=SimileAjax.DOM.getEventPageCoordinates(N);
-var H=SimileAjax.DOM.hittest(L.x,L.y,[SimileAjax.WindowManager._ghostElmt,SimileAjax.WindowManager._dropTargetHighlightElement]);
-H=SimileAjax.WindowManager._findDropTarget(H);
-if(H!=SimileAjax.WindowManager._potentialDropTarget){if(SimileAjax.WindowManager._dropTargetHighlightElement!=null){document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
-SimileAjax.WindowManager._dropTargetHighlightElement=null;
-SimileAjax.WindowManager._potentialDropTarget=null;
-}var F=false;
-if(H!=null){if((!("canDropOn" in P)||P.canDropOn(H))&&(!("canDrop" in H)||H.canDrop(SimileAjax.WindowManager._draggedElement))){F=true;
-}}if(F){var C=4;
-var D=SimileAjax.DOM.getPageCoordinates(H);
-var B=document.createElement("div");
-B.style.border=C+"px solid yellow";
-B.style.backgroundColor="yellow";
-B.style.position="absolute";
-B.style.left=D.left+"px";
-B.style.top=D.top+"px";
-B.style.width=(H.offsetWidth-C*2)+"px";
-B.style.height=(H.offsetHeight-C*2)+"px";
-SimileAjax.Graphics.setOpacity(B,30);
-document.body.appendChild(B);
-SimileAjax.WindowManager._potentialDropTarget=H;
-SimileAjax.WindowManager._dropTargetHighlightElement=B;
-}}}}}catch(G){SimileAjax.Debug.exception("WindowManager: Error handling mouse move",G);
-SimileAjax.WindowManager._cancelDragging();
-}}SimileAjax.DOM.cancelEvent(N);
-return false;
-}};
-SimileAjax.WindowManager._onBodyMouseUp=function(B,A,C){if(SimileAjax.WindowManager._draggedElement!=null){try{if(SimileAjax.WindowManager._dragging){var E=SimileAjax.WindowManager._draggedElementCallback;
-if("onDragEnd" in E){E.onDragEnd();
-}if("droppable" in E&&E.droppable){var D=false;
-var C=SimileAjax.WindowManager._potentialDropTarget;
-if(C!=null){if((!("canDropOn" in E)||E.canDropOn(C))&&(!("canDrop" in C)||C.canDrop(SimileAjax.WindowManager._draggedElement))){if("onDropOn" in E){E.onDropOn(C);
-}C.ondrop(SimileAjax.WindowManager._draggedElement,SimileAjax.WindowManager._draggingMode);
-D=true;
-}}if(!D){}}}}finally{SimileAjax.WindowManager._cancelDragging();
-}SimileAjax.DOM.cancelEvent(A);
-return false;
-}};
-SimileAjax.WindowManager._cancelDragging=function(){var B=SimileAjax.WindowManager._draggedElementCallback;
-if("_ghostElmt" in B){var A=B._ghostElmt;
-document.body.removeChild(A);
-delete B._ghostElmt;
-}if(SimileAjax.WindowManager._dropTargetHighlightElement!=null){document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
-SimileAjax.WindowManager._dropTargetHighlightElement=null;
-}if(SimileAjax.WindowManager._draggingModeIndicatorElmt!=null){document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
-SimileAjax.WindowManager._draggingModeIndicatorElmt=null;
-}SimileAjax.WindowManager._draggedElement=null;
-SimileAjax.WindowManager._draggedElementCallback=null;
-SimileAjax.WindowManager._potentialDropTarget=null;
-SimileAjax.WindowManager._dropTargetHighlightElement=null;
-SimileAjax.WindowManager._lastCoords=null;
-SimileAjax.WindowManager._ghostCoords=null;
-SimileAjax.WindowManager._draggingMode="";
-SimileAjax.WindowManager._dragging=false;
-};
-SimileAjax.WindowManager._findDropTarget=function(A){while(A!=null){if("ondrop" in A&&(typeof A.ondrop)=="function"){break;
-}A=A.parentNode;
-}return A;
-};
-
-
-/* xmlhttp.js */
-SimileAjax.XmlHttp=new Object();
-SimileAjax.XmlHttp._onReadyStateChange=function(A,D,B){switch(A.readyState){case 4:try{if(A.status==0||A.status==200){if(B){B(A);
-}}else{if(D){D(A.statusText,A.status,A);
-}}}catch(C){SimileAjax.Debug.exception("XmlHttp: Error handling onReadyStateChange",C);
-}break;
-}};
-SimileAjax.XmlHttp._createRequest=function(){if(SimileAjax.Platform.browser.isIE){var A=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"];
-for(var B=0;
-B<A.length;
-B++){try{var C=A[B];
-var D=function(){return new ActiveXObject(C);
-};
-var F=D();
-SimileAjax.XmlHttp._createRequest=D;
-return F;
-}catch(E){}}}try{var D=function(){return new XMLHttpRequest();
-};
-var F=D();
-SimileAjax.XmlHttp._createRequest=D;
-return F;
-}catch(E){throw new Error("Failed to create an XMLHttpRequest object");
-}};
-SimileAjax.XmlHttp.get=function(A,D,C){var B=SimileAjax.XmlHttp._createRequest();
-B.open("GET",A,true);
-B.onreadystatechange=function(){SimileAjax.XmlHttp._onReadyStateChange(B,D,C);
-};
-B.send(null);
-};
-SimileAjax.XmlHttp.post=function(B,A,E,D){var C=SimileAjax.XmlHttp._createRequest();
-C.open("POST",B,true);
-C.onreadystatechange=function(){SimileAjax.XmlHttp._onReadyStateChange(C,E,D);
-};
-C.send(A);
-};
-SimileAjax.XmlHttp._forceXML=function(A){try{A.overrideMimeType("text/xml");
-}catch(B){A.setrequestheader("Content-Type","text/xml");
-}};
-
-/******** end of simile-ajax-bundle.js ********/
-
-/******** start of simile-timeline-bundle.js ********/
-
-/* band.js */
-Timeline._Band=function(B,C,A){if(B!==undefined){this.initialize(B,C,A);
-}};
-Timeline._Band.prototype.initialize=function(F,G,B){if(F.autoWidth&&typeof G.width=="string"){G.width=G.width.indexOf("%")>-1?0:parseInt(G.width);
-}this._timeline=F;
-this._bandInfo=G;
-this._index=B;
-this._locale=("locale" in G)?G.locale:Timeline.getDefaultLocale();
-this._timeZone=("timeZone" in G)?G.timeZone:0;
-this._labeller=("labeller" in G)?G.labeller:(("createLabeller" in F.getUnit())?F.getUnit().createLabeller(this._locale,this._timeZone):new Timeline.GregorianDateLabeller(this._locale,this._timeZone));
-this._theme=G.theme;
-this._zoomIndex=("zoomIndex" in G)?G.zoomIndex:0;
-this._zoomSteps=("zoomSteps" in G)?G.zoomSteps:null;
-this._dragging=false;
-this._changing=false;
-this._originalScrollSpeed=5;
-this._scrollSpeed=this._originalScrollSpeed;
-this._onScrollListeners=[];
-var A=this;
-this._syncWithBand=null;
-this._syncWithBandHandler=function(H){A._onHighlightBandScroll();
-};
-this._selectorListener=function(H){A._onHighlightBandScroll();
-};
-var D=this._timeline.getDocument().createElement("div");
-D.className="timeline-band-input";
-this._timeline.addDiv(D);
-this._keyboardInput=document.createElement("input");
-this._keyboardInput.type="text";
-D.appendChild(this._keyboardInput);
-SimileAjax.DOM.registerEventWithObject(this._keyboardInput,"keydown",this,"_onKeyDown");
-SimileAjax.DOM.registerEventWithObject(this._keyboardInput,"keyup",this,"_onKeyUp");
-this._div=this._timeline.getDocument().createElement("div");
-this._div.id="timeline-band-"+B;
-this._div.className="timeline-band timeline-band-"+B;
-this._timeline.addDiv(this._div);
-SimileAjax.DOM.registerEventWithObject(this._div,"mousedown",this,"_onMouseDown");
-SimileAjax.DOM.registerEventWithObject(this._div,"mousemove",this,"_onMouseMove");
-SimileAjax.DOM.registerEventWithObject(this._div,"mouseup",this,"_onMouseUp");
-SimileAjax.DOM.registerEventWithObject(this._div,"mouseout",this,"_onMouseOut");
-SimileAjax.DOM.registerEventWithObject(this._div,"dblclick",this,"_onDblClick");
-var E=this._theme!=null?this._theme.mouseWheel:"scroll";
-if(E==="zoom"||E==="scroll"||this._zoomSteps){if(SimileAjax.Platform.browser.isFirefox){SimileAjax.DOM.registerEventWithObject(this._div,"DOMMouseScroll",this,"_onMouseScroll");
-}else{SimileAjax.DOM.registerEventWithObject(this._div,"mousewheel",this,"_onMouseScroll");
-}}this._innerDiv=this._timeline.getDocument().createElement("div");
-this._innerDiv.className="timeline-band-inner";
-this._div.appendChild(this._innerDiv);
-this._ether=G.ether;
-G.ether.initialize(this,F);
-this._etherPainter=G.etherPainter;
-G.etherPainter.initialize(this,F);
-this._eventSource=G.eventSource;
-if(this._eventSource){this._eventListener={onAddMany:function(){A._onAddMany();
-},onClear:function(){A._onClear();
-}};
-this._eventSource.addListener(this._eventListener);
-}this._eventPainter=G.eventPainter;
-this._eventTracksNeeded=0;
-this._eventTrackIncrement=0;
-G.eventPainter.initialize(this,F);
-this._decorators=("decorators" in G)?G.decorators:[];
-for(var C=0;
-C<this._decorators.length;
-C++){this._decorators[C].initialize(this,F);
-}};
-Timeline._Band.SCROLL_MULTIPLES=5;
-Timeline._Band.prototype.dispose=function(){this.closeBubble();
-if(this._eventSource){this._eventSource.removeListener(this._eventListener);
-this._eventListener=null;
-this._eventSource=null;
-}this._timeline=null;
-this._bandInfo=null;
-this._labeller=null;
-this._ether=null;
-this._etherPainter=null;
-this._eventPainter=null;
-this._decorators=null;
-this._onScrollListeners=null;
-this._syncWithBandHandler=null;
-this._selectorListener=null;
-this._div=null;
-this._innerDiv=null;
-this._keyboardInput=null;
-};
-Timeline._Band.prototype.addOnScrollListener=function(A){this._onScrollListeners.push(A);
-};
-Timeline._Band.prototype.removeOnScrollListener=function(B){for(var A=0;
-A<this._onScrollListeners.length;
-A++){if(this._onScrollListeners[A]==B){this._onScrollListeners.splice(A,1);
-break;
-}}};
-Timeline._Band.prototype.setSyncWithBand=function(B,A){if(this._syncWithBand){this._syncWithBand.removeOnScrollListener(this._syncWithBandHandler);
-}this._syncWithBand=B;
-this._syncWithBand.addOnScrollListener(this._syncWithBandHandler);
-this._highlight=A;
-this._positionHighlight();
-};
-Timeline._Band.prototype.getLocale=function(){return this._locale;
-};
-Timeline._Band.prototype.getTimeZone=function(){return this._timeZone;
-};
-Timeline._Band.prototype.getLabeller=function(){return this._labeller;
-};
-Timeline._Band.prototype.getIndex=function(){return this._index;
-};
-Timeline._Band.prototype.getEther=function(){return this._ether;
-};
-Timeline._Band.prototype.getEtherPainter=function(){return this._etherPainter;
-};
-Timeline._Band.prototype.getEventSource=function(){return this._eventSource;
-};
-Timeline._Band.prototype.getEventPainter=function(){return this._eventPainter;
-};
-Timeline._Band.prototype.getTimeline=function(){return this._timeline;
-};
-Timeline._Band.prototype.updateEventTrackInfo=function(B,A){this._eventTrackIncrement=A;
-if(B>this._eventTracksNeeded){this._eventTracksNeeded=B;
-}};
-Timeline._Band.prototype.checkAutoWidth=function(){if(!this._timeline.autoWidth){return ;
-}var A=this._eventPainter.getType()=="overview";
-var C=A?this._theme.event.overviewTrack.autoWidthMargin:this._theme.event.track.autoWidthMargin;
-var B=Math.ceil((this._eventTracksNeeded+C)*this._eventTrackIncrement);
-B+=A?this._theme.event.overviewTrack.offset:this._theme.event.track.offset;
-var D=this._bandInfo;
-if(B!=D.width){D.width=B;
-}};
-Timeline._Band.prototype.layout=function(){this.paint();
-};
-Timeline._Band.prototype.paint=function(){this._etherPainter.paint();
-this._paintDecorators();
-this._paintEvents();
-};
-Timeline._Band.prototype.softLayout=function(){this.softPaint();
-};
-Timeline._Band.prototype.softPaint=function(){this._etherPainter.softPaint();
-this._softPaintDecorators();
-this._softPaintEvents();
-};
-Timeline._Band.prototype.setBandShiftAndWidth=function(A,D){var C=this._keyboardInput.parentNode;
-var B=A+Math.floor(D/2);
-if(this._timeline.isHorizontal()){this._div.style.top=A+"px";
-this._div.style.height=D+"px";
-C.style.top=B+"px";
-C.style.left="-1em";
-}else{this._div.style.left=A+"px";
-this._div.style.width=D+"px";
-C.style.left=B+"px";
-C.style.top="-1em";
-}};
-Timeline._Band.prototype.getViewWidth=function(){if(this._timeline.isHorizontal()){return this._div.offsetHeight;
-}else{return this._div.offsetWidth;
-}};
-Timeline._Band.prototype.setViewLength=function(A){this._viewLength=A;
-this._recenterDiv();
-this._onChanging();
-};
-Timeline._Band.prototype.getViewLength=function(){return this._viewLength;
-};
-Timeline._Band.prototype.getTotalViewLength=function(){return Timeline._Band.SCROLL_MULTIPLES*this._viewLength;
-};
-Timeline._Band.prototype.getViewOffset=function(){return this._viewOffset;
-};
-Timeline._Band.prototype.getMinDate=function(){return this._ether.pixelOffsetToDate(this._viewOffset);
-};
-Timeline._Band.prototype.getMaxDate=function(){return this._ether.pixelOffsetToDate(this._viewOffset+Timeline._Band.SCROLL_MULTIPLES*this._viewLength);
-};
-Timeline._Band.prototype.getMinVisibleDate=function(){return this._ether.pixelOffsetToDate(0);
-};
-Timeline._Band.prototype.getMaxVisibleDate=function(){return this._ether.pixelOffsetToDate(this._viewLength);
-};
-Timeline._Band.prototype.getCenterVisibleDate=function(){return this._ether.pixelOffsetToDate(this._viewLength/2);
-};
-Timeline._Band.prototype.setMinVisibleDate=function(A){if(!this._changing){this._moveEther(Math.round(-this._ether.dateToPixelOffset(A)));
-}};
-Timeline._Band.prototype.setMaxVisibleDate=function(A){if(!this._changing){this._moveEther(Math.round(this._viewLength-this._ether.dateToPixelOffset(A)));
-}};
-Timeline._Band.prototype.setCenterVisibleDate=function(A){if(!this._changing){this._moveEther(Math.round(this._viewLength/2-this._ether.dateToPixelOffset(A)));
-}};
-Timeline._Band.prototype.dateToPixelOffset=function(A){return this._ether.dateToPixelOffset(A)-this._viewOffset;
-};
-Timeline._Band.prototype.pixelOffsetToDate=function(A){return this._ether.pixelOffsetToDate(A+this._viewOffset);
-};
-Timeline._Band.prototype.createLayerDiv=function(D,B){var C=this._timeline.getDocument().createElement("div");
-C.className="timeline-band-layer"+(typeof B=="string"?(" "+B):"");
-C.style.zIndex=D;
-this._innerDiv.appendChild(C);
-var A=this._timeline.getDocument().createElement("div");
-A.className="timeline-band-layer-inner";
-if(SimileAjax.Platform.browser.isIE){A.style.cursor="move";
-}else{A.style.cursor="-moz-grab";
-}C.appendChild(A);
-return A;
-};
-Timeline._Band.prototype.removeLayerDiv=function(A){this._innerDiv.removeChild(A.parentNode);
-};
-Timeline._Band.prototype.scrollToCenter=function(B,C){var A=this._ether.dateToPixelOffset(B);
-if(A<-this._viewLength/2){this.setCenterVisibleDate(this.pixelOffsetToDate(A+this._viewLength));
-}else{if(A>3*this._viewLength/2){this.setCenterVisibleDate(this.pixelOffsetToDate(A-this._viewLength));
-}}this._autoScroll(Math.round(this._viewLength/2-this._ether.dateToPixelOffset(B)),C);
-};
-Timeline._Band.prototype.showBubbleForEvent=function(C){var A=this.getEventSource().getEvent(C);
-if(A){var B=this;
-this.scrollToCenter(A.getStart(),function(){B._eventPainter.showBubble(A);
-});
-}};
-Timeline._Band.prototype.zoom=function(F,A,E,C){if(!this._zoomSteps){return ;
-}A+=this._viewOffset;
-var D=this._ether.pixelOffsetToDate(A);
-var B=this._ether.zoom(F);
-this._etherPainter.zoom(B);
-this._moveEther(Math.round(-this._ether.dateToPixelOffset(D)));
-this._moveEther(A);
-};
-Timeline._Band.prototype._onMouseDown=function(B,A,C){this.closeBubble();
-this._dragging=true;
-this._dragX=A.clientX;
-this._dragY=A.clientY;
-};
-Timeline._Band.prototype._onMouseMove=function(D,A,E){if(this._dragging){var C=A.clientX-this._dragX;
-var B=A.clientY-this._dragY;
-this._dragX=A.clientX;
-this._dragY=A.clientY;
-this._moveEther(this._timeline.isHorizontal()?C:B);
-this._positionHighlight();
-}};
-Timeline._Band.prototype._onMouseUp=function(B,A,C){this._dragging=false;
-this._keyboardInput.focus();
-};
-Timeline._Band.prototype._onMouseOut=function(B,A,D){var C=SimileAjax.DOM.getEventRelativeCoordinates(A,B);
-C.x+=this._viewOffset;
-if(C.x<0||C.x>B.offsetWidth||C.y<0||C.y>B.offsetHeight){this._dragging=false;
-}};
-Timeline._Band.prototype._onMouseScroll=function(G,I,E){var A=new Date();
-A=A.getTime();
-if(!this._lastScrollTime||((A-this._lastScrollTime)>50)){this._lastScrollTime=A;
-var H=0;
-if(I.wheelDelta){H=I.wheelDelta/120;
-}else{if(I.detail){H=-I.detail/3;
-}}var F=this._theme.mouseWheel;
-if(this._zoomSteps||F==="zoom"){var D=SimileAjax.DOM.getEventRelativeCoordinates(I,G);
-if(H!=0){var C;
-if(H>0){C=true;
-}if(H<0){C=false;
-}this._timeline.zoom(C,D.x,D.y,G);
-}}else{if(F==="scroll"){var B=50*(H<0?-1:1);
-this._moveEther(B);
-}}}if(I.stopPropagation){I.stopPropagation();
-}I.cancelBubble=true;
-if(I.preventDefault){I.preventDefault();
-}I.returnValue=false;
-};
-Timeline._Band.prototype._onDblClick=function(B,A,D){var C=SimileAjax.DOM.getEventRelativeCoordinates(A,B);
-var E=C.x-(this._viewLength/2-this._viewOffset);
-this._autoScroll(-E);
-};
-Timeline._Band.prototype._onKeyDown=function(B,A,C){if(!this._dragging){switch(A.keyCode){case 27:break;
-case 37:case 38:this._scrollSpeed=Math.min(50,Math.abs(this._scrollSpeed*1.05));
-this._moveEther(this._scrollSpeed);
-break;
-case 39:case 40:this._scrollSpeed=-Math.min(50,Math.abs(this._scrollSpeed*1.05));
-this._moveEther(this._scrollSpeed);
-break;
-default:return true;
-}this.closeBubble();
-SimileAjax.DOM.cancelEvent(A);
-return false;
-}return true;
-};
-Timeline._Band.prototype._onKeyUp=function(B,A,C){if(!this._dragging){this._scrollSpeed=this._originalScrollSpeed;
-switch(A.keyCode){case 35:this.setCenterVisibleDate(this._eventSource.getLatestDate());
-break;
-case 36:this.setCenterVisibleDate(this._eventSource.getEarliestDate());
-break;
-case 33:this._autoScroll(this._timeline.getPixelLength());
-break;
-case 34:this._autoScroll(-this._timeline.getPixelLength());
-break;
-default:return true;
-}this.closeBubble();
-SimileAjax.DOM.cancelEvent(A);
-return false;
-}return true;
-};
-Timeline._Band.prototype._autoScroll=function(D,C){var A=this;
-var B=SimileAjax.Graphics.createAnimation(function(E,F){A._moveEther(F);
-},0,D,1000,C);
-B.run();
-};
-Timeline._Band.prototype._moveEther=function(A){this.closeBubble();
-this._viewOffset+=A;
-this._ether.shiftPixels(-A);
-if(this._timeline.isHorizontal()){this._div.style.left=this._viewOffset+"px";
-}else{this._div.style.top=this._viewOffset+"px";
-}if(this._viewOffset>-this._viewLength*0.5||this._viewOffset<-this._viewLength*(Timeline._Band.SCROLL_MULTIPLES-1.5)){this._recenterDiv();
-}else{this.softLayout();
-}this._onChanging();
-};
-Timeline._Band.prototype._onChanging=function(){this._changing=true;
-this._fireOnScroll();
-this._setSyncWithBandDate();
-this._changing=false;
-};
-Timeline._Band.prototype._fireOnScroll=function(){for(var A=0;
-A<this._onScrollListeners.length;
-A++){this._onScrollListeners[A](this);
-}};
-Timeline._Band.prototype._setSyncWithBandDate=function(){if(this._syncWithBand){var A=this._ether.pixelOffsetToDate(this.getViewLength()/2);
-this._syncWithBand.setCenterVisibleDate(A);
-}};
-Timeline._Band.prototype._onHighlightBandScroll=function(){if(this._syncWithBand){var A=this._syncWithBand.getCenterVisibleDate();
-var B=this._ether.dateToPixelOffset(A);
-this._moveEther(Math.round(this._viewLength/2-B));
-if(this._highlight){this._etherPainter.setHighlight(this._syncWithBand.getMinVisibleDate(),this._syncWithBand.getMaxVisibleDate());
-}}};
-Timeline._Band.prototype._onAddMany=function(){this._paintEvents();
-};
-Timeline._Band.prototype._onClear=function(){this._paintEvents();
-};
-Timeline._Band.prototype._positionHighlight=function(){if(this._syncWithBand){var A=this._syncWithBand.getMinVisibleDate();
-var B=this._syncWithBand.getMaxVisibleDate();
-if(this._highlight){this._etherPainter.setHighlight(A,B);
-}}};
-Timeline._Band.prototype._recenterDiv=function(){this._viewOffset=-this._viewLength*(Timeline._Band.SCROLL_MULTIPLES-1)/2;
-if(this._timeline.isHorizontal()){this._div.style.left=this._viewOffset+"px";
-this._div.style.width=(Timeline._Band.SCROLL_MULTIPLES*this._viewLength)+"px";
-}else{this._div.style.top=this._viewOffset+"px";
-this._div.style.height=(Timeline._Band.SCROLL_MULTIPLES*this._viewLength)+"px";
-}this.layout();
-};
-Timeline._Band.prototype._paintEvents=function(){this._eventPainter.paint();
-};
-Timeline._Band.prototype._softPaintEvents=function(){this._eventPainter.softPaint();
-};
-Timeline._Band.prototype._paintDecorators=function(){for(var A=0;
-A<this._decorators.length;
-A++){this._decorators[A].paint();
-}};
-Timeline._Band.prototype._softPaintDecorators=function(){for(var A=0;
-A<this._decorators.length;
-A++){this._decorators[A].softPaint();
-}};
-Timeline._Band.prototype.closeBubble=function(){SimileAjax.WindowManager.cancelPopups();
-};
-
-
-/* decorators.js */
-Timeline.SpanHighlightDecorator=function(A){this._unit=("unit" in A)?A.unit:SimileAjax.NativeDateUnit;
-this._startDate=(typeof A.startDate=="string")?this._unit.parseFromObject(A.startDate):A.startDate;
-this._endDate=(typeof A.endDate=="string")?this._unit.parseFromObject(A.endDate):A.endDate;
-this._startLabel=A.startLabel;
-this._endLabel=A.endLabel;
-this._color=A.color;
-this._cssClass=("cssClass" in A)?A.cssClass:null;
-this._opacity=("opacity" in A)?A.opacity:100;
-};
-Timeline.SpanHighlightDecorator.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._layerDiv=null;
-};
-Timeline.SpanHighlightDecorator.prototype.paint=function(){if(this._layerDiv!=null){this._band.removeLayerDiv(this._layerDiv);
-}this._layerDiv=this._band.createLayerDiv(10);
-this._layerDiv.setAttribute("name","span-highlight-decorator");
-this._layerDiv.style.display="none";
-var F=this._band.getMinDate();
-var C=this._band.getMaxDate();
-if(this._unit.compare(this._startDate,C)<0&&this._unit.compare(this._endDate,F)>0){F=this._unit.later(F,this._startDate);
-C=this._unit.earlier(C,this._endDate);
-var D=this._band.dateToPixelOffset(F);
-var K=this._band.dateToPixelOffset(C);
-var I=this._timeline.getDocument();
-var H=function(){var L=I.createElement("table");
-L.insertRow(0).insertCell(0);
-return L;
-};
-var B=I.createElement("div");
-B.className="timeline-highlight-decorator";
-if(this._cssClass){B.className+=" "+this._cssClass;
-}if(this._opacity<100){SimileAjax.Graphics.setOpacity(B,this._opacity);
-}this._layerDiv.appendChild(B);
-var J=H();
-J.className="timeline-highlight-label timeline-highlight-label-start";
-var G=J.rows[0].cells[0];
-G.innerHTML=this._startLabel;
-if(this._cssClass){G.className="label_"+this._cssClass;
-}this._layerDiv.appendChild(J);
-var A=H();
-A.className="timeline-highlight-label timeline-highlight-label-end";
-var E=A.rows[0].cells[0];
-E.innerHTML=this._endLabel;
-if(this._cssClass){E.className="label_"+this._cssClass;
-}this._layerDiv.appendChild(A);
-if(this._timeline.isHorizontal()){B.style.left=D+"px";
-B.style.width=(K-D)+"px";
-J.style.right=(this._band.getTotalViewLength()-D)+"px";
-J.style.width=(this._startLabel.length)+"em";
-A.style.left=K+"px";
-A.style.width=(this._endLabel.length)+"em";
-}else{B.style.top=D+"px";
-B.style.height=(K-D)+"px";
-J.style.bottom=D+"px";
-J.style.height="1.5px";
-A.style.top=K+"px";
-A.style.height="1.5px";
-}}this._layerDiv.style.display="block";
-};
-Timeline.SpanHighlightDecorator.prototype.softPaint=function(){};
-Timeline.PointHighlightDecorator=function(A){this._unit=("unit" in A)?A.unit:SimileAjax.NativeDateUnit;
-this._date=(typeof A.date=="string")?this._unit.parseFromObject(A.date):A.date;
-this._width=("width" in A)?A.width:10;
-this._color=A.color;
-this._cssClass=("cssClass" in A)?A.cssClass:"";
-this._opacity=("opacity" in A)?A.opacity:100;
-};
-Timeline.PointHighlightDecorator.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._layerDiv=null;
-};
-Timeline.PointHighlightDecorator.prototype.paint=function(){if(this._layerDiv!=null){this._band.removeLayerDiv(this._layerDiv);
-}this._layerDiv=this._band.createLayerDiv(10);
-this._layerDiv.setAttribute("name","span-highlight-decorator");
-this._layerDiv.style.display="none";
-var C=this._band.getMinDate();
-var E=this._band.getMaxDate();
-if(this._unit.compare(this._date,E)<0&&this._unit.compare(this._date,C)>0){var B=this._band.dateToPixelOffset(this._date);
-var A=B-Math.round(this._width/2);
-var D=this._timeline.getDocument();
-var F=D.createElement("div");
-F.className="timeline-highlight-point-decorator";
-F.className+=" "+this._cssClass;
-if(this._opacity<100){SimileAjax.Graphics.setOpacity(F,this._opacity);
-}this._layerDiv.appendChild(F);
-if(this._timeline.isHorizontal()){F.style.left=A+"px";
-}else{F.style.top=A+"px";
-}}this._layerDiv.style.display="block";
-};
-Timeline.PointHighlightDecorator.prototype.softPaint=function(){};
-
-
-/* detailed-painter.js */
-Timeline.DetailedEventPainter=function(A){this._params=A;
-this._onSelectListeners=[];
-this._filterMatcher=null;
-this._highlightMatcher=null;
-this._frc=null;
-this._eventIdToElmt={};
-};
-Timeline.DetailedEventPainter.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._backLayer=null;
-this._eventLayer=null;
-this._lineLayer=null;
-this._highlightLayer=null;
-this._eventIdToElmt=null;
-};
-Timeline.DetailedEventPainter.prototype.getType=function(){return"detailed";
-};
-Timeline.DetailedEventPainter.prototype.addOnSelectListener=function(A){this._onSelectListeners.push(A);
-};
-Timeline.DetailedEventPainter.prototype.removeOnSelectListener=function(B){for(var A=0;
-A<this._onSelectListeners.length;
-A++){if(this._onSelectListeners[A]==B){this._onSelectListeners.splice(A,1);
-break;
-}}};
-Timeline.DetailedEventPainter.prototype.getFilterMatcher=function(){return this._filterMatcher;
-};
-Timeline.DetailedEventPainter.prototype.setFilterMatcher=function(A){this._filterMatcher=A;
-};
-Timeline.DetailedEventPainter.prototype.getHighlightMatcher=function(){return this._highlightMatcher;
-};
-Timeline.DetailedEventPainter.prototype.setHighlightMatcher=function(A){this._highlightMatcher=A;
-};
-Timeline.DetailedEventPainter.prototype.paint=function(){var B=this._band.getEventSource();
-if(B==null){return ;
-}this._eventIdToElmt={};
-this._prepareForPainting();
-var I=this._params.theme.event;
-var G=Math.max(I.track.height,this._frc.getLineHeight());
-var F={trackOffset:Math.round(this._band.getViewWidth()/2-G/2),trackHeight:G,trackGap:I.track.gap,trackIncrement:G+I.track.gap,icon:I.instant.icon,iconWidth:I.instant.iconWidth,iconHeight:I.instant.iconHeight,labelWidth:I.label.width};
-var C=this._band.getMinDate();
-var A=this._band.getMaxDate();
-var J=(this._filterMatcher!=null)?this._filterMatcher:function(K){return true;
-};
-var E=(this._highlightMatcher!=null)?this._highlightMatcher:function(K){return -1;
-};
-var D=B.getEventReverseIterator(C,A);
-while(D.hasNext()){var H=D.next();
-if(J(H)){this.paintEvent(H,F,this._params.theme,E(H));
-}}this._highlightLayer.style.display="block";
-this._lineLayer.style.display="block";
-this._eventLayer.style.display="block";
-this._band.updateEventTrackInfo(this._lowerTracks.length+this._upperTracks.length,F.trackIncrement);
-};
-Timeline.DetailedEventPainter.prototype.softPaint=function(){};
-Timeline.DetailedEventPainter.prototype._prepareForPainting=function(){var B=this._band;
-if(this._backLayer==null){this._backLayer=this._band.createLayerDiv(0,"timeline-band-events");
-this._backLayer.style.visibility="hidden";
-var A=document.createElement("span");
-A.className="timeline-event-label";
-this._backLayer.appendChild(A);
-this._frc=SimileAjax.Graphics.getFontRenderingContext(A);
-}this._frc.update();
-this._lowerTracks=[];
-this._upperTracks=[];
-if(this._highlightLayer!=null){B.removeLayerDiv(this._highlightLayer);
-}this._highlightLayer=B.createLayerDiv(105,"timeline-band-highlights");
-this._highlightLayer.style.display="none";
-if(this._lineLayer!=null){B.removeLayerDiv(this._lineLayer);
-}this._lineLayer=B.createLayerDiv(110,"timeline-band-lines");
-this._lineLayer.style.display="none";
-if(this._eventLayer!=null){B.removeLayerDiv(this._eventLayer);
-}this._eventLayer=B.createLayerDiv(110,"timeline-band-events");
-this._eventLayer.style.display="none";
-};
-Timeline.DetailedEventPainter.prototype.paintEvent=function(B,C,D,A){if(B.isInstant()){this.paintInstantEvent(B,C,D,A);
-}else{this.paintDurationEvent(B,C,D,A);
-}};
-Timeline.DetailedEventPainter.prototype.paintInstantEvent=function(B,C,D,A){if(B.isImprecise()){this.paintImpreciseInstantEvent(B,C,D,A);
-}else{this.paintPreciseInstantEvent(B,C,D,A);
-}};
-Timeline.DetailedEventPainter.prototype.paintDurationEvent=function(B,C,D,A){if(B.isImprecise()){this.paintImpreciseDurationEvent(B,C,D,A);
-}else{this.paintPreciseDurationEvent(B,C,D,A);
-}};
-Timeline.DetailedEventPainter.prototype.paintPreciseInstantEvent=function(K,N,Q,O){var S=this._timeline.getDocument();
-var J=K.getText();
-var E=K.getStart();
-var C=Math.round(this._band.dateToPixelOffset(E));
-var A=Math.round(C+N.iconWidth/2);
-var I=Math.round(C-N.iconWidth/2);
-var G=this._frc.computeSize(J);
-var D=this._findFreeTrackForSolid(A,C);
-var B=this._paintEventIcon(K,D,I,N,Q);
-var T=A+Q.event.label.offsetFromLine;
-var P=D;
-var F=this._getTrackData(D);
-if(Math.min(F.solid,F.text)>=T+G.width){F.solid=I;
-F.text=T;
-}else{F.solid=I;
-T=C+Q.event.label.offsetFromLine;
-P=this._findFreeTrackForText(D,T+G.width,function(U){U.line=C-2;
-});
-this._getTrackData(P).text=I;
-this._paintEventLine(K,C,D,P,N,Q);
-}var R=Math.round(N.trackOffset+P*N.trackIncrement+N.trackHeight/2-G.height/2);
-var M=this._paintEventLabel(K,J,T,R,G.width,G.height,Q);
-var L=this;
-var H=function(U,V,W){return L._onClickInstantEvent(B.elmt,V,K);
-};
-SimileAjax.DOM.registerEvent(B.elmt,"mousedown",H);
-SimileAjax.DOM.registerEvent(M.elmt,"mousedown",H);
-this._createHighlightDiv(O,B,Q);
-this._eventIdToElmt[K.getID()]=B.elmt;
-};
-Timeline.DetailedEventPainter.prototype.paintImpreciseInstantEvent=function(N,Q,V,R){var X=this._timeline.getDocument();
-var M=N.getText();
-var H=N.getStart();
-var S=N.getEnd();
-var E=Math.round(this._band.dateToPixelOffset(H));
-var B=Math.round(this._band.dateToPixelOffset(S));
-var A=Math.round(E+Q.iconWidth/2);
-var L=Math.round(E-Q.iconWidth/2);
-var J=this._frc.computeSize(M);
-var F=this._findFreeTrackForSolid(B,E);
-var G=this._paintEventTape(N,F,E,B,V.event.instant.impreciseColor,V.event.instant.impreciseOpacity,Q,V);
-var C=this._paintEventIcon(N,F,L,Q,V);
-var I=this._getTrackData(F);
-I.solid=L;
-var W=A+V.event.label.offsetFromLine;
-var D=W+J.width;
-var T;
-if(D<B){T=F;
-}else{W=E+V.event.label.offsetFromLine;
-D=W+J.width;
-T=this._findFreeTrackForText(F,D,function(Y){Y.line=E-2;
-});
-this._getTrackData(T).text=L;
-this._paintEventLine(N,E,F,T,Q,V);
-}var U=Math.round(Q.trackOffset+T*Q.trackIncrement+Q.trackHeight/2-J.height/2);
-var P=this._paintEventLabel(N,M,W,U,J.width,J.height,V);
-var O=this;
-var K=function(Y,Z,a){return O._onClickInstantEvent(C.elmt,Z,N);
-};
-SimileAjax.DOM.registerEvent(C.elmt,"mousedown",K);
-SimileAjax.DOM.registerEvent(G.elmt,"mousedown",K);
-SimileAjax.DOM.registerEvent(P.elmt,"mousedown",K);
-this._createHighlightDiv(R,C,V);
-this._eventIdToElmt[N.getID()]=C.elmt;
-};
-Timeline.DetailedEventPainter.prototype.paintPreciseDurationEvent=function(J,M,S,O){var T=this._timeline.getDocument();
-var I=J.getText();
-var D=J.getStart();
-var P=J.getEnd();
-var B=Math.round(this._band.dateToPixelOffset(D));
-var A=Math.round(this._band.dateToPixelOffset(P));
-var F=this._frc.computeSize(I);
-var E=this._findFreeTrackForSolid(A);
-var N=J.getColor();
-N=N!=null?N:S.event.duration.color;
-var C=this._paintEventTape(J,E,B,A,N,100,M,S);
-var H=this._getTrackData(E);
-H.solid=B;
-var U=B+S.event.label.offsetFromLine;
-var Q=this._findFreeTrackForText(E,U+F.width,function(V){V.line=B-2;
-});
-this._getTrackData(Q).text=B-2;
-this._paintEventLine(J,B,E,Q,M,S);
-var R=Math.round(M.trackOffset+Q*M.trackIncrement+M.trackHeight/2-F.height/2);
-var L=this._paintEventLabel(J,I,U,R,F.width,F.height,S);
-var K=this;
-var G=function(V,W,X){return K._onClickDurationEvent(C.elmt,W,J);
-};
-SimileAjax.DOM.registerEvent(C.elmt,"mousedown",G);
-SimileAjax.DOM.registerEvent(L.elmt,"mousedown",G);
-this._createHighlightDiv(O,C,S);
-this._eventIdToElmt[J.getID()]=C.elmt;
-};
-Timeline.DetailedEventPainter.prototype.paintImpreciseDurationEvent=function(L,P,W,S){var Z=this._timeline.getDocument();
-var K=L.getText();
-var D=L.getStart();
-var Q=L.getLatestStart();
-var T=L.getEnd();
-var X=L.getEarliestEnd();
-var B=Math.round(this._band.dateToPixelOffset(D));
-var F=Math.round(this._band.dateToPixelOffset(Q));
-var A=Math.round(this._band.dateToPixelOffset(T));
-var G=Math.round(this._band.dateToPixelOffset(X));
-var H=this._frc.computeSize(K);
-var E=this._findFreeTrackForSolid(A);
-var R=L.getColor();
-R=R!=null?R:W.event.duration.color;
-var O=this._paintEventTape(L,E,B,A,W.event.duration.impreciseColor,W.event.duration.impreciseOpacity,P,W);
-var C=this._paintEventTape(L,E,F,G,R,100,P,W);
-var J=this._getTrackData(E);
-J.solid=B;
-var Y=F+W.event.label.offsetFromLine;
-var U=this._findFreeTrackForText(E,Y+H.width,function(a){a.line=F-2;
-});
-this._getTrackData(U).text=F-2;
-this._paintEventLine(L,F,E,U,P,W);
-var V=Math.round(P.trackOffset+U*P.trackIncrement+P.trackHeight/2-H.height/2);
-var N=this._paintEventLabel(L,K,Y,V,H.width,H.height,W);
-var M=this;
-var I=function(a,b,c){return M._onClickDurationEvent(C.elmt,b,L);
-};
-SimileAjax.DOM.registerEvent(C.elmt,"mousedown",I);
-SimileAjax.DOM.registerEvent(N.elmt,"mousedown",I);
-this._createHighlightDiv(S,C,W);
-this._eventIdToElmt[L.getID()]=C.elmt;
-};
-Timeline.DetailedEventPainter.prototype._findFreeTrackForSolid=function(B,A){for(var D=0;
-true;
-D++){if(D<this._lowerTracks.length){var C=this._lowerTracks[D];
-if(Math.min(C.solid,C.text)>B&&(!(A)||C.line>A)){return D;
-}}else{this._lowerTracks.push({solid:Number.POSITIVE_INFINITY,text:Number.POSITIVE_INFINITY,line:Number.POSITIVE_INFINITY});
-return D;
-}if(D<this._upperTracks.length){var C=this._upperTracks[D];
-if(Math.min(C.solid,C.text)>B&&(!(A)||C.line>A)){return -1-D;
-}}else{this._upperTracks.push({solid:Number.POSITIVE_INFINITY,text:Number.POSITIVE_INFINITY,line:Number.POSITIVE_INFINITY});
-return -1-D;
-}}};
-Timeline.DetailedEventPainter.prototype._findFreeTrackForText=function(D,C,H){var F;
-var G;
-var B;
-var J;
-if(D<0){F=true;
-B=-D;
-G=this._findFreeUpperTrackForText(B,C);
-J=-1-G;
-}else{if(D>0){F=false;
-B=D+1;
-G=this._findFreeLowerTrackForText(B,C);
-J=G;
-}else{var A=this._findFreeUpperTrackForText(0,C);
-var I=this._findFreeLowerTrackForText(1,C);
-if(I-1<=A){F=false;
-B=1;
-G=I;
-J=G;
-}else{F=true;
-B=0;
-G=A;
-J=-1-G;
-}}}if(F){if(G==this._upperTracks.length){this._upperTracks.push({solid:Number.POSITIVE_INFINITY,text:Number.POSITIVE_INFINITY,line:Number.POSITIVE_INFINITY});
-}for(var E=B;
-E<G;
-E++){H(this._upperTracks[E]);
-}}else{if(G==this._lowerTracks.length){this._lowerTracks.push({solid:Number.POSITIVE_INFINITY,text:Number.POSITIVE_INFINITY,line:Number.POSITIVE_INFINITY});
-}for(var E=B;
-E<G;
-E++){H(this._lowerTracks[E]);
-}}return J;
-};
-Timeline.DetailedEventPainter.prototype._findFreeLowerTrackForText=function(A,C){for(;
-A<this._lowerTracks.length;
-A++){var B=this._lowerTracks[A];
-if(Math.min(B.solid,B.text)>=C){break;
-}}return A;
-};
-Timeline.DetailedEventPainter.prototype._findFreeUpperTrackForText=function(A,C){for(;
-A<this._upperTracks.length;
-A++){var B=this._upperTracks[A];
-if(Math.min(B.solid,B.text)>=C){break;
-}}return A;
-};
-Timeline.DetailedEventPainter.prototype._getTrackData=function(A){return(A<0)?this._upperTracks[-A-1]:this._lowerTracks[A];
-};
-Timeline.DetailedEventPainter.prototype._paintEventLine=function(I,C,F,A,G,D){var H=Math.round(G.trackOffset+F*G.trackIncrement+G.trackHeight/2);
-var J=Math.round(Math.abs(A-F)*G.trackIncrement);
-var E="1px solid "+D.event.label.lineColor;
-var B=this._timeline.getDocument().createElement("div");
-B.style.position="absolute";
-B.style.left=C+"px";
-B.style.width=D.event.label.offsetFromLine+"px";
-B.style.height=J+"px";
-if(F>A){B.style.top=(H-J)+"px";
-B.style.borderTop=E;
-}else{B.style.top=H+"px";
-B.style.borderBottom=E;
-}B.style.borderLeft=E;
-this._lineLayer.appendChild(B);
-};
-Timeline.DetailedEventPainter.prototype._paintEventIcon=function(I,E,B,F,D){var H=I.getIcon();
-H=H!=null?H:F.icon;
-var J=F.trackOffset+E*F.trackIncrement+F.trackHeight/2;
-var G=Math.round(J-F.iconHeight/2);
-var C=SimileAjax.Graphics.createTranslucentImage(H);
-var A=this._timeline.getDocument().createElement("div");
-A.style.position="absolute";
-A.style.left=B+"px";
-A.style.top=G+"px";
-A.appendChild(C);
-A.style.cursor="pointer";
-if(I._title!=null){A.title=I._title;
-}this._eventLayer.appendChild(A);
-return{left:B,top:G,width:F.iconWidth,height:F.iconHeight,elmt:A};
-};
-Timeline.DetailedEventPainter.prototype._paintEventLabel=function(H,I,B,F,A,J,D){var G=this._timeline.getDocument();
-var K=G.createElement("div");
-K.style.position="absolute";
-K.style.left=B+"px";
-K.style.width=A+"px";
-K.style.top=F+"px";
-K.style.height=J+"px";
-K.style.backgroundColor=D.event.label.backgroundColor;
-SimileAjax.Graphics.setOpacity(K,D.event.label.backgroundOpacity);
-this._eventLayer.appendChild(K);
-var E=G.createElement("div");
-E.style.position="absolute";
-E.style.left=B+"px";
-E.style.width=A+"px";
-E.style.top=F+"px";
-E.innerHTML=I;
-E.style.cursor="pointer";
-if(H._title!=null){E.title=H._title;
-}var C=H.getTextColor();
-if(C==null){C=H.getColor();
-}if(C!=null){E.style.color=C;
-}this._eventLayer.appendChild(E);
-return{left:B,top:F,width:A,height:J,elmt:E};
-};
-Timeline.DetailedEventPainter.prototype._paintEventTape=function(L,H,E,A,C,G,I,F){var B=A-E;
-var D=F.event.tape.height;
-var M=I.trackOffset+H*I.trackIncrement+I.trackHeight/2;
-var J=Math.round(M-D/2);
-var K=this._timeline.getDocument().createElement("div");
-K.style.position="absolute";
-K.style.left=E+"px";
-K.style.width=B+"px";
-K.style.top=J+"px";
-K.style.height=D+"px";
-K.style.backgroundColor=C;
-K.style.overflow="hidden";
-K.style.cursor="pointer";
-if(L._title!=null){K.title=L._title;
-}SimileAjax.Graphics.setOpacity(K,G);
-this._eventLayer.appendChild(K);
-return{left:E,top:J,width:B,height:D,elmt:K};
-};
-Timeline.DetailedEventPainter.prototype._createHighlightDiv=function(A,C,E){if(A>=0){var D=this._timeline.getDocument();
-var G=E.event;
-var B=G.highlightColors[Math.min(A,G.highlightColors.length-1)];
-var F=D.createElement("div");
-F.style.position="absolute";
-F.style.overflow="hidden";
-F.style.left=(C.left-2)+"px";
-F.style.width=(C.width+4)+"px";
-F.style.top=(C.top-2)+"px";
-F.style.height=(C.height+4)+"px";
-F.style.background=B;
-this._highlightLayer.appendChild(F);
-}};
-Timeline.DetailedEventPainter.prototype._onClickInstantEvent=function(B,C,A){var D=SimileAjax.DOM.getPageCoordinates(B);
-this._showBubble(D.left+Math.ceil(B.offsetWidth/2),D.top+Math.ceil(B.offsetHeight/2),A);
-this._fireOnSelect(A.getID());
-C.cancelBubble=true;
-SimileAjax.DOM.cancelEvent(C);
-return false;
-};
-Timeline.DetailedEventPainter.prototype._onClickDurationEvent=function(D,C,B){if("pageX" in C){var A=C.pageX;
-var F=C.pageY;
-}else{var E=SimileAjax.DOM.getPageCoordinates(D);
-var A=C.offsetX+E.left;
-var F=C.offsetY+E.top;
-}this._showBubble(A,F,B);
-this._fireOnSelect(B.getID());
-C.cancelBubble=true;
-SimileAjax.DOM.cancelEvent(C);
-return false;
-};
-Timeline.DetailedEventPainter.prototype.showBubble=function(A){var B=this._eventIdToElmt[A.getID()];
-if(B){var C=SimileAjax.DOM.getPageCoordinates(B);
-this._showBubble(C.left+B.offsetWidth/2,C.top+B.offsetHeight/2,A);
-}};
-Timeline.DetailedEventPainter.prototype._showBubble=function(A,E,B){var D=document.createElement("div");
-var C=this._params.theme.event.bubble;
-B.fillInfoBubble(D,this._params.theme,this._band.getLabeller());
-SimileAjax.WindowManager.cancelPopups();
-SimileAjax.Graphics.createBubbleForContentAndPoint(D,A,E,C.width,null,C.maxHeight);
-};
-Timeline.DetailedEventPainter.prototype._fireOnSelect=function(B){for(var A=0;
-A<this._onSelectListeners.length;
-A++){this._onSelectListeners[A](B);
-}};
-
-
-/* ether-painters.js */
-Timeline.GregorianEtherPainter=function(A){this._params=A;
-this._theme=A.theme;
-this._unit=A.unit;
-this._multiple=("multiple" in A)?A.multiple:1;
-};
-Timeline.GregorianEtherPainter.prototype.initialize=function(C,B){this._band=C;
-this._timeline=B;
-this._backgroundLayer=C.createLayerDiv(0);
-this._backgroundLayer.setAttribute("name","ether-background");
-this._backgroundLayer.className="timeline-ether-bg";
-this._markerLayer=null;
-this._lineLayer=null;
-var D=("align" in this._params&&this._params.align!=undefined)?this._params.align:this._theme.ether.interval.marker[B.isHorizontal()?"hAlign":"vAlign"];
-var A=("showLine" in this._params)?this._params.showLine:this._theme.ether.interval.line.show;
-this._intervalMarkerLayout=new Timeline.EtherIntervalMarkerLayout(this._timeline,this._band,this._theme,D,A);
-this._highlight=new Timeline.EtherHighlight(this._timeline,this._band,this._theme,this._backgroundLayer);
-};
-Timeline.GregorianEtherPainter.prototype.setHighlight=function(A,B){this._highlight.position(A,B);
-};
-Timeline.GregorianEtherPainter.prototype.paint=function(){if(this._markerLayer){this._band.removeLayerDiv(this._markerLayer);
-}this._markerLayer=this._band.createLayerDiv(100);
-this._markerLayer.setAttribute("name","ether-markers");
-this._markerLayer.style.display="none";
-if(this._lineLayer){this._band.removeLayerDiv(this._lineLayer);
-}this._lineLayer=this._band.createLayerDiv(1);
-this._lineLayer.setAttribute("name","ether-lines");
-this._lineLayer.style.display="none";
-var C=this._band.getMinDate();
-var F=this._band.getMaxDate();
-var B=this._band.getTimeZone();
-var E=this._band.getLabeller();
-SimileAjax.DateTime.roundDownToInterval(C,this._unit,B,this._multiple,this._theme.firstDayOfWeek);
-var D=this;
-var A=function(G){for(var H=0;
-H<D._multiple;
-H++){SimileAjax.DateTime.incrementByInterval(G,D._unit);
-}};
-while(C.getTime()<F.getTime()){this._intervalMarkerLayout.createIntervalMarker(C,E,this._unit,this._markerLayer,this._lineLayer);
-A(C);
-}this._markerLayer.style.display="block";
-this._lineLayer.style.display="block";
-};
-Timeline.GregorianEtherPainter.prototype.softPaint=function(){};
-Timeline.GregorianEtherPainter.prototype.zoom=function(A){if(A!=0){this._unit+=A;
-}};
-Timeline.HotZoneGregorianEtherPainter=function(G){this._params=G;
-this._theme=G.theme;
-this._zones=[{startTime:Number.NEGATIVE_INFINITY,endTime:Number.POSITIVE_INFINITY,unit:G.unit,multiple:1}];
-for(var E=0;
-E<G.zones.length;
-E++){var B=G.zones[E];
-var D=SimileAjax.DateTime.parseGregorianDateTime(B.start).getTime();
-var F=SimileAjax.DateTime.parseGregorianDateTime(B.end).getTime();
-for(var C=0;
-C<this._zones.length&&F>D;
-C++){var A=this._zones[C];
-if(D<A.endTime){if(D>A.startTime){this._zones.splice(C,0,{startTime:A.startTime,endTime:D,unit:A.unit,multiple:A.multiple});
-C++;
-A.startTime=D;
-}if(F<A.endTime){this._zones.splice(C,0,{startTime:D,endTime:F,unit:B.unit,multiple:(B.multiple)?B.multiple:1});
-C++;
-A.startTime=F;
-D=F;
-}else{A.multiple=B.multiple;
-A.unit=B.unit;
-D=A.endTime;
-}}}}};
-Timeline.HotZoneGregorianEtherPainter.prototype.initialize=function(C,B){this._band=C;
-this._timeline=B;
-this._backgroundLayer=C.createLayerDiv(0);
-this._backgroundLayer.setAttribute("name","ether-background");
-this._backgroundLayer.className="timeline-ether-bg";
-this._markerLayer=null;
-this._lineLayer=null;
-var D=("align" in this._params&&this._params.align!=undefined)?this._params.align:this._theme.ether.interval.marker[B.isHorizontal()?"hAlign":"vAlign"];
-var A=("showLine" in this._params)?this._params.showLine:this._theme.ether.interval.line.show;
-this._intervalMarkerLayout=new Timeline.EtherIntervalMarkerLayout(this._timeline,this._band,this._theme,D,A);
-this._highlight=new Timeline.EtherHighlight(this._timeline,this._band,this._theme,this._backgroundLayer);
-};
-Timeline.HotZoneGregorianEtherPainter.prototype.setHighlight=function(A,B){this._highlight.position(A,B);
-};
-Timeline.HotZoneGregorianEtherPainter.prototype.paint=function(){if(this._markerLayer){this._band.removeLayerDiv(this._markerLayer);
-}this._markerLayer=this._band.createLayerDiv(100);
-this._markerLayer.setAttribute("name","ether-markers");
-this._markerLayer.style.display="none";
-if(this._lineLayer){this._band.removeLayerDiv(this._lineLayer);
-}this._lineLayer=this._band.createLayerDiv(1);
-this._lineLayer.setAttribute("name","ether-lines");
-this._lineLayer.style.display="none";
-var D=this._band.getMinDate();
-var A=this._band.getMaxDate();
-var K=this._band.getTimeZone();
-var I=this._band.getLabeller();
-var B=this;
-var L=function(N,M){for(var O=0;
-O<M.multiple;
-O++){SimileAjax.DateTime.incrementByInterval(N,M.unit);
-}};
-var C=0;
-while(C<this._zones.length){if(D.getTime()<this._zones[C].endTime){break;
-}C++;
-}var E=this._zones.length-1;
-while(E>=0){if(A.getTime()>this._zones[E].startTime){break;
-}E--;
-}for(var H=C;
-H<=E;
-H++){var G=this._zones[H];
-var J=new Date(Math.max(D.getTime(),G.startTime));
-var F=new Date(Math.min(A.getTime(),G.endTime));
-SimileAjax.DateTime.roundDownToInterval(J,G.unit,K,G.multiple,this._theme.firstDayOfWeek);
-SimileAjax.DateTime.roundUpToInterval(F,G.unit,K,G.multiple,this._theme.firstDayOfWeek);
-while(J.getTime()<F.getTime()){this._intervalMarkerLayout.createIntervalMarker(J,I,G.unit,this._markerLayer,this._lineLayer);
-L(J,G);
-}}this._markerLayer.style.display="block";
-this._lineLayer.style.display="block";
-};
-Timeline.HotZoneGregorianEtherPainter.prototype.softPaint=function(){};
-Timeline.HotZoneGregorianEtherPainter.prototype.zoom=function(B){if(B!=0){for(var A=0;
-A<this._zones.length;
-++A){if(this._zones[A]){this._zones[A].unit+=B;
-}}}};
-Timeline.YearCountEtherPainter=function(A){this._params=A;
-this._theme=A.theme;
-this._startDate=SimileAjax.DateTime.parseGregorianDateTime(A.startDate);
-this._multiple=("multiple" in A)?A.multiple:1;
-};
-Timeline.YearCountEtherPainter.prototype.initialize=function(C,B){this._band=C;
-this._timeline=B;
-this._backgroundLayer=C.createLayerDiv(0);
-this._backgroundLayer.setAttribute("name","ether-background");
-this._backgroundLayer.className="timeline-ether-bg";
-this._markerLayer=null;
-this._lineLayer=null;
-var D=("align" in this._params)?this._params.align:this._theme.ether.interval.marker[B.isHorizontal()?"hAlign":"vAlign"];
-var A=("showLine" in this._params)?this._params.showLine:this._theme.ether.interval.line.show;
-this._intervalMarkerLayout=new Timeline.EtherIntervalMarkerLayout(this._timeline,this._band,this._theme,D,A);
-this._highlight=new Timeline.EtherHighlight(this._timeline,this._band,this._theme,this._backgroundLayer);
-};
-Timeline.YearCountEtherPainter.prototype.setHighlight=function(A,B){this._highlight.position(A,B);
-};
-Timeline.YearCountEtherPainter.prototype.paint=function(){if(this._markerLayer){this._band.removeLayerDiv(this._markerLayer);
-}this._markerLayer=this._band.createLayerDiv(100);
-this._markerLayer.setAttribute("name","ether-markers");
-this._markerLayer.style.display="none";
-if(this._lineLayer){this._band.removeLayerDiv(this._lineLayer);
-}this._lineLayer=this._band.createLayerDiv(1);
-this._lineLayer.setAttribute("name","ether-lines");
-this._lineLayer.style.display="none";
-var B=new Date(this._startDate.getTime());
-var F=this._band.getMaxDate();
-var E=this._band.getMinDate().getUTCFullYear()-this._startDate.getUTCFullYear();
-B.setUTCFullYear(this._band.getMinDate().getUTCFullYear()-E%this._multiple);
-var C=this;
-var A=function(G){for(var H=0;
-H<C._multiple;
-H++){SimileAjax.DateTime.incrementByInterval(G,SimileAjax.DateTime.YEAR);
-}};
-var D={labelInterval:function(G,I){var H=G.getUTCFullYear()-C._startDate.getUTCFullYear();
-return{text:H,emphasized:H==0};
-}};
-while(B.getTime()<F.getTime()){this._intervalMarkerLayout.createIntervalMarker(B,D,SimileAjax.DateTime.YEAR,this._markerLayer,this._lineLayer);
-A(B);
-}this._markerLayer.style.display="block";
-this._lineLayer.style.display="block";
-};
-Timeline.YearCountEtherPainter.prototype.softPaint=function(){};
-Timeline.QuarterlyEtherPainter=function(A){this._params=A;
-this._theme=A.theme;
-this._startDate=SimileAjax.DateTime.parseGregorianDateTime(A.startDate);
-};
-Timeline.QuarterlyEtherPainter.prototype.initialize=function(C,B){this._band=C;
-this._timeline=B;
-this._backgroundLayer=C.createLayerDiv(0);
-this._backgroundLayer.setAttribute("name","ether-background");
-this._backgroundLayer.className="timeline-ether-bg";
-this._markerLayer=null;
-this._lineLayer=null;
-var D=("align" in this._params)?this._params.align:this._theme.ether.interval.marker[B.isHorizontal()?"hAlign":"vAlign"];
-var A=("showLine" in this._params)?this._params.showLine:this._theme.ether.interval.line.show;
-this._intervalMarkerLayout=new Timeline.EtherIntervalMarkerLayout(this._timeline,this._band,this._theme,D,A);
-this._highlight=new Timeline.EtherHighlight(this._timeline,this._band,this._theme,this._backgroundLayer);
-};
-Timeline.QuarterlyEtherPainter.prototype.setHighlight=function(A,B){this._highlight.position(A,B);
-};
-Timeline.QuarterlyEtherPainter.prototype.paint=function(){if(this._markerLayer){this._band.removeLayerDiv(this._markerLayer);
-}this._markerLayer=this._band.createLayerDiv(100);
-this._markerLayer.setAttribute("name","ether-markers");
-this._markerLayer.style.display="none";
-if(this._lineLayer){this._band.removeLayerDiv(this._lineLayer);
-}this._lineLayer=this._band.createLayerDiv(1);
-this._lineLayer.setAttribute("name","ether-lines");
-this._lineLayer.style.display="none";
-var B=new Date(0);
-var E=this._band.getMaxDate();
-B.setUTCFullYear(Math.max(this._startDate.getUTCFullYear(),this._band.getMinDate().getUTCFullYear()));
-B.setUTCMonth(this._startDate.getUTCMonth());
-var C=this;
-var A=function(F){F.setUTCMonth(F.getUTCMonth()+3);
-};
-var D={labelInterval:function(F,H){var G=(4+(F.getUTCMonth()-C._startDate.getUTCMonth())/3)%4;
-if(G!=0){return{text:"Q"+(G+1),emphasized:false};
-}else{return{text:"Y"+(F.getUTCFullYear()-C._startDate.getUTCFullYear()+1),emphasized:true};
-}}};
-while(B.getTime()<E.getTime()){this._intervalMarkerLayout.createIntervalMarker(B,D,SimileAjax.DateTime.YEAR,this._markerLayer,this._lineLayer);
-A(B);
-}this._markerLayer.style.display="block";
-this._lineLayer.style.display="block";
-};
-Timeline.QuarterlyEtherPainter.prototype.softPaint=function(){};
-Timeline.EtherIntervalMarkerLayout=function(M,L,C,E,H){var A=M.isHorizontal();
-if(A){if(E=="Top"){this.positionDiv=function(O,N){O.style.left=N+"px";
-O.style.top="0px";
-};
-}else{this.positionDiv=function(O,N){O.style.left=N+"px";
-O.style.bottom="0px";
-};
-}}else{if(E=="Left"){this.positionDiv=function(O,N){O.style.top=N+"px";
-O.style.left="0px";
-};
-}else{this.positionDiv=function(O,N){O.style.top=N+"px";
-O.style.right="0px";
-};
-}}var D=C.ether.interval.marker;
-var I=C.ether.interval.line;
-var B=C.ether.interval.weekend;
-var K=(A?"h":"v")+E;
-var G=D[K+"Styler"];
-var J=D[K+"EmphasizedStyler"];
-var F=SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY];
-this.createIntervalMarker=function(T,a,b,c,Q){var U=Math.round(L.dateToPixelOffset(T));
-if(H&&b!=SimileAjax.DateTime.WEEK){var V=M.getDocument().createElement("div");
-V.className="timeline-ether-lines";
-if(I.opacity<100){SimileAjax.Graphics.setOpacity(V,I.opacity);
-}if(A){V.style.left=U+"px";
-}else{V.style.top=U+"px";
-}Q.appendChild(V);
-}if(b==SimileAjax.DateTime.WEEK){var N=C.firstDayOfWeek;
-var W=new Date(T.getTime()+(6-N-7)*F);
-var Z=new Date(W.getTime()+2*F);
-var X=Math.round(L.dateToPixelOffset(W));
-var S=Math.round(L.dateToPixelOffset(Z));
-var R=Math.max(1,S-X);
-var P=M.getDocument().createElement("div");
-P.className="timeline-ether-weekends";
-if(B.opacity<100){SimileAjax.Graphics.setOpacity(P,B.opacity);
-}if(A){P.style.left=X+"px";
-P.style.width=R+"px";
-}else{P.style.top=X+"px";
-P.style.height=R+"px";
-}Q.appendChild(P);
-}var Y=a.labelInterval(T,b);
-var O=M.getDocument().createElement("div");
-O.innerHTML=Y.text;
-O.className="timeline-date-label";
-if(Y.emphasized){O.className+=" timeline-date-label-em";
-}this.positionDiv(O,U);
-c.appendChild(O);
-return O;
-};
-};
-Timeline.EtherHighlight=function(C,E,D,B){var A=C.isHorizontal();
-this._highlightDiv=null;
-this._createHighlightDiv=function(){if(this._highlightDiv==null){this._highlightDiv=C.getDocument().createElement("div");
-this._highlightDiv.setAttribute("name","ether-highlight");
-this._highlightDiv.className="timeline-ether-highlight";
-var F=D.ether.highlightOpacity;
-if(F<100){SimileAjax.Graphics.setOpacity(this._highlightDiv,F);
-}B.appendChild(this._highlightDiv);
-}};
-this.position=function(F,I){this._createHighlightDiv();
-var J=Math.round(E.dateToPixelOffset(F));
-var H=Math.round(E.dateToPixelOffset(I));
-var G=Math.max(H-J,3);
-if(A){this._highlightDiv.style.left=J+"px";
-this._highlightDiv.style.width=G+"px";
-this._highlightDiv.style.height=(E.getViewWidth()-4)+"px";
-}else{this._highlightDiv.style.top=J+"px";
-this._highlightDiv.style.height=G+"px";
-this._highlightDiv.style.width=(E.getViewWidth()-4)+"px";
-}};
-};
-
-
-/* ethers.js */
-Timeline.LinearEther=function(A){this._params=A;
-this._interval=A.interval;
-this._pixelsPerInterval=A.pixelsPerInterval;
-};
-Timeline.LinearEther.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._unit=A.getUnit();
-if("startsOn" in this._params){this._start=this._unit.parseFromObject(this._params.startsOn);
-}else{if("endsOn" in this._params){this._start=this._unit.parseFromObject(this._params.endsOn);
-this.shiftPixels(-this._timeline.getPixelLength());
-}else{if("centersOn" in this._params){this._start=this._unit.parseFromObject(this._params.centersOn);
-this.shiftPixels(-this._timeline.getPixelLength()/2);
-}else{this._start=this._unit.makeDefaultValue();
-this.shiftPixels(-this._timeline.getPixelLength()/2);
-}}}};
-Timeline.LinearEther.prototype.setDate=function(A){this._start=this._unit.cloneValue(A);
-};
-Timeline.LinearEther.prototype.shiftPixels=function(B){var A=this._interval*B/this._pixelsPerInterval;
-this._start=this._unit.change(this._start,A);
-};
-Timeline.LinearEther.prototype.dateToPixelOffset=function(A){var B=this._unit.compare(A,this._start);
-return this._pixelsPerInterval*B/this._interval;
-};
-Timeline.LinearEther.prototype.pixelOffsetToDate=function(B){var A=B*this._interval/this._pixelsPerInterval;
-return this._unit.change(this._start,A);
-};
-Timeline.LinearEther.prototype.zoom=function(D){var B=0;
-var A=this._band._zoomIndex;
-var C=A;
-if(D&&(A>0)){C=A-1;
-}if(!D&&(A<(this._band._zoomSteps.length-1))){C=A+1;
-}this._band._zoomIndex=C;
-this._interval=SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[C].unit];
-this._pixelsPerInterval=this._band._zoomSteps[C].pixelsPerInterval;
-B=this._band._zoomSteps[C].unit-this._band._zoomSteps[A].unit;
-return B;
-};
-Timeline.HotZoneEther=function(A){this._params=A;
-this._interval=A.interval;
-this._pixelsPerInterval=A.pixelsPerInterval;
-this._theme=A.theme;
-};
-Timeline.HotZoneEther.prototype.initialize=function(H,I){this._band=H;
-this._timeline=I;
-this._unit=I.getUnit();
-this._zones=[{startTime:Number.NEGATIVE_INFINITY,endTime:Number.POSITIVE_INFINITY,magnify:1}];
-var B=this._params;
-for(var D=0;
-D<B.zones.length;
-D++){var G=B.zones[D];
-var E=this._unit.parseFromObject(G.start);
-var F=this._unit.parseFromObject(G.end);
-for(var C=0;
-C<this._zones.length&&this._unit.compare(F,E)>0;
-C++){var A=this._zones[C];
-if(this._unit.compare(E,A.endTime)<0){if(this._unit.compare(E,A.startTime)>0){this._zones.splice(C,0,{startTime:A.startTime,endTime:E,magnify:A.magnify});
-C++;
-A.startTime=E;
-}if(this._unit.compare(F,A.endTime)<0){this._zones.splice(C,0,{startTime:E,endTime:F,magnify:G.magnify*A.magnify});
-C++;
-A.startTime=F;
-E=F;
-}else{A.magnify*=G.magnify;
-E=A.endTime;
-}}}}if("startsOn" in this._params){this._start=this._unit.parseFromObject(this._params.startsOn);
-}else{if("endsOn" in this._params){this._start=this._unit.parseFromObject(this._params.endsOn);
-this.shiftPixels(-this._timeline.getPixelLength());
-}else{if("centersOn" in this._params){this._start=this._unit.parseFromObject(this._params.centersOn);
-this.shiftPixels(-this._timeline.getPixelLength()/2);
-}else{this._start=this._unit.makeDefaultValue();
-this.shiftPixels(-this._timeline.getPixelLength()/2);
-}}}};
-Timeline.HotZoneEther.prototype.setDate=function(A){this._start=this._unit.cloneValue(A);
-};
-Timeline.HotZoneEther.prototype.shiftPixels=function(A){this._start=this.pixelOffsetToDate(A);
-};
-Timeline.HotZoneEther.prototype.dateToPixelOffset=function(A){return this._dateDiffToPixelOffset(this._start,A);
-};
-Timeline.HotZoneEther.prototype.pixelOffsetToDate=function(A){return this._pixelOffsetToDate(A,this._start);
-};
-Timeline.HotZoneEther.prototype.zoom=function(D){var B=0;
-var A=this._band._zoomIndex;
-var C=A;
-if(D&&(A>0)){C=A-1;
-}if(!D&&(A<(this._band._zoomSteps.length-1))){C=A+1;
-}this._band._zoomIndex=C;
-this._interval=SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[C].unit];
-this._pixelsPerInterval=this._band._zoomSteps[C].pixelsPerInterval;
-B=this._band._zoomSteps[C].unit-this._band._zoomSteps[A].unit;
-return B;
-};
-Timeline.HotZoneEther.prototype._dateDiffToPixelOffset=function(I,D){var B=this._getScale();
-var H=I;
-var C=D;
-var A=0;
-if(this._unit.compare(H,C)<0){var G=0;
-while(G<this._zones.length){if(this._unit.compare(H,this._zones[G].endTime)<0){break;
-}G++;
-}while(this._unit.compare(H,C)<0){var E=this._zones[G];
-var F=this._unit.earlier(C,E.endTime);
-A+=(this._unit.compare(F,H)/(B/E.magnify));
-H=F;
-G++;
-}}else{var G=this._zones.length-1;
-while(G>=0){if(this._unit.compare(H,this._zones[G].startTime)>0){break;
-}G--;
-}while(this._unit.compare(H,C)>0){var E=this._zones[G];
-var F=this._unit.later(C,E.startTime);
-A+=(this._unit.compare(F,H)/(B/E.magnify));
-H=F;
-G--;
-}}return A;
-};
-Timeline.HotZoneEther.prototype._pixelOffsetToDate=function(H,C){var G=this._getScale();
-var E=C;
-if(H>0){var F=0;
-while(F<this._zones.length){if(this._unit.compare(E,this._zones[F].endTime)<0){break;
-}F++;
-}while(H>0){var A=this._zones[F];
-var D=G/A.magnify;
-if(A.endTime==Number.POSITIVE_INFINITY){E=this._unit.change(E,H*D);
-H=0;
-}else{var B=this._unit.compare(A.endTime,E)/D;
-if(B>H){E=this._unit.change(E,H*D);
-H=0;
-}else{E=A.endTime;
-H-=B;
-}}F++;
-}}else{var F=this._zones.length-1;
-while(F>=0){if(this._unit.compare(E,this._zones[F].startTime)>0){break;
-}F--;
-}H=-H;
-while(H>0){var A=this._zones[F];
-var D=G/A.magnify;
-if(A.startTime==Number.NEGATIVE_INFINITY){E=this._unit.change(E,-H*D);
-H=0;
-}else{var B=this._unit.compare(E,A.startTime)/D;
-if(B>H){E=this._unit.change(E,-H*D);
-H=0;
-}else{E=A.startTime;
-H-=B;
-}}F--;
-}}return E;
-};
-Timeline.HotZoneEther.prototype._getScale=function(){return this._interval/this._pixelsPerInterval;
-};
-
-
-/* event-utils.js */
-Timeline.EventUtils={};
-Timeline.EventUtils.getNewEventID=function(){if(this._lastEventID==null){this._lastEventID=0;
-}this._lastEventID+=1;
-return"e"+this._lastEventID;
-};
-Timeline.EventUtils.decodeEventElID=function(B){var D=B.split("-");
-if(D[1]!="tl"){alert("Internal Timeline problem 101, please consult support");
-return{band:null,evt:null};
-}var C=Timeline.getTimelineFromID(D[2]);
-var E=C.getBand(D[3]);
-var A=E.getEventSource.getEvent(D[4]);
-return{band:E,evt:A};
-};
-Timeline.EventUtils.encodeEventElID=function(C,D,B,A){return B+"-tl-"+C.timelineID+"-"+D.getIndex()+"-"+A.getID();
-};
-
-
-/* labellers.js */
-Timeline.GregorianDateLabeller=function(A,B){this._locale=A;
-this._timeZone=B;
-};
-Timeline.GregorianDateLabeller.monthNames=[];
-Timeline.GregorianDateLabeller.dayNames=[];
-Timeline.GregorianDateLabeller.labelIntervalFunctions=[];
-Timeline.GregorianDateLabeller.getMonthName=function(B,A){return Timeline.GregorianDateLabeller.monthNames[A][B];
-};
-Timeline.GregorianDateLabeller.prototype.labelInterval=function(A,C){var B=Timeline.GregorianDateLabeller.labelIntervalFunctions[this._locale];
-if(B==null){B=Timeline.GregorianDateLabeller.prototype.defaultLabelInterval;
-}return B.call(this,A,C);
-};
-Timeline.GregorianDateLabeller.prototype.labelPrecise=function(A){return SimileAjax.DateTime.removeTimeZoneOffset(A,this._timeZone).toUTCString();
-};
-Timeline.GregorianDateLabeller.prototype.defaultLabelInterval=function(B,F){var C;
-var E=false;
-B=SimileAjax.DateTime.removeTimeZoneOffset(B,this._timeZone);
-switch(F){case SimileAjax.DateTime.MILLISECOND:C=B.getUTCMilliseconds();
-break;
-case SimileAjax.DateTime.SECOND:C=B.getUTCSeconds();
-break;
-case SimileAjax.DateTime.MINUTE:var A=B.getUTCMinutes();
-if(A==0){C=B.getUTCHours()+":00";
-E=true;
-}else{C=A;
-}break;
-case SimileAjax.DateTime.HOUR:C=B.getUTCHours()+"hr";
-break;
-case SimileAjax.DateTime.DAY:C=Timeline.GregorianDateLabeller.getMonthName(B.getUTCMonth(),this._locale)+" "+B.getUTCDate();
-break;
-case SimileAjax.DateTime.WEEK:C=Timeline.GregorianDateLabeller.getMonthName(B.getUTCMonth(),this._locale)+" "+B.getUTCDate();
-break;
-case SimileAjax.DateTime.MONTH:var A=B.getUTCMonth();
-if(A!=0){C=Timeline.GregorianDateLabeller.getMonthName(A,this._locale);
-break;
-}case SimileAjax.DateTime.YEAR:case SimileAjax.DateTime.DECADE:case SimileAjax.DateTime.CENTURY:case SimileAjax.DateTime.MILLENNIUM:var D=B.getUTCFullYear();
-if(D>0){C=B.getUTCFullYear();
-}else{C=(1-D)+"BC";
-}E=(F==SimileAjax.DateTime.MONTH)||(F==SimileAjax.DateTime.DECADE&&D%100==0)||(F==SimileAjax.DateTime.CENTURY&&D%1000==0);
-break;
-default:C=B.toUTCString();
-}return{text:C,emphasized:E};
-};
-
-
-/* original-painter.js */
-Timeline.OriginalEventPainter=function(A){this._params=A;
-this._onSelectListeners=[];
-this._eventPaintListeners=[];
-this._filterMatcher=null;
-this._highlightMatcher=null;
-this._frc=null;
-this._eventIdToElmt={};
-};
-Timeline.OriginalEventPainter.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._backLayer=null;
-this._eventLayer=null;
-this._lineLayer=null;
-this._highlightLayer=null;
-this._eventIdToElmt=null;
-};
-Timeline.OriginalEventPainter.prototype.getType=function(){return"original";
-};
-Timeline.OriginalEventPainter.prototype.addOnSelectListener=function(A){this._onSelectListeners.push(A);
-};
-Timeline.OriginalEventPainter.prototype.removeOnSelectListener=function(B){for(var A=0;
-A<this._onSelectListeners.length;
-A++){if(this._onSelectListeners[A]==B){this._onSelectListeners.splice(A,1);
-break;
-}}};
-Timeline.OriginalEventPainter.prototype.addEventPaintListener=function(A){this._eventPaintListeners.push(A);
-};
-Timeline.OriginalEventPainter.prototype.removeEventPaintListener=function(B){for(var A=0;
-A<this._eventPaintListeners.length;
-A++){if(this._eventPaintListeners[A]==B){this._eventPaintListeners.splice(A,1);
-break;
-}}};
-Timeline.OriginalEventPainter.prototype.getFilterMatcher=function(){return this._filterMatcher;
-};
-Timeline.OriginalEventPainter.prototype.setFilterMatcher=function(A){this._filterMatcher=A;
-};
-Timeline.OriginalEventPainter.prototype.getHighlightMatcher=function(){return this._highlightMatcher;
-};
-Timeline.OriginalEventPainter.prototype.setHighlightMatcher=function(A){this._highlightMatcher=A;
-};
-Timeline.OriginalEventPainter.prototype.paint=function(){var B=this._band.getEventSource();
-if(B==null){return ;
-}this._eventIdToElmt={};
-this._fireEventPaintListeners("paintStarting",null,null);
-this._prepareForPainting();
-var I=this._params.theme.event;
-var G=Math.max(I.track.height,I.tape.height+this._frc.getLineHeight());
-var F={trackOffset:I.track.offset,trackHeight:G,trackGap:I.track.gap,trackIncrement:G+I.track.gap,icon:I.instant.icon,iconWidth:I.instant.iconWidth,iconHeight:I.instant.iconHeight,labelWidth:I.label.width,maxLabelChar:I.label.maxLabelChar,impreciseIconMargin:I.instant.impreciseIconMargin};
-var C=this._band.getMinDate();
-var A=this._band.getMaxDate();
-var J=(this._filterMatcher!=null)?this._filterMatcher:function(K){return true;
-};
-var E=(this._highlightMatcher!=null)?this._highlightMatcher:function(K){return -1;
-};
-var D=B.getEventReverseIterator(C,A);
-while(D.hasNext()){var H=D.next();
-if(J(H)){this.paintEvent(H,F,this._params.theme,E(H));
-}}this._highlightLayer.style.display="block";
-this._lineLayer.style.display="block";
-this._eventLayer.style.display="block";
-this._band.updateEventTrackInfo(this._tracks.length,F.trackIncrement);
-this._fireEventPaintListeners("paintEnded",null,null);
-};
-Timeline.OriginalEventPainter.prototype.softPaint=function(){};
-Timeline.OriginalEventPainter.prototype._prepareForPainting=function(){var B=this._band;
-if(this._backLayer==null){this._backLayer=this._band.createLayerDiv(0,"timeline-band-events");
-this._backLayer.style.visibility="hidden";
-var A=document.createElement("span");
-A.className="timeline-event-label";
-this._backLayer.appendChild(A);
-this._frc=SimileAjax.Graphics.getFontRenderingContext(A);
-}this._frc.update();
-this._tracks=[];
-if(this._highlightLayer!=null){B.removeLayerDiv(this._highlightLayer);
-}this._highlightLayer=B.createLayerDiv(105,"timeline-band-highlights");
-this._highlightLayer.style.display="none";
-if(this._lineLayer!=null){B.removeLayerDiv(this._lineLayer);
-}this._lineLayer=B.createLayerDiv(110,"timeline-band-lines");
-this._lineLayer.style.display="none";
-if(this._eventLayer!=null){B.removeLayerDiv(this._eventLayer);
-}this._eventLayer=B.createLayerDiv(115,"timeline-band-events");
-this._eventLayer.style.display="none";
-};
-Timeline.OriginalEventPainter.prototype.paintEvent=function(B,C,D,A){if(B.isInstant()){this.paintInstantEvent(B,C,D,A);
-}else{this.paintDurationEvent(B,C,D,A);
-}};
-Timeline.OriginalEventPainter.prototype.paintInstantEvent=function(B,C,D,A){if(B.isImprecise()){this.paintImpreciseInstantEvent(B,C,D,A);
-}else{this.paintPreciseInstantEvent(B,C,D,A);
-}};
-Timeline.OriginalEventPainter.prototype.paintDurationEvent=function(B,C,D,A){if(B.isImprecise()){this.paintImpreciseDurationEvent(B,C,D,A);
-}else{this.paintPreciseDurationEvent(B,C,D,A);
-}};
-Timeline.OriginalEventPainter.prototype.paintPreciseInstantEvent=function(M,Q,S,R){var V=this._timeline.getDocument();
-var L=M.getText();
-var E=M.getStart();
-var C=Math.round(this._band.dateToPixelOffset(E));
-var A=Math.round(C+Q.iconWidth/2);
-var K=Math.round(C-Q.iconWidth/2);
-var H=this._getLabelDivClassName(M);
-var I=this._frc.computeSize(L,H);
-var W=A+S.event.label.offsetFromLine;
-var D=W+I.width;
-var U=D;
-var O=this._findFreeTrack(M,U);
-var T=Math.round(Q.trackOffset+O*Q.trackIncrement+Q.trackHeight/2-I.height/2);
-var B=this._paintEventIcon(M,O,K,Q,S,0);
-var P=this._paintEventLabel(M,L,W,T,I.width,I.height,S,H,R);
-var F=[B.elmt,P.elmt];
-var N=this;
-var J=function(X,Y,Z){return N._onClickInstantEvent(B.elmt,Y,M);
-};
-SimileAjax.DOM.registerEvent(B.elmt,"mousedown",J);
-SimileAjax.DOM.registerEvent(P.elmt,"mousedown",J);
-var G=this._createHighlightDiv(R,B,S,M);
-if(G!=null){F.push(G);
-}this._fireEventPaintListeners("paintedEvent",M,F);
-this._eventIdToElmt[M.getID()]=B.elmt;
-this._tracks[O]=K;
-};
-Timeline.OriginalEventPainter.prototype.paintImpreciseInstantEvent=function(P,T,Y,V){var a=this._timeline.getDocument();
-var N=P.getText();
-var G=P.getStart();
-var W=P.getEnd();
-var D=Math.round(this._band.dateToPixelOffset(G));
-var B=Math.round(this._band.dateToPixelOffset(W));
-var A=Math.round(D+T.iconWidth/2);
-var M=Math.round(D-T.iconWidth/2);
-var J=this._getLabelDivClassName(P);
-var K=this._frc.computeSize(N,J);
-var b=A+Y.event.label.offsetFromLine;
-var E=b+K.width;
-var Z=Math.max(E,B);
-var R=this._findFreeTrack(P,Z);
-var O=Y.event.tape.height;
-var X=Math.round(T.trackOffset+R*T.trackIncrement+O);
-var C=this._paintEventIcon(P,R,M,T,Y,O);
-var S=this._paintEventLabel(P,N,b,X,K.width,K.height,Y,J,V);
-var U=P.getColor();
-U=U!=null?U:Y.event.instant.impreciseColor;
-var F=this._paintEventTape(P,R,D,B,U,Y.event.instant.impreciseOpacity,T,Y,0);
-var H=[C.elmt,S.elmt,F.elmt];
-var Q=this;
-var L=function(c,d,e){return Q._onClickInstantEvent(C.elmt,d,P);
-};
-SimileAjax.DOM.registerEvent(C.elmt,"mousedown",L);
-SimileAjax.DOM.registerEvent(F.elmt,"mousedown",L);
-SimileAjax.DOM.registerEvent(S.elmt,"mousedown",L);
-var I=this._createHighlightDiv(V,C,Y,P);
-if(I!=null){H.push(I);
-}this._fireEventPaintListeners("paintedEvent",P,H);
-this._eventIdToElmt[P.getID()]=C.elmt;
-this._tracks[R]=M;
-};
-Timeline.OriginalEventPainter.prototype.paintPreciseDurationEvent=function(L,P,T,R){var W=this._timeline.getDocument();
-var K=L.getText();
-var E=L.getStart();
-var S=L.getEnd();
-var B=Math.round(this._band.dateToPixelOffset(E));
-var A=Math.round(this._band.dateToPixelOffset(S));
-var H=this._getLabelDivClassName(L);
-var I=this._frc.computeSize(K,H);
-var X=B;
-var C=X+I.width;
-var V=Math.max(C,A);
-var N=this._findFreeTrack(L,V);
-var U=Math.round(P.trackOffset+N*P.trackIncrement+T.event.tape.height);
-var Q=L.getColor();
-Q=Q!=null?Q:T.event.duration.color;
-var D=this._paintEventTape(L,N,B,A,Q,100,P,T,0);
-var O=this._paintEventLabel(L,K,X,U,I.width,I.height,T,H,R);
-var F=[D.elmt,O.elmt];
-var M=this;
-var J=function(Y,Z,a){return M._onClickDurationEvent(D.elmt,Z,L);
-};
-SimileAjax.DOM.registerEvent(D.elmt,"mousedown",J);
-SimileAjax.DOM.registerEvent(O.elmt,"mousedown",J);
-var G=this._createHighlightDiv(R,D,T,L);
-if(G!=null){F.push(G);
-}this._fireEventPaintListeners("paintedEvent",L,F);
-this._eventIdToElmt[L.getID()]=D.elmt;
-this._tracks[N]=B;
-};
-Timeline.OriginalEventPainter.prototype.paintImpreciseDurationEvent=function(N,S,Y,V){var b=this._timeline.getDocument();
-var M=N.getText();
-var E=N.getStart();
-var T=N.getLatestStart();
-var W=N.getEnd();
-var a=N.getEarliestEnd();
-var C=Math.round(this._band.dateToPixelOffset(E));
-var G=Math.round(this._band.dateToPixelOffset(T));
-var A=Math.round(this._band.dateToPixelOffset(W));
-var H=Math.round(this._band.dateToPixelOffset(a));
-var J=this._getLabelDivClassName(N);
-var K=this._frc.computeSize(M,J);
-var c=G;
-var B=c+K.width;
-var Z=Math.max(B,A);
-var P=this._findFreeTrack(N,Z);
-var X=Math.round(S.trackOffset+P*S.trackIncrement+Y.event.tape.height);
-var U=N.getColor();
-U=U!=null?U:Y.event.duration.color;
-var R=this._paintEventTape(N,P,C,A,Y.event.duration.impreciseColor,Y.event.duration.impreciseOpacity,S,Y,0);
-var D=this._paintEventTape(N,P,G,H,U,100,S,Y,1);
-var Q=this._paintEventLabel(N,M,c,X,K.width,K.height,Y,J,V);
-var F=[R.elmt,D.elmt,Q.elmt];
-var O=this;
-var L=function(d,e,f){return O._onClickDurationEvent(D.elmt,e,N);
-};
-SimileAjax.DOM.registerEvent(D.elmt,"mousedown",L);
-SimileAjax.DOM.registerEvent(Q.elmt,"mousedown",L);
-var I=this._createHighlightDiv(V,D,Y,N);
-if(I!=null){F.push(I);
-}this._fireEventPaintListeners("paintedEvent",N,F);
-this._eventIdToElmt[N.getID()]=D.elmt;
-this._tracks[P]=C;
-};
-Timeline.OriginalEventPainter.prototype._encodeEventElID=function(B,A){return Timeline.EventUtils.encodeEventElID(this._timeline,this._band,B,A);
-};
-Timeline.OriginalEventPainter.prototype._findFreeTrack=function(E,A){var D=E.getTrackNum();
-if(D!=null){return D;
-}for(var C=0;
-C<this._tracks.length;
-C++){var B=this._tracks[C];
-if(B>A){break;
-}}return C;
-};
-Timeline.OriginalEventPainter.prototype._paintEventIcon=function(J,F,B,G,E,C){var I=J.getIcon();
-I=I!=null?I:G.icon;
-var H;
-if(C>0){H=G.trackOffset+F*G.trackIncrement+C+G.impreciseIconMargin;
-}else{var K=G.trackOffset+F*G.trackIncrement+G.trackHeight/2;
-H=Math.round(K-G.iconHeight/2);
-}var D=SimileAjax.Graphics.createTranslucentImage(I);
-var A=this._timeline.getDocument().createElement("div");
-A.className=this._getElClassName("timeline-event-icon",J);
-A.id=this._encodeEventElID("icon",J);
-A.style.left=B+"px";
-A.style.top=H+"px";
-A.appendChild(D);
-if(J._title!=null){A.title=J._title;
-}this._eventLayer.appendChild(A);
-return{left:B,top:H,width:G.iconWidth,height:G.iconHeight,elmt:A};
-};
-Timeline.OriginalEventPainter.prototype._paintEventLabel=function(J,K,C,H,A,L,E,F,B){var I=this._timeline.getDocument();
-var G=I.createElement("div");
-G.className=F;
-G.id=this._encodeEventElID("label",J);
-G.style.left=C+"px";
-G.style.width=A+"px";
-G.style.top=H+"px";
-G.innerHTML=K;
-if(J._title!=null){G.title=J._title;
-}var D=J.getTextColor();
-if(D==null){D=J.getColor();
-}if(D!=null){G.style.color=D;
-}if(E.event.highlightLabelBackground&&B>=0){G.style.background=this._getHighlightColor(B,E);
-}this._eventLayer.appendChild(G);
-return{left:C,top:H,width:A,height:L,elmt:G};
-};
-Timeline.OriginalEventPainter.prototype._paintEventTape=function(N,I,F,A,C,H,J,G,O){var B=A-F;
-var E=G.event.tape.height;
-var K=J.trackOffset+I*J.trackIncrement;
-var M=this._timeline.getDocument().createElement("div");
-M.className=this._getElClassName("timeline-event-tape",N);
-M.id=this._encodeEventElID("tape"+O,N);
-M.style.left=F+"px";
-M.style.width=B+"px";
-M.style.height=E+"px";
-M.style.top=K+"px";
-if(N._title!=null){M.title=N._title;
-}if(C!=null){M.style.backgroundColor=C;
-}var L=N.getTapeImage();
-var D=N.getTapeRepeat();
-D=D!=null?D:"repeat";
-if(L!=null){M.style.backgroundImage="url("+L+")";
-M.style.backgroundRepeat=D;
-}SimileAjax.Graphics.setOpacity(M,H);
-this._eventLayer.appendChild(M);
-return{left:F,top:K,width:B,height:E,elmt:M};
-};
-Timeline.OriginalEventPainter.prototype._getLabelDivClassName=function(A){return this._getElClassName("timeline-event-label",A);
-};
-Timeline.OriginalEventPainter.prototype._getElClassName=function(B,A){var C=A.getClassName();
-return B+(C!=null?(" "+C):"");
-};
-Timeline.OriginalEventPainter.prototype._getHighlightColor=function(A,C){var B=C.event.highlightColors;
-return B[Math.min(A,B.length-1)];
-};
-Timeline.OriginalEventPainter.prototype._createHighlightDiv=function(A,D,F,B){var G=null;
-if(A>=0){var E=this._timeline.getDocument();
-var C=this._getHighlightColor(A,F);
-G=E.createElement("div");
-G.className=this._getElClassName("timeline-event-highlight",B);
-G.id=this._encodeEventElID("highlight0",B);
-G.style.position="absolute";
-G.style.overflow="hidden";
-G.style.left=(D.left-2)+"px";
-G.style.width=(D.width+4)+"px";
-G.style.top=(D.top-2)+"px";
-G.style.height=(D.height+4)+"px";
-G.style.background=C;
-this._highlightLayer.appendChild(G);
-}return G;
-};
-Timeline.OriginalEventPainter.prototype._onClickInstantEvent=function(B,C,A){var D=SimileAjax.DOM.getPageCoordinates(B);
-this._showBubble(D.left+Math.ceil(B.offsetWidth/2),D.top+Math.ceil(B.offsetHeight/2),A);
-this._fireOnSelect(A.getID());
-C.cancelBubble=true;
-SimileAjax.DOM.cancelEvent(C);
-return false;
-};
-Timeline.OriginalEventPainter.prototype._onClickDurationEvent=function(D,C,B){if("pageX" in C){var A=C.pageX;
-var F=C.pageY;
-}else{var E=SimileAjax.DOM.getPageCoordinates(D);
-var A=C.offsetX+E.left;
-var F=C.offsetY+E.top;
-}this._showBubble(A,F,B);
-this._fireOnSelect(B.getID());
-C.cancelBubble=true;
-SimileAjax.DOM.cancelEvent(C);
-return false;
-};
-Timeline.OriginalEventPainter.prototype.showBubble=function(A){var B=this._eventIdToElmt[A.getID()];
-if(B){var C=SimileAjax.DOM.getPageCoordinates(B);
-this._showBubble(C.left+B.offsetWidth/2,C.top+B.offsetHeight/2,A);
-}};
-Timeline.OriginalEventPainter.prototype._showBubble=function(A,E,B){var D=document.createElement("div");
-var C=this._params.theme.event.bubble;
-B.fillInfoBubble(D,this._params.theme,this._band.getLabeller());
-SimileAjax.WindowManager.cancelPopups();
-SimileAjax.Graphics.createBubbleForContentAndPoint(D,A,E,C.width,null,C.maxHeight);
-};
-Timeline.OriginalEventPainter.prototype._fireOnSelect=function(B){for(var A=0;
-A<this._onSelectListeners.length;
-A++){this._onSelectListeners[A](B);
-}};
-Timeline.OriginalEventPainter.prototype._fireEventPaintListeners=function(D,A,C){for(var B=0;
-B<this._eventPaintListeners.length;
-B++){this._eventPaintListeners[B](this._band,D,A,C);
-}};
-
-
-/* overview-painter.js */
-Timeline.OverviewEventPainter=function(A){this._params=A;
-this._onSelectListeners=[];
-this._filterMatcher=null;
-this._highlightMatcher=null;
-};
-Timeline.OverviewEventPainter.prototype.initialize=function(B,A){this._band=B;
-this._timeline=A;
-this._eventLayer=null;
-this._highlightLayer=null;
-};
-Timeline.OverviewEventPainter.prototype.getType=function(){return"overview";
-};
-Timeline.OverviewEventPainter.prototype.addOnSelectListener=function(A){this._onSelectListeners.push(A);
-};
-Timeline.OverviewEventPainter.prototype.removeOnSelectListener=function(B){for(var A=0;
-A<this._onSelectListeners.length;
-A++){if(this._onSelectListeners[A]==B){this._onSelectListeners.splice(A,1);
-break;
-}}};
-Timeline.OverviewEventPainter.prototype.getFilterMatcher=function(){return this._filterMatcher;
-};
-Timeline.OverviewEventPainter.prototype.setFilterMatcher=function(A){this._filterMatcher=A;
-};
-Timeline.OverviewEventPainter.prototype.getHighlightMatcher=function(){return this._highlightMatcher;
-};
-Timeline.OverviewEventPainter.prototype.setHighlightMatcher=function(A){this._highlightMatcher=A;
-};
-Timeline.OverviewEventPainter.prototype.paint=function(){var B=this._band.getEventSource();
-if(B==null){return ;
-}this._prepareForPainting();
-var H=this._params.theme.event;
-var F={trackOffset:H.overviewTrack.offset,trackHeight:H.overviewTrack.height,trackGap:H.overviewTrack.gap,trackIncrement:H.overviewTrack.height+H.overviewTrack.gap};
-var C=this._band.getMinDate();
-var A=this._band.getMaxDate();
-var I=(this._filterMatcher!=null)?this._filterMatcher:function(J){return true;
-};
-var E=(this._highlightMatcher!=null)?this._highlightMatcher:function(J){return -1;
-};
-var D=B.getEventReverseIterator(C,A);
-while(D.hasNext()){var G=D.next();
-if(I(G)){this.paintEvent(G,F,this._params.theme,E(G));
-}}this._highlightLayer.style.display="block";
-this._eventLayer.style.display="block";
-this._band.updateEventTrackInfo(this._tracks.length,F.trackIncrement);
-};
-Timeline.OverviewEventPainter.prototype.softPaint=function(){};
-Timeline.OverviewEventPainter.prototype._prepareForPainting=function(){var A=this._band;
-this._tracks=[];
-if(this._highlightLayer!=null){A.removeLayerDiv(this._highlightLayer);
-}this._highlightLayer=A.createLayerDiv(105,"timeline-band-highlights");
-this._highlightLayer.style.display="none";
-if(this._eventLayer!=null){A.removeLayerDiv(this._eventLayer);
-}this._eventLayer=A.createLayerDiv(110,"timeline-band-events");
-this._eventLayer.style.display="none";
-};
-Timeline.OverviewEventPainter.prototype.paintEvent=function(B,C,D,A){if(B.isInstant()){this.paintInstantEvent(B,C,D,A);
-}else{this.paintDurationEvent(B,C,D,A);
-}};
-Timeline.OverviewEventPainter.prototype.paintInstantEvent=function(C,F,G,B){var A=C.getStart();
-var H=Math.round(this._band.dateToPixelOffset(A));
-var D=C.getColor();
-D=D!=null?D:G.event.duration.color;
-var E=this._paintEventTick(C,H,D,100,F,G);
-this._createHighlightDiv(B,E,G);
-};
-Timeline.OverviewEventPainter.prototype.paintDurationEvent=function(K,J,I,D){var A=K.getLatestStart();
-var C=K.getEarliestEnd();
-var B=Math.round(this._band.dateToPixelOffset(A));
-var E=Math.round(this._band.dateToPixelOffset(C));
-var H=0;
-for(;
-H<this._tracks.length;
-H++){if(E<this._tracks[H]){break;
-}}this._tracks[H]=E;
-var G=K.getColor();
-G=G!=null?G:I.event.duration.color;
-var F=this._paintEventTape(K,H,B,E,G,100,J,I);
-this._createHighlightDiv(D,F,I);
-};
-Timeline.OverviewEventPainter.prototype._paintEventTape=function(K,B,C,J,D,F,G,E){var H=G.trackOffset+B*G.trackIncrement;
-var A=J-C;
-var L=G.trackHeight;
-var I=this._timeline.getDocument().createElement("div");
-I.className="timeline-small-event-tape";
-I.style.left=C+"px";
-I.style.width=A+"px";
-I.style.top=H+"px";
-I.style.height=L+"px";
-if(D!=null){I.style.backgroundColor=D;
-}if(F<100){SimileAjax.Graphics.setOpacity(I,F);
-}this._eventLayer.appendChild(I);
-return{left:C,top:H,width:A,height:L,elmt:I};
-};
-Timeline.OverviewEventPainter.prototype._paintEventTick=function(J,B,D,F,G,E){var K=E.event.overviewTrack.tickHeight;
-var H=G.trackOffset-K;
-var A=1;
-var I=this._timeline.getDocument().createElement("div");
-I.className="timeline-small-event-icon";
-I.style.left=B+"px";
-I.style.top=H+"px";
-var C=J.getClassName();
-if(C){I.className+=" small-"+C;
-}if(F<100){SimileAjax.Graphics.setOpacity(I,F);
-}this._eventLayer.appendChild(I);
-return{left:B,top:H,width:A,height:K,elmt:I};
-};
-Timeline.OverviewEventPainter.prototype._createHighlightDiv=function(A,C,E){if(A>=0){var D=this._timeline.getDocument();
-var G=E.event;
-var B=G.highlightColors[Math.min(A,G.highlightColors.length-1)];
-var F=D.createElement("div");
-F.style.position="absolute";
-F.style.overflow="hidden";
-F.style.left=(C.left-1)+"px";
-F.style.width=(C.width+2)+"px";
-F.style.top=(C.top-1)+"px";
-F.style.height=(C.height+2)+"px";
-F.style.background=B;
-this._highlightLayer.appendChild(F);
-}};
-Timeline.OverviewEventPainter.prototype.showBubble=function(A){};
-
-
-/* sources.js */
-Timeline.DefaultEventSource=function(A){this._events=(A instanceof Object)?A:new SimileAjax.EventIndex();
-this._listeners=[];
-};
-Timeline.DefaultEventSource.prototype.addListener=function(A){this._listeners.push(A);
-};
-Timeline.DefaultEventSource.prototype.removeListener=function(B){for(var A=0;
-A<this._listeners.length;
-A++){if(this._listeners[A]==B){this._listeners.splice(A,1);
-break;
-}}};
-Timeline.DefaultEventSource.prototype.loadXML=function(G,A){var C=this._getBaseURL(A);
-var H=G.documentElement.getAttribute("wiki-url");
-var L=G.documentElement.getAttribute("wiki-section");
-var E=G.documentElement.getAttribute("date-time-format");
-var F=this._events.getUnit().getParser(E);
-var D=G.documentElement.firstChild;
-var I=false;
-while(D!=null){if(D.nodeType==1){var K="";
-if(D.firstChild!=null&&D.firstChild.nodeType==3){K=D.firstChild.nodeValue;
-}var B=(D.getAttribute("isDuration")===null&&D.getAttribute("durationEvent")===null)||D.getAttribute("isDuration")=="false"||D.getAttribute("durationEvent")=="false";
-var J=new Timeline.DefaultEventSource.Event({id:D.getAttribute("id"),start:F(D.getAttribute("start")),end:F(D.getAttribute("end")),latestStart:F(D.getAttribute("latestStart")),earliestEnd:F(D.getAttribute("earliestEnd")),instant:B,text:D.getAttribute("title"),description:K,image:this._resolveRelativeURL(D.getAttribute("image"),C),link:this._resolveRelativeURL(D.getAttribute("link"),C),icon:this._resolveRelativeURL(D.getAttribute("icon"),C),color:D.getAttribute("color"),textColor:D.getAttribute("textColor"),hoverText:D.getAttribute("hoverText"),classname:D.getAttribute("classname"),tapeImage:D.getAttribute("tapeImage"),tapeRepeat:D.getAttribute("tapeRepeat"),caption:D.getAttribute("caption"),eventID:D.getAttribute("eventID"),trackNum:D.getAttribute("trackNum")});
-J._node=D;
-J.getProperty=function(M){return this._node.getAttribute(M);
-};
-J.setWikiInfo(H,L);
-this._events.add(J);
-I=true;
-}D=D.nextSibling;
-}if(I){this._fire("onAddMany",[]);
-}};
-Timeline.DefaultEventSource.prototype.loadJSON=function(G,B){var D=this._getBaseURL(B);
-var J=false;
-if(G&&G.events){var I=("wikiURL" in G)?G.wikiURL:null;
-var L=("wikiSection" in G)?G.wikiSection:null;
-var E=("dateTimeFormat" in G)?G.dateTimeFormat:null;
-var H=this._events.getUnit().getParser(E);
-for(var F=0;
-F<G.events.length;
-F++){var A=G.events[F];
-var C=A.isDuration||(A.durationEvent!=null&&!A.durationEvent);
-var K=new Timeline.DefaultEventSource.Event({id:("id" in A)?A.id:undefined,start:H(A.start),end:H(A.end),latestStart:H(A.latestStart),earliestEnd:H(A.earliestEnd),instant:C,text:A.title,description:A.description,image:this._resolveRelativeURL(A.image,D),link:this._resolveRelativeURL(A.link,D),icon:this._resolveRelativeURL(A.icon,D),color:A.color,textColor:A.textColor,hoverText:A.hoverText,classname:A.classname,tapeImage:A.tapeImage,tapeRepeat:A.tapeRepeat,caption:A.caption,eventID:A.eventID,trackNum:A.trackNum});
-K._obj=A;
-K.getProperty=function(M){return this._obj[M];
-};
-K.setWikiInfo(I,L);
-this._events.add(K);
-J=true;
-}}if(J){this._fire("onAddMany",[]);
-}};
-Timeline.DefaultEventSource.prototype.loadSPARQL=function(H,A){var D=this._getBaseURL(A);
-var F="iso8601";
-var G=this._events.getUnit().getParser(F);
-if(H==null){return ;
-}var E=H.documentElement.firstChild;
-while(E!=null&&(E.nodeType!=1||E.nodeName!="results")){E=E.nextSibling;
-}var J=null;
-var M=null;
-if(E!=null){J=E.getAttribute("wiki-url");
-M=E.getAttribute("wiki-section");
-E=E.firstChild;
-}var K=false;
-while(E!=null){if(E.nodeType==1){var C={};
-var I=E.firstChild;
-while(I!=null){if(I.nodeType==1&&I.firstChild!=null&&I.firstChild.nodeType==1&&I.firstChild.firstChild!=null&&I.firstChild.firstChild.nodeType==3){C[I.getAttribute("name")]=I.firstChild.firstChild.nodeValue;
-}I=I.nextSibling;
-}if(C["start"]==null&&C["date"]!=null){C["start"]=C["date"];
-}var B=(C["isDuration"]===null&&C["durationEvent"]===null)||C["isDuration"]=="false"||C["durationEvent"]=="false";
-var L=new Timeline.DefaultEventSource.Event({id:C["id"],start:G(C["start"]),end:G(C["end"]),latestStart:G(C["latestStart"]),earliestEnd:G(C["earliestEnd"]),instant:B,text:C["title"],description:C["description"],image:this._resolveRelativeURL(C["image"],D),link:this._resolveRelativeURL(C["link"],D),icon:this._resolveRelativeURL(C["icon"],D),color:C["color"],textColor:C["textColor"],hoverText:C["hoverText"],caption:C["caption"],classname:C["classname"],tapeImage:C["tapeImage"],tapeRepeat:C["tapeRepeat"],eventID:C["eventID"],trackNum:C["trackNum"]});
-L._bindings=C;
-L.getProperty=function(N){return this._bindings[N];
-};
-L.setWikiInfo(J,M);
-this._events.add(L);
-K=true;
-}E=E.nextSibling;
-}if(K){this._fire("onAddMany",[]);
-}};
-Timeline.DefaultEventSource.prototype.add=function(A){this._events.add(A);
-this._fire("onAddOne",[A]);
-};
-Timeline.DefaultEventSource.prototype.addMany=function(B){for(var A=0;
-A<B.length;
-A++){this._events.add(B[A]);
-}this._fire("onAddMany",[]);
-};
-Timeline.DefaultEventSource.prototype.clear=function(){this._events.removeAll();
-this._fire("onClear",[]);
-};
-Timeline.DefaultEventSource.prototype.getEvent=function(A){return this._events.getEvent(A);
-};
-Timeline.DefaultEventSource.prototype.getEventIterator=function(A,B){return this._events.getIterator(A,B);
-};
-Timeline.DefaultEventSource.prototype.getEventReverseIterator=function(A,B){return this._events.getReverseIterator(A,B);
-};
-Timeline.DefaultEventSource.prototype.getAllEventIterator=function(){return this._events.getAllIterator();
-};
-Timeline.DefaultEventSource.prototype.getCount=function(){return this._events.getCount();
-};
-Timeline.DefaultEventSource.prototype.getEarliestDate=function(){return this._events.getEarliestDate();
-};
-Timeline.DefaultEventSource.prototype.getLatestDate=function(){return this._events.getLatestDate();
-};
-Timeline.DefaultEventSource.prototype._fire=function(B,A){for(var C=0;
-C<this._listeners.length;
-C++){var D=this._listeners[C];
-if(B in D){try{D[B].apply(D,A);
-}catch(E){SimileAjax.Debug.exception(E);
-}}}};
-Timeline.DefaultEventSource.prototype._getBaseURL=function(A){if(A.indexOf("://")<0){var C=this._getBaseURL(document.location.href);
-if(A.substr(0,1)=="/"){A=C.substr(0,C.indexOf("/",C.indexOf("://")+3))+A;
-}else{A=C+A;
-}}var B=A.lastIndexOf("/");
-if(B<0){return"";
-}else{return A.substr(0,B+1);
-}};
-Timeline.DefaultEventSource.prototype._resolveRelativeURL=function(A,B){if(A==null||A==""){return A;
-}else{if(A.indexOf("://")>0){return A;
-}else{if(A.substr(0,1)=="/"){return B.substr(0,B.indexOf("/",B.indexOf("://")+3))+A;
-}else{return B+A;
-}}}};
-Timeline.DefaultEventSource.Event=function(A){function D(E){return(A[E]!=null&&A[E]!="")?A[E]:null;
-}var C=A.id?A.id.trim():"";
-this._id=C.length>0?C:Timeline.EventUtils.getNewEventID();
-this._instant=A.instant||(A.end==null);
-this._start=A.start;
-this._end=(A.end!=null)?A.end:A.start;
-this._latestStart=(A.latestStart!=null)?A.latestStart:(A.instant?this._end:this._start);
-this._earliestEnd=(A.earliestEnd!=null)?A.earliestEnd:this._end;
-var B=[];
-if(this._start>this._latestStart){this._latestStart=this._start;
-B.push("start is > latestStart");
-}if(this._start>this._earliestEnd){this._earliestEnd=this._latestStart;
-B.push("start is > earliestEnd");
-}if(this._start>this._end){this._end=this._earliestEnd;
-B.push("start is > end");
-}if(this._latestStart>this._earliestEnd){this._earliestEnd=this._latestStart;
-B.push("latestStart is > earliestEnd");
-}if(this._latestStart>this._end){this._end=this._earliestEnd;
-B.push("latestStart is > end");
-}if(this._earliestEnd>this._end){this._end=this._earliestEnd;
-B.push("earliestEnd is > end");
-}this._eventID=D("eventID");
-this._text=(A.text!=null)?SimileAjax.HTML.deEntify(A.text):"";
-if(B.length>0){this._text+=" PROBLEM: "+B.join(", ");
-}this._description=SimileAjax.HTML.deEntify(A.description);
-this._image=D("image");
-this._link=D("link");
-this._title=D("hoverText");
-this._title=D("caption");
-this._icon=D("icon");
-this._color=D("color");
-this._textColor=D("textColor");
-this._classname=D("classname");
-this._tapeImage=D("tapeImage");
-this._tapeRepeat=D("tapeRepeat");
-this._trackNum=D("trackNum");
-if(this._trackNum!=null){this._trackNum=parseInt(this._trackNum);
-}this._wikiURL=null;
-this._wikiSection=null;
-};
-Timeline.DefaultEventSource.Event.prototype={getID:function(){return this._id;
-},isInstant:function(){return this._instant;
-},isImprecise:function(){return this._start!=this._latestStart||this._end!=this._earliestEnd;
-},getStart:function(){return this._start;
-},getEnd:function(){return this._end;
-},getLatestStart:function(){return this._latestStart;
-},getEarliestEnd:function(){return this._earliestEnd;
-},getEventID:function(){return this._eventID;
-},getText:function(){return this._text;
-},getDescription:function(){return this._description;
-},getImage:function(){return this._image;
-},getLink:function(){return this._link;
-},getIcon:function(){return this._icon;
-},getColor:function(){return this._color;
-},getTextColor:function(){return this._textColor;
-},getClassName:function(){return this._classname;
-},getTapeImage:function(){return this._tapeImage;
-},getTapeRepeat:function(){return this._tapeRepeat;
-},getTrackNum:function(){return this._trackNum;
-},getProperty:function(A){return null;
-},getWikiURL:function(){return this._wikiURL;
-},getWikiSection:function(){return this._wikiSection;
-},setWikiInfo:function(B,A){this._wikiURL=B;
-this._wikiSection=A;
-},fillDescription:function(A){A.innerHTML=this._description;
-},fillWikiInfo:function(D){D.style.display="none";
-if(this._wikiURL==null||this._wikiSection==null){return ;
-}var C=this.getProperty("wikiID");
-if(C==null||C.length==0){C=this.getText();
-}if(C==null||C.length==0){return ;
-}D.style.display="inline";
-C=C.replace(/\s/g,"_");
-var B=this._wikiURL+this._wikiSection.replace(/\s/g,"_")+"/"+C;
-var A=document.createElement("a");
-A.href=B;
-A.target="new";
-A.innerHTML=Timeline.strings[Timeline.clientLocale].wikiLinkLabel;
-D.appendChild(document.createTextNode("["));
-D.appendChild(A);
-D.appendChild(document.createTextNode("]"));
-},fillTime:function(A,B){if(this._instant){if(this.isImprecise()){A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._start)));
-A.appendChild(A.ownerDocument.createElement("br"));
-A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._end)));
-}else{A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._start)));
-}}else{if(this.isImprecise()){A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._start)+" ~ "+B.labelPrecise(this._latestStart)));
-A.appendChild(A.ownerDocument.createElement("br"));
-A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._earliestEnd)+" ~ "+B.labelPrecise(this._end)));
-}else{A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._start)));
-A.appendChild(A.ownerDocument.createElement("br"));
-A.appendChild(A.ownerDocument.createTextNode(B.labelPrecise(this._end)));
-}}},fillInfoBubble:function(A,D,K){var L=A.ownerDocument;
-var J=this.getText();
-var H=this.getLink();
-var C=this.getImage();
-if(C!=null){var E=L.createElement("img");
-E.src=C;
-D.event.bubble.imageStyler(E);
-A.appendChild(E);
-}var M=L.createElement("div");
-var B=L.createTextNode(J);
-if(H!=null){var I=L.createElement("a");
-I.href=H;
-I.appendChild(B);
-M.appendChild(I);
-}else{M.appendChild(B);
-}D.event.bubble.titleStyler(M);
-A.appendChild(M);
-var N=L.createElement("div");
-this.fillDescription(N);
-D.event.bubble.bodyStyler(N);
-A.appendChild(N);
-var G=L.createElement("div");
-this.fillTime(G,K);
-D.event.bubble.timeStyler(G);
-A.appendChild(G);
-var F=L.createElement("div");
-this.fillWikiInfo(F);
-D.event.bubble.wikiStyler(F);
-A.appendChild(F);
-}};
-
-
-/* themes.js */
-Timeline.ClassicTheme=new Object();
-Timeline.ClassicTheme.implementations=[];
-Timeline.ClassicTheme.create=function(A){if(A==null){A=Timeline.getDefaultLocale();
-}var B=Timeline.ClassicTheme.implementations[A];
-if(B==null){B=Timeline.ClassicTheme._Impl;
-}return new B();
-};
-Timeline.ClassicTheme._Impl=function(){this.firstDayOfWeek=0;
-this.autoWidth=false;
-this.autoWidthAnimationTime=1000;
-this.ether={backgroundColors:[],highlightOpacity:50,interval:{line:{show:true,opacity:25},weekend:{opacity:30},marker:{hAlign:"Bottom",vAlign:"Right"}}};
-this.event={track:{height:10,gap:2,offset:2,autoWidthMargin:1.5},overviewTrack:{offset:20,tickHeight:6,height:2,gap:1,autoWidthMargin:5},tape:{height:4},instant:{icon:Timeline.urlPrefix+"data/timeline/dull-blue-circle.png",iconWidth:10,iconHeight:10,impreciseOpacity:20,impreciseIconMargin:3},duration:{impreciseOpacity:20},label:{backgroundOpacity:50,offsetFromLine:3},highlightColors:["#FFFF00","#FFC000","#FF0000","#0000FF"],highlightLabelBackground:false,bubble:{width:250,maxHeight:0,titleStyler:function(A){A.className="timeline-event-bubble-title";
-},bodyStyler:function(A){A.className="timeline-event-bubble-body";
-},imageStyler:function(A){A.className="timeline-event-bubble-image";
-},wikiStyler:function(A){A.className="timeline-event-bubble-wiki";
-},timeStyler:function(A){A.className="timeline-event-bubble-time";
-}}};
-this.mouseWheel="scroll";
-};
-
-
-/* timeline.js */
-Timeline.strings={};
-Timeline.HORIZONTAL=0;
-Timeline.VERTICAL=1;
-Timeline._defaultTheme=null;
-Timeline.getDefaultLocale=function(){return Timeline.clientLocale;
-};
-Timeline.create=function(D,C,B,F){if(Timeline.timelines==null){Timeline.timelines=[];
-}var A=Timeline.timelines.length;
-Timeline.timelines[A]=null;
-var E=new Timeline._Impl(D,C,B,F,A);
-Timeline.timelines[A]=E;
-return E;
-};
-Timeline.createBandInfo=function(D){var E=("theme" in D)?D.theme:Timeline.getDefaultTheme();
-var B=("eventSource" in D)?D.eventSource:null;
-var F={interval:SimileAjax.DateTime.gregorianUnitLengths[D.intervalUnit],pixelsPerInterval:D.intervalPixels,theme:E};
-if("startsOn" in D||"endsOn" in D){if("startsOn" in D){F.startsOn=D.startsOn;
-}if("endsOn" in D){F.endsOn=D.endsOn;
-}}else{F.centersOn=("date" in D)?D.date:new Date();
-}var G=new Timeline.LinearEther(F);
-var H=new Timeline.GregorianEtherPainter({unit:D.intervalUnit,multiple:("multiple" in D)?D.multiple:1,theme:E,align:("align" in D)?D.align:undefined});
-var J={showText:("showEventText" in D)?D.showEventText:true,theme:E};
-if("eventPainterParams" in D){for(var A in D.eventPainterParams){J[A]=D.eventPainterParams[A];
-}}if("trackHeight" in D){J.trackHeight=D.trackHeight;
-}if("trackGap" in D){J.trackGap=D.trackGap;
-}var I=("overview" in D&&D.overview)?"overview":("layout" in D?D.layout:"original");
-var C;
-if("eventPainter" in D){C=new D.eventPainter(J);
-}else{switch(I){case"overview":C=new Timeline.OverviewEventPainter(J);
-break;
-case"detailed":C=new Timeline.DetailedEventPainter(J);
-break;
-default:C=new Timeline.OriginalEventPainter(J);
-}}return{width:D.width,eventSource:B,timeZone:("timeZone" in D)?D.timeZone:0,ether:G,etherPainter:H,eventPainter:C,theme:E,zoomIndex:("zoomIndex" in D)?D.zoomIndex:0,zoomSteps:("zoomSteps" in D)?D.zoomSteps:null};
-};
-Timeline.createHotZoneBandInfo=function(D){var E=("theme" in D)?D.theme:Timeline.getDefaultTheme();
-var B=("eventSource" in D)?D.eventSource:null;
-var F=new Timeline.HotZoneEther({centersOn:("date" in D)?D.date:new Date(),interval:SimileAjax.DateTime.gregorianUnitLengths[D.intervalUnit],pixelsPerInterval:D.intervalPixels,zones:D.zones,theme:E});
-var G=new Timeline.HotZoneGregorianEtherPainter({unit:D.intervalUnit,zones:D.zones,theme:E,align:("align" in D)?D.align:undefined});
-var I={showText:("showEventText" in D)?D.showEventText:true,theme:E};
-if("eventPainterParams" in D){for(var A in D.eventPainterParams){I[A]=D.eventPainterParams[A];
-}}if("trackHeight" in D){I.trackHeight=D.trackHeight;
-}if("trackGap" in D){I.trackGap=D.trackGap;
-}var H=("overview" in D&&D.overview)?"overview":("layout" in D?D.layout:"original");
-var C;
-if("eventPainter" in D){C=new D.eventPainter(I);
-}else{switch(H){case"overview":C=new Timeline.OverviewEventPainter(I);
-break;
-case"detailed":C=new Timeline.DetailedEventPainter(I);
-break;
-default:C=new Timeline.OriginalEventPainter(I);
-}}return{width:D.width,eventSource:B,timeZone:("timeZone" in D)?D.timeZone:0,ether:F,etherPainter:G,eventPainter:C,theme:E,zoomIndex:("zoomIndex" in D)?D.zoomIndex:0,zoomSteps:("zoomSteps" in D)?D.zoomSteps:null};
-};
-Timeline.getDefaultTheme=function(){if(Timeline._defaultTheme==null){Timeline._defaultTheme=Timeline.ClassicTheme.create(Timeline.getDefaultLocale());
-}return Timeline._defaultTheme;
-};
-Timeline.setDefaultTheme=function(A){Timeline._defaultTheme=A;
-};
-Timeline.loadXML=function(A,C){var D=function(G,E,F){alert("Failed to load data xml from "+A+"\n"+G);
-};
-var B=function(F){var E=F.responseXML;
-if(!E.documentElement&&F.responseStream){E.load(F.responseStream);
-}C(E,A);
-};
-SimileAjax.XmlHttp.get(A,D,B);
-};
-Timeline.loadJSON=function(url,f){var fError=function(statusText,status,xmlhttp){alert("Failed to load json data from "+url+"\n"+statusText);
-};
-var fDone=function(xmlhttp){f(eval("("+xmlhttp.responseText+")"),url);
-};
-SimileAjax.XmlHttp.get(url,fError,fDone);
-};
-Timeline.getTimelineFromID=function(A){return Timeline.timelines[A];
-};
-Timeline._Impl=function(D,C,B,E,A){SimileAjax.WindowManager.initialize();
-this._containerDiv=D;
-this._bandInfos=C;
-this._orientation=B==null?Timeline.HORIZONTAL:B;
-this._unit=(E!=null)?E:SimileAjax.NativeDateUnit;
-this._starting=true;
-this._autoResizing=false;
-this.autoWidth=C&&C[0]&&C[0].theme&&C[0].theme.autoWidth;
-this.autoWidthAnimationTime=C&&C[0]&&C[0].theme&&C[0].theme.autoWidthAnimationTime;
-this.timelineID=A;
-this._initialize();
-};
-Timeline._Impl.prototype.dispose=function(){for(var A=0;
-A<this._bands.length;
-A++){this._bands[A].dispose();
-}this._bands=null;
-this._bandInfos=null;
-this._containerDiv.innerHTML="";
-Timeline.timelines[this.timelineID]=null;
-};
-Timeline._Impl.prototype.getBandCount=function(){return this._bands.length;
-};
-Timeline._Impl.prototype.getBand=function(A){return this._bands[A];
-};
-Timeline._Impl.prototype.finishedEventLoading=function(){this._autoWidthCheck(true);
-this._starting=false;
-};
-Timeline._Impl.prototype.layout=function(){this._autoWidthCheck(true);
-this._distributeWidths();
-};
-Timeline._Impl.prototype.paint=function(){for(var A=0;
-A<this._bands.length;
-A++){this._bands[A].paint();
-}};
-Timeline._Impl.prototype.getDocument=function(){return this._containerDiv.ownerDocument;
-};
-Timeline._Impl.prototype.addDiv=function(A){this._containerDiv.appendChild(A);
-};
-Timeline._Impl.prototype.removeDiv=function(A){this._containerDiv.removeChild(A);
-};
-Timeline._Impl.prototype.isHorizontal=function(){return this._orientation==Timeline.HORIZONTAL;
-};
-Timeline._Impl.prototype.isVertical=function(){return this._orientation==Timeline.VERTICAL;
-};
-Timeline._Impl.prototype.getPixelLength=function(){return this._orientation==Timeline.HORIZONTAL?this._containerDiv.offsetWidth:this._containerDiv.offsetHeight;
-};
-Timeline._Impl.prototype.getPixelWidth=function(){return this._orientation==Timeline.VERTICAL?this._containerDiv.offsetWidth:this._containerDiv.offsetHeight;
-};
-Timeline._Impl.prototype.getUnit=function(){return this._unit;
-};
-Timeline._Impl.prototype.getWidthStyle=function(){return this._orientation==Timeline.HORIZONTAL?"height":"width";
-};
-Timeline._Impl.prototype.loadXML=function(B,D){var A=this;
-var E=function(H,F,G){alert("Failed to load data xml from "+B+"\n"+H);
-A.hideLoadingMessage();
-};
-var C=function(G){try{var F=G.responseXML;
-if(!F.documentElement&&G.responseStream){F.load(G.responseStream);
-}D(F,B);
-}finally{A.hideLoadingMessage();
-}};
-this.showLoadingMessage();
-window.setTimeout(function(){SimileAjax.XmlHttp.get(B,E,C);
-},0);
-};
-Timeline._Impl.prototype.loadJSON=function(url,f){var tl=this;
-var fError=function(statusText,status,xmlhttp){alert("Failed to load json data from "+url+"\n"+statusText);
-tl.hideLoadingMessage();
-};
-var fDone=function(xmlhttp){try{f(eval("("+xmlhttp.responseText+")"),url);
-}finally{tl.hideLoadingMessage();
-}};
-this.showLoadingMessage();
-window.setTimeout(function(){SimileAjax.XmlHttp.get(url,fError,fDone);
-},0);
-};
-Timeline._Impl.prototype._autoWidthScrollListener=function(A){A.getTimeline()._autoWidthCheck(false);
-};
-Timeline._Impl.prototype._autoWidthCheck=function(C){var E=this;
-var B=E._starting;
-var D=0;
-function A(){var H=E.getWidthStyle();
-if(B){E._containerDiv.style[H]=D+"px";
-}else{E._autoResizing=true;
-var G={};
-G[H]=D+"px";
-SimileAjax.jQuery(E._containerDiv).animate(G,E.autoWidthAnimationTime,"linear",function(){E._autoResizing=false;
-});
-}}function F(){var I=0;
-var G=E.getPixelWidth();
-if(E._autoResizing){return ;
-}for(var H=0;
-H<E._bands.length;
-H++){E._bands[H].checkAutoWidth();
-I+=E._bandInfos[H].width;
-}if(I>G||C){D=I;
-A();
-E._distributeWidths();
-}}if(!E.autoWidth){return ;
-}F();
-};
-Timeline._Impl.prototype._initialize=function(){var E=this._containerDiv;
-var G=E.ownerDocument;
-E.className=E.className.split(" ").concat("timeline-container").join(" ");
-var A=(this.isHorizontal())?"horizontal":"vertical";
-E.className+=" timeline-"+A;
-while(E.firstChild){E.removeChild(E.firstChild);
-}var B=SimileAjax.Graphics.createTranslucentImage(Timeline.urlPrefix+(this.isHorizontal()?"data/timeline/copyright-vertical.png":"data/timeline/copyright.png"));
-B.className="timeline-copyright";
-B.title="Timeline &copy; SIMILE - http://simile.mit.edu/timeline/";
-SimileAjax.DOM.registerEvent(B,"click",function(){window.location="http://simile.mit.edu/timeline/";
-});
-E.appendChild(B);
-this._bands=[];
-for(var C=0;
-C<this._bandInfos.length;
-C++){var F=this._bandInfos[C];
-var D=F.bandClass||Timeline._Band;
-var H=new D(this,this._bandInfos[C],C);
-this._bands.push(H);
-}this._distributeWidths();
-for(var C=0;
-C<this._bandInfos.length;
-C++){var F=this._bandInfos[C];
-if("syncWith" in F){this._bands[C].setSyncWithBand(this._bands[F.syncWith],("highlight" in F)?F.highlight:false);
-}}if(this.autoWidth){for(var C=0;
-C<this._bands.length;
-C++){this._bands[C].addOnScrollListener(this._autoWidthScrollListener);
-}}var I=SimileAjax.Graphics.createMessageBubble(G);
-I.containerDiv.className="timeline-message-container";
-E.appendChild(I.containerDiv);
-I.contentDiv.className="timeline-message";
-I.contentDiv.innerHTML="<img src='"+Timeline.urlPrefix+"data/timeline/progress-running.gif' /> Loading...";
-this.showLoadingMessage=function(){I.containerDiv.style.display="block";
-};
-this.hideLoadingMessage=function(){I.containerDiv.style.display="none";
-};
-};
-Timeline._Impl.prototype._distributeWidths=function(){var B=this.getPixelLength();
-var A=this.getPixelWidth();
-var C=0;
-for(var E=0;
-E<this._bands.length;
-E++){var I=this._bands[E];
-var J=this._bandInfos[E];
-var F=J.width;
-var D;
-if(typeof F=="string"){var H=F.indexOf("%");
-if(H>0){var G=parseInt(F.substr(0,H));
-D=Math.round(G*A/100);
-}else{D=parseInt(F);
-}}else{D=F;
-}I.setBandShiftAndWidth(C,D);
-I.setViewLength(B);
-C+=D;
-}};
-Timeline._Impl.prototype.zoom=function(G,B,F,D){var C=new RegExp("^timeline-band-([0-9]+)$");
-var E=null;
-var A=C.exec(D.id);
-if(A){E=parseInt(A[1]);
-}if(E!=null){this._bands[E].zoom(G,B,F,D);
-}this.paint();
-};
-
-
-/* units.js */
-Timeline.NativeDateUnit=new Object();
-Timeline.NativeDateUnit.createLabeller=function(A,B){return new Timeline.GregorianDateLabeller(A,B);
-};
-Timeline.NativeDateUnit.makeDefaultValue=function(){return new Date();
-};
-Timeline.NativeDateUnit.cloneValue=function(A){return new Date(A.getTime());
-};
-Timeline.NativeDateUnit.getParser=function(A){if(typeof A=="string"){A=A.toLowerCase();
-}return(A=="iso8601"||A=="iso 8601")?Timeline.DateTime.parseIso8601DateTime:Timeline.DateTime.parseGregorianDateTime;
-};
-Timeline.NativeDateUnit.parseFromObject=function(A){return Timeline.DateTime.parseGregorianDateTime(A);
-};
-Timeline.NativeDateUnit.toNumber=function(A){return A.getTime();
-};
-Timeline.NativeDateUnit.fromNumber=function(A){return new Date(A);
-};
-Timeline.NativeDateUnit.compare=function(D,C){var B,A;
-if(typeof D=="object"){B=D.getTime();
-}else{B=Number(D);
-}if(typeof C=="object"){A=C.getTime();
-}else{A=Number(C);
-}return B-A;
-};
-Timeline.NativeDateUnit.earlier=function(B,A){return Timeline.NativeDateUnit.compare(B,A)<0?B:A;
-};
-Timeline.NativeDateUnit.later=function(B,A){return Timeline.NativeDateUnit.compare(B,A)>0?B:A;
-};
-Timeline.NativeDateUnit.change=function(A,B){return new Date(A.getTime()+B);
-};
-
-/******** end of simile-timeline-bundle.js ********/
+/*=================================================
+ *
+ * Coding standards:
+ *
+ * We aim towards Douglas Crockford's Javascript conventions.
+ * See:  http://javascript.crockford.com/code.html
+ * See also: http://www.crockford.com/javascript/javascript.html
+ *
+ * That said, this JS code was written before some recent JS
+ * support libraries became widely used or available.
+ * In particular, the _ character is used to indicate a class function or
+ * variable that should be considered private to the class.
+ *
+ * The code mostly uses accessor methods for getting/setting the private
+ * class variables.
+ *
+ * Over time, we'd like to formalize the convention by using support libraries
+ * which enforce privacy in objects.
+ *
+ * We also want to use jslint:  http://www.jslint.com/
+ *
+ *
+ *==================================================
+ */
+
+
+
+/*==================================================
+ *  Timeline VERSION
+ *==================================================
+ */
+// Note: version is also stored in the build.xml file
+Timeline.version = 'pre 2.4.0';  // use format 'pre 1.2.3' for trunk versions
+Timeline.ajax_lib_version = SimileAjax.version;
+Timeline.display_version = Timeline.version + ' (with Ajax lib ' + Timeline.ajax_lib_version + ')';
+ // cf method Timeline.writeVersion
+
+/*==================================================
+ *  Timeline
+ *==================================================
+ */
+Timeline.strings = {}; // localization string tables
+Timeline.HORIZONTAL = 0;
+Timeline.VERTICAL = 1;
+Timeline._defaultTheme = null;
+
+Timeline.getDefaultLocale = function() {
+    return Timeline.clientLocale;
+};
+
+Timeline.create = function(elmt, bandInfos, orientation, unit) {
+    if (Timeline.timelines == null) {
+        Timeline.timelines = [];
+        // Timeline.timelines array can have null members--Timelines that
+        // once existed on the page, but were later disposed of.
+    }
+
+    var timelineID = Timeline.timelines.length;
+    Timeline.timelines[timelineID] = null; // placeholder until we have the object
+    var new_tl = new Timeline._Impl(elmt, bandInfos, orientation, unit,
+      timelineID);
+    Timeline.timelines[timelineID] = new_tl;
+    return new_tl;
+};
+
+Timeline.createBandInfo = function(params) {
+    var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme();
+
+    var eventSource = ("eventSource" in params) ? params.eventSource : null;
+
+    var etherParams = {
+        interval:           SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit],
+        pixelsPerInterval: params.intervalPixels,
+	theme: theme
+    };
+    if ('startsOn' in params || 'endsOn' in params) {
+	if ('startsOn' in params) {
+	    etherParams.startsOn = params.startsOn;
+	}
+	if ('endsOn' in params) {
+	    etherParams.endsOn = params.endsOn;
+	}
+    } else {
+	etherParams.centersOn = ("date" in params) ? params.date : new Date();
+    }
+    var ether = new Timeline.LinearEther(etherParams);
+
+    var etherPainter = new Timeline.GregorianEtherPainter({
+        unit:       params.intervalUnit,
+        multiple:   ("multiple" in params) ? params.multiple : 1,
+        theme:      theme,
+        align:      ("align" in params) ? params.align : undefined
+    });
+
+    var eventPainterParams = {
+        showText:   ("showEventText" in params) ? params.showEventText : true,
+        theme:      theme
+    };
+    // pass in custom parameters for the event painter
+    if ("eventPainterParams" in params) {
+        for (var prop in params.eventPainterParams) {
+            eventPainterParams[prop] = params.eventPainterParams[prop];
+        }
+    }
+
+    if ("trackHeight" in params) {
+        eventPainterParams.trackHeight = params.trackHeight;
+    }
+    if ("trackGap" in params) {
+        eventPainterParams.trackGap = params.trackGap;
+    }
+
+    var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original");
+    var eventPainter;
+    if ("eventPainter" in params) {
+        eventPainter = new params.eventPainter(eventPainterParams);
+    } else {
+        switch (layout) {
+            case "overview" :
+                eventPainter = new Timeline.OverviewEventPainter(eventPainterParams);
+                break;
+            case "detailed" :
+                eventPainter = new Timeline.DetailedEventPainter(eventPainterParams);
+                break;
+            default:
+                eventPainter = new Timeline.OriginalEventPainter(eventPainterParams);
+        }
+    }
+
+    return {
+        width:          params.width,
+        eventSource:    eventSource,
+        timeZone:       ("timeZone" in params) ? params.timeZone : 0,
+        ether:          ether,
+        etherPainter:   etherPainter,
+        eventPainter:   eventPainter,
+        theme:          theme,
+        zoomIndex:      ("zoomIndex" in params) ? params.zoomIndex : 0,
+        zoomSteps:      ("zoomSteps" in params) ? params.zoomSteps : null
+    };
+};
+
+Timeline.createHotZoneBandInfo = function(params) {
+    var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme();
+
+    var eventSource = ("eventSource" in params) ? params.eventSource : null;
+
+    var ether = new Timeline.HotZoneEther({
+        centersOn:          ("date" in params) ? params.date : new Date(),
+        interval:           SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit],
+        pixelsPerInterval:  params.intervalPixels,
+        zones:              params.zones,
+        theme:              theme
+    });
+
+    var etherPainter = new Timeline.HotZoneGregorianEtherPainter({
+        unit:       params.intervalUnit,
+        zones:      params.zones,
+        theme:      theme,
+        align:      ("align" in params) ? params.align : undefined
+    });
+
+    var eventPainterParams = {
+        showText:   ("showEventText" in params) ? params.showEventText : true,
+        theme:      theme
+    };
+    // pass in custom parameters for the event painter
+    if ("eventPainterParams" in params) {
+        for (var prop in params.eventPainterParams) {
+            eventPainterParams[prop] = params.eventPainterParams[prop];
+        }
+    }
+    if ("trackHeight" in params) {
+        eventPainterParams.trackHeight = params.trackHeight;
+    }
+    if ("trackGap" in params) {
+        eventPainterParams.trackGap = params.trackGap;
+    }
+
+    var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original");
+    var eventPainter;
+    if ("eventPainter" in params) {
+        eventPainter = new params.eventPainter(eventPainterParams);
+    } else {
+        switch (layout) {
+            case "overview" :
+                eventPainter = new Timeline.OverviewEventPainter(eventPainterParams);
+                break;
+            case "detailed" :
+                eventPainter = new Timeline.DetailedEventPainter(eventPainterParams);
+                break;
+            default:
+                eventPainter = new Timeline.OriginalEventPainter(eventPainterParams);
+        }
+    }
+    return {
+        width:          params.width,
+        eventSource:    eventSource,
+        timeZone:       ("timeZone" in params) ? params.timeZone : 0,
+        ether:          ether,
+        etherPainter:   etherPainter,
+        eventPainter:   eventPainter,
+        theme:          theme,
+        zoomIndex:      ("zoomIndex" in params) ? params.zoomIndex : 0,
+        zoomSteps:      ("zoomSteps" in params) ? params.zoomSteps : null
+    };
+};
+
+Timeline.getDefaultTheme = function() {
+    if (Timeline._defaultTheme == null) {
+        Timeline._defaultTheme = Timeline.ClassicTheme.create(Timeline.getDefaultLocale());
+    }
+    return Timeline._defaultTheme;
+};
+
+Timeline.setDefaultTheme = function(theme) {
+    Timeline._defaultTheme = theme;
+};
+
+Timeline.loadXML = function(url, f) {
+    var fError = function(statusText, status, xmlhttp) {
+        alert("Failed to load data xml from " + url + "\n" + statusText);
+    };
+    var fDone = function(xmlhttp) {
+        var xml = xmlhttp.responseXML;
+        if (!xml.documentElement && xmlhttp.responseStream) {
+            xml.load(xmlhttp.responseStream);
+        }
+        f(xml, url);
+    };
+    SimileAjax.XmlHttp.get(url, fError, fDone);
+};
+
+
+Timeline.loadJSON = function(url, f) {
+    var fError = function(statusText, status, xmlhttp) {
+        alert("Failed to load json data from " + url + "\n" + statusText);
+    };
+    var fDone = function(xmlhttp) {
+        f(eval('(' + xmlhttp.responseText + ')'), url);
+    };
+    SimileAjax.XmlHttp.get(url, fError, fDone);
+};
+
+Timeline.getTimelineFromID = function(timelineID) {
+    return Timeline.timelines[timelineID];
+};
+
+// Write the current Timeline version as the contents of element with id el_id
+Timeline.writeVersion = function(el_id) {
+  document.getElementById(el_id).innerHTML = this.display_version;
+};
+
+
+
+/*==================================================
+ *  Timeline Implementation object
+ *==================================================
+ */
+Timeline._Impl = function(elmt, bandInfos, orientation, unit, timelineID) {
+    SimileAjax.WindowManager.initialize();
+
+    this._containerDiv = elmt;
+
+    this._bandInfos = bandInfos;
+    this._orientation = orientation == null ? Timeline.HORIZONTAL : orientation;
+    this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit;
+    this._starting = true; // is the Timeline being created? Used by autoWidth
+                           // functions
+    this._autoResizing = false;
+
+    // autoWidth is a "public" property of the Timeline object
+    this.autoWidth = bandInfos && bandInfos[0] && bandInfos[0].theme &&
+                     bandInfos[0].theme.autoWidth;
+    this.autoWidthAnimationTime = bandInfos && bandInfos[0] && bandInfos[0].theme &&
+                     bandInfos[0].theme.autoWidthAnimationTime;
+    this.timelineID = timelineID; // also public attribute
+    this.timeline_start = bandInfos && bandInfos[0] && bandInfos[0].theme &&
+                     bandInfos[0].theme.timeline_start;
+    this.timeline_stop  = bandInfos && bandInfos[0] && bandInfos[0].theme &&
+                     bandInfos[0].theme.timeline_stop;
+    this.timeline_at_start = false; // already at start or stop? Then won't
+    this.timeline_at_stop = false;  // try to move further in the wrong direction
+
+    this._initialize();
+};
+
+//
+// Public functions used by client sw
+//
+Timeline._Impl.prototype.dispose = function() {
+    for (var i = 0; i < this._bands.length; i++) {
+        this._bands[i].dispose();
+    }
+    this._bands = null;
+    this._bandInfos = null;
+    this._containerDiv.innerHTML = "";
+    // remove from array of Timelines
+    Timeline.timelines[this.timelineID] = null;
+};
+
+Timeline._Impl.prototype.getBandCount = function() {
+    return this._bands.length;
+};
+
+Timeline._Impl.prototype.getBand = function(index) {
+    return this._bands[index];
+};
+
+Timeline._Impl.prototype.finishedEventLoading = function() {
+    // Called by client after events have been loaded into Timeline
+    // Only used if the client has set autoWidth
+    // Sets width to Timeline's requested amount and will shrink down the div if
+    // need be.
+    this._autoWidthCheck(true);
+    this._starting = false;
+};
+
+Timeline._Impl.prototype.layout = function() {
+    // called by client when browser is resized
+    this._autoWidthCheck(true);
+    this._distributeWidths();
+};
+
+Timeline._Impl.prototype.paint = function() {
+    for (var i = 0; i < this._bands.length; i++) {
+        this._bands[i].paint();
+    }
+};
+
+Timeline._Impl.prototype.getDocument = function() {
+    return this._containerDiv.ownerDocument;
+};
+
+Timeline._Impl.prototype.addDiv = function(div) {
+    this._containerDiv.appendChild(div);
+};
+
+Timeline._Impl.prototype.removeDiv = function(div) {
+    this._containerDiv.removeChild(div);
+};
+
+Timeline._Impl.prototype.isHorizontal = function() {
+    return this._orientation == Timeline.HORIZONTAL;
+};
+
+Timeline._Impl.prototype.isVertical = function() {
+    return this._orientation == Timeline.VERTICAL;
+};
+
+Timeline._Impl.prototype.getPixelLength = function() {
+    return this._orientation == Timeline.HORIZONTAL ?
+        this._containerDiv.offsetWidth : this._containerDiv.offsetHeight;
+};
+
+Timeline._Impl.prototype.getPixelWidth = function() {
+    return this._orientation == Timeline.VERTICAL ?
+        this._containerDiv.offsetWidth : this._containerDiv.offsetHeight;
+};
+
+Timeline._Impl.prototype.getUnit = function() {
+    return this._unit;
+};
+
+Timeline._Impl.prototype.getWidthStyle = function() {
+    // which element.style attribute should be changed to affect Timeline's "width"
+    return this._orientation == Timeline.HORIZONTAL ? 'height' : 'width';
+};
+
+Timeline._Impl.prototype.loadXML = function(url, f) {
+    var tl = this;
+
+
+    var fError = function(statusText, status, xmlhttp) {
+        alert("Failed to load data xml from " + url + "\n" + statusText);
+        tl.hideLoadingMessage();
+    };
+    var fDone = function(xmlhttp) {
+        try {
+            var xml = xmlhttp.responseXML;
+            if (!xml.documentElement && xmlhttp.responseStream) {
+                xml.load(xmlhttp.responseStream);
+            }
+            f(xml, url);
+        } finally {
+            tl.hideLoadingMessage();
+        }
+    };
+
+    this.showLoadingMessage();
+    window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0);
+};
+
+Timeline._Impl.prototype.loadJSON = function(url, f) {
+    var tl = this;
+
+    var fError = function(statusText, status, xmlhttp) {
+        alert("Failed to load json data from " + url + "\n" + statusText);
+        tl.hideLoadingMessage();
+    };
+    var fDone = function(xmlhttp) {
+        try {
+            f(eval('(' + xmlhttp.responseText + ')'), url);
+        } finally {
+            tl.hideLoadingMessage();
+        }
+    };
+
+    this.showLoadingMessage();
+    window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0);
+};
+
+
+//
+// Private functions used by Timeline object functions
+//
+
+Timeline._Impl.prototype._autoWidthScrollListener = function(band) {
+    band.getTimeline()._autoWidthCheck(false);
+};
+
+// called to re-calculate auto width and adjust the overall Timeline div if needed
+Timeline._Impl.prototype._autoWidthCheck = function(okToShrink) {
+    var timeline = this; // this Timeline
+    var immediateChange = timeline._starting;
+    var newWidth = 0;
+
+    function changeTimelineWidth() {
+        var widthStyle = timeline.getWidthStyle();
+        if (immediateChange) {
+            timeline._containerDiv.style[widthStyle] = newWidth + 'px';
+        } else {
+        	  // animate change
+        	  timeline._autoResizing = true;
+        	  var animateParam ={};
+        	  animateParam[widthStyle] = newWidth + 'px';
+
+        	  SimileAjax.jQuery(timeline._containerDiv).animate(
+        	      animateParam, timeline.autoWidthAnimationTime,
+        	      'linear', function(){timeline._autoResizing = false;});
+        }
+    }
+
+    function checkTimelineWidth() {
+        var targetWidth = 0; // the new desired width
+        var currentWidth = timeline.getPixelWidth();
+
+        if (timeline._autoResizing) {
+        	return; // early return
+        }
+
+        // compute targetWidth
+        for (var i = 0; i < timeline._bands.length; i++) {
+            timeline._bands[i].checkAutoWidth();
+            targetWidth += timeline._bandInfos[i].width;
+        }
+
+        if (targetWidth > currentWidth || okToShrink) {
+            // yes, let's change the size
+            newWidth = targetWidth;
+            changeTimelineWidth();
+            timeline._distributeWidths();
+        }
+    }
+
+    // function's mainline
+    if (!timeline.autoWidth) {
+        return; // early return
+    }
+
+    checkTimelineWidth();
+};
+
+Timeline._Impl.prototype._initialize = function() {
+    var containerDiv = this._containerDiv;
+    var doc = containerDiv.ownerDocument;
+
+    containerDiv.className =
+        containerDiv.className.split(" ").concat("timeline-container").join(" ");
+
+	/*
+	 * Set css-class on container div that will define orientation
+	 */
+	var orientation = (this.isHorizontal()) ? 'horizontal' : 'vertical'
+	containerDiv.className +=' timeline-'+orientation;
+
+
+    while (containerDiv.firstChild) {
+        containerDiv.removeChild(containerDiv.firstChild);
+    }
+
+    /*
+     *  inserting copyright and link to simile
+     */
+    var elmtCopyright = SimileAjax.Graphics.createTranslucentImage(Timeline.urlPrefix + (this.isHorizontal() ? "images/copyright-vertical.png" : "images/copyright.png"));
+    elmtCopyright.className = "timeline-copyright";
+    elmtCopyright.title = "Timeline copyright SIMILE - www.code.google.com/p/simile-widgets/";
+    SimileAjax.DOM.registerEvent(elmtCopyright, "click", function() { window.location = "http://www.simile-widgets.org/"; });
+    containerDiv.appendChild(elmtCopyright);
+
+    /*
+     *  creating bands
+     */
+    this._bands = [];
+    for (var i = 0; i < this._bandInfos.length; i++) {
+        var bandInfo = this._bandInfos[i];
+        var bandClass = bandInfo.bandClass || Timeline._Band;
+        var band = new bandClass(this, this._bandInfos[i], i);
+        this._bands.push(band);
+    }
+    this._distributeWidths();
+
+    /*
+     *  sync'ing bands
+     */
+    for (var i = 0; i < this._bandInfos.length; i++) {
+        var bandInfo = this._bandInfos[i];
+        if ("syncWith" in bandInfo) {
+            this._bands[i].setSyncWithBand(
+                this._bands[bandInfo.syncWith],
+                ("highlight" in bandInfo) ? bandInfo.highlight : false
+            );
+        }
+    }
+
+
+    if (this.autoWidth) {
+        for (var i = 0; i < this._bands.length; i++) {
+            this._bands[i].addOnScrollListener(this._autoWidthScrollListener);
+        }
+    }
+
+
+    /*
+     *  creating loading UI
+     */
+    var message = SimileAjax.Graphics.createMessageBubble(doc);
+    message.containerDiv.className = "timeline-message-container";
+    containerDiv.appendChild(message.containerDiv);
+
+    message.contentDiv.className = "timeline-message";
+    message.contentDiv.innerHTML = "<img src='" + Timeline.urlPrefix + "images/progress-running.gif' /> Loading...";
+
+    this.showLoadingMessage = function() { message.containerDiv.style.display = "block"; };
+    this.hideLoadingMessage = function() { message.containerDiv.style.display = "none"; };
+};
+
+Timeline._Impl.prototype._distributeWidths = function() {
+    var length = this.getPixelLength();
+    var width = this.getPixelWidth();
+    var cumulativeWidth = 0;
+
+    for (var i = 0; i < this._bands.length; i++) {
+        var band = this._bands[i];
+        var bandInfos = this._bandInfos[i];
+        var widthString = bandInfos.width;
+        var bandWidth;
+
+        if (typeof widthString == 'string') {
+          var x =  widthString.indexOf("%");
+          if (x > 0) {
+              var percent = parseInt(widthString.substr(0, x));
+              bandWidth = Math.round(percent * width / 100);
+          } else {
+              bandWidth = parseInt(widthString);
+          }
+        } else {
+        	// was given an integer
+        	bandWidth = widthString;
+        }
+
+        band.setBandShiftAndWidth(cumulativeWidth, bandWidth);
+        band.setViewLength(length);
+
+        cumulativeWidth += bandWidth;
+    }
+};
+
+Timeline._Impl.prototype.shiftOK = function(index, shift) {
+    // Returns true if the proposed shift is ok
+    //
+    // Positive shift means going back in time
+    var going_back = shift > 0,
+        going_forward = shift < 0;
+
+    // Is there an edge?
+    if ((going_back    && this.timeline_start == null) ||
+        (going_forward && this.timeline_stop  == null) ||
+        (shift == 0)) {
+        return (true);  // early return
+    }
+
+    // If any of the bands has noted that it is changing the others,
+    // then this shift is a secondary shift in reaction to the real shift,
+    // which already happened. In such cases, ignore it. (The issue is
+    // that a positive original shift can cause a negative secondary shift,
+    // as the bands adjust.)
+    var secondary_shift = false;
+    for (var i = 0; i < this._bands.length && !secondary_shift; i++) {
+       secondary_shift = this._bands[i].busy();
+    }
+    if (secondary_shift) {
+        return(true); // early return
+    }
+
+    // If we are already at an edge, then don't even think about going any further
+    if ((going_back    && this.timeline_at_start) ||
+        (going_forward && this.timeline_at_stop)) {
+        return (false);  // early return
+    }
+
+    // Need to check all the bands
+    var ok = false; // return value
+    // If any of the bands will be or are showing an ok date, then let the shift proceed.
+    for (var i = 0; i < this._bands.length && !ok; i++) {
+       var band = this._bands[i];
+       if (going_back) {
+           ok = (i == index ? band.getMinVisibleDateAfterDelta(shift) : band.getMinVisibleDate())
+                >= this.timeline_start;
+       } else {
+           ok = (i == index ? band.getMaxVisibleDateAfterDelta(shift) : band.getMaxVisibleDate())
+                <= this.timeline_stop;
+       }
+    }
+
+    // process results
+    if (going_back) {
+       this.timeline_at_start = !ok;
+       this.timeline_at_stop = false;
+    } else {
+       this.timeline_at_stop = !ok;
+       this.timeline_at_start = false;
+    }
+    // This is where you could have an effect once per hitting an
+    // edge of the Timeline. Eg jitter the Timeline
+    //if (!ok) {
+        //alert(going_back ? "At beginning" : "At end");
+    //}
+    return (ok);
+};
+
+Timeline._Impl.prototype.zoom = function (zoomIn, x, y, target) {
+  var matcher = new RegExp("^timeline-band-([0-9]+)$");
+  var bandIndex = null;
+
+  var result = matcher.exec(target.id);
+  if (result) {
+    bandIndex = parseInt(result[1]);
+  }
+
+  if (bandIndex != null) {
+    this._bands[bandIndex].zoom(zoomIn, x, y, target);
+  }
+
+  this.paint();
+};
+
+/*=================================================
+ *
+ * Coding standards:
+ *
+ * We aim towards Douglas Crockford's Javascript conventions.
+ * See:  http://javascript.crockford.com/code.html
+ * See also: http://www.crockford.com/javascript/javascript.html
+ *
+ * That said, this JS code was written before some recent JS
+ * support libraries became widely used or available.
+ * In particular, the _ character is used to indicate a class function or
+ * variable that should be considered private to the class.
+ *
+ * The code mostly uses accessor methods for getting/setting the private
+ * class variables.
+ *
+ * Over time, we'd like to formalize the convention by using support libraries
+ * which enforce privacy in objects.
+ *
+ * We also want to use jslint:  http://www.jslint.com/
+ *
+ *
+ *==================================================
+ */
+
+
+
+/*==================================================
+ *  Band
+ *==================================================
+ */
+Timeline._Band = function(timeline, bandInfo, index) {
+    // hack for easier subclassing
+    if (timeline !== undefined) {
+        this.initialize(timeline, bandInfo, index);
+    }
+};
+
+Timeline._Band.prototype.initialize = function(timeline, bandInfo, index) {
+    // Set up the band's object
+
+    // Munge params: If autoWidth is on for the Timeline, then ensure that
+    // bandInfo.width is an integer
+    if (timeline.autoWidth && typeof bandInfo.width == 'string') {
+        bandInfo.width = bandInfo.width.indexOf("%") > -1 ? 0 : parseInt(bandInfo.width);
+    }
+
+    this._timeline = timeline;
+    this._bandInfo = bandInfo;
+
+    this._index = index;
+
+    this._locale = ("locale" in bandInfo) ? bandInfo.locale : Timeline.getDefaultLocale();
+    this._timeZone = ("timeZone" in bandInfo) ? bandInfo.timeZone : 0;
+    this._labeller = ("labeller" in bandInfo) ? bandInfo.labeller :
+        (("createLabeller" in timeline.getUnit()) ?
+            timeline.getUnit().createLabeller(this._locale, this._timeZone) :
+            new Timeline.GregorianDateLabeller(this._locale, this._timeZone));
+    this._theme = bandInfo.theme;
+    this._zoomIndex = ("zoomIndex" in bandInfo) ? bandInfo.zoomIndex : 0;
+    this._zoomSteps = ("zoomSteps" in bandInfo) ? bandInfo.zoomSteps : null;
+
+    this._dragging = false;
+    this._changing = false;
+    this._originalScrollSpeed = 5; // pixels
+    this._scrollSpeed = this._originalScrollSpeed;
+    this._viewOrthogonalOffset= 0; // vertical offset if the timeline is horizontal, and vice versa
+    this._onScrollListeners = [];
+
+    var b = this;
+    this._syncWithBand = null;
+    this._syncWithBandHandler = function(band) {
+        b._onHighlightBandScroll();
+    };
+    this._selectorListener = function(band) {
+        b._onHighlightBandScroll();
+    };
+
+    /*
+     *  Install a textbox to capture keyboard events
+     */
+    var inputDiv = this._timeline.getDocument().createElement("div");
+    inputDiv.className = "timeline-band-input";
+    this._timeline.addDiv(inputDiv);
+
+    this._keyboardInput = document.createElement("input");
+    this._keyboardInput.type = "text";
+    inputDiv.appendChild(this._keyboardInput);
+    SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keydown", this, "_onKeyDown");
+    SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keyup", this, "_onKeyUp");
+
+    /*
+     *  The band's outer most div that slides with respect to the timeline's div
+     */
+    this._div = this._timeline.getDocument().createElement("div");
+    this._div.id = "timeline-band-" + index;
+    this._div.className = "timeline-band timeline-band-" + index;
+    this._timeline.addDiv(this._div);
+
+    SimileAjax.DOM.registerEventWithObject(this._div, "mousedown", this, "_onMouseDown");
+    SimileAjax.DOM.registerEventWithObject(this._div, "mousemove", this, "_onMouseMove");
+    SimileAjax.DOM.registerEventWithObject(this._div, "mouseup", this, "_onMouseUp");
+    SimileAjax.DOM.registerEventWithObject(this._div, "mouseout", this, "_onMouseOut");
+    SimileAjax.DOM.registerEventWithObject(this._div, "dblclick", this, "_onDblClick");
+
+    var mouseWheel = this._theme!= null ? this._theme.mouseWheel : 'scroll'; // theme is not always defined
+    if (mouseWheel === 'zoom' || mouseWheel === 'scroll' || this._zoomSteps) {
+        // capture mouse scroll
+        if (SimileAjax.Platform.browser.isFirefox) {
+            SimileAjax.DOM.registerEventWithObject(this._div, "DOMMouseScroll", this, "_onMouseScroll");
+        } else {
+            SimileAjax.DOM.registerEventWithObject(this._div, "mousewheel", this, "_onMouseScroll");
+        }
+    }
+
+    /*
+     *  The inner div that contains layers
+     */
+    this._innerDiv = this._timeline.getDocument().createElement("div");
+    this._innerDiv.className = "timeline-band-inner";
+    this._div.appendChild(this._innerDiv);
+
+    /*
+     *  Initialize parts of the band
+     */
+    this._ether = bandInfo.ether;
+    bandInfo.ether.initialize(this, timeline);
+
+    this._etherPainter = bandInfo.etherPainter;
+    bandInfo.etherPainter.initialize(this, timeline);
+
+    this._eventSource = bandInfo.eventSource;
+    if (this._eventSource) {
+        this._eventListener = {
+            onAddMany: function() { b._onAddMany(); },
+            onClear:   function() { b._onClear(); }
+        }
+        this._eventSource.addListener(this._eventListener);
+    }
+
+    this._eventPainter = bandInfo.eventPainter;
+    this._eventTracksNeeded = 0;   // set by painter via updateEventTrackInfo
+    this._eventTrackIncrement = 0;
+    bandInfo.eventPainter.initialize(this, timeline);
+
+    this._decorators = ("decorators" in bandInfo) ? bandInfo.decorators : [];
+    for (var i = 0; i < this._decorators.length; i++) {
+        this._decorators[i].initialize(this, timeline);
+    }
+};
+
+Timeline._Band.SCROLL_MULTIPLES = 5;
+
+Timeline._Band.prototype.dispose = function() {
+    this.closeBubble();
+
+    if (this._eventSource) {
+        this._eventSource.removeListener(this._eventListener);
+        this._eventListener = null;
+        this._eventSource = null;
+    }
+
+    this._timeline = null;
+    this._bandInfo = null;
+
+    this._labeller = null;
+    this._ether = null;
+    this._etherPainter = null;
+    this._eventPainter = null;
+    this._decorators = null;
+
+    this._onScrollListeners = null;
+    this._syncWithBandHandler = null;
+    this._selectorListener = null;
+
+    this._div = null;
+    this._innerDiv = null;
+    this._keyboardInput = null;
+};
+
+Timeline._Band.prototype.addOnScrollListener = function(listener) {
+    this._onScrollListeners.push(listener);
+};
+
+Timeline._Band.prototype.removeOnScrollListener = function(listener) {
+    for (var i = 0; i < this._onScrollListeners.length; i++) {
+        if (this._onScrollListeners[i] == listener) {
+            this._onScrollListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline._Band.prototype.setSyncWithBand = function(band, highlight) {
+    if (this._syncWithBand) {
+        this._syncWithBand.removeOnScrollListener(this._syncWithBandHandler);
+    }
+
+    this._syncWithBand = band;
+    this._syncWithBand.addOnScrollListener(this._syncWithBandHandler);
+    this._highlight = highlight;
+    this._positionHighlight();
+};
+
+Timeline._Band.prototype.getLocale = function() {
+    return this._locale;
+};
+
+Timeline._Band.prototype.getTimeZone = function() {
+    return this._timeZone;
+};
+
+Timeline._Band.prototype.getLabeller = function() {
+    return this._labeller;
+};
+
+Timeline._Band.prototype.getIndex = function() {
+    return this._index;
+};
+
+Timeline._Band.prototype.getEther = function() {
+    return this._ether;
+};
+
+Timeline._Band.prototype.getEtherPainter = function() {
+    return this._etherPainter;
+};
+
+Timeline._Band.prototype.getEventSource = function() {
+    return this._eventSource;
+};
+
+Timeline._Band.prototype.getEventPainter = function() {
+    return this._eventPainter;
+};
+
+Timeline._Band.prototype.getTimeline = function() {
+    return this._timeline;
+};
+
+// Autowidth support
+Timeline._Band.prototype.updateEventTrackInfo = function(tracks, increment) {
+    this._eventTrackIncrement = increment; // doesn't vary for a specific band
+
+    if (tracks > this._eventTracksNeeded) {
+        this._eventTracksNeeded = tracks;
+    }
+};
+
+// Autowidth support
+Timeline._Band.prototype.checkAutoWidth = function() {
+    // if a new (larger) width is needed by the band
+    // then: a) updates the band's bandInfo.width
+    //
+    // desiredWidth for the band is
+    //   (number of tracks + margin) * track increment
+    if (! this._timeline.autoWidth) {
+      return; // early return
+    }
+
+    var overviewBand = this._eventPainter.getType() == 'overview';
+    var margin = overviewBand ?
+       this._theme.event.overviewTrack.autoWidthMargin :
+       this._theme.event.track.autoWidthMargin;
+    var desiredWidth = Math.ceil((this._eventTracksNeeded + margin) *
+                       this._eventTrackIncrement);
+    // add offset amount (additional margin)
+    desiredWidth += overviewBand ? this._theme.event.overviewTrack.offset :
+                                   this._theme.event.track.offset;
+    var bandInfo = this._bandInfo;
+
+    if (desiredWidth != bandInfo.width) {
+        bandInfo.width = desiredWidth;
+    }
+};
+
+Timeline._Band.prototype.layout = function() {
+    this.paint();
+};
+
+Timeline._Band.prototype.paint = function() {
+    this._etherPainter.paint();
+    this._paintDecorators();
+    this._paintEvents();
+};
+
+Timeline._Band.prototype.softLayout = function() {
+    this.softPaint();
+};
+
+Timeline._Band.prototype.softPaint = function() {
+    this._etherPainter.softPaint();
+    this._softPaintDecorators();
+    this._softPaintEvents();
+};
+
+Timeline._Band.prototype.setBandShiftAndWidth = function(shift, width) {
+    var inputDiv = this._keyboardInput.parentNode;
+    var middle = shift + Math.floor(width / 2);
+    if (this._timeline.isHorizontal()) {
+        this._div.style.top = shift + "px";
+        this._div.style.height = width + "px";
+
+        inputDiv.style.top = middle + "px";
+        inputDiv.style.left = "-1em";
+    } else {
+        this._div.style.left = shift + "px";
+        this._div.style.width = width + "px";
+
+        inputDiv.style.left = middle + "px";
+        inputDiv.style.top = "-1em";
+    }
+};
+
+Timeline._Band.prototype.getViewWidth = function() {
+    if (this._timeline.isHorizontal()) {
+        return this._div.offsetHeight;
+    } else {
+        return this._div.offsetWidth;
+    }
+};
+
+Timeline._Band.prototype.setViewLength = function(length) {
+    this._viewLength = length;
+    this._recenterDiv();
+    this._onChanging();
+};
+
+Timeline._Band.prototype.getViewLength = function() {
+    return this._viewLength;
+};
+
+Timeline._Band.prototype.getTotalViewLength = function() {
+    return Timeline._Band.SCROLL_MULTIPLES * this._viewLength;
+};
+
+Timeline._Band.prototype.getViewOffset = function() {
+    return this._viewOffset;
+};
+
+Timeline._Band.prototype.getMinDate = function() {
+    return this._ether.pixelOffsetToDate(this._viewOffset);
+};
+
+Timeline._Band.prototype.getMaxDate = function() {
+    return this._ether.pixelOffsetToDate(this._viewOffset + Timeline._Band.SCROLL_MULTIPLES * this._viewLength);
+};
+
+Timeline._Band.prototype.getMinVisibleDate = function() {
+    return this._ether.pixelOffsetToDate(0);
+};
+
+Timeline._Band.prototype.getMinVisibleDateAfterDelta = function(delta) {
+    return this._ether.pixelOffsetToDate(delta);
+};
+
+Timeline._Band.prototype.getMaxVisibleDate = function() {
+    // Max date currently visible on band
+    return this._ether.pixelOffsetToDate(this._viewLength);
+};
+
+Timeline._Band.prototype.getMaxVisibleDateAfterDelta = function(delta) {
+    // Max date visible on band after delta px view change is applied
+    return this._ether.pixelOffsetToDate(this._viewLength + delta);
+};
+
+Timeline._Band.prototype.getCenterVisibleDate = function() {
+    return this._ether.pixelOffsetToDate(this._viewLength / 2);
+};
+
+Timeline._Band.prototype.setMinVisibleDate = function(date) {
+    if (!this._changing) {
+        this._moveEther(Math.round(-this._ether.dateToPixelOffset(date)));
+    }
+};
+
+Timeline._Band.prototype.setMaxVisibleDate = function(date) {
+    if (!this._changing) {
+        this._moveEther(Math.round(this._viewLength - this._ether.dateToPixelOffset(date)));
+    }
+};
+
+Timeline._Band.prototype.setCenterVisibleDate = function(date) {
+    if (!this._changing) {
+        this._moveEther(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date)));
+    }
+};
+
+Timeline._Band.prototype.dateToPixelOffset = function(date) {
+    return this._ether.dateToPixelOffset(date) - this._viewOffset;
+};
+
+Timeline._Band.prototype.pixelOffsetToDate = function(pixels) {
+    return this._ether.pixelOffsetToDate(pixels + this._viewOffset);
+};
+
+Timeline._Band.prototype.getViewOrthogonalOffset = function() {
+    return this._viewOrthogonalOffset;
+};
+
+Timeline._Band.prototype.setViewOrthogonalOffset = function(offset) {
+    this._viewOrthogonalOffset = Math.max(0, offset);
+};
+
+Timeline._Band.prototype.createLayerDiv = function(zIndex, className) {
+    var div = this._timeline.getDocument().createElement("div");
+    div.className = "timeline-band-layer" + (typeof className == "string" ? (" " + className) : "");
+    div.style.zIndex = zIndex;
+    this._innerDiv.appendChild(div);
+
+    var innerDiv = this._timeline.getDocument().createElement("div");
+    innerDiv.className = "timeline-band-layer-inner";
+    if (SimileAjax.Platform.browser.isIE) {
+        innerDiv.style.cursor = "move";
+    } else {
+        innerDiv.style.cursor = "-moz-grab";
+    }
+    div.appendChild(innerDiv);
+
+    return innerDiv;
+};
+
+Timeline._Band.prototype.removeLayerDiv = function(div) {
+    this._innerDiv.removeChild(div.parentNode);
+};
+
+Timeline._Band.prototype.scrollToCenter = function(date, f) {
+    var pixelOffset = this._ether.dateToPixelOffset(date);
+    if (pixelOffset < -this._viewLength / 2) {
+        this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset + this._viewLength));
+    } else if (pixelOffset > 3 * this._viewLength / 2) {
+        this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset - this._viewLength));
+    }
+    this._autoScroll(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date)), f);
+};
+
+Timeline._Band.prototype.showBubbleForEvent = function(eventID) {
+    var evt = this.getEventSource().getEvent(eventID);
+    if (evt) {
+        var self = this;
+        this.scrollToCenter(evt.getStart(), function() {
+            self._eventPainter.showBubble(evt);
+        });
+    }
+};
+
+Timeline._Band.prototype.zoom = function(zoomIn, x, y, target) {
+  if (!this._zoomSteps) {
+    // zoom disabled
+    return;
+  }
+
+  // shift the x value by our offset
+  x += this._viewOffset;
+
+  var zoomDate = this._ether.pixelOffsetToDate(x);
+  var netIntervalChange = this._ether.zoom(zoomIn);
+  this._etherPainter.zoom(netIntervalChange);
+
+  // shift our zoom date to the far left
+  this._moveEther(Math.round(-this._ether.dateToPixelOffset(zoomDate)));
+  // then shift it back to where the mouse was
+  this._moveEther(x);
+};
+
+Timeline._Band.prototype._onMouseDown = function(innerFrame, evt, target) {
+    this.closeBubble();
+
+    this._dragging = true;
+    this._dragX = evt.clientX;
+    this._dragY = evt.clientY;
+};
+
+Timeline._Band.prototype._onMouseMove = function(innerFrame, evt, target) {
+    if (this._dragging) {
+        var diffX = evt.clientX - this._dragX;
+        var diffY = evt.clientY - this._dragY;
+
+        this._dragX = evt.clientX;
+        this._dragY = evt.clientY;
+
+        if (this._timeline.isHorizontal()) {
+            this._moveEther(diffX, diffY);
+        } else {
+            this._moveEther(diffY, diffX);
+        }
+        this._positionHighlight();
+    }
+};
+
+Timeline._Band.prototype._onMouseUp = function(innerFrame, evt, target) {
+    this._dragging = false;
+    this._keyboardInput.focus();
+};
+
+Timeline._Band.prototype._onMouseOut = function(innerFrame, evt, target) {
+    var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
+    coords.x += this._viewOffset;
+    if (coords.x < 0 || coords.x > innerFrame.offsetWidth ||
+        coords.y < 0 || coords.y > innerFrame.offsetHeight) {
+        this._dragging = false;
+    }
+};
+
+Timeline._Band.prototype._onMouseScroll = function(innerFrame, evt, target) {
+  var now = new Date();
+  now = now.getTime();
+
+  if (!this._lastScrollTime || ((now - this._lastScrollTime) > 50)) {
+    // limit 1 scroll per 200ms due to FF3 sending multiple events back to back
+    this._lastScrollTime = now;
+
+    var delta = 0;
+    if (evt.wheelDelta) {
+      delta = evt.wheelDelta/120;
+    } else if (evt.detail) {
+      delta = -evt.detail/3;
+    }
+
+    // either scroll or zoom
+    var mouseWheel = this._theme.mouseWheel;
+
+    if (this._zoomSteps || mouseWheel === 'zoom') {
+      var loc = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
+      if (delta != 0) {
+        var zoomIn;
+        if (delta > 0)
+          zoomIn = true;
+        if (delta < 0)
+          zoomIn = false;
+        // call zoom on the timeline so we could zoom multiple bands if desired
+        this._timeline.zoom(zoomIn, loc.x, loc.y, innerFrame);
+      }
+    }
+    else if (mouseWheel === 'scroll') {
+    	var move_amt = 50 * (delta < 0 ? -1 : 1);
+      this._moveEther(move_amt);
+    }
+  }
+
+  // prevent bubble
+  if (evt.stopPropagation) {
+    evt.stopPropagation();
+  }
+  evt.cancelBubble = true;
+
+  // prevent the default action
+  if (evt.preventDefault) {
+    evt.preventDefault();
+  }
+  evt.returnValue = false;
+};
+
+Timeline._Band.prototype._onDblClick = function(innerFrame, evt, target) {
+    var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame);
+    var distance = coords.x - (this._viewLength / 2 - this._viewOffset);
+
+    this._autoScroll(-distance);
+};
+
+Timeline._Band.prototype._onKeyDown = function(keyboardInput, evt, target) {
+    if (!this._dragging) {
+        switch (evt.keyCode) {
+        case 27: // ESC
+            break;
+        case 37: // left arrow
+        case 38: // up arrow
+            this._scrollSpeed = Math.min(50, Math.abs(this._scrollSpeed * 1.05));
+            this._moveEther(this._scrollSpeed);
+            break;
+        case 39: // right arrow
+        case 40: // down arrow
+            this._scrollSpeed = -Math.min(50, Math.abs(this._scrollSpeed * 1.05));
+            this._moveEther(this._scrollSpeed);
+            break;
+        default:
+            return true;
+        }
+        this.closeBubble();
+
+        SimileAjax.DOM.cancelEvent(evt);
+        return false;
+    }
+    return true;
+};
+
+Timeline._Band.prototype._onKeyUp = function(keyboardInput, evt, target) {
+    if (!this._dragging) {
+        this._scrollSpeed = this._originalScrollSpeed;
+
+        switch (evt.keyCode) {
+        case 35: // end
+            this.setCenterVisibleDate(this._eventSource.getLatestDate());
+            break;
+        case 36: // home
+            this.setCenterVisibleDate(this._eventSource.getEarliestDate());
+            break;
+        case 33: // page up
+            this._autoScroll(this._timeline.getPixelLength());
+            break;
+        case 34: // page down
+            this._autoScroll(-this._timeline.getPixelLength());
+            break;
+        default:
+            return true;
+        }
+
+        this.closeBubble();
+
+        SimileAjax.DOM.cancelEvent(evt);
+        return false;
+    }
+    return true;
+};
+
+Timeline._Band.prototype._autoScroll = function(distance, f) {
+    var b = this;
+    var a = SimileAjax.Graphics.createAnimation(
+        function(abs, diff) {
+            b._moveEther(diff);
+        },
+        0,
+        distance,
+        1000,
+        f
+    );
+    a.run();
+};
+
+Timeline._Band.prototype._moveEther = function(shift, orthogonalShift) {
+    if (orthogonalShift === undefined) {
+        orthogonalShift = 0;
+    }
+
+    this.closeBubble();
+
+    // A positive shift means back in time
+    // Check that we're not moving beyond Timeline's limits
+    if (!this._timeline.shiftOK(this._index, shift)) {
+        return; // early return
+    }
+
+    this._viewOffset += shift;
+    this._viewOrthogonalOffset = Math.min(0, this._viewOrthogonalOffset + orthogonalShift);
+
+    this._ether.shiftPixels(-shift);
+    if (this._timeline.isHorizontal()) {
+        this._div.style.left = this._viewOffset + "px";
+    } else {
+        this._div.style.top = this._viewOffset + "px";
+    }
+
+    if (this._viewOffset > -this._viewLength * 0.5 ||
+        this._viewOffset < -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1.5)) {
+
+        this._recenterDiv();
+    } else {
+        this.softLayout();
+    }
+
+    this._onChanging();
+}
+
+Timeline._Band.prototype._onChanging = function() {
+    this._changing = true;
+
+    this._fireOnScroll();
+    this._setSyncWithBandDate();
+
+    this._changing = false;
+};
+
+Timeline._Band.prototype.busy = function() {
+    // Is this band busy changing other bands?
+    return(this._changing);
+};
+
+Timeline._Band.prototype._fireOnScroll = function() {
+    for (var i = 0; i < this._onScrollListeners.length; i++) {
+        this._onScrollListeners[i](this);
+    }
+};
+
+Timeline._Band.prototype._setSyncWithBandDate = function() {
+    if (this._syncWithBand) {
+        var centerDate = this._ether.pixelOffsetToDate(this.getViewLength() / 2);
+        this._syncWithBand.setCenterVisibleDate(centerDate);
+    }
+};
+
+Timeline._Band.prototype._onHighlightBandScroll = function() {
+    if (this._syncWithBand) {
+        var centerDate = this._syncWithBand.getCenterVisibleDate();
+        var centerPixelOffset = this._ether.dateToPixelOffset(centerDate);
+
+        this._moveEther(Math.round(this._viewLength / 2 - centerPixelOffset));
+
+        if (this._highlight) {
+            this._etherPainter.setHighlight(
+                this._syncWithBand.getMinVisibleDate(),
+                this._syncWithBand.getMaxVisibleDate());
+        }
+    }
+};
+
+Timeline._Band.prototype._onAddMany = function() {
+    this._paintEvents();
+};
+
+Timeline._Band.prototype._onClear = function() {
+    this._paintEvents();
+};
+
+Timeline._Band.prototype._positionHighlight = function() {
+    if (this._syncWithBand) {
+        var startDate = this._syncWithBand.getMinVisibleDate();
+        var endDate = this._syncWithBand.getMaxVisibleDate();
+
+        if (this._highlight) {
+            this._etherPainter.setHighlight(startDate, endDate);
+        }
+    }
+};
+
+Timeline._Band.prototype._recenterDiv = function() {
+    this._viewOffset = -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1) / 2;
+    if (this._timeline.isHorizontal()) {
+        this._div.style.left = this._viewOffset + "px";
+        this._div.style.width = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px";
+    } else {
+        this._div.style.top = this._viewOffset + "px";
+        this._div.style.height = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px";
+    }
+    this.layout();
+};
+
+Timeline._Band.prototype._paintEvents = function() {
+    this._eventPainter.paint();
+};
+
+Timeline._Band.prototype._softPaintEvents = function() {
+    this._eventPainter.softPaint();
+};
+
+Timeline._Band.prototype._paintDecorators = function() {
+    for (var i = 0; i < this._decorators.length; i++) {
+        this._decorators[i].paint();
+    }
+};
+
+Timeline._Band.prototype._softPaintDecorators = function() {
+    for (var i = 0; i < this._decorators.length; i++) {
+        this._decorators[i].softPaint();
+    }
+};
+
+Timeline._Band.prototype.closeBubble = function() {
+    SimileAjax.WindowManager.cancelPopups();
+};
+/*==================================================
+ *  Classic Theme
+ *==================================================
+ */
+
+
+
+Timeline.ClassicTheme = new Object();
+
+Timeline.ClassicTheme.implementations = [];
+
+Timeline.ClassicTheme.create = function(locale) {
+    if (locale == null) {
+        locale = Timeline.getDefaultLocale();
+    }
+
+    var f = Timeline.ClassicTheme.implementations[locale];
+    if (f == null) {
+        f = Timeline.ClassicTheme._Impl;
+    }
+    return new f();
+};
+
+Timeline.ClassicTheme._Impl = function() {
+    this.firstDayOfWeek = 0; // Sunday
+
+    // Note: Many styles previously set here are now set using CSS
+    //       The comments indicate settings controlled by CSS, not
+    //       lines to be un-commented.
+    //
+    //
+    // Attributes autoWidth, autoWidthAnimationTime, timeline_start
+    // and timeline_stop must be set on the first band's theme.
+    // The other attributes can be set differently for each
+    // band by using different themes for the bands.
+    this.autoWidth = false; // Should the Timeline automatically grow itself, as
+                            // needed when too many events for the available width
+                            // are painted on the visible part of the Timeline?
+    this.autoWidthAnimationTime = 500; // mSec
+    this.timeline_start = null; // Setting a date, eg new Date(Date.UTC(2008,0,17,20,00,00,0)) will prevent the
+                                // Timeline from being moved to anytime before the date.
+    this.timeline_stop = null;  // Use for setting a maximum date. The Timeline will not be able
+                                // to be moved to anytime after this date.
+    this.ether = {
+        backgroundColors: [
+        //    "#EEE",
+        //    "#DDD",
+        //    "#CCC",
+        //    "#AAA"
+        ],
+     //   highlightColor:     "white",
+        highlightOpacity:   50,
+        interval: {
+            line: {
+                show:       true,
+                opacity:    25
+               // color:      "#aaa",
+            },
+            weekend: {
+                opacity:    30
+              //  color:      "#FFFFE0",
+            },
+            marker: {
+                hAlign:     "Bottom",
+                vAlign:     "Right"
+                                        /*
+                hBottomStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-bottom";
+                },
+                hBottomEmphasizedStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-bottom-emphasized";
+                },
+                hTopStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-top";
+                },
+                hTopEmphasizedStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-top-emphasized";
+                },
+                */
+
+
+               /*
+                                  vRightStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-right";
+                },
+                vRightEmphasizedStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-right-emphasized";
+                },
+                vLeftStyler: function(elmt) {
+                    elmt.className = "timeline-ether-marker-left";
+                },
+                vLeftEmphasizedStyler:function(elmt) {
+                    elmt.className = "timeline-ether-marker-left-emphasized";
+                }
+                */
+            }
+        }
+    };
+
+    this.event = {
+        track: {
+                   height: 10, // px. You will need to change the track
+                               //     height if you change the tape height.
+                      gap:  2, // px. Gap between tracks
+                   offset:  2, // px. top margin above tapes
+          autoWidthMargin:  1.5
+          /* autoWidthMargin is only used if autoWidth (see above) is true.
+             The autoWidthMargin setting is used to set how close the bottom of the
+             lowest track is to the edge of the band's div. The units are total track
+             width (tape + label + gap). A min of 0.5 is suggested. Use this setting to
+             move the bottom track's tapes above the axis markers, if needed for your
+             Timeline.
+          */
+        },
+        overviewTrack: {
+                  offset: 20, // px -- top margin above tapes
+              tickHeight:  6, // px
+                  height:  2, // px
+                     gap:  1, // px
+         autoWidthMargin:  5 // This attribute is only used if autoWidth (see above) is true.
+        },
+        tape: {
+            height:         4 // px. For thicker tapes, remember to change track height too.
+        },
+        instant: {
+                           icon: Timeline.urlPrefix + "images/dull-blue-circle.png",
+                                 // default icon. Icon can also be specified per event
+                      iconWidth: 10,
+                     iconHeight: 10,
+               impreciseOpacity: 20, // opacity of the tape when durationEvent is false
+            impreciseIconMargin: 3   // A tape and an icon are painted for imprecise instant
+                                     // events. This attribute is the margin between the
+                                     // bottom of the tape and the top of the icon in that
+                                     // case.
+    //        color:             "#58A0DC",
+    //        impreciseColor:    "#58A0DC",
+        },
+        duration: {
+            impreciseOpacity: 20 // tape opacity for imprecise part of duration events
+      //      color:            "#58A0DC",
+      //      impreciseColor:   "#58A0DC",
+        },
+        label: {
+            backgroundOpacity: 50,// only used in detailed painter
+               offsetFromLine:  3 // px left margin amount from icon's right edge
+      //      backgroundColor:   "white",
+      //      lineColor:         "#58A0DC",
+        },
+        highlightColors: [  // Use with getEventPainter().setHighlightMatcher
+                            // See webapp/examples/examples.js
+            "#FFFF00",
+            "#FFC000",
+            "#FF0000",
+            "#0000FF"
+        ],
+        highlightLabelBackground: false, // When highlighting an event, also change the event's label background?
+        bubble: {
+            width:          250, // px
+            maxHeight:        0, // px Maximum height of bubbles. 0 means no max height.
+                                 // scrollbar will be added for taller bubbles
+            titleStyler: function(elmt) {
+                elmt.className = "timeline-event-bubble-title";
+            },
+            bodyStyler: function(elmt) {
+                elmt.className = "timeline-event-bubble-body";
+            },
+            imageStyler: function(elmt) {
+                elmt.className = "timeline-event-bubble-image";
+            },
+            wikiStyler: function(elmt) {
+                elmt.className = "timeline-event-bubble-wiki";
+            },
+            timeStyler: function(elmt) {
+                elmt.className = "timeline-event-bubble-time";
+            }
+        }
+    };
+
+    this.mouseWheel = 'scroll'; // 'default', 'zoom', 'scroll'
+};/*==================================================
+ *  An "ether" is a object that maps date/time to pixel coordinates.
+ *==================================================
+ */
+
+/*==================================================
+ *  Linear Ether
+ *==================================================
+ */
+
+Timeline.LinearEther = function(params) {
+    this._params = params;
+    this._interval = params.interval;
+    this._pixelsPerInterval = params.pixelsPerInterval;
+};
+
+Timeline.LinearEther.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+    this._unit = timeline.getUnit();
+
+    if ("startsOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.startsOn);
+    } else if ("endsOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.endsOn);
+        this.shiftPixels(-this._timeline.getPixelLength());
+    } else if ("centersOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.centersOn);
+        this.shiftPixels(-this._timeline.getPixelLength() / 2);
+    } else {
+        this._start = this._unit.makeDefaultValue();
+        this.shiftPixels(-this._timeline.getPixelLength() / 2);
+    }
+};
+
+Timeline.LinearEther.prototype.setDate = function(date) {
+    this._start = this._unit.cloneValue(date);
+};
+
+Timeline.LinearEther.prototype.shiftPixels = function(pixels) {
+    var numeric = this._interval * pixels / this._pixelsPerInterval;
+    this._start = this._unit.change(this._start, numeric);
+};
+
+Timeline.LinearEther.prototype.dateToPixelOffset = function(date) {
+    var numeric = this._unit.compare(date, this._start);
+    return this._pixelsPerInterval * numeric / this._interval;
+};
+
+Timeline.LinearEther.prototype.pixelOffsetToDate = function(pixels) {
+    var numeric = pixels * this._interval / this._pixelsPerInterval;
+    return this._unit.change(this._start, numeric);
+};
+
+Timeline.LinearEther.prototype.zoom = function(zoomIn) {
+  var netIntervalChange = 0;
+  var currentZoomIndex = this._band._zoomIndex;
+  var newZoomIndex = currentZoomIndex;
+
+  if (zoomIn && (currentZoomIndex > 0)) {
+    newZoomIndex = currentZoomIndex - 1;
+  }
+
+  if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) {
+    newZoomIndex = currentZoomIndex + 1;
+  }
+
+  this._band._zoomIndex = newZoomIndex;
+  this._interval =
+    SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit];
+  this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval;
+  netIntervalChange = this._band._zoomSteps[newZoomIndex].unit -
+    this._band._zoomSteps[currentZoomIndex].unit;
+
+  return netIntervalChange;
+};
+
+
+/*==================================================
+ *  Hot Zone Ether
+ *==================================================
+ */
+
+Timeline.HotZoneEther = function(params) {
+    this._params = params;
+    this._interval = params.interval;
+    this._pixelsPerInterval = params.pixelsPerInterval;
+    this._theme = params.theme;
+};
+
+Timeline.HotZoneEther.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+    this._unit = timeline.getUnit();
+
+    this._zones = [{
+        startTime:  Number.NEGATIVE_INFINITY,
+        endTime:    Number.POSITIVE_INFINITY,
+        magnify:    1
+    }];
+    var params = this._params;
+    for (var i = 0; i < params.zones.length; i++) {
+        var zone = params.zones[i];
+        var zoneStart = this._unit.parseFromObject(zone.start);
+        var zoneEnd =   this._unit.parseFromObject(zone.end);
+
+        for (var j = 0; j < this._zones.length && this._unit.compare(zoneEnd, zoneStart) > 0; j++) {
+            var zone2 = this._zones[j];
+
+            if (this._unit.compare(zoneStart, zone2.endTime) < 0) {
+                if (this._unit.compare(zoneStart, zone2.startTime) > 0) {
+                    this._zones.splice(j, 0, {
+                        startTime:   zone2.startTime,
+                        endTime:     zoneStart,
+                        magnify:     zone2.magnify
+                    });
+                    j++;
+
+                    zone2.startTime = zoneStart;
+                }
+
+                if (this._unit.compare(zoneEnd, zone2.endTime) < 0) {
+                    this._zones.splice(j, 0, {
+                        startTime:  zoneStart,
+                        endTime:    zoneEnd,
+                        magnify:    zone.magnify * zone2.magnify
+                    });
+                    j++;
+
+                    zone2.startTime = zoneEnd;
+                    zoneStart = zoneEnd;
+                } else {
+                    zone2.magnify *= zone.magnify;
+                    zoneStart = zone2.endTime;
+                }
+            } // else, try the next existing zone
+        }
+    }
+
+    if ("startsOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.startsOn);
+    } else if ("endsOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.endsOn);
+        this.shiftPixels(-this._timeline.getPixelLength());
+    } else if ("centersOn" in this._params) {
+        this._start = this._unit.parseFromObject(this._params.centersOn);
+        this.shiftPixels(-this._timeline.getPixelLength() / 2);
+    } else {
+        this._start = this._unit.makeDefaultValue();
+        this.shiftPixels(-this._timeline.getPixelLength() / 2);
+    }
+};
+
+Timeline.HotZoneEther.prototype.setDate = function(date) {
+    this._start = this._unit.cloneValue(date);
+};
+
+Timeline.HotZoneEther.prototype.shiftPixels = function(pixels) {
+    this._start = this.pixelOffsetToDate(pixels);
+};
+
+Timeline.HotZoneEther.prototype.dateToPixelOffset = function(date) {
+    return this._dateDiffToPixelOffset(this._start, date);
+};
+
+Timeline.HotZoneEther.prototype.pixelOffsetToDate = function(pixels) {
+    return this._pixelOffsetToDate(pixels, this._start);
+};
+
+Timeline.HotZoneEther.prototype.zoom = function(zoomIn) {
+  var netIntervalChange = 0;
+  var currentZoomIndex = this._band._zoomIndex;
+  var newZoomIndex = currentZoomIndex;
+
+  if (zoomIn && (currentZoomIndex > 0)) {
+    newZoomIndex = currentZoomIndex - 1;
+  }
+
+  if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) {
+    newZoomIndex = currentZoomIndex + 1;
+  }
+
+  this._band._zoomIndex = newZoomIndex;
+  this._interval =
+    SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit];
+  this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval;
+  netIntervalChange = this._band._zoomSteps[newZoomIndex].unit -
+    this._band._zoomSteps[currentZoomIndex].unit;
+
+  return netIntervalChange;
+};
+
+Timeline.HotZoneEther.prototype._dateDiffToPixelOffset = function(fromDate, toDate) {
+    var scale = this._getScale();
+    var fromTime = fromDate;
+    var toTime = toDate;
+
+    var pixels = 0;
+    if (this._unit.compare(fromTime, toTime) < 0) {
+        var z = 0;
+        while (z < this._zones.length) {
+            if (this._unit.compare(fromTime, this._zones[z].endTime) < 0) {
+                break;
+            }
+            z++;
+        }
+
+        while (this._unit.compare(fromTime, toTime) < 0) {
+            var zone = this._zones[z];
+            var toTime2 = this._unit.earlier(toTime, zone.endTime);
+
+            pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify));
+
+            fromTime = toTime2;
+            z++;
+        }
+    } else {
+        var z = this._zones.length - 1;
+        while (z >= 0) {
+            if (this._unit.compare(fromTime, this._zones[z].startTime) > 0) {
+                break;
+            }
+            z--;
+        }
+
+        while (this._unit.compare(fromTime, toTime) > 0) {
+            var zone = this._zones[z];
+            var toTime2 = this._unit.later(toTime, zone.startTime);
+
+            pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify));
+
+            fromTime = toTime2;
+            z--;
+        }
+    }
+    return pixels;
+};
+
+Timeline.HotZoneEther.prototype._pixelOffsetToDate = function(pixels, fromDate) {
+    var scale = this._getScale();
+    var time = fromDate;
+    if (pixels > 0) {
+        var z = 0;
+        while (z < this._zones.length) {
+            if (this._unit.compare(time, this._zones[z].endTime) < 0) {
+                break;
+            }
+            z++;
+        }
+
+        while (pixels > 0) {
+            var zone = this._zones[z];
+            var scale2 = scale / zone.magnify;
+
+            if (zone.endTime == Number.POSITIVE_INFINITY) {
+                time = this._unit.change(time, pixels * scale2);
+                pixels = 0;
+            } else {
+                var pixels2 = this._unit.compare(zone.endTime, time) / scale2;
+                if (pixels2 > pixels) {
+                    time = this._unit.change(time, pixels * scale2);
+                    pixels = 0;
+                } else {
+                    time = zone.endTime;
+                    pixels -= pixels2;
+                }
+            }
+            z++;
+        }
+    } else {
+        var z = this._zones.length - 1;
+        while (z >= 0) {
+            if (this._unit.compare(time, this._zones[z].startTime) > 0) {
+                break;
+            }
+            z--;
+        }
+
+        pixels = -pixels;
+        while (pixels > 0) {
+            var zone = this._zones[z];
+            var scale2 = scale / zone.magnify;
+
+            if (zone.startTime == Number.NEGATIVE_INFINITY) {
+                time = this._unit.change(time, -pixels * scale2);
+                pixels = 0;
+            } else {
+                var pixels2 = this._unit.compare(time, zone.startTime) / scale2;
+                if (pixels2 > pixels) {
+                    time = this._unit.change(time, -pixels * scale2);
+                    pixels = 0;
+                } else {
+                    time = zone.startTime;
+                    pixels -= pixels2;
+                }
+            }
+            z--;
+        }
+    }
+    return time;
+};
+
+Timeline.HotZoneEther.prototype._getScale = function() {
+    return this._interval / this._pixelsPerInterval;
+};
+/*==================================================
+ *  Gregorian Ether Painter
+ *==================================================
+ */
+
+Timeline.GregorianEtherPainter = function(params) {
+    this._params = params;
+    this._theme = params.theme;
+    this._unit = params.unit;
+    this._multiple = ("multiple" in params) ? params.multiple : 1;
+};
+
+Timeline.GregorianEtherPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backgroundLayer = band.createLayerDiv(0);
+    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
+    this._backgroundLayer.className = 'timeline-ether-bg';
+  //  this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
+
+
+    this._markerLayer = null;
+    this._lineLayer = null;
+
+    var align = ("align" in this._params && this._params.align != undefined) ? this._params.align :
+        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
+    var showLine = ("showLine" in this._params) ? this._params.showLine :
+        this._theme.ether.interval.line.show;
+
+    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
+        this._timeline, this._band, this._theme, align, showLine);
+
+    this._highlight = new Timeline.EtherHighlight(
+        this._timeline, this._band, this._theme, this._backgroundLayer);
+}
+
+Timeline.GregorianEtherPainter.prototype.setHighlight = function(startDate, endDate) {
+    this._highlight.position(startDate, endDate);
+}
+
+Timeline.GregorianEtherPainter.prototype.paint = function() {
+    if (this._markerLayer) {
+        this._band.removeLayerDiv(this._markerLayer);
+    }
+    this._markerLayer = this._band.createLayerDiv(100);
+    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
+    this._markerLayer.style.display = "none";
+
+    if (this._lineLayer) {
+        this._band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = this._band.createLayerDiv(1);
+    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
+    this._lineLayer.style.display = "none";
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var timeZone = this._band.getTimeZone();
+    var labeller = this._band.getLabeller();
+
+    SimileAjax.DateTime.roundDownToInterval(minDate, this._unit, timeZone, this._multiple, this._theme.firstDayOfWeek);
+
+    var p = this;
+    var incrementDate = function(date) {
+        for (var i = 0; i < p._multiple; i++) {
+            SimileAjax.DateTime.incrementByInterval(date, p._unit);
+        }
+    };
+
+    while (minDate.getTime() < maxDate.getTime()) {
+        this._intervalMarkerLayout.createIntervalMarker(
+            minDate, labeller, this._unit, this._markerLayer, this._lineLayer);
+
+        incrementDate(minDate);
+    }
+    this._markerLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+};
+
+Timeline.GregorianEtherPainter.prototype.softPaint = function() {
+};
+
+Timeline.GregorianEtherPainter.prototype.zoom = function(netIntervalChange) {
+  if (netIntervalChange != 0) {
+    this._unit += netIntervalChange;
+  }
+};
+
+
+/*==================================================
+ *  Hot Zone Gregorian Ether Painter
+ *==================================================
+ */
+
+Timeline.HotZoneGregorianEtherPainter = function(params) {
+    this._params = params;
+    this._theme = params.theme;
+
+    this._zones = [{
+        startTime:  Number.NEGATIVE_INFINITY,
+        endTime:    Number.POSITIVE_INFINITY,
+        unit:       params.unit,
+        multiple:   1
+    }];
+    for (var i = 0; i < params.zones.length; i++) {
+        var zone = params.zones[i];
+        var zoneStart = SimileAjax.DateTime.parseGregorianDateTime(zone.start).getTime();
+        var zoneEnd = SimileAjax.DateTime.parseGregorianDateTime(zone.end).getTime();
+
+        for (var j = 0; j < this._zones.length && zoneEnd > zoneStart; j++) {
+            var zone2 = this._zones[j];
+
+            if (zoneStart < zone2.endTime) {
+                if (zoneStart > zone2.startTime) {
+                    this._zones.splice(j, 0, {
+                        startTime:   zone2.startTime,
+                        endTime:     zoneStart,
+                        unit:        zone2.unit,
+                        multiple:    zone2.multiple
+                    });
+                    j++;
+
+                    zone2.startTime = zoneStart;
+                }
+
+                if (zoneEnd < zone2.endTime) {
+                    this._zones.splice(j, 0, {
+                        startTime:  zoneStart,
+                        endTime:    zoneEnd,
+                        unit:       zone.unit,
+                        multiple:   (zone.multiple) ? zone.multiple : 1
+                    });
+                    j++;
+
+                    zone2.startTime = zoneEnd;
+                    zoneStart = zoneEnd;
+                } else {
+                    zone2.multiple = zone.multiple;
+                    zone2.unit = zone.unit;
+                    zoneStart = zone2.endTime;
+                }
+            } // else, try the next existing zone
+        }
+    }
+};
+
+Timeline.HotZoneGregorianEtherPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backgroundLayer = band.createLayerDiv(0);
+    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
+    this._backgroundLayer.className ='timeline-ether-bg';
+    //this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
+
+    this._markerLayer = null;
+    this._lineLayer = null;
+
+    var align = ("align" in this._params && this._params.align != undefined) ? this._params.align :
+        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
+    var showLine = ("showLine" in this._params) ? this._params.showLine :
+        this._theme.ether.interval.line.show;
+
+    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
+        this._timeline, this._band, this._theme, align, showLine);
+
+    this._highlight = new Timeline.EtherHighlight(
+        this._timeline, this._band, this._theme, this._backgroundLayer);
+}
+
+Timeline.HotZoneGregorianEtherPainter.prototype.setHighlight = function(startDate, endDate) {
+    this._highlight.position(startDate, endDate);
+}
+
+Timeline.HotZoneGregorianEtherPainter.prototype.paint = function() {
+    if (this._markerLayer) {
+        this._band.removeLayerDiv(this._markerLayer);
+    }
+    this._markerLayer = this._band.createLayerDiv(100);
+    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
+    this._markerLayer.style.display = "none";
+
+    if (this._lineLayer) {
+        this._band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = this._band.createLayerDiv(1);
+    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
+    this._lineLayer.style.display = "none";
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var timeZone = this._band.getTimeZone();
+    var labeller = this._band.getLabeller();
+
+    var p = this;
+    var incrementDate = function(date, zone) {
+        for (var i = 0; i < zone.multiple; i++) {
+            SimileAjax.DateTime.incrementByInterval(date, zone.unit);
+        }
+    };
+
+    var zStart = 0;
+    while (zStart < this._zones.length) {
+        if (minDate.getTime() < this._zones[zStart].endTime) {
+            break;
+        }
+        zStart++;
+    }
+    var zEnd = this._zones.length - 1;
+    while (zEnd >= 0) {
+        if (maxDate.getTime() > this._zones[zEnd].startTime) {
+            break;
+        }
+        zEnd--;
+    }
+
+    for (var z = zStart; z <= zEnd; z++) {
+        var zone = this._zones[z];
+
+        var minDate2 = new Date(Math.max(minDate.getTime(), zone.startTime));
+        var maxDate2 = new Date(Math.min(maxDate.getTime(), zone.endTime));
+
+        SimileAjax.DateTime.roundDownToInterval(minDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek);
+        SimileAjax.DateTime.roundUpToInterval(maxDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek);
+
+        while (minDate2.getTime() < maxDate2.getTime()) {
+            this._intervalMarkerLayout.createIntervalMarker(
+                minDate2, labeller, zone.unit, this._markerLayer, this._lineLayer);
+
+            incrementDate(minDate2, zone);
+        }
+    }
+    this._markerLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+};
+
+Timeline.HotZoneGregorianEtherPainter.prototype.softPaint = function() {
+};
+
+Timeline.HotZoneGregorianEtherPainter.prototype.zoom = function(netIntervalChange) {
+  if (netIntervalChange != 0) {
+    for (var i = 0; i < this._zones.length; ++i) {
+      if (this._zones[i]) {
+        this._zones[i].unit += netIntervalChange;
+      }
+    }
+  }
+};
+
+/*==================================================
+ *  Year Count Ether Painter
+ *==================================================
+ */
+
+Timeline.YearCountEtherPainter = function(params) {
+    this._params = params;
+    this._theme = params.theme;
+    this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate);
+    this._multiple = ("multiple" in params) ? params.multiple : 1;
+};
+
+Timeline.YearCountEtherPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backgroundLayer = band.createLayerDiv(0);
+    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
+    this._backgroundLayer.className = 'timeline-ether-bg';
+   // this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
+
+    this._markerLayer = null;
+    this._lineLayer = null;
+
+    var align = ("align" in this._params) ? this._params.align :
+        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
+    var showLine = ("showLine" in this._params) ? this._params.showLine :
+        this._theme.ether.interval.line.show;
+
+    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
+        this._timeline, this._band, this._theme, align, showLine);
+
+    this._highlight = new Timeline.EtherHighlight(
+        this._timeline, this._band, this._theme, this._backgroundLayer);
+};
+
+Timeline.YearCountEtherPainter.prototype.setHighlight = function(startDate, endDate) {
+    this._highlight.position(startDate, endDate);
+};
+
+Timeline.YearCountEtherPainter.prototype.paint = function() {
+    if (this._markerLayer) {
+        this._band.removeLayerDiv(this._markerLayer);
+    }
+    this._markerLayer = this._band.createLayerDiv(100);
+    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
+    this._markerLayer.style.display = "none";
+
+    if (this._lineLayer) {
+        this._band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = this._band.createLayerDiv(1);
+    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
+    this._lineLayer.style.display = "none";
+
+    var minDate = new Date(this._startDate.getTime());
+    var maxDate = this._band.getMaxDate();
+    var yearDiff = this._band.getMinDate().getUTCFullYear() - this._startDate.getUTCFullYear();
+    minDate.setUTCFullYear(this._band.getMinDate().getUTCFullYear() - yearDiff % this._multiple);
+
+    var p = this;
+    var incrementDate = function(date) {
+        for (var i = 0; i < p._multiple; i++) {
+            SimileAjax.DateTime.incrementByInterval(date, SimileAjax.DateTime.YEAR);
+        }
+    };
+    var labeller = {
+        labelInterval: function(date, intervalUnit) {
+            var diff = date.getUTCFullYear() - p._startDate.getUTCFullYear();
+            return {
+                text: diff,
+                emphasized: diff == 0
+            };
+        }
+    };
+
+    while (minDate.getTime() < maxDate.getTime()) {
+        this._intervalMarkerLayout.createIntervalMarker(
+            minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer);
+
+        incrementDate(minDate);
+    }
+    this._markerLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+};
+
+Timeline.YearCountEtherPainter.prototype.softPaint = function() {
+};
+
+/*==================================================
+ *  Quarterly Ether Painter
+ *==================================================
+ */
+
+Timeline.QuarterlyEtherPainter = function(params) {
+    this._params = params;
+    this._theme = params.theme;
+    this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate);
+};
+
+Timeline.QuarterlyEtherPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backgroundLayer = band.createLayerDiv(0);
+    this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging
+    this._backgroundLayer.className = 'timeline-ether-bg';
+ //   this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()];
+
+    this._markerLayer = null;
+    this._lineLayer = null;
+
+    var align = ("align" in this._params) ? this._params.align :
+        this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"];
+    var showLine = ("showLine" in this._params) ? this._params.showLine :
+        this._theme.ether.interval.line.show;
+
+    this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout(
+        this._timeline, this._band, this._theme, align, showLine);
+
+    this._highlight = new Timeline.EtherHighlight(
+        this._timeline, this._band, this._theme, this._backgroundLayer);
+};
+
+Timeline.QuarterlyEtherPainter.prototype.setHighlight = function(startDate, endDate) {
+    this._highlight.position(startDate, endDate);
+};
+
+Timeline.QuarterlyEtherPainter.prototype.paint = function() {
+    if (this._markerLayer) {
+        this._band.removeLayerDiv(this._markerLayer);
+    }
+    this._markerLayer = this._band.createLayerDiv(100);
+    this._markerLayer.setAttribute("name", "ether-markers"); // for debugging
+    this._markerLayer.style.display = "none";
+
+    if (this._lineLayer) {
+        this._band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = this._band.createLayerDiv(1);
+    this._lineLayer.setAttribute("name", "ether-lines"); // for debugging
+    this._lineLayer.style.display = "none";
+
+    var minDate = new Date(0);
+    var maxDate = this._band.getMaxDate();
+
+    minDate.setUTCFullYear(Math.max(this._startDate.getUTCFullYear(), this._band.getMinDate().getUTCFullYear()));
+    minDate.setUTCMonth(this._startDate.getUTCMonth());
+
+    var p = this;
+    var incrementDate = function(date) {
+        date.setUTCMonth(date.getUTCMonth() + 3);
+    };
+    var labeller = {
+        labelInterval: function(date, intervalUnit) {
+            var quarters = (4 + (date.getUTCMonth() - p._startDate.getUTCMonth()) / 3) % 4;
+            if (quarters != 0) {
+                return { text: "Q" + (quarters + 1), emphasized: false };
+            } else {
+                return { text: "Y" + (date.getUTCFullYear() - p._startDate.getUTCFullYear() + 1), emphasized: true };
+            }
+        }
+    };
+
+    while (minDate.getTime() < maxDate.getTime()) {
+        this._intervalMarkerLayout.createIntervalMarker(
+            minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer);
+
+        incrementDate(minDate);
+    }
+    this._markerLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+};
+
+Timeline.QuarterlyEtherPainter.prototype.softPaint = function() {
+};
+
+/*==================================================
+ *  Ether Interval Marker Layout
+ *==================================================
+ */
+
+Timeline.EtherIntervalMarkerLayout = function(timeline, band, theme, align, showLine) {
+    var horizontal = timeline.isHorizontal();
+    if (horizontal) {
+        if (align == "Top") {
+            this.positionDiv = function(div, offset) {
+                div.style.left = offset + "px";
+                div.style.top = "0px";
+            };
+        } else {
+            this.positionDiv = function(div, offset) {
+                div.style.left = offset + "px";
+                div.style.bottom = "0px";
+            };
+        }
+    } else {
+        if (align == "Left") {
+            this.positionDiv = function(div, offset) {
+                div.style.top = offset + "px";
+                div.style.left = "0px";
+            };
+        } else {
+            this.positionDiv = function(div, offset) {
+                div.style.top = offset + "px";
+                div.style.right = "0px";
+            };
+        }
+    }
+
+    var markerTheme = theme.ether.interval.marker;
+    var lineTheme = theme.ether.interval.line;
+    var weekendTheme = theme.ether.interval.weekend;
+
+    var stylePrefix = (horizontal ? "h" : "v") + align;
+    var labelStyler = markerTheme[stylePrefix + "Styler"];
+    var emphasizedLabelStyler = markerTheme[stylePrefix + "EmphasizedStyler"];
+    var day = SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY];
+
+    this.createIntervalMarker = function(date, labeller, unit, markerDiv, lineDiv) {
+        var offset = Math.round(band.dateToPixelOffset(date));
+
+        if (showLine && unit != SimileAjax.DateTime.WEEK) {
+            var divLine = timeline.getDocument().createElement("div");
+            divLine.className = "timeline-ether-lines";
+
+            if (lineTheme.opacity < 100) {
+                SimileAjax.Graphics.setOpacity(divLine, lineTheme.opacity);
+            }
+
+            if (horizontal) {
+				//divLine.className += " timeline-ether-lines-vertical";
+				divLine.style.left = offset + "px";
+            } else {
+				//divLine.className += " timeline-ether-lines-horizontal";
+                divLine.style.top = offset + "px";
+            }
+            lineDiv.appendChild(divLine);
+        }
+        if (unit == SimileAjax.DateTime.WEEK) {
+            var firstDayOfWeek = theme.firstDayOfWeek;
+
+            var saturday = new Date(date.getTime() + (6 - firstDayOfWeek - 7) * day);
+            var monday = new Date(saturday.getTime() + 2 * day);
+
+            var saturdayPixel = Math.round(band.dateToPixelOffset(saturday));
+            var mondayPixel = Math.round(band.dateToPixelOffset(monday));
+            var length = Math.max(1, mondayPixel - saturdayPixel);
+
+            var divWeekend = timeline.getDocument().createElement("div");
+			divWeekend.className = 'timeline-ether-weekends'
+
+            if (weekendTheme.opacity < 100) {
+                SimileAjax.Graphics.setOpacity(divWeekend, weekendTheme.opacity);
+            }
+
+            if (horizontal) {
+                divWeekend.style.left = saturdayPixel + "px";
+                divWeekend.style.width = length + "px";
+            } else {
+                divWeekend.style.top = saturdayPixel + "px";
+                divWeekend.style.height = length + "px";
+            }
+            lineDiv.appendChild(divWeekend);
+        }
+
+        var label = labeller.labelInterval(date, unit);
+
+        var div = timeline.getDocument().createElement("div");
+        div.innerHTML = label.text;
+
+
+
+		div.className = 'timeline-date-label'
+		if(label.emphasized) div.className += ' timeline-date-label-em'
+
+        this.positionDiv(div, offset);
+        markerDiv.appendChild(div);
+
+        return div;
+    };
+};
+
+/*==================================================
+ *  Ether Highlight Layout
+ *==================================================
+ */
+
+Timeline.EtherHighlight = function(timeline, band, theme, backgroundLayer) {
+    var horizontal = timeline.isHorizontal();
+
+    this._highlightDiv = null;
+    this._createHighlightDiv = function() {
+        if (this._highlightDiv == null) {
+            this._highlightDiv = timeline.getDocument().createElement("div");
+            this._highlightDiv.setAttribute("name", "ether-highlight"); // for debugging
+            this._highlightDiv.className = 'timeline-ether-highlight'
+
+            var opacity = theme.ether.highlightOpacity;
+            if (opacity < 100) {
+                SimileAjax.Graphics.setOpacity(this._highlightDiv, opacity);
+            }
+
+            backgroundLayer.appendChild(this._highlightDiv);
+        }
+    }
+
+    this.position = function(startDate, endDate) {
+        this._createHighlightDiv();
+
+        var startPixel = Math.round(band.dateToPixelOffset(startDate));
+        var endPixel = Math.round(band.dateToPixelOffset(endDate));
+        var length = Math.max(endPixel - startPixel, 3);
+        if (horizontal) {
+            this._highlightDiv.style.left = startPixel + "px";
+            this._highlightDiv.style.width = length + "px";
+            this._highlightDiv.style.height = (band.getViewWidth() - 4) + "px";
+        } else {
+            this._highlightDiv.style.top = startPixel + "px";
+            this._highlightDiv.style.height = length + "px";
+            this._highlightDiv.style.width = (band.getViewWidth() - 4) + "px";
+        }
+    }
+};
+/*==================================================
+ *  Event Utils
+ *==================================================
+ */
+Timeline.EventUtils = {};
+
+Timeline.EventUtils.getNewEventID = function() {
+    // global across page
+    if (this._lastEventID == null) {
+        this._lastEventID = 0;
+    }
+
+    this._lastEventID += 1;
+    return "e" + this._lastEventID;
+};
+
+Timeline.EventUtils.decodeEventElID = function(elementID) {
+    /*==================================================
+     *
+     * Use this function to decode an event element's id on a band (label div,
+     * tape div or icon img).
+     *
+     * Returns {band: <bandObj>, evt: <eventObj>}
+     *
+     * To enable a single event listener to monitor everything
+     * on a Timeline, a set format is used for the id's of the
+     * elements on the Timeline--
+     *
+     * element id format for labels, icons, tapes:
+     *   labels: label-tl-<timelineID>-<band_index>-<evt.id>
+     *    icons: icon-tl-<timelineID>-<band_index>-<evt.id>
+     *    tapes: tape1-tl-<timelineID>-<band_index>-<evt.id>
+     *           tape2-tl-<timelineID>-<band_index>-<evt.id>
+     *           // some events have more than one tape
+     *    highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id>
+     *               highlight2-tl-<timelineID>-<band_index>-<evt.id>
+     *           // some events have more than one highlight div (future)
+     * Note: use split('-') to get array of the format's parts
+     *
+     * You can then retrieve the timeline object and event object
+     * by using Timeline.getTimeline, Timeline.getBand, or
+     * Timeline.getEvent and passing in the element's id
+     *
+     *==================================================
+     */
+
+    var parts = elementID.split('-');
+    if (parts[1] != 'tl') {
+        alert("Internal Timeline problem 101, please consult support");
+        return {band: null, evt: null}; // early return
+    }
+
+    var timeline = Timeline.getTimelineFromID(parts[2]);
+    var band = timeline.getBand(parts[3]);
+    var evt = band.getEventSource.getEvent(parts[4]);
+
+    return {band: band, evt: evt};
+};
+
+Timeline.EventUtils.encodeEventElID = function(timeline, band, elType, evt) {
+    // elType should be one of {label | icon | tapeN | highlightN}
+    return elType + "-tl-" + timeline.timelineID +
+       "-" + band.getIndex() + "-" + evt.getID();
+};/*==================================================
+ *  Gregorian Date Labeller
+ *==================================================
+ */
+
+Timeline.GregorianDateLabeller = function(locale, timeZone) {
+    this._locale = locale;
+    this._timeZone = timeZone;
+};
+
+Timeline.GregorianDateLabeller.monthNames = [];
+Timeline.GregorianDateLabeller.dayNames = [];
+Timeline.GregorianDateLabeller.labelIntervalFunctions = [];
+
+Timeline.GregorianDateLabeller.getMonthName = function(month, locale) {
+    return Timeline.GregorianDateLabeller.monthNames[locale][month];
+};
+
+Timeline.GregorianDateLabeller.prototype.labelInterval = function(date, intervalUnit) {
+    var f = Timeline.GregorianDateLabeller.labelIntervalFunctions[this._locale];
+    if (f == null) {
+        f = Timeline.GregorianDateLabeller.prototype.defaultLabelInterval;
+    }
+    return f.call(this, date, intervalUnit);
+};
+
+Timeline.GregorianDateLabeller.prototype.labelPrecise = function(date) {
+    return SimileAjax.DateTime.removeTimeZoneOffset(
+        date,
+        this._timeZone //+ (new Date().getTimezoneOffset() / 60)
+    ).toUTCString();
+};
+
+Timeline.GregorianDateLabeller.prototype.defaultLabelInterval = function(date, intervalUnit) {
+    var text;
+    var emphasized = false;
+
+    date = SimileAjax.DateTime.removeTimeZoneOffset(date, this._timeZone);
+
+    switch(intervalUnit) {
+    case SimileAjax.DateTime.MILLISECOND:
+        text = date.getUTCMilliseconds();
+        break;
+    case SimileAjax.DateTime.SECOND:
+        text = date.getUTCSeconds();
+        break;
+    case SimileAjax.DateTime.MINUTE:
+        var m = date.getUTCMinutes();
+        if (m == 0) {
+            text = date.getUTCHours() + ":00";
+            emphasized = true;
+        } else {
+            text = m;
+        }
+        break;
+    case SimileAjax.DateTime.HOUR:
+        text = date.getUTCHours() + "hr";
+        break;
+    case SimileAjax.DateTime.DAY:
+        text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate();
+        break;
+    case SimileAjax.DateTime.WEEK:
+        text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate();
+        break;
+    case SimileAjax.DateTime.MONTH:
+        var m = date.getUTCMonth();
+        if (m != 0) {
+            text = Timeline.GregorianDateLabeller.getMonthName(m, this._locale);
+            break;
+        } // else, fall through
+    case SimileAjax.DateTime.YEAR:
+    case SimileAjax.DateTime.DECADE:
+    case SimileAjax.DateTime.CENTURY:
+    case SimileAjax.DateTime.MILLENNIUM:
+        var y = date.getUTCFullYear();
+        if (y > 0) {
+            text = date.getUTCFullYear();
+        } else {
+            text = (1 - y) + "BC";
+        }
+        emphasized =
+            (intervalUnit == SimileAjax.DateTime.MONTH) ||
+            (intervalUnit == SimileAjax.DateTime.DECADE && y % 100 == 0) ||
+            (intervalUnit == SimileAjax.DateTime.CENTURY && y % 1000 == 0);
+        break;
+    default:
+        text = date.toUTCString();
+    }
+    return { text: text, emphasized: emphasized };
+}
+
+/*==================================================
+ *  Default Event Source
+ *==================================================
+ */
+
+
+Timeline.DefaultEventSource = function(eventIndex) {
+    this._events = (eventIndex instanceof Object) ? eventIndex : new SimileAjax.EventIndex();
+    this._listeners = [];
+};
+
+Timeline.DefaultEventSource.prototype.addListener = function(listener) {
+    this._listeners.push(listener);
+};
+
+Timeline.DefaultEventSource.prototype.removeListener = function(listener) {
+    for (var i = 0; i < this._listeners.length; i++) {
+        if (this._listeners[i] == listener) {
+            this._listeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.DefaultEventSource.prototype.loadXML = function(xml, url) {
+    var base = this._getBaseURL(url);
+
+    var wikiURL = xml.documentElement.getAttribute("wiki-url");
+    var wikiSection = xml.documentElement.getAttribute("wiki-section");
+
+    var dateTimeFormat = xml.documentElement.getAttribute("date-time-format");
+    var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
+
+    var node = xml.documentElement.firstChild;
+    var added = false;
+    while (node != null) {
+        if (node.nodeType == 1) {
+            var description = "";
+            if (node.firstChild != null && node.firstChild.nodeType == 3) {
+                description = node.firstChild.nodeValue;
+            }
+            // instant event: default is true. Or use values from isDuration or durationEvent
+            var instant = (node.getAttribute("isDuration")    === null &&
+                           node.getAttribute("durationEvent") === null) ||
+                          node.getAttribute("isDuration") == "false" ||
+                          node.getAttribute("durationEvent") == "false";
+
+            var evt = new Timeline.DefaultEventSource.Event( {
+                          id: node.getAttribute("id"),
+                       start: parseDateTimeFunction(node.getAttribute("start")),
+                         end: parseDateTimeFunction(node.getAttribute("end")),
+                 latestStart: parseDateTimeFunction(node.getAttribute("latestStart")),
+                 earliestEnd: parseDateTimeFunction(node.getAttribute("earliestEnd")),
+                     instant: instant,
+                        text: node.getAttribute("title"),
+                 description: description,
+                       image: this._resolveRelativeURL(node.getAttribute("image"), base),
+                        link: this._resolveRelativeURL(node.getAttribute("link") , base),
+                        icon: this._resolveRelativeURL(node.getAttribute("icon") , base),
+                       color: node.getAttribute("color"),
+                   textColor: node.getAttribute("textColor"),
+                   hoverText: node.getAttribute("hoverText"),
+                   classname: node.getAttribute("classname"),
+                   tapeImage: node.getAttribute("tapeImage"),
+                  tapeRepeat: node.getAttribute("tapeRepeat"),
+                     caption: node.getAttribute("caption"),
+                     eventID: node.getAttribute("eventID"),
+                    trackNum: node.getAttribute("trackNum")
+            });
+
+            evt._node = node;
+            evt.getProperty = function(name) {
+                return this._node.getAttribute(name);
+            };
+            evt.setWikiInfo(wikiURL, wikiSection);
+
+            this._events.add(evt);
+
+            added = true;
+        }
+        node = node.nextSibling;
+    }
+
+    if (added) {
+        this._fire("onAddMany", []);
+    }
+};
+
+
+Timeline.DefaultEventSource.prototype.loadJSON = function(data, url) {
+    var base = this._getBaseURL(url);
+    var added = false;
+    if (data && data.events){
+        var wikiURL = ("wikiURL" in data) ? data.wikiURL : null;
+        var wikiSection = ("wikiSection" in data) ? data.wikiSection : null;
+
+        var dateTimeFormat = ("dateTimeFormat" in data) ? data.dateTimeFormat : null;
+        var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
+
+        for (var i=0; i < data.events.length; i++){
+            var event = data.events[i];
+            // Fixing issue 33:
+            // instant event: default (for JSON only) is false. Or use values from isDuration or durationEvent
+            // isDuration was negated (see issue 33, so keep that interpretation
+            var instant = event.isDuration || (event.durationEvent != null && !event.durationEvent);
+
+            var evt = new Timeline.DefaultEventSource.Event({
+                          id: ("id" in event) ? event.id : undefined,
+                       start: parseDateTimeFunction(event.start),
+                         end: parseDateTimeFunction(event.end),
+                 latestStart: parseDateTimeFunction(event.latestStart),
+                 earliestEnd: parseDateTimeFunction(event.earliestEnd),
+                     instant: instant,
+                        text: event.title,
+                 description: event.description,
+                       image: this._resolveRelativeURL(event.image, base),
+                        link: this._resolveRelativeURL(event.link , base),
+                        icon: this._resolveRelativeURL(event.icon , base),
+                       color: event.color,
+                   textColor: event.textColor,
+                   hoverText: event.hoverText,
+                   classname: event.classname,
+                   tapeImage: event.tapeImage,
+                  tapeRepeat: event.tapeRepeat,
+                     caption: event.caption,
+                     eventID: event.eventID,
+                    trackNum: event.trackNum
+            });
+            evt._obj = event;
+            evt.getProperty = function(name) {
+                return this._obj[name];
+            };
+            evt.setWikiInfo(wikiURL, wikiSection);
+
+            this._events.add(evt);
+            added = true;
+        }
+    }
+
+    if (added) {
+        this._fire("onAddMany", []);
+    }
+};
+
+/*
+ *  Contributed by Morten Frederiksen, http://www.wasab.dk/morten/
+ */
+Timeline.DefaultEventSource.prototype.loadSPARQL = function(xml, url) {
+    var base = this._getBaseURL(url);
+
+    var dateTimeFormat = 'iso8601';
+    var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat);
+
+    if (xml == null) {
+        return;
+    }
+
+    /*
+     *  Find <results> tag
+     */
+    var node = xml.documentElement.firstChild;
+    while (node != null && (node.nodeType != 1 || node.nodeName != 'results')) {
+        node = node.nextSibling;
+    }
+
+    var wikiURL = null;
+    var wikiSection = null;
+    if (node != null) {
+        wikiURL = node.getAttribute("wiki-url");
+        wikiSection = node.getAttribute("wiki-section");
+
+        node = node.firstChild;
+    }
+
+    var added = false;
+    while (node != null) {
+        if (node.nodeType == 1) {
+            var bindings = { };
+            var binding = node.firstChild;
+            while (binding != null) {
+                if (binding.nodeType == 1 &&
+                    binding.firstChild != null &&
+                    binding.firstChild.nodeType == 1 &&
+                    binding.firstChild.firstChild != null &&
+                    binding.firstChild.firstChild.nodeType == 3) {
+                    bindings[binding.getAttribute('name')] = binding.firstChild.firstChild.nodeValue;
+                }
+                binding = binding.nextSibling;
+            }
+
+            if (bindings["start"] == null && bindings["date"] != null) {
+                bindings["start"] = bindings["date"];
+            }
+
+            // instant event: default is true. Or use values from isDuration or durationEvent
+            var instant = (bindings["isDuration"]    === null &&
+                           bindings["durationEvent"] === null) ||
+                          bindings["isDuration"] == "false" ||
+                          bindings["durationEvent"] == "false";
+
+            var evt = new Timeline.DefaultEventSource.Event({
+                          id: bindings["id"],
+                       start: parseDateTimeFunction(bindings["start"]),
+                         end: parseDateTimeFunction(bindings["end"]),
+                 latestStart: parseDateTimeFunction(bindings["latestStart"]),
+                 earliestEnd: parseDateTimeFunction(bindings["earliestEnd"]),
+                     instant: instant, // instant
+                        text: bindings["title"], // text
+                 description: bindings["description"],
+                       image: this._resolveRelativeURL(bindings["image"], base),
+                        link: this._resolveRelativeURL(bindings["link"] , base),
+                        icon: this._resolveRelativeURL(bindings["icon"] , base),
+                       color: bindings["color"],
+                   textColor: bindings["textColor"],
+                   hoverText: bindings["hoverText"],
+                     caption: bindings["caption"],
+                   classname: bindings["classname"],
+                   tapeImage: bindings["tapeImage"],
+                  tapeRepeat: bindings["tapeRepeat"],
+                     eventID: bindings["eventID"],
+                    trackNum: bindings["trackNum"]
+            });
+            evt._bindings = bindings;
+            evt.getProperty = function(name) {
+                return this._bindings[name];
+            };
+            evt.setWikiInfo(wikiURL, wikiSection);
+
+            this._events.add(evt);
+            added = true;
+        }
+        node = node.nextSibling;
+    }
+
+    if (added) {
+        this._fire("onAddMany", []);
+    }
+};
+
+Timeline.DefaultEventSource.prototype.add = function(evt) {
+    this._events.add(evt);
+    this._fire("onAddOne", [evt]);
+};
+
+Timeline.DefaultEventSource.prototype.addMany = function(events) {
+    for (var i = 0; i < events.length; i++) {
+        this._events.add(events[i]);
+    }
+    this._fire("onAddMany", []);
+};
+
+Timeline.DefaultEventSource.prototype.clear = function() {
+    this._events.removeAll();
+    this._fire("onClear", []);
+};
+
+Timeline.DefaultEventSource.prototype.getEvent = function(id) {
+    return this._events.getEvent(id);
+};
+
+Timeline.DefaultEventSource.prototype.getEventIterator = function(startDate, endDate) {
+    return this._events.getIterator(startDate, endDate);
+};
+
+Timeline.DefaultEventSource.prototype.getEventReverseIterator = function(startDate, endDate) {
+    return this._events.getReverseIterator(startDate, endDate);
+};
+
+Timeline.DefaultEventSource.prototype.getAllEventIterator = function() {
+    return this._events.getAllIterator();
+};
+
+Timeline.DefaultEventSource.prototype.getCount = function() {
+    return this._events.getCount();
+};
+
+Timeline.DefaultEventSource.prototype.getEarliestDate = function() {
+    return this._events.getEarliestDate();
+};
+
+Timeline.DefaultEventSource.prototype.getLatestDate = function() {
+    return this._events.getLatestDate();
+};
+
+Timeline.DefaultEventSource.prototype._fire = function(handlerName, args) {
+    for (var i = 0; i < this._listeners.length; i++) {
+        var listener = this._listeners[i];
+        if (handlerName in listener) {
+            try {
+                listener[handlerName].apply(listener, args);
+            } catch (e) {
+                SimileAjax.Debug.exception(e);
+            }
+        }
+    }
+};
+
+Timeline.DefaultEventSource.prototype._getBaseURL = function(url) {
+    if (url.indexOf("://") < 0) {
+        var url2 = this._getBaseURL(document.location.href);
+        if (url.substr(0,1) == "/") {
+            url = url2.substr(0, url2.indexOf("/", url2.indexOf("://") + 3)) + url;
+        } else {
+            url = url2 + url;
+        }
+    }
+
+    var i = url.lastIndexOf("/");
+    if (i < 0) {
+        return "";
+    } else {
+        return url.substr(0, i+1);
+    }
+};
+
+Timeline.DefaultEventSource.prototype._resolveRelativeURL = function(url, base) {
+    if (url == null || url == "") {
+        return url;
+    } else if (url.indexOf("://") > 0) {
+        return url;
+    } else if (url.substr(0,1) == "/") {
+        return base.substr(0, base.indexOf("/", base.indexOf("://") + 3)) + url;
+    } else {
+        return base + url;
+    }
+};
+
+
+Timeline.DefaultEventSource.Event = function(args) {
+  //
+  // Attention developers!
+  // If you add a new event attribute, please be sure to add it to
+  // all three load functions: loadXML, loadSPARCL, loadJSON.
+  // Thanks!
+  //
+  // args is a hash/object. It supports the following keys. Most are optional
+  //   id            -- an internal id. Really shouldn't be used by events.
+  //                    Timeline library clients should use eventID
+  //   eventID       -- For use by library client when writing custom painters or
+  //                    custom fillInfoBubble
+  //   start
+  //   end
+  //   latestStart
+  //   earliestEnd
+  //   instant      -- boolean. Controls precise/non-precise logic & duration/instant issues
+  //   text         -- event source attribute 'title' -- used as the label on Timelines and in bubbles.
+  //   description  -- used in bubbles
+  //   image        -- used in bubbles
+  //   link         -- used in bubbles
+  //   icon         -- on the Timeline
+  //   color        -- Timeline label and tape color
+  //   textColor    -- Timeline label color, overrides color attribute
+  //   hoverText    -- deprecated, here for backwards compatibility.
+  //                   Superceeded by caption
+  //   caption      -- tooltip-like caption on the Timeline. Uses HTML title attribute
+  //   classname    -- used to set classname in Timeline. Enables better CSS selector rules
+  //   tapeImage    -- background image of the duration event's tape div on the Timeline
+  //   tapeRepeat   -- repeat attribute for tapeImage. {repeat | repeat-x | repeat-y }
+
+  function cleanArg(arg) {
+      // clean up an arg
+      return (args[arg] != null && args[arg] != "") ? args[arg] : null;
+  }
+
+  var id = args.id ? args.id.trim() : "";
+  this._id = id.length > 0 ? id : Timeline.EventUtils.getNewEventID();
+
+  this._instant = args.instant || (args.end == null);
+
+  this._start = args.start;
+  this._end = (args.end != null) ? args.end : args.start;
+
+  this._latestStart = (args.latestStart != null) ?
+                       args.latestStart : (args.instant ? this._end : this._start);
+  this._earliestEnd = (args.earliestEnd != null) ? args.earliestEnd : this._end;
+
+  // check sanity of dates since incorrect dates will later cause calculation errors
+  // when painting
+  var err=[];
+  if (this._start > this._latestStart) {
+          this._latestStart = this._start;
+          err.push("start is > latestStart");}
+  if (this._start > this._earliestEnd) {
+          this._earliestEnd = this._latestStart;
+          err.push("start is > earliestEnd");}
+  if (this._start > this._end) {
+          this._end = this._earliestEnd;
+          err.push("start is > end");}
+  if (this._latestStart > this._earliestEnd) {
+          this._earliestEnd = this._latestStart;
+          err.push("latestStart is > earliestEnd");}
+  if (this._latestStart > this._end) {
+          this._end = this._earliestEnd;
+          err.push("latestStart is > end");}
+  if (this._earliestEnd > this._end) {
+          this._end = this._earliestEnd;
+          err.push("earliestEnd is > end");}
+
+  this._eventID = cleanArg('eventID');
+  this._text = (args.text != null) ? SimileAjax.HTML.deEntify(args.text) : ""; // Change blank titles to ""
+  if (err.length > 0) {
+          this._text += " PROBLEM: " + err.join(", ");
+  }
+
+  this._description = SimileAjax.HTML.deEntify(args.description);
+  this._image = cleanArg('image');
+  this._link =  cleanArg('link');
+  this._title = cleanArg('hoverText');
+  this._title = cleanArg('caption');
+
+  this._icon = cleanArg('icon');
+  this._color = cleanArg('color');
+  this._textColor = cleanArg('textColor');
+  this._classname = cleanArg('classname');
+  this._tapeImage = cleanArg('tapeImage');
+  this._tapeRepeat = cleanArg('tapeRepeat');
+  this._trackNum = cleanArg('trackNum');
+  if (this._trackNum != null) {
+      this._trackNum = parseInt(this._trackNum);
+  }
+
+  this._wikiURL = null;
+  this._wikiSection = null;
+};
+
+Timeline.DefaultEventSource.Event.prototype = {
+    getID:          function() { return this._id; },
+
+    isInstant:      function() { return this._instant; },
+    isImprecise:    function() { return this._start != this._latestStart || this._end != this._earliestEnd; },
+
+    getStart:       function() { return this._start; },
+    getEnd:         function() { return this._end; },
+    getLatestStart: function() { return this._latestStart; },
+    getEarliestEnd: function() { return this._earliestEnd; },
+
+    getEventID:     function() { return this._eventID; },
+    getText:        function() { return this._text; }, // title
+    getDescription: function() { return this._description; },
+    getImage:       function() { return this._image; },
+    getLink:        function() { return this._link; },
+
+    getIcon:        function() { return this._icon; },
+    getColor:       function() { return this._color; },
+    getTextColor:   function() { return this._textColor; },
+    getClassName:   function() { return this._classname; },
+    getTapeImage:   function() { return this._tapeImage; },
+    getTapeRepeat:  function() { return this._tapeRepeat; },
+    getTrackNum:    function() { return this._trackNum; },
+
+    getProperty:    function(name) { return null; },
+
+    getWikiURL:     function() { return this._wikiURL; },
+    getWikiSection: function() { return this._wikiSection; },
+    setWikiInfo: function(wikiURL, wikiSection) {
+        this._wikiURL = wikiURL;
+        this._wikiSection = wikiSection;
+    },
+
+    fillDescription: function(elmt) {
+        elmt.innerHTML = this._description;
+    },
+    fillWikiInfo: function(elmt) {
+        // Many bubbles will not support a wiki link.
+        //
+        // Strategy: assume no wiki link. If we do have
+        // enough parameters for one, then create it.
+        elmt.style.display = "none"; // default
+
+        if (this._wikiURL == null || this._wikiSection == null) {
+          return; // EARLY RETURN
+        }
+
+        // create the wikiID from the property or from the event text (the title)
+        var wikiID = this.getProperty("wikiID");
+        if (wikiID == null || wikiID.length == 0) {
+            wikiID = this.getText(); // use the title as the backup wiki id
+        }
+
+        if (wikiID == null || wikiID.length == 0) {
+          return; // No wikiID. Thus EARLY RETURN
+        }
+
+        // ready to go...
+        elmt.style.display = "inline";
+        wikiID = wikiID.replace(/\s/g, "_");
+        var url = this._wikiURL + this._wikiSection.replace(/\s/g, "_") + "/" + wikiID;
+        var a = document.createElement("a");
+        a.href = url;
+        a.target = "new";
+        a.innerHTML = Timeline.strings[Timeline.clientLocale].wikiLinkLabel;
+
+        elmt.appendChild(document.createTextNode("["));
+        elmt.appendChild(a);
+        elmt.appendChild(document.createTextNode("]"));
+    },
+
+    fillTime: function(elmt, labeller) {
+        if (this._instant) {
+            if (this.isImprecise()) {
+                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
+                elmt.appendChild(elmt.ownerDocument.createElement("br"));
+                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end)));
+            } else {
+                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
+            }
+        } else {
+            if (this.isImprecise()) {
+                elmt.appendChild(elmt.ownerDocument.createTextNode(
+                    labeller.labelPrecise(this._start) + " ~ " + labeller.labelPrecise(this._latestStart)));
+                elmt.appendChild(elmt.ownerDocument.createElement("br"));
+                elmt.appendChild(elmt.ownerDocument.createTextNode(
+                    labeller.labelPrecise(this._earliestEnd) + " ~ " + labeller.labelPrecise(this._end)));
+            } else {
+                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start)));
+                elmt.appendChild(elmt.ownerDocument.createElement("br"));
+                elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end)));
+            }
+        }
+    },
+
+    fillInfoBubble: function(elmt, theme, labeller) {
+        var doc = elmt.ownerDocument;
+
+        var title = this.getText();
+        var link = this.getLink();
+        var image = this.getImage();
+
+        if (image != null) {
+            var img = doc.createElement("img");
+            img.src = image;
+
+            theme.event.bubble.imageStyler(img);
+            elmt.appendChild(img);
+        }
+
+        var divTitle = doc.createElement("div");
+        var textTitle = doc.createTextNode(title);
+        if (link != null) {
+            var a = doc.createElement("a");
+            a.href = link;
+            a.appendChild(textTitle);
+            divTitle.appendChild(a);
+        } else {
+            divTitle.appendChild(textTitle);
+        }
+        theme.event.bubble.titleStyler(divTitle);
+        elmt.appendChild(divTitle);
+
+        var divBody = doc.createElement("div");
+        this.fillDescription(divBody);
+        theme.event.bubble.bodyStyler(divBody);
+        elmt.appendChild(divBody);
+
+        var divTime = doc.createElement("div");
+        this.fillTime(divTime, labeller);
+        theme.event.bubble.timeStyler(divTime);
+        elmt.appendChild(divTime);
+
+        var divWiki = doc.createElement("div");
+        this.fillWikiInfo(divWiki);
+        theme.event.bubble.wikiStyler(divWiki);
+        elmt.appendChild(divWiki);
+    }
+};
+
+
+/*==================================================
+ *  Original Event Painter
+ *==================================================
+ */
+
+/*==================================================
+ *
+ * To enable a single event listener to monitor everything
+ * on a Timeline, we need a way to map from an event's icon,
+ * label or tape element to the associated timeline, band and
+ * specific event.
+ *
+ * Thus a set format is used for the id's of the
+ * events' elements on the Timeline--
+ *
+ * element id format for labels, icons, tapes:
+ *   labels: label-tl-<timelineID>-<band_index>-<evt.id>
+ *    icons: icon-tl-<timelineID>-<band_index>-<evt.id>
+ *    tapes: tape1-tl-<timelineID>-<band_index>-<evt.id>
+ *           tape2-tl-<timelineID>-<band_index>-<evt.id>
+ *           // some events have more than one tape
+ *    highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id>
+ *               highlight2-tl-<timelineID>-<band_index>-<evt.id>
+ *           // some events have more than one highlight div (future)
+ * You can then retrieve the band/timeline objects and event object
+ * by using Timeline.EventUtils.decodeEventElID
+ *
+ *==================================================
+ */
+
+/*
+ *    eventPaintListener functions receive calls about painting.
+ *    function(band, op, evt, els)
+ *       context: 'this' will be an OriginalEventPainter object.
+ *                It has properties and methods for obtaining
+ *                the relevant band, timeline, etc
+ *       band = the band being painted
+ *       op = 'paintStarting' // the painter is about to remove
+ *            all previously painted events, if any. It will
+ *            then start painting all of the visible events that
+ *            pass the filter.
+ *            evt = null, els = null
+ *       op = 'paintEnded' // the painter has finished painting
+ *            all of the visible events that passed the filter
+ *            evt = null, els = null
+ *       op = 'paintedEvent' // the painter just finished painting an event
+ *            evt = event just painted
+ *            els = array of painted elements' divs. Depending on the event,
+ *                  the array could be just a tape or icon (if no label).
+ *                  Or could include label, multiple tape divs (imprecise event),
+ *                  highlight divs. The array is not ordered. The meaning of
+ *                  each el is available by decoding the el's id
+ *      Note that there may be no paintedEvent calls if no events were visible
+ *      or passed the filter.
+ */
+
+Timeline.OriginalEventPainter = function(params) {
+    this._params = params;
+    this._onSelectListeners = [];
+    this._eventPaintListeners = [];
+
+    this._filterMatcher = null;
+    this._highlightMatcher = null;
+    this._frc = null;
+
+    this._eventIdToElmt = {};
+};
+
+Timeline.OriginalEventPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backLayer = null;
+    this._eventLayer = null;
+    this._lineLayer = null;
+    this._highlightLayer = null;
+
+    this._eventIdToElmt = null;
+};
+
+Timeline.OriginalEventPainter.prototype.getType = function() {
+    return 'original';
+};
+
+Timeline.OriginalEventPainter.prototype.addOnSelectListener = function(listener) {
+    this._onSelectListeners.push(listener);
+};
+
+Timeline.OriginalEventPainter.prototype.removeOnSelectListener = function(listener) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        if (this._onSelectListeners[i] == listener) {
+            this._onSelectListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.OriginalEventPainter.prototype.addEventPaintListener = function(listener) {
+    this._eventPaintListeners.push(listener);
+};
+
+Timeline.OriginalEventPainter.prototype.removeEventPaintListener = function(listener) {
+    for (var i = 0; i < this._eventPaintListeners.length; i++) {
+        if (this._eventPaintListeners[i] == listener) {
+            this._eventPaintListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.OriginalEventPainter.prototype.getFilterMatcher = function() {
+    return this._filterMatcher;
+};
+
+Timeline.OriginalEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
+    this._filterMatcher = filterMatcher;
+};
+
+Timeline.OriginalEventPainter.prototype.getHighlightMatcher = function() {
+    return this._highlightMatcher;
+};
+
+Timeline.OriginalEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
+    this._highlightMatcher = highlightMatcher;
+};
+
+Timeline.OriginalEventPainter.prototype.paint = function() {
+    // Paints the events for a given section of the band--what is
+    // visible on screen and some extra.
+    var eventSource = this._band.getEventSource();
+    if (eventSource == null) {
+        return;
+    }
+
+    this._eventIdToElmt = {};
+    this._fireEventPaintListeners('paintStarting', null, null);
+    this._prepareForPainting();
+
+    var metrics = this._computeMetrics();
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var filterMatcher = (this._filterMatcher != null) ?
+        this._filterMatcher :
+        function(evt) { return true; };
+    var highlightMatcher = (this._highlightMatcher != null) ?
+        this._highlightMatcher :
+        function(evt) { return -1; };
+
+    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
+    while (iterator.hasNext()) {
+        var evt = iterator.next();
+        if (filterMatcher(evt)) {
+            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
+        }
+    }
+
+    this._highlightLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+    this._eventLayer.style.display = "block";
+    // update the band object for max number of tracks in this section of the ether
+    this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement);
+    this._fireEventPaintListeners('paintEnded', null, null);
+
+    this._setOrthogonalOffset(metrics);
+};
+
+Timeline.OriginalEventPainter.prototype.softPaint = function() {
+    this._setOrthogonalOffset(this._computeMetrics());
+};
+
+Timeline.OriginalEventPainter.prototype._setOrthogonalOffset = function(metrics) {
+    var actualViewWidth = 2 * metrics.trackOffset + this._tracks.length * metrics.trackIncrement;
+    var minOrthogonalOffset = Math.min(0, this._band.getViewWidth() - actualViewWidth);
+    var orthogonalOffset = Math.max(minOrthogonalOffset, this._band.getViewOrthogonalOffset());
+
+    this._highlightLayer.style.top =
+        this._lineLayer.style.top =
+            this._eventLayer.style.top =
+                orthogonalOffset + "px";
+};
+
+Timeline.OriginalEventPainter.prototype._computeMetrics = function() {
+     var eventTheme = this._params.theme.event;
+     var trackHeight = Math.max(eventTheme.track.height, eventTheme.tape.height +
+                         this._frc.getLineHeight());
+     var metrics = {
+            trackOffset: eventTheme.track.offset,
+            trackHeight: trackHeight,
+               trackGap: eventTheme.track.gap,
+         trackIncrement: trackHeight + eventTheme.track.gap,
+                   icon: eventTheme.instant.icon,
+              iconWidth: eventTheme.instant.iconWidth,
+             iconHeight: eventTheme.instant.iconHeight,
+             labelWidth: eventTheme.label.width,
+           maxLabelChar: eventTheme.label.maxLabelChar,
+    impreciseIconMargin: eventTheme.instant.impreciseIconMargin
+     };
+
+     return metrics;
+};
+
+Timeline.OriginalEventPainter.prototype._prepareForPainting = function() {
+    // Remove everything previously painted: highlight, line and event layers.
+    // Prepare blank layers for painting.
+    var band = this._band;
+
+    if (this._backLayer == null) {
+        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
+        this._backLayer.style.visibility = "hidden";
+
+        var eventLabelPrototype = document.createElement("span");
+        eventLabelPrototype.className = "timeline-event-label";
+        this._backLayer.appendChild(eventLabelPrototype);
+        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
+    }
+    this._frc.update();
+    this._tracks = [];
+
+    if (this._highlightLayer != null) {
+        band.removeLayerDiv(this._highlightLayer);
+    }
+    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
+    this._highlightLayer.style.display = "none";
+
+    if (this._lineLayer != null) {
+        band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
+    this._lineLayer.style.display = "none";
+
+    if (this._eventLayer != null) {
+        band.removeLayerDiv(this._eventLayer);
+    }
+    this._eventLayer = band.createLayerDiv(115, "timeline-band-events");
+    this._eventLayer.style.display = "none";
+};
+
+Timeline.OriginalEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isInstant()) {
+        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+};
+
+Timeline.OriginalEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.OriginalEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.OriginalEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
+    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
+
+    var labelDivClassName = this._getLabelDivClassName(evt);
+    var labelSize = this._frc.computeSize(text, labelDivClassName);
+    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
+    var labelRight = labelLeft + labelSize.width;
+
+    var rightEdge = labelRight;
+    var track = this._findFreeTrack(evt, rightEdge);
+
+    var labelTop = Math.round(
+        metrics.trackOffset + track * metrics.trackIncrement +
+        metrics.trackHeight / 2 - labelSize.height / 2);
+
+    var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, 0);
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
+        labelSize.height, theme, labelDivClassName, highlightIndex);
+    var els = [iconElmtData.elmt, labelElmtData.elmt];
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt);
+    if (hDiv != null) {els.push(hDiv);}
+    this._fireEventPaintListeners('paintedEvent', evt, els);
+
+
+    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
+    this._tracks[track] = iconLeftEdge;
+};
+
+Timeline.OriginalEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var endDate = evt.getEnd();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+
+    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
+    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
+
+    var labelDivClassName = this._getLabelDivClassName(evt);
+    var labelSize = this._frc.computeSize(text, labelDivClassName);
+    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
+    var labelRight = labelLeft + labelSize.width;
+
+    var rightEdge = Math.max(labelRight, endPixel);
+    var track = this._findFreeTrack(evt, rightEdge);
+    var tapeHeight = theme.event.tape.height;
+    var labelTop = Math.round(
+        metrics.trackOffset + track * metrics.trackIncrement + tapeHeight);
+
+    var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, tapeHeight);
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
+                        labelSize.height, theme, labelDivClassName, highlightIndex);
+
+    var color = evt.getColor();
+    color = color != null ? color : theme.event.instant.impreciseColor;
+
+    var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel,
+        color, theme.event.instant.impreciseOpacity, metrics, theme, 0);
+    var els = [iconElmtData.elmt, labelElmtData.elmt, tapeElmtData.elmt];
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt);
+    if (hDiv != null) {els.push(hDiv);}
+    this._fireEventPaintListeners('paintedEvent', evt, els);
+
+    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
+    this._tracks[track] = iconLeftEdge;
+};
+
+Timeline.OriginalEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var endDate = evt.getEnd();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+
+    var labelDivClassName = this._getLabelDivClassName(evt);
+    var labelSize = this._frc.computeSize(text, labelDivClassName);
+    var labelLeft = startPixel;
+    var labelRight = labelLeft + labelSize.width;
+
+    var rightEdge = Math.max(labelRight, endPixel);
+    var track = this._findFreeTrack(evt, rightEdge);
+    var labelTop = Math.round(
+        metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height);
+
+    var color = evt.getColor();
+    color = color != null ? color : theme.event.duration.color;
+
+    var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel, color, 100, metrics, theme, 0);
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width,
+      labelSize.height, theme, labelDivClassName, highlightIndex);
+    var els = [tapeElmtData.elmt, labelElmtData.elmt];
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt);
+    if (hDiv != null) {els.push(hDiv);}
+    this._fireEventPaintListeners('paintedEvent', evt, els);
+
+    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
+    this._tracks[track] = startPixel;
+};
+
+Timeline.OriginalEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var latestStartDate = evt.getLatestStart();
+    var endDate = evt.getEnd();
+    var earliestEndDate = evt.getEarliestEnd();
+
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
+
+    var labelDivClassName = this._getLabelDivClassName(evt);
+    var labelSize = this._frc.computeSize(text, labelDivClassName);
+    var labelLeft = latestStartPixel;
+    var labelRight = labelLeft + labelSize.width;
+
+    var rightEdge = Math.max(labelRight, endPixel);
+    var track = this._findFreeTrack(evt, rightEdge);
+    var labelTop = Math.round(
+        metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height);
+
+    var color = evt.getColor();
+    color = color != null ? color : theme.event.duration.color;
+
+    // Imprecise events can have two event tapes
+    // The imprecise dates tape, uses opacity to be dimmer than precise dates
+    var impreciseTapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel,
+        theme.event.duration.impreciseColor,
+        theme.event.duration.impreciseOpacity, metrics, theme, 0);
+    // The precise dates tape, regular (100%) opacity
+    var tapeElmtData = this._paintEventTape(evt, track, latestStartPixel,
+        earliestEndPixel, color, 100, metrics, theme, 1);
+
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop,
+        labelSize.width, labelSize.height, theme, labelDivClassName, highlightIndex);
+    var els = [impreciseTapeElmtData.elmt, tapeElmtData.elmt, labelElmtData.elmt];
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt);
+    if (hDiv != null) {els.push(hDiv);}
+    this._fireEventPaintListeners('paintedEvent', evt, els);
+
+    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
+    this._tracks[track] = startPixel;
+};
+
+Timeline.OriginalEventPainter.prototype._encodeEventElID = function(elType, evt) {
+    return Timeline.EventUtils.encodeEventElID(this._timeline, this._band, elType, evt);
+};
+
+Timeline.OriginalEventPainter.prototype._findFreeTrack = function(event, rightEdge) {
+    var trackAttribute = event.getTrackNum();
+    if (trackAttribute != null) {
+        return trackAttribute; // early return since event includes track number
+    }
+
+    // normal case: find an open track
+    for (var i = 0; i < this._tracks.length; i++) {
+        var t = this._tracks[i];
+        if (t > rightEdge) {
+            break;
+        }
+    }
+    return i;
+};
+
+Timeline.OriginalEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme, tapeHeight) {
+    // If no tape, then paint the icon in the middle of the track.
+    // If there is a tape, paint the icon below the tape + impreciseIconMargin
+    var icon = evt.getIcon();
+    icon = icon != null ? icon : metrics.icon;
+
+    var top; // top of the icon
+    if (tapeHeight > 0) {
+        top = metrics.trackOffset + iconTrack * metrics.trackIncrement +
+              tapeHeight + metrics.impreciseIconMargin;
+    } else {
+        var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement +
+                     metrics.trackHeight / 2;
+        top = Math.round(middle - metrics.iconHeight / 2);
+    }
+    var img = SimileAjax.Graphics.createTranslucentImage(icon);
+    var iconDiv = this._timeline.getDocument().createElement("div");
+    iconDiv.className = this._getElClassName('timeline-event-icon', evt, 'icon');
+    iconDiv.id = this._encodeEventElID('icon', evt);
+    iconDiv.style.left = left + "px";
+    iconDiv.style.top = top + "px";
+    iconDiv.appendChild(img);
+
+    if(evt._title != null)
+        iconDiv.title = evt._title;
+
+    this._eventLayer.appendChild(iconDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  metrics.iconWidth,
+        height: metrics.iconHeight,
+        elmt:   iconDiv
+    };
+};
+
+Timeline.OriginalEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width,
+    height, theme, labelDivClassName, highlightIndex) {
+    var doc = this._timeline.getDocument();
+
+    var labelDiv = doc.createElement("div");
+    labelDiv.className = labelDivClassName;
+    labelDiv.id = this._encodeEventElID('label', evt);
+    labelDiv.style.left = left + "px";
+    labelDiv.style.width = width + "px";
+    labelDiv.style.top = top + "px";
+    labelDiv.innerHTML = text;
+
+    if(evt._title != null)
+        labelDiv.title = evt._title;
+
+    var color = evt.getTextColor();
+    if (color == null) {
+        color = evt.getColor();
+    }
+    if (color != null) {
+        labelDiv.style.color = color;
+    }
+    if (theme.event.highlightLabelBackground && highlightIndex >= 0) {
+        labelDiv.style.background = this._getHighlightColor(highlightIndex, theme);
+    }
+
+    this._eventLayer.appendChild(labelDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   labelDiv
+    };
+};
+
+Timeline.OriginalEventPainter.prototype._paintEventTape = function(
+    evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme, tape_index) {
+
+    var tapeWidth = endPixel - startPixel;
+    var tapeHeight = theme.event.tape.height;
+    var top = metrics.trackOffset + iconTrack * metrics.trackIncrement;
+
+    var tapeDiv = this._timeline.getDocument().createElement("div");
+    tapeDiv.className = this._getElClassName('timeline-event-tape', evt, 'tape');
+    tapeDiv.id = this._encodeEventElID('tape' + tape_index, evt);
+    tapeDiv.style.left = startPixel + "px";
+    tapeDiv.style.width = tapeWidth + "px";
+    tapeDiv.style.height = tapeHeight + "px";
+    tapeDiv.style.top = top + "px";
+
+    if(evt._title != null)
+        tapeDiv.title = evt._title;
+
+    if(color != null) {
+        tapeDiv.style.backgroundColor = color;
+    }
+
+    var backgroundImage = evt.getTapeImage();
+    var backgroundRepeat = evt.getTapeRepeat();
+    backgroundRepeat = backgroundRepeat != null ? backgroundRepeat : 'repeat';
+    if(backgroundImage != null) {
+      tapeDiv.style.backgroundImage = "url(" + backgroundImage + ")";
+      tapeDiv.style.backgroundRepeat = backgroundRepeat;
+    }
+
+    SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
+
+    this._eventLayer.appendChild(tapeDiv);
+
+    return {
+        left:   startPixel,
+        top:    top,
+        width:  tapeWidth,
+        height: tapeHeight,
+        elmt:   tapeDiv
+    };
+}
+
+Timeline.OriginalEventPainter.prototype._getLabelDivClassName = function(evt) {
+    return this._getElClassName('timeline-event-label', evt, 'label');
+};
+
+Timeline.OriginalEventPainter.prototype._getElClassName = function(elClassName, evt, prefix) {
+    // Prefix and '_' is added to the event's classname. Set to null for no prefix
+    var evt_classname = evt.getClassName(),
+        pieces = [];
+
+    if (evt_classname) {
+      if (prefix) {pieces.push(prefix + '-' + evt_classname + ' ');}
+      pieces.push(evt_classname + ' ');
+    }
+    pieces.push(elClassName);
+    return(pieces.join(''));
+};
+
+Timeline.OriginalEventPainter.prototype._getHighlightColor = function(highlightIndex, theme) {
+    var highlightColors = theme.event.highlightColors;
+    return highlightColors[Math.min(highlightIndex, highlightColors.length - 1)];
+};
+
+Timeline.OriginalEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme, evt) {
+    var div = null;
+    if (highlightIndex >= 0) {
+        var doc = this._timeline.getDocument();
+        var color = this._getHighlightColor(highlightIndex, theme);
+
+        div = doc.createElement("div");
+        div.className = this._getElClassName('timeline-event-highlight', evt, 'highlight');
+        div.id = this._encodeEventElID('highlight0', evt); // in future will have other
+                                                           // highlight divs for tapes + icons
+        div.style.position = "absolute";
+        div.style.overflow = "hidden";
+        div.style.left =    (dimensions.left - 2) + "px";
+        div.style.width =   (dimensions.width + 4) + "px";
+        div.style.top =     (dimensions.top - 2) + "px";
+        div.style.height =  (dimensions.height + 4) + "px";
+        div.style.background = color;
+
+        this._highlightLayer.appendChild(div);
+    }
+    return div;
+};
+
+Timeline.OriginalEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
+    var c = SimileAjax.DOM.getPageCoordinates(icon);
+    this._showBubble(
+        c.left + Math.ceil(icon.offsetWidth / 2),
+        c.top + Math.ceil(icon.offsetHeight / 2),
+        evt
+    );
+    this._fireOnSelect(evt.getID());
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.OriginalEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) {
+    if ("pageX" in domEvt) {
+        var x = domEvt.pageX;
+        var y = domEvt.pageY;
+    } else {
+        var c = SimileAjax.DOM.getPageCoordinates(target);
+        var x = domEvt.offsetX + c.left;
+        var y = domEvt.offsetY + c.top;
+    }
+    this._showBubble(x, y, evt);
+    this._fireOnSelect(evt.getID());
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.OriginalEventPainter.prototype.showBubble = function(evt) {
+    var elmt = this._eventIdToElmt[evt.getID()];
+    if (elmt) {
+        var c = SimileAjax.DOM.getPageCoordinates(elmt);
+        this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt);
+    }
+};
+
+Timeline.OriginalEventPainter.prototype._showBubble = function(x, y, evt) {
+    var div = document.createElement("div");
+    var themeBubble = this._params.theme.event.bubble;
+    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
+
+    SimileAjax.WindowManager.cancelPopups();
+    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y,
+        themeBubble.width, null, themeBubble.maxHeight);
+};
+
+Timeline.OriginalEventPainter.prototype._fireOnSelect = function(eventID) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        this._onSelectListeners[i](eventID);
+    }
+};
+
+Timeline.OriginalEventPainter.prototype._fireEventPaintListeners = function(op, evt, els) {
+    for (var i = 0; i < this._eventPaintListeners.length; i++) {
+        this._eventPaintListeners[i](this._band, op, evt, els);
+    }
+};
+/*==================================================
+ *  Detailed Event Painter
+ *==================================================
+ */
+
+// Note: a number of features from original-painter
+//       are not yet implemented in detailed painter.
+//       Eg classname, id attributes for icons, labels, tapes
+
+Timeline.DetailedEventPainter = function(params) {
+    this._params = params;
+    this._onSelectListeners = [];
+
+    this._filterMatcher = null;
+    this._highlightMatcher = null;
+    this._frc = null;
+
+    this._eventIdToElmt = {};
+};
+
+Timeline.DetailedEventPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backLayer = null;
+    this._eventLayer = null;
+    this._lineLayer = null;
+    this._highlightLayer = null;
+
+    this._eventIdToElmt = null;
+};
+
+Timeline.DetailedEventPainter.prototype.getType = function() {
+    return 'detailed';
+};
+
+Timeline.DetailedEventPainter.prototype.addOnSelectListener = function(listener) {
+    this._onSelectListeners.push(listener);
+};
+
+Timeline.DetailedEventPainter.prototype.removeOnSelectListener = function(listener) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        if (this._onSelectListeners[i] == listener) {
+            this._onSelectListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.DetailedEventPainter.prototype.getFilterMatcher = function() {
+    return this._filterMatcher;
+};
+
+Timeline.DetailedEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
+    this._filterMatcher = filterMatcher;
+};
+
+Timeline.DetailedEventPainter.prototype.getHighlightMatcher = function() {
+    return this._highlightMatcher;
+};
+
+Timeline.DetailedEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
+    this._highlightMatcher = highlightMatcher;
+};
+
+Timeline.DetailedEventPainter.prototype.paint = function() {
+    var eventSource = this._band.getEventSource();
+    if (eventSource == null) {
+        return;
+    }
+
+    this._eventIdToElmt = {};
+    this._prepareForPainting();
+
+    var eventTheme = this._params.theme.event;
+    var trackHeight = Math.max(eventTheme.track.height, this._frc.getLineHeight());
+    var metrics = {
+        trackOffset:    Math.round(this._band.getViewWidth() / 2 - trackHeight / 2),
+        trackHeight:    trackHeight,
+        trackGap:       eventTheme.track.gap,
+        trackIncrement: trackHeight + eventTheme.track.gap,
+        icon:           eventTheme.instant.icon,
+        iconWidth:      eventTheme.instant.iconWidth,
+        iconHeight:     eventTheme.instant.iconHeight,
+        labelWidth:     eventTheme.label.width
+    }
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var filterMatcher = (this._filterMatcher != null) ?
+        this._filterMatcher :
+        function(evt) { return true; };
+    var highlightMatcher = (this._highlightMatcher != null) ?
+        this._highlightMatcher :
+        function(evt) { return -1; };
+
+    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
+    while (iterator.hasNext()) {
+        var evt = iterator.next();
+        if (filterMatcher(evt)) {
+            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
+        }
+    }
+
+    this._highlightLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+    this._eventLayer.style.display = "block";
+    // update the band object for max number of tracks in this section of the ether
+    this._band.updateEventTrackInfo(this._lowerTracks.length + this._upperTracks.length,
+                                 metrics.trackIncrement);
+};
+
+Timeline.DetailedEventPainter.prototype.softPaint = function() {
+};
+
+Timeline.DetailedEventPainter.prototype._prepareForPainting = function() {
+    var band = this._band;
+
+    if (this._backLayer == null) {
+        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
+        this._backLayer.style.visibility = "hidden";
+
+        var eventLabelPrototype = document.createElement("span");
+        eventLabelPrototype.className = "timeline-event-label";
+        this._backLayer.appendChild(eventLabelPrototype);
+        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
+    }
+    this._frc.update();
+    this._lowerTracks = [];
+    this._upperTracks = [];
+
+    if (this._highlightLayer != null) {
+        band.removeLayerDiv(this._highlightLayer);
+    }
+    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
+    this._highlightLayer.style.display = "none";
+
+    if (this._lineLayer != null) {
+        band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
+    this._lineLayer.style.display = "none";
+
+    if (this._eventLayer != null) {
+        band.removeLayerDiv(this._eventLayer);
+    }
+    this._eventLayer = band.createLayerDiv(110, "timeline-band-events");
+    this._eventLayer.style.display = "none";
+};
+
+Timeline.DetailedEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isInstant()) {
+        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+};
+
+Timeline.DetailedEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.DetailedEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.DetailedEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
+    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
+
+    var labelSize = this._frc.computeSize(text);
+    var iconTrack = this._findFreeTrackForSolid(iconRightEdge, startPixel);
+    var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme);
+
+    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
+    var labelTrack = iconTrack;
+
+    var iconTrackData = this._getTrackData(iconTrack);
+    if (Math.min(iconTrackData.solid, iconTrackData.text) >= labelLeft + labelSize.width) { // label on the same track, to the right of icon
+        iconTrackData.solid = iconLeftEdge;
+        iconTrackData.text = labelLeft;
+    } else { // label on a different track, below icon
+        iconTrackData.solid = iconLeftEdge;
+
+        labelLeft = startPixel + theme.event.label.offsetFromLine;
+        labelTrack = this._findFreeTrackForText(iconTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; });
+        this._getTrackData(labelTrack).text = iconLeftEdge;
+
+        this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme);
+    }
+
+    var labelTop = Math.round(
+        metrics.trackOffset + labelTrack * metrics.trackIncrement +
+        metrics.trackHeight / 2 - labelSize.height / 2);
+
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    this._createHighlightDiv(highlightIndex, iconElmtData, theme);
+
+    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
+};
+
+Timeline.DetailedEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var endDate = evt.getEnd();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+
+    var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2);
+    var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2);
+
+    var labelSize = this._frc.computeSize(text);
+    var iconTrack = this._findFreeTrackForSolid(endPixel, startPixel);
+
+    var tapeElmtData = this._paintEventTape(evt, iconTrack, startPixel, endPixel,
+        theme.event.instant.impreciseColor, theme.event.instant.impreciseOpacity, metrics, theme);
+    var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme);
+
+    var iconTrackData = this._getTrackData(iconTrack);
+    iconTrackData.solid = iconLeftEdge;
+
+    var labelLeft = iconRightEdge + theme.event.label.offsetFromLine;
+    var labelRight = labelLeft + labelSize.width;
+    var labelTrack;
+    if (labelRight < endPixel) {
+        labelTrack = iconTrack;
+    } else {
+        labelLeft = startPixel + theme.event.label.offsetFromLine;
+        labelRight = labelLeft + labelSize.width;
+
+        labelTrack = this._findFreeTrackForText(iconTrack, labelRight, function(t) { t.line = startPixel - 2; });
+        this._getTrackData(labelTrack).text = iconLeftEdge;
+
+        this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme);
+    }
+    var labelTop = Math.round(
+        metrics.trackOffset + labelTrack * metrics.trackIncrement +
+        metrics.trackHeight / 2 - labelSize.height / 2);
+
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    this._createHighlightDiv(highlightIndex, iconElmtData, theme);
+
+    this._eventIdToElmt[evt.getID()] = iconElmtData.elmt;
+};
+
+Timeline.DetailedEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var endDate = evt.getEnd();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+
+    var labelSize = this._frc.computeSize(text);
+    var tapeTrack = this._findFreeTrackForSolid(endPixel);
+    var color = evt.getColor();
+    color = color != null ? color : theme.event.duration.color;
+
+    var tapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, color, 100, metrics, theme);
+
+    var tapeTrackData = this._getTrackData(tapeTrack);
+    tapeTrackData.solid = startPixel;
+
+    var labelLeft = startPixel + theme.event.label.offsetFromLine;
+    var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; });
+    this._getTrackData(labelTrack).text = startPixel - 2;
+
+    this._paintEventLine(evt, startPixel, tapeTrack, labelTrack, metrics, theme);
+
+    var labelTop = Math.round(
+        metrics.trackOffset + labelTrack * metrics.trackIncrement +
+        metrics.trackHeight / 2 - labelSize.height / 2);
+
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
+
+    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
+};
+
+Timeline.DetailedEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var doc = this._timeline.getDocument();
+    var text = evt.getText();
+
+    var startDate = evt.getStart();
+    var latestStartDate = evt.getLatestStart();
+    var endDate = evt.getEnd();
+    var earliestEndDate = evt.getEarliestEnd();
+
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
+    var endPixel = Math.round(this._band.dateToPixelOffset(endDate));
+    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
+
+    var labelSize = this._frc.computeSize(text);
+    var tapeTrack = this._findFreeTrackForSolid(endPixel);
+    var color = evt.getColor();
+    color = color != null ? color : theme.event.duration.color;
+
+    var impreciseTapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel,
+        theme.event.duration.impreciseColor, theme.event.duration.impreciseOpacity, metrics, theme);
+    var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel, color, 100, metrics, theme);
+
+    var tapeTrackData = this._getTrackData(tapeTrack);
+    tapeTrackData.solid = startPixel;
+
+    var labelLeft = latestStartPixel + theme.event.label.offsetFromLine;
+    var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = latestStartPixel - 2; });
+    this._getTrackData(labelTrack).text = latestStartPixel - 2;
+
+    this._paintEventLine(evt, latestStartPixel, tapeTrack, labelTrack, metrics, theme);
+
+    var labelTop = Math.round(
+        metrics.trackOffset + labelTrack * metrics.trackIncrement +
+        metrics.trackHeight / 2 - labelSize.height / 2);
+
+    var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme);
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
+
+    this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt;
+};
+
+Timeline.DetailedEventPainter.prototype._findFreeTrackForSolid = function(solidEdge, softEdge) {
+    for (var i = 0; true; i++) {
+        if (i < this._lowerTracks.length) {
+            var t = this._lowerTracks[i];
+            if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) {
+                return i;
+            }
+        } else {
+            this._lowerTracks.push({
+                solid:  Number.POSITIVE_INFINITY,
+                text:   Number.POSITIVE_INFINITY,
+                line:   Number.POSITIVE_INFINITY
+            });
+
+            return i;
+        }
+
+        if (i < this._upperTracks.length) {
+            var t = this._upperTracks[i];
+            if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) {
+                return -1 - i;
+            }
+        } else {
+            this._upperTracks.push({
+                solid:  Number.POSITIVE_INFINITY,
+                text:   Number.POSITIVE_INFINITY,
+                line:   Number.POSITIVE_INFINITY
+            });
+
+            return -1 - i;
+        }
+    }
+};
+
+Timeline.DetailedEventPainter.prototype._findFreeTrackForText = function(fromTrack, edge, occupiedTrackVisitor) {
+    var extendUp;
+    var index;
+    var firstIndex;
+    var result;
+
+    if (fromTrack < 0) {
+        extendUp = true;
+        firstIndex = -fromTrack;
+
+        index = this._findFreeUpperTrackForText(firstIndex, edge);
+        result = -1 - index;
+    } else if (fromTrack > 0) {
+        extendUp = false;
+        firstIndex = fromTrack + 1;
+
+        index = this._findFreeLowerTrackForText(firstIndex, edge);
+        result = index;
+    } else {
+        var upIndex = this._findFreeUpperTrackForText(0, edge);
+        var downIndex = this._findFreeLowerTrackForText(1, edge);
+
+        if (downIndex - 1 <= upIndex) {
+            extendUp = false;
+            firstIndex = 1;
+            index = downIndex;
+            result = index;
+        } else {
+            extendUp = true;
+            firstIndex = 0;
+            index = upIndex;
+            result = -1 - index;
+        }
+    }
+
+    if (extendUp) {
+        if (index == this._upperTracks.length) {
+            this._upperTracks.push({
+                solid:  Number.POSITIVE_INFINITY,
+                text:   Number.POSITIVE_INFINITY,
+                line:   Number.POSITIVE_INFINITY
+            });
+        }
+        for (var i = firstIndex; i < index; i++) {
+            occupiedTrackVisitor(this._upperTracks[i]);
+        }
+    } else {
+        if (index == this._lowerTracks.length) {
+            this._lowerTracks.push({
+                solid:  Number.POSITIVE_INFINITY,
+                text:   Number.POSITIVE_INFINITY,
+                line:   Number.POSITIVE_INFINITY
+            });
+        }
+        for (var i = firstIndex; i < index; i++) {
+            occupiedTrackVisitor(this._lowerTracks[i]);
+        }
+    }
+    return result;
+};
+
+Timeline.DetailedEventPainter.prototype._findFreeLowerTrackForText = function(index, edge) {
+    for (; index < this._lowerTracks.length; index++) {
+        var t = this._lowerTracks[index];
+        if (Math.min(t.solid, t.text) >= edge) {
+            break;
+        }
+    }
+    return index;
+};
+
+Timeline.DetailedEventPainter.prototype._findFreeUpperTrackForText = function(index, edge) {
+    for (; index < this._upperTracks.length; index++) {
+        var t = this._upperTracks[index];
+        if (Math.min(t.solid, t.text) >= edge) {
+            break;
+        }
+    }
+    return index;
+};
+
+Timeline.DetailedEventPainter.prototype._getTrackData = function(index) {
+    return (index < 0) ? this._upperTracks[-index - 1] : this._lowerTracks[index];
+};
+
+Timeline.DetailedEventPainter.prototype._paintEventLine = function(evt, left, startTrack, endTrack, metrics, theme) {
+    var top = Math.round(metrics.trackOffset + startTrack * metrics.trackIncrement + metrics.trackHeight / 2);
+    var height = Math.round(Math.abs(endTrack - startTrack) * metrics.trackIncrement);
+
+    var lineStyle = "1px solid " + theme.event.label.lineColor;
+    var lineDiv = this._timeline.getDocument().createElement("div");
+	lineDiv.style.position = "absolute";
+    lineDiv.style.left = left + "px";
+    lineDiv.style.width = theme.event.label.offsetFromLine + "px";
+    lineDiv.style.height = height + "px";
+    if (startTrack > endTrack) {
+        lineDiv.style.top = (top - height) + "px";
+        lineDiv.style.borderTop = lineStyle;
+    } else {
+        lineDiv.style.top = top + "px";
+        lineDiv.style.borderBottom = lineStyle;
+    }
+    lineDiv.style.borderLeft = lineStyle;
+    this._lineLayer.appendChild(lineDiv);
+};
+
+Timeline.DetailedEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme) {
+    var icon = evt.getIcon();
+    icon = icon != null ? icon : metrics.icon;
+
+    var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2;
+    var top = Math.round(middle - metrics.iconHeight / 2);
+
+    var img = SimileAjax.Graphics.createTranslucentImage(icon);
+    var iconDiv = this._timeline.getDocument().createElement("div");
+    iconDiv.style.position = "absolute";
+    iconDiv.style.left = left + "px";
+    iconDiv.style.top = top + "px";
+    iconDiv.appendChild(img);
+    iconDiv.style.cursor = "pointer";
+
+    if(evt._title != null)
+        iconDiv.title = evt._title
+
+    this._eventLayer.appendChild(iconDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  metrics.iconWidth,
+        height: metrics.iconHeight,
+        elmt:   iconDiv
+    };
+};
+
+Timeline.DetailedEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width, height, theme) {
+    var doc = this._timeline.getDocument();
+
+    var labelBackgroundDiv = doc.createElement("div");
+    labelBackgroundDiv.style.position = "absolute";
+    labelBackgroundDiv.style.left = left + "px";
+    labelBackgroundDiv.style.width = width + "px";
+    labelBackgroundDiv.style.top = top + "px";
+    labelBackgroundDiv.style.height = height + "px";
+    labelBackgroundDiv.style.backgroundColor = theme.event.label.backgroundColor;
+    SimileAjax.Graphics.setOpacity(labelBackgroundDiv, theme.event.label.backgroundOpacity);
+    this._eventLayer.appendChild(labelBackgroundDiv);
+
+    var labelDiv = doc.createElement("div");
+    labelDiv.style.position = "absolute";
+    labelDiv.style.left = left + "px";
+    labelDiv.style.width = width + "px";
+    labelDiv.style.top = top + "px";
+    labelDiv.innerHTML = text;
+    labelDiv.style.cursor = "pointer";
+
+    if(evt._title != null)
+        labelDiv.title = evt._title;
+
+    var color = evt.getTextColor();
+    if (color == null) {
+        color = evt.getColor();
+    }
+    if (color != null) {
+        labelDiv.style.color = color;
+    }
+
+    this._eventLayer.appendChild(labelDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   labelDiv
+    };
+};
+
+Timeline.DetailedEventPainter.prototype._paintEventTape = function(
+    evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme) {
+
+    var tapeWidth = endPixel - startPixel;
+    var tapeHeight = theme.event.tape.height;
+    var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2;
+    var top = Math.round(middle - tapeHeight / 2);
+
+    var tapeDiv = this._timeline.getDocument().createElement("div");
+    tapeDiv.style.position = "absolute";
+    tapeDiv.style.left = startPixel + "px";
+    tapeDiv.style.width = tapeWidth + "px";
+    tapeDiv.style.top = top + "px";
+    tapeDiv.style.height = tapeHeight + "px";
+    tapeDiv.style.backgroundColor = color;
+    tapeDiv.style.overflow = "hidden";
+    tapeDiv.style.cursor = "pointer";
+
+    if(evt._title != null)
+        tapeDiv.title = evt._title;
+
+    SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
+
+    this._eventLayer.appendChild(tapeDiv);
+
+    return {
+        left:   startPixel,
+        top:    top,
+        width:  tapeWidth,
+        height: tapeHeight,
+        elmt:   tapeDiv
+    };
+}
+
+Timeline.DetailedEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) {
+    if (highlightIndex >= 0) {
+        var doc = this._timeline.getDocument();
+        var eventTheme = theme.event;
+
+        var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)];
+
+        var div = doc.createElement("div");
+        div.style.position = "absolute";
+        div.style.overflow = "hidden";
+        div.style.left =    (dimensions.left - 2) + "px";
+        div.style.width =   (dimensions.width + 4) + "px";
+        div.style.top =     (dimensions.top - 2) + "px";
+        div.style.height =  (dimensions.height + 4) + "px";
+        div.style.background = color;
+
+        this._highlightLayer.appendChild(div);
+    }
+};
+
+Timeline.DetailedEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
+    var c = SimileAjax.DOM.getPageCoordinates(icon);
+    this._showBubble(
+        c.left + Math.ceil(icon.offsetWidth / 2),
+        c.top + Math.ceil(icon.offsetHeight / 2),
+        evt
+    );
+    this._fireOnSelect(evt.getID());
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.DetailedEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) {
+    if ("pageX" in domEvt) {
+        var x = domEvt.pageX;
+        var y = domEvt.pageY;
+    } else {
+        var c = SimileAjax.DOM.getPageCoordinates(target);
+        var x = domEvt.offsetX + c.left;
+        var y = domEvt.offsetY + c.top;
+    }
+    this._showBubble(x, y, evt);
+    this._fireOnSelect(evt.getID());
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.DetailedEventPainter.prototype.showBubble = function(evt) {
+    var elmt = this._eventIdToElmt[evt.getID()];
+    if (elmt) {
+        var c = SimileAjax.DOM.getPageCoordinates(elmt);
+        this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt);
+    }
+};
+
+Timeline.DetailedEventPainter.prototype._showBubble = function(x, y, evt) {
+    var div = document.createElement("div");
+    var themeBubble = this._params.theme.event.bubble;
+    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
+
+    SimileAjax.WindowManager.cancelPopups();
+    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y,
+       themeBubble.width, null, themeBubble.maxHeight);
+};
+
+Timeline.DetailedEventPainter.prototype._fireOnSelect = function(eventID) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        this._onSelectListeners[i](eventID);
+    }
+};
+/*==================================================
+ *  Overview Event Painter
+ *==================================================
+ */
+
+Timeline.OverviewEventPainter = function(params) {
+    this._params = params;
+    this._onSelectListeners = [];
+
+    this._filterMatcher = null;
+    this._highlightMatcher = null;
+};
+
+Timeline.OverviewEventPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._eventLayer = null;
+    this._highlightLayer = null;
+};
+
+Timeline.OverviewEventPainter.prototype.getType = function() {
+    return 'overview';
+};
+
+Timeline.OverviewEventPainter.prototype.addOnSelectListener = function(listener) {
+    this._onSelectListeners.push(listener);
+};
+
+Timeline.OverviewEventPainter.prototype.removeOnSelectListener = function(listener) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        if (this._onSelectListeners[i] == listener) {
+            this._onSelectListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.OverviewEventPainter.prototype.getFilterMatcher = function() {
+    return this._filterMatcher;
+};
+
+Timeline.OverviewEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
+    this._filterMatcher = filterMatcher;
+};
+
+Timeline.OverviewEventPainter.prototype.getHighlightMatcher = function() {
+    return this._highlightMatcher;
+};
+
+Timeline.OverviewEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
+    this._highlightMatcher = highlightMatcher;
+};
+
+Timeline.OverviewEventPainter.prototype.paint = function() {
+    var eventSource = this._band.getEventSource();
+    if (eventSource == null) {
+        return;
+    }
+
+    this._prepareForPainting();
+
+    var eventTheme = this._params.theme.event;
+    var metrics = {
+        trackOffset:    eventTheme.overviewTrack.offset,
+        trackHeight:    eventTheme.overviewTrack.height,
+        trackGap:       eventTheme.overviewTrack.gap,
+        trackIncrement: eventTheme.overviewTrack.height + eventTheme.overviewTrack.gap
+    }
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var filterMatcher = (this._filterMatcher != null) ?
+        this._filterMatcher :
+        function(evt) { return true; };
+    var highlightMatcher = (this._highlightMatcher != null) ?
+        this._highlightMatcher :
+        function(evt) { return -1; };
+
+    var iterator = eventSource.getEventReverseIterator(minDate, maxDate);
+    while (iterator.hasNext()) {
+        var evt = iterator.next();
+        if (filterMatcher(evt)) {
+            this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
+        }
+    }
+
+    this._highlightLayer.style.display = "block";
+    this._eventLayer.style.display = "block";
+    // update the band object for max number of tracks in this section of the ether
+    this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement);
+};
+
+Timeline.OverviewEventPainter.prototype.softPaint = function() {
+};
+
+Timeline.OverviewEventPainter.prototype._prepareForPainting = function() {
+    var band = this._band;
+
+    this._tracks = [];
+
+    if (this._highlightLayer != null) {
+        band.removeLayerDiv(this._highlightLayer);
+    }
+    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
+    this._highlightLayer.style.display = "none";
+
+    if (this._eventLayer != null) {
+        band.removeLayerDiv(this._eventLayer);
+    }
+    this._eventLayer = band.createLayerDiv(110, "timeline-band-events");
+    this._eventLayer.style.display = "none";
+};
+
+Timeline.OverviewEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isInstant()) {
+        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+};
+
+Timeline.OverviewEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var startDate = evt.getStart();
+    var startPixel = Math.round(this._band.dateToPixelOffset(startDate));
+
+    var color = evt.getColor(),
+        klassName = evt.getClassName();
+    if (klassName) {
+      color = null;
+    } else {
+      color = color != null ? color : theme.event.duration.color;
+    }
+
+    var tickElmtData = this._paintEventTick(evt, startPixel, color, 100, metrics, theme);
+
+    this._createHighlightDiv(highlightIndex, tickElmtData, theme);
+};
+
+Timeline.OverviewEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var latestStartDate = evt.getLatestStart();
+    var earliestEndDate = evt.getEarliestEnd();
+
+    var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate));
+    var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate));
+
+    var tapeTrack = 0;
+    for (; tapeTrack < this._tracks.length; tapeTrack++) {
+        if (earliestEndPixel < this._tracks[tapeTrack]) {
+            break;
+        }
+    }
+    this._tracks[tapeTrack] = earliestEndPixel;
+
+    var color = evt.getColor(),
+        klassName = evt.getClassName();
+    if (klassName) {
+      color = null;
+    } else {
+      color = color != null ? color : theme.event.duration.color;
+    }
+
+    var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel,
+      color, 100, metrics, theme, klassName);
+
+    this._createHighlightDiv(highlightIndex, tapeElmtData, theme);
+};
+
+Timeline.OverviewEventPainter.prototype._paintEventTape = function(
+    evt, track, left, right, color, opacity, metrics, theme, klassName) {
+
+    var top = metrics.trackOffset + track * metrics.trackIncrement;
+    var width = right - left;
+    var height = metrics.trackHeight;
+
+    var tapeDiv = this._timeline.getDocument().createElement("div");
+    tapeDiv.className = 'timeline-small-event-tape'
+    if (klassName) {tapeDiv.className += ' small-' + klassName;}
+    tapeDiv.style.left = left + "px";
+    tapeDiv.style.width = width + "px";
+    tapeDiv.style.top = top + "px";
+    tapeDiv.style.height = height + "px";
+
+    if (color) {
+      tapeDiv.style.backgroundColor = color; // set color here if defined by event. Else use css
+    }
+ //   tapeDiv.style.overflow = "hidden";   // now set in css
+ //   tapeDiv.style.position = "absolute";
+    if(opacity<100) SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
+
+    this._eventLayer.appendChild(tapeDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   tapeDiv
+    };
+}
+
+Timeline.OverviewEventPainter.prototype._paintEventTick = function(
+    evt, left, color, opacity, metrics, theme) {
+
+    var height = theme.event.overviewTrack.tickHeight;
+    var top = metrics.trackOffset - height;
+    var width = 1;
+
+    var tickDiv = this._timeline.getDocument().createElement("div");
+	  tickDiv.className = 'timeline-small-event-icon'
+    tickDiv.style.left = left + "px";
+    tickDiv.style.top = top + "px";
+  //  tickDiv.style.width = width + "px";
+  //  tickDiv.style.position = "absolute";
+  //  tickDiv.style.height = height + "px";
+  //  tickDiv.style.backgroundColor = color;
+  //  tickDiv.style.overflow = "hidden";
+
+    var klassName = evt.getClassName()
+    if (klassName) {tickDiv.className +=' small-' + klassName};
+
+    if(opacity<100) {SimileAjax.Graphics.setOpacity(tickDiv, opacity)};
+
+    this._eventLayer.appendChild(tickDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   tickDiv
+    };
+}
+
+Timeline.OverviewEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) {
+    if (highlightIndex >= 0) {
+        var doc = this._timeline.getDocument();
+        var eventTheme = theme.event;
+
+        var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)];
+
+        var div = doc.createElement("div");
+        div.style.position = "absolute";
+        div.style.overflow = "hidden";
+        div.style.left =    (dimensions.left - 1) + "px";
+        div.style.width =   (dimensions.width + 2) + "px";
+        div.style.top =     (dimensions.top - 1) + "px";
+        div.style.height =  (dimensions.height + 2) + "px";
+        div.style.background = color;
+
+        this._highlightLayer.appendChild(div);
+    }
+};
+
+Timeline.OverviewEventPainter.prototype.showBubble = function(evt) {
+    // not implemented
+};
+/*==================================================
+ *  Compact Event Painter
+ *==================================================
+ */
+
+Timeline.CompactEventPainter = function(params) {
+    this._params = params;
+    this._onSelectListeners = [];
+
+    this._filterMatcher = null;
+    this._highlightMatcher = null;
+    this._frc = null;
+
+    this._eventIdToElmt = {};
+};
+
+Timeline.CompactEventPainter.prototype.getType = function() {
+    return 'compact';
+};
+
+Timeline.CompactEventPainter.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._backLayer = null;
+    this._eventLayer = null;
+    this._lineLayer = null;
+    this._highlightLayer = null;
+
+    this._eventIdToElmt = null;
+};
+
+Timeline.CompactEventPainter.prototype.addOnSelectListener = function(listener) {
+    this._onSelectListeners.push(listener);
+};
+
+Timeline.CompactEventPainter.prototype.removeOnSelectListener = function(listener) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        if (this._onSelectListeners[i] == listener) {
+            this._onSelectListeners.splice(i, 1);
+            break;
+        }
+    }
+};
+
+Timeline.CompactEventPainter.prototype.getFilterMatcher = function() {
+    return this._filterMatcher;
+};
+
+Timeline.CompactEventPainter.prototype.setFilterMatcher = function(filterMatcher) {
+    this._filterMatcher = filterMatcher;
+};
+
+Timeline.CompactEventPainter.prototype.getHighlightMatcher = function() {
+    return this._highlightMatcher;
+};
+
+Timeline.CompactEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) {
+    this._highlightMatcher = highlightMatcher;
+};
+
+Timeline.CompactEventPainter.prototype.paint = function() {
+    var eventSource = this._band.getEventSource();
+    if (eventSource == null) {
+        return;
+    }
+
+    this._eventIdToElmt = {};
+    this._prepareForPainting();
+
+    var metrics = this._computeMetrics();
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    var filterMatcher = (this._filterMatcher != null) ?
+        this._filterMatcher :
+        function(evt) { return true; };
+
+    var highlightMatcher = (this._highlightMatcher != null) ?
+        this._highlightMatcher :
+        function(evt) { return -1; };
+
+    var iterator = eventSource.getEventIterator(minDate, maxDate);
+
+    var stackConcurrentPreciseInstantEvents = "stackConcurrentPreciseInstantEvents" in this._params && typeof this._params.stackConcurrentPreciseInstantEvents == "object";
+    var collapseConcurrentPreciseInstantEvents = "collapseConcurrentPreciseInstantEvents" in this._params && this._params.collapseConcurrentPreciseInstantEvents;
+    if (collapseConcurrentPreciseInstantEvents || stackConcurrentPreciseInstantEvents) {
+        var bufferedEvents = [];
+        var previousInstantEvent = null;
+
+        while (iterator.hasNext()) {
+            var evt = iterator.next();
+            if (filterMatcher(evt)) {
+                if (!evt.isInstant() || evt.isImprecise()) {
+                    this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
+                } else if (previousInstantEvent != null &&
+                        previousInstantEvent.getStart().getTime() == evt.getStart().getTime()) {
+                    bufferedEvents[bufferedEvents.length - 1].push(evt);
+                } else {
+                    bufferedEvents.push([ evt ]);
+                    previousInstantEvent = evt;
+                }
+            }
+        }
+
+        for (var i = 0; i < bufferedEvents.length; i++) {
+            var compositeEvents = bufferedEvents[i];
+            if (compositeEvents.length == 1) {
+                this.paintEvent(compositeEvents[0], metrics, this._params.theme, highlightMatcher(evt));
+            } else {
+                var match = -1;
+                for (var j = 0; match < 0 && j < compositeEvents.length; j++) {
+                    match = highlightMatcher(compositeEvents[j]);
+                }
+
+                if (stackConcurrentPreciseInstantEvents) {
+                    this.paintStackedPreciseInstantEvents(compositeEvents, metrics, this._params.theme, match);
+                } else {
+                    this.paintCompositePreciseInstantEvents(compositeEvents, metrics, this._params.theme, match);
+                }
+            }
+        }
+    } else {
+        while (iterator.hasNext()) {
+            var evt = iterator.next();
+            if (filterMatcher(evt)) {
+                this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt));
+            }
+        }
+    }
+
+    this._highlightLayer.style.display = "block";
+    this._lineLayer.style.display = "block";
+    this._eventLayer.style.display = "block";
+
+    this._setOrthogonalOffset(metrics);
+};
+
+Timeline.CompactEventPainter.prototype.softPaint = function() {
+    this._setOrthogonalOffset(this._computeMetrics());
+};
+
+Timeline.CompactEventPainter.prototype._setOrthogonalOffset = function(metrics) {
+    var actualViewWidth = 2 * metrics.trackOffset + this._tracks.length * metrics.trackHeight;
+    var minOrthogonalOffset = Math.min(0, this._band.getViewWidth() - actualViewWidth);
+    var orthogonalOffset = Math.max(minOrthogonalOffset, this._band.getViewOrthogonalOffset());
+
+    this._highlightLayer.style.top =
+        this._lineLayer.style.top =
+            this._eventLayer.style.top =
+                orthogonalOffset + "px";
+};
+
+Timeline.CompactEventPainter.prototype._computeMetrics = function() {
+    var theme = this._params.theme;
+    var eventTheme = theme.event;
+
+    var metrics = {
+        trackOffset:            "trackOffset" in this._params ? this._params.trackOffset : 10,
+        trackHeight:            "trackHeight" in this._params ? this._params.trackHeight : 10,
+
+        tapeHeight:             theme.event.tape.height,
+        tapeBottomMargin:       "tapeBottomMargin" in this._params ? this._params.tapeBottomMargin : 2,
+
+        labelBottomMargin:      "labelBottomMargin" in this._params ? this._params.labelBottomMargin : 5,
+        labelRightMargin:       "labelRightMargin" in this._params ? this._params.labelRightMargin : 5,
+
+        defaultIcon:            eventTheme.instant.icon,
+        defaultIconWidth:       eventTheme.instant.iconWidth,
+        defaultIconHeight:      eventTheme.instant.iconHeight,
+
+        customIconWidth:        "iconWidth" in this._params ? this._params.iconWidth : eventTheme.instant.iconWidth,
+        customIconHeight:       "iconHeight" in this._params ? this._params.iconHeight : eventTheme.instant.iconHeight,
+
+        iconLabelGap:           "iconLabelGap" in this._params ? this._params.iconLabelGap : 2,
+        iconBottomMargin:       "iconBottomMargin" in this._params ? this._params.iconBottomMargin : 2
+    };
+    if ("compositeIcon" in this._params) {
+        metrics.compositeIcon = this._params.compositeIcon;
+        metrics.compositeIconWidth = this._params.compositeIconWidth || metrics.customIconWidth;
+        metrics.compositeIconHeight = this._params.compositeIconHeight || metrics.customIconHeight;
+    } else {
+        metrics.compositeIcon = metrics.defaultIcon;
+        metrics.compositeIconWidth = metrics.defaultIconWidth;
+        metrics.compositeIconHeight = metrics.defaultIconHeight;
+    }
+    metrics.defaultStackIcon = "icon" in this._params.stackConcurrentPreciseInstantEvents ?
+        this._params.stackConcurrentPreciseInstantEvents.icon : metrics.defaultIcon;
+    metrics.defaultStackIconWidth = "iconWidth" in this._params.stackConcurrentPreciseInstantEvents ?
+        this._params.stackConcurrentPreciseInstantEvents.iconWidth : metrics.defaultIconWidth;
+    metrics.defaultStackIconHeight = "iconHeight" in this._params.stackConcurrentPreciseInstantEvents ?
+        this._params.stackConcurrentPreciseInstantEvents.iconHeight : metrics.defaultIconHeight;
+
+    return metrics;
+};
+
+Timeline.CompactEventPainter.prototype._prepareForPainting = function() {
+    var band = this._band;
+
+    if (this._backLayer == null) {
+        this._backLayer = this._band.createLayerDiv(0, "timeline-band-events");
+        this._backLayer.style.visibility = "hidden";
+
+        var eventLabelPrototype = document.createElement("span");
+        eventLabelPrototype.className = "timeline-event-label";
+        this._backLayer.appendChild(eventLabelPrototype);
+        this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype);
+    }
+    this._frc.update();
+    this._tracks = [];
+
+    if (this._highlightLayer != null) {
+        band.removeLayerDiv(this._highlightLayer);
+    }
+    this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights");
+    this._highlightLayer.style.display = "none";
+
+    if (this._lineLayer != null) {
+        band.removeLayerDiv(this._lineLayer);
+    }
+    this._lineLayer = band.createLayerDiv(110, "timeline-band-lines");
+    this._lineLayer.style.display = "none";
+
+    if (this._eventLayer != null) {
+        band.removeLayerDiv(this._eventLayer);
+    }
+    this._eventLayer = band.createLayerDiv(115, "timeline-band-events");
+    this._eventLayer.style.display = "none";
+};
+
+Timeline.CompactEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isInstant()) {
+        this.paintInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+};
+
+Timeline.CompactEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.CompactEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    if (evt.isImprecise()) {
+        this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    } else {
+        this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex);
+    }
+}
+
+Timeline.CompactEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var commonData = {
+        tooltip: evt.getProperty("tooltip") || evt.getText()
+    };
+
+    var iconData = {
+        url: evt.getIcon()
+    };
+    if (iconData.url == null) {
+        iconData.url = metrics.defaultIcon;
+        iconData.width = metrics.defaultIconWidth;
+        iconData.height = metrics.defaultIconHeight;
+        iconData.className = "timeline-event-icon-default";
+    } else {
+        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
+        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
+    }
+
+    var labelData = {
+        text:       evt.getText(),
+        color:      evt.getTextColor() || evt.getColor(),
+        className:  evt.getClassName()
+    };
+
+    var result = this.paintTapeIconLabel(
+        evt.getStart(),
+        commonData,
+        null, // no tape data
+        iconData,
+        labelData,
+        metrics,
+        theme,
+        highlightIndex
+    );
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
+    };
+    SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
+
+    this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
+};
+
+Timeline.CompactEventPainter.prototype.paintCompositePreciseInstantEvents = function(events, metrics, theme, highlightIndex) {
+    var evt = events[0];
+
+    var tooltips = [];
+    for (var i = 0; i < events.length; i++) {
+        tooltips.push(events[i].getProperty("tooltip") || events[i].getText());
+    }
+    var commonData = {
+        tooltip: tooltips.join("; ")
+    };
+
+    var iconData = {
+        url: metrics.compositeIcon,
+        width: metrics.compositeIconWidth,
+        height: metrics.compositeIconHeight,
+        className: "timeline-event-icon-composite"
+    };
+
+    var labelData = {
+        text: String.substitute(this._params.compositeEventLabelTemplate, [ events.length ])
+    };
+
+    var result = this.paintTapeIconLabel(
+        evt.getStart(),
+        commonData,
+        null, // no tape data
+        iconData,
+        labelData,
+        metrics,
+        theme,
+        highlightIndex
+    );
+
+    var self = this;
+    var clickHandler = function(elmt, domEvt, target) {
+        return self._onClickMultiplePreciseInstantEvent(result.iconElmtData.elmt, domEvt, events);
+    };
+
+    SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
+
+    for (var i = 0; i < events.length; i++) {
+        this._eventIdToElmt[events[i].getID()] = result.iconElmtData.elmt;
+    }
+};
+
+Timeline.CompactEventPainter.prototype.paintStackedPreciseInstantEvents = function(events, metrics, theme, highlightIndex) {
+    var limit = "limit" in this._params.stackConcurrentPreciseInstantEvents ?
+        this._params.stackConcurrentPreciseInstantEvents.limit : 10;
+    var moreMessageTemplate = "moreMessageTemplate" in this._params.stackConcurrentPreciseInstantEvents ?
+        this._params.stackConcurrentPreciseInstantEvents.moreMessageTemplate : "%0 More Events";
+    var showMoreMessage = limit <= events.length - 2; // We want at least 2 more events above the limit.
+                                                      // Otherwise we'd need the singular case of "1 More Event"
+
+    var band = this._band;
+    var getPixelOffset = function(date) {
+        return Math.round(band.dateToPixelOffset(date));
+    };
+    var getIconData = function(evt) {
+        var iconData = {
+            url: evt.getIcon()
+        };
+        if (iconData.url == null) {
+            iconData.url = metrics.defaultStackIcon;
+            iconData.width = metrics.defaultStackIconWidth;
+            iconData.height = metrics.defaultStackIconHeight;
+            iconData.className = "timeline-event-icon-stack timeline-event-icon-default";
+        } else {
+            iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
+            iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
+            iconData.className = "timeline-event-icon-stack";
+        }
+        return iconData;
+    };
+
+    var firstIconData = getIconData(events[0]);
+    var horizontalIncrement = 5;
+    var leftIconEdge = 0;
+    var totalLabelWidth = 0;
+    var totalLabelHeight = 0;
+    var totalIconHeight = 0;
+
+    var records = [];
+    for (var i = 0; i < events.length && (!showMoreMessage || i < limit); i++) {
+        var evt = events[i];
+        var text = evt.getText();
+        var iconData = getIconData(evt);
+        var labelSize = this._frc.computeSize(text);
+        var record = {
+            text:       text,
+            iconData:   iconData,
+            labelSize:  labelSize,
+            iconLeft:   firstIconData.width + i * horizontalIncrement - iconData.width
+        };
+        record.labelLeft = firstIconData.width + i * horizontalIncrement + metrics.iconLabelGap;
+        record.top = totalLabelHeight;
+        records.push(record);
+
+        leftIconEdge = Math.min(leftIconEdge, record.iconLeft);
+        totalLabelHeight += labelSize.height;
+        totalLabelWidth = Math.max(totalLabelWidth, record.labelLeft + labelSize.width);
+        totalIconHeight = Math.max(totalIconHeight, record.top + iconData.height);
+    }
+    if (showMoreMessage) {
+        var moreMessage = String.substitute(moreMessageTemplate, [ events.length - limit ]);
+
+        var moreMessageLabelSize = this._frc.computeSize(moreMessage);
+        var moreMessageLabelLeft = firstIconData.width + (limit - 1) * horizontalIncrement + metrics.iconLabelGap;
+        var moreMessageLabelTop = totalLabelHeight;
+
+        totalLabelHeight += moreMessageLabelSize.height;
+        totalLabelWidth = Math.max(totalLabelWidth, moreMessageLabelLeft + moreMessageLabelSize.width);
+    }
+    totalLabelWidth += metrics.labelRightMargin;
+    totalLabelHeight += metrics.labelBottomMargin;
+    totalIconHeight += metrics.iconBottomMargin;
+
+    var anchorPixel = getPixelOffset(events[0].getStart());
+    var newTracks = [];
+
+    var trackCount = Math.ceil(Math.max(totalIconHeight, totalLabelHeight) / metrics.trackHeight);
+    var rightIconEdge = firstIconData.width + (events.length - 1) * horizontalIncrement;
+    for (var i = 0; i < trackCount; i++) {
+        newTracks.push({ start: leftIconEdge, end: rightIconEdge });
+    }
+    var labelTrackCount = Math.ceil(totalLabelHeight / metrics.trackHeight);
+    for (var i = 0; i < labelTrackCount; i++) {
+        var track = newTracks[i];
+        track.end = Math.max(track.end, totalLabelWidth);
+    }
+
+    var firstTrack = this._fitTracks(anchorPixel, newTracks);
+    var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset;
+
+    var iconStackDiv = this._timeline.getDocument().createElement("div");
+    iconStackDiv.className = 'timeline-event-icon-stack';
+    iconStackDiv.style.position = "absolute";
+    iconStackDiv.style.overflow = "visible";
+    iconStackDiv.style.left = anchorPixel + "px";
+    iconStackDiv.style.top = verticalPixelOffset + "px";
+    iconStackDiv.style.width = rightIconEdge + "px";
+    iconStackDiv.style.height = totalIconHeight + "px";
+    iconStackDiv.innerHTML = "<div style='position: relative'></div>";
+    this._eventLayer.appendChild(iconStackDiv);
+
+    var self = this;
+    var onMouseOver = function(domEvt) {
+        try {
+            var n = parseInt(this.getAttribute("index"));
+            var childNodes = iconStackDiv.firstChild.childNodes;
+            for (var i = 0; i < childNodes.length; i++) {
+                var child = childNodes[i];
+                if (i == n) {
+                    child.style.zIndex = childNodes.length;
+                } else {
+                    child.style.zIndex = childNodes.length - i;
+                }
+            }
+        } catch (e) {
+        }
+    };
+    var paintEvent = function(index) {
+        var record = records[index];
+        var evt = events[index];
+        var tooltip = evt.getProperty("tooltip") || evt.getText();
+
+        var labelElmtData = self._paintEventLabel(
+            { tooltip: tooltip },
+            { text: record.text },
+            anchorPixel + record.labelLeft,
+            verticalPixelOffset + record.top,
+            record.labelSize.width,
+            record.labelSize.height,
+            theme
+        );
+        labelElmtData.elmt.setAttribute("index", index);
+        labelElmtData.elmt.onmouseover = onMouseOver;
+
+        var img = SimileAjax.Graphics.createTranslucentImage(record.iconData.url);
+        var iconDiv = self._timeline.getDocument().createElement("div");
+        iconDiv.className = 'timeline-event-icon' + ("className" in record.iconData ? (" " + record.iconData.className) : "");
+        iconDiv.style.left = record.iconLeft + "px";
+        iconDiv.style.top = record.top + "px";
+        iconDiv.style.zIndex = (records.length - index);
+        iconDiv.appendChild(img);
+        iconDiv.setAttribute("index", index);
+        iconDiv.onmouseover = onMouseOver;
+
+        iconStackDiv.firstChild.appendChild(iconDiv);
+
+        var clickHandler = function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(labelElmtData.elmt, domEvt, evt);
+        };
+
+        SimileAjax.DOM.registerEvent(iconDiv, "mousedown", clickHandler);
+        SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler);
+
+        self._eventIdToElmt[evt.getID()] = iconDiv;
+    };
+    for (var i = 0; i < records.length; i++) {
+        paintEvent(i);
+    }
+
+    if (showMoreMessage) {
+        var moreEvents = events.slice(limit);
+        var moreMessageLabelElmtData = this._paintEventLabel(
+            { tooltip: moreMessage },
+            { text: moreMessage },
+            anchorPixel + moreMessageLabelLeft,
+            verticalPixelOffset + moreMessageLabelTop,
+            moreMessageLabelSize.width,
+            moreMessageLabelSize.height,
+            theme
+        );
+
+        var moreMessageClickHandler = function(elmt, domEvt, target) {
+            return self._onClickMultiplePreciseInstantEvent(moreMessageLabelElmtData.elmt, domEvt, moreEvents);
+        };
+        SimileAjax.DOM.registerEvent(moreMessageLabelElmtData.elmt, "mousedown", moreMessageClickHandler);
+
+        for (var i = 0; i < moreEvents.length; i++) {
+            this._eventIdToElmt[moreEvents[i].getID()] = moreMessageLabelElmtData.elmt;
+        }
+    }
+    //this._createHighlightDiv(highlightIndex, iconElmtData, theme);
+};
+
+Timeline.CompactEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) {
+    var commonData = {
+        tooltip: evt.getProperty("tooltip") || evt.getText()
+    };
+
+    var tapeData = {
+        start:          evt.getStart(),
+        end:            evt.getEnd(),
+        latestStart:    evt.getLatestStart(),
+        earliestEnd:    evt.getEarliestEnd(),
+        isInstant:      true
+    };
+
+    var iconData = {
+        url: evt.getIcon()
+    };
+    if (iconData.url == null) {
+        iconData = null;
+    } else {
+        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
+        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
+    }
+
+    var labelData = {
+        text:       evt.getText(),
+        color:      evt.getTextColor() || evt.getColor(),
+        className:  evt.getClassName()
+    };
+
+    var result = this.paintTapeIconLabel(
+        evt.getStart(),
+        commonData,
+        tapeData, // no tape data
+        iconData,
+        labelData,
+        metrics,
+        theme,
+        highlightIndex
+    );
+
+    var self = this;
+    var clickHandler = iconData != null ?
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
+        } :
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
+        };
+
+    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(result.impreciseTapeElmtData.elmt, "mousedown", clickHandler);
+
+    if (iconData != null) {
+        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
+        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
+    } else {
+        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
+    }
+};
+
+Timeline.CompactEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var commonData = {
+        tooltip: evt.getProperty("tooltip") || evt.getText()
+    };
+
+    var tapeData = {
+        start:          evt.getStart(),
+        end:            evt.getEnd(),
+        isInstant:      false
+    };
+
+    var iconData = {
+        url: evt.getIcon()
+    };
+    if (iconData.url == null) {
+        iconData = null;
+    } else {
+        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
+        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
+    }
+
+    var labelData = {
+        text:       evt.getText(),
+        color:      evt.getTextColor() || evt.getColor(),
+        className:  evt.getClassName()
+    };
+
+    var result = this.paintTapeIconLabel(
+        evt.getLatestStart(),
+        commonData,
+        tapeData, // no tape data
+        iconData,
+        labelData,
+        metrics,
+        theme,
+        highlightIndex
+    );
+
+    var self = this;
+    var clickHandler = iconData != null ?
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
+        } :
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
+        };
+
+    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler);
+
+    if (iconData != null) {
+        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
+        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
+    } else {
+        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
+    }
+};
+
+Timeline.CompactEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) {
+    var commonData = {
+        tooltip: evt.getProperty("tooltip") || evt.getText()
+    };
+
+    var tapeData = {
+        start:          evt.getStart(),
+        end:            evt.getEnd(),
+        latestStart:    evt.getLatestStart(),
+        earliestEnd:    evt.getEarliestEnd(),
+        isInstant:      false
+    };
+
+    var iconData = {
+        url: evt.getIcon()
+    };
+    if (iconData.url == null) {
+        iconData = null;
+    } else {
+        iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth;
+        iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight;
+    }
+
+    var labelData = {
+        text:       evt.getText(),
+        color:      evt.getTextColor() || evt.getColor(),
+        className:  evt.getClassName()
+    };
+
+    var result = this.paintTapeIconLabel(
+        evt.getLatestStart(),
+        commonData,
+        tapeData, // no tape data
+        iconData,
+        labelData,
+        metrics,
+        theme,
+        highlightIndex
+    );
+
+    var self = this;
+    var clickHandler = iconData != null ?
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt);
+        } :
+        function(elmt, domEvt, target) {
+            return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt);
+        };
+
+    SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler);
+    SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler);
+
+    if (iconData != null) {
+        SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler);
+        this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt;
+    } else {
+        this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt;
+    }
+};
+
+Timeline.CompactEventPainter.prototype.paintTapeIconLabel = function(
+    anchorDate,
+    commonData,
+    tapeData,
+    iconData,
+    labelData,
+    metrics,
+    theme,
+    highlightIndex
+) {
+    var band = this._band;
+    var getPixelOffset = function(date) {
+        return Math.round(band.dateToPixelOffset(date));
+    };
+
+    var anchorPixel = getPixelOffset(anchorDate);
+    var newTracks = [];
+
+    var tapeHeightOccupied = 0;         // how many pixels (vertically) the tape occupies, including bottom margin
+    var tapeTrackCount = 0;             // how many tracks the tape takes up, usually just 1
+    var tapeLastTrackExtraSpace = 0;    // on the last track that the tape occupies, how many pixels are left (for icon and label to occupy as well)
+    if (tapeData != null) {
+        tapeHeightOccupied = metrics.tapeHeight + metrics.tapeBottomMargin;
+        tapeTrackCount = Math.ceil(metrics.tapeHeight / metrics.trackHeight);
+
+        var tapeEndPixelOffset = getPixelOffset(tapeData.end) - anchorPixel;
+        var tapeStartPixelOffset = getPixelOffset(tapeData.start) - anchorPixel;
+
+        for (var t = 0; t < tapeTrackCount; t++) {
+            newTracks.push({ start: tapeStartPixelOffset, end: tapeEndPixelOffset });
+        }
+
+        tapeLastTrackExtraSpace = metrics.trackHeight - (tapeHeightOccupied % metrics.tapeHeight);
+    }
+
+    var iconStartPixelOffset = 0;        // where the icon starts compared to the anchor pixel;
+                                         // this can be negative if the icon is center-aligned around the anchor
+    var iconHorizontalSpaceOccupied = 0; // how many pixels the icon take up from the anchor pixel,
+                                         // including the gap between the icon and the label
+    if (iconData != null) {
+        if ("iconAlign" in iconData && iconData.iconAlign == "center") {
+            iconStartPixelOffset = -Math.floor(iconData.width / 2);
+        }
+        iconHorizontalSpaceOccupied = iconStartPixelOffset + iconData.width + metrics.iconLabelGap;
+
+        if (tapeTrackCount > 0) {
+            newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, iconHorizontalSpaceOccupied);
+        }
+
+        var iconHeight = iconData.height + metrics.iconBottomMargin + tapeLastTrackExtraSpace;
+        while (iconHeight > 0) {
+            newTracks.push({ start: iconStartPixelOffset, end: iconHorizontalSpaceOccupied });
+            iconHeight -= metrics.trackHeight;
+        }
+    }
+
+    var text = labelData.text;
+    var labelSize = this._frc.computeSize(text);
+    var labelHeight = labelSize.height + metrics.labelBottomMargin + tapeLastTrackExtraSpace;
+    var labelEndPixelOffset = iconHorizontalSpaceOccupied + labelSize.width + metrics.labelRightMargin;
+    if (tapeTrackCount > 0) {
+        newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, labelEndPixelOffset);
+    }
+    for (var i = 0; labelHeight > 0; i++) {
+        if (tapeTrackCount + i < newTracks.length) {
+            var track = newTracks[tapeTrackCount + i];
+            track.end = labelEndPixelOffset;
+        } else {
+            newTracks.push({ start: 0, end: labelEndPixelOffset });
+        }
+        labelHeight -= metrics.trackHeight;
+    }
+
+    /*
+     *  Try to fit the new track on top of the existing tracks, then
+     *  render the various elements.
+     */
+    var firstTrack = this._fitTracks(anchorPixel, newTracks);
+    var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset;
+    var result = {};
+
+    result.labelElmtData = this._paintEventLabel(
+        commonData,
+        labelData,
+        anchorPixel + iconHorizontalSpaceOccupied,
+        verticalPixelOffset + tapeHeightOccupied,
+        labelSize.width,
+        labelSize.height,
+        theme
+    );
+
+    if (tapeData != null) {
+        if ("latestStart" in tapeData || "earliestEnd" in tapeData) {
+            result.impreciseTapeElmtData = this._paintEventTape(
+                commonData,
+                tapeData,
+                metrics.tapeHeight,
+                verticalPixelOffset,
+                getPixelOffset(tapeData.start),
+                getPixelOffset(tapeData.end),
+                theme.event.duration.impreciseColor,
+                theme.event.duration.impreciseOpacity,
+                metrics,
+                theme
+            );
+        }
+        if (!tapeData.isInstant && "start" in tapeData && "end" in tapeData) {
+            result.tapeElmtData = this._paintEventTape(
+                commonData,
+                tapeData,
+                metrics.tapeHeight,
+                verticalPixelOffset,
+                anchorPixel,
+                getPixelOffset("earliestEnd" in tapeData ? tapeData.earliestEnd : tapeData.end),
+                tapeData.color,
+                100,
+                metrics,
+                theme
+            );
+        }
+    }
+
+    if (iconData != null) {
+        result.iconElmtData = this._paintEventIcon(
+            commonData,
+            iconData,
+            verticalPixelOffset + tapeHeightOccupied,
+            anchorPixel + iconStartPixelOffset,
+            metrics,
+            theme
+        );
+    }
+    //this._createHighlightDiv(highlightIndex, iconElmtData, theme);
+
+    return result;
+};
+
+Timeline.CompactEventPainter.prototype._fitTracks = function(anchorPixel, newTracks) {
+    var firstTrack;
+    for (firstTrack = 0; firstTrack < this._tracks.length; firstTrack++) {
+        var fit = true;
+        for (var j = 0; j < newTracks.length && (firstTrack + j) < this._tracks.length; j++) {
+            var existingTrack = this._tracks[firstTrack + j];
+            var newTrack = newTracks[j];
+            if (anchorPixel + newTrack.start < existingTrack) {
+                fit = false;
+                break;
+            }
+        }
+
+        if (fit) {
+            break;
+        }
+    }
+    for (var i = 0; i < newTracks.length; i++) {
+        this._tracks[firstTrack + i] = anchorPixel + newTracks[i].end;
+    }
+
+    return firstTrack;
+};
+
+
+Timeline.CompactEventPainter.prototype._paintEventIcon = function(commonData, iconData, top, left, metrics, theme) {
+    var img = SimileAjax.Graphics.createTranslucentImage(iconData.url);
+    var iconDiv = this._timeline.getDocument().createElement("div");
+    iconDiv.className = 'timeline-event-icon' + ("className" in iconData ? (" " + iconData.className) : "");
+    iconDiv.style.left = left + "px";
+    iconDiv.style.top = top + "px";
+    iconDiv.appendChild(img);
+
+    if ("tooltip" in commonData && typeof commonData.tooltip == "string") {
+        iconDiv.title = commonData.tooltip;
+    }
+
+    this._eventLayer.appendChild(iconDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  metrics.iconWidth,
+        height: metrics.iconHeight,
+        elmt:   iconDiv
+    };
+};
+
+Timeline.CompactEventPainter.prototype._paintEventLabel = function(commonData, labelData, left, top, width, height, theme) {
+    var doc = this._timeline.getDocument();
+
+    var labelDiv = doc.createElement("div");
+    labelDiv.className = 'timeline-event-label';
+
+    labelDiv.style.left = left + "px";
+    labelDiv.style.width = (width + 1) + "px";
+    labelDiv.style.top = top + "px";
+    labelDiv.innerHTML = labelData.text;
+
+    if ("tooltip" in commonData && typeof commonData.tooltip == "string") {
+        labelDiv.title = commonData.tooltip;
+    }
+    if ("color" in labelData && typeof labelData.color == "string") {
+        labelDiv.style.color = labelData.color;
+    }
+    if ("className" in labelData && typeof labelData.className == "string") {
+        labelDiv.className += ' ' + labelData.className;
+    }
+
+    this._eventLayer.appendChild(labelDiv);
+
+    return {
+        left:   left,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   labelDiv
+    };
+};
+
+Timeline.CompactEventPainter.prototype._paintEventTape = function(
+    commonData, tapeData, height, top, startPixel, endPixel, color, opacity, metrics, theme) {
+
+    var width = endPixel - startPixel;
+
+    var tapeDiv = this._timeline.getDocument().createElement("div");
+    tapeDiv.className = "timeline-event-tape"
+
+    tapeDiv.style.left = startPixel + "px";
+    tapeDiv.style.top = top + "px";
+    tapeDiv.style.width = width + "px";
+    tapeDiv.style.height = height + "px";
+
+    if ("tooltip" in commonData && typeof commonData.tooltip == "string") {
+        tapeDiv.title = commonData.tooltip;
+    }
+    if (color != null && typeof tapeData.color == "string") {
+        tapeDiv.style.backgroundColor = color;
+    }
+
+    if ("backgroundImage" in tapeData && typeof tapeData.backgroundImage == "string") {
+        tapeDiv.style.backgroundImage = "url(" + backgroundImage + ")";
+        tapeDiv.style.backgroundRepeat =
+            ("backgroundRepeat" in tapeData && typeof tapeData.backgroundRepeat == "string")
+                ? tapeData.backgroundRepeat : 'repeat';
+    }
+
+    SimileAjax.Graphics.setOpacity(tapeDiv, opacity);
+
+    if ("className" in tapeData && typeof tapeData.className == "string") {
+        tapeDiv.className += ' ' + tapeData.className;
+    }
+
+    this._eventLayer.appendChild(tapeDiv);
+
+    return {
+        left:   startPixel,
+        top:    top,
+        width:  width,
+        height: height,
+        elmt:   tapeDiv
+    };
+}
+
+Timeline.CompactEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) {
+    if (highlightIndex >= 0) {
+        var doc = this._timeline.getDocument();
+        var eventTheme = theme.event;
+
+        var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)];
+
+        var div = doc.createElement("div");
+        div.style.position = "absolute";
+        div.style.overflow = "hidden";
+        div.style.left =    (dimensions.left - 2) + "px";
+        div.style.width =   (dimensions.width + 4) + "px";
+        div.style.top =     (dimensions.top - 2) + "px";
+        div.style.height =  (dimensions.height + 4) + "px";
+//        div.style.background = color;
+
+        this._highlightLayer.appendChild(div);
+    }
+};
+
+Timeline.CompactEventPainter.prototype._onClickMultiplePreciseInstantEvent = function(icon, domEvt, events) {
+    var c = SimileAjax.DOM.getPageCoordinates(icon);
+    this._showBubble(
+        c.left + Math.ceil(icon.offsetWidth / 2),
+        c.top + Math.ceil(icon.offsetHeight / 2),
+        events
+    );
+
+    var ids = [];
+    for (var i = 0; i < events.length; i++) {
+        ids.push(events[i].getID());
+    }
+    this._fireOnSelect(ids);
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+
+    return false;
+};
+
+Timeline.CompactEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) {
+    var c = SimileAjax.DOM.getPageCoordinates(icon);
+    this._showBubble(
+        c.left + Math.ceil(icon.offsetWidth / 2),
+        c.top + Math.ceil(icon.offsetHeight / 2),
+        [evt]
+    );
+    this._fireOnSelect([evt.getID()]);
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.CompactEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) {
+    if ("pageX" in domEvt) {
+        var x = domEvt.pageX;
+        var y = domEvt.pageY;
+    } else {
+        var c = SimileAjax.DOM.getPageCoordinates(target);
+        var x = domEvt.offsetX + c.left;
+        var y = domEvt.offsetY + c.top;
+    }
+    this._showBubble(x, y, [evt]);
+    this._fireOnSelect([evt.getID()]);
+
+    domEvt.cancelBubble = true;
+    SimileAjax.DOM.cancelEvent(domEvt);
+    return false;
+};
+
+Timeline.CompactEventPainter.prototype.showBubble = function(evt) {
+    var elmt = this._eventIdToElmt[evt.getID()];
+    if (elmt) {
+        var c = SimileAjax.DOM.getPageCoordinates(elmt);
+        this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, [evt]);
+    }
+};
+
+Timeline.CompactEventPainter.prototype._showBubble = function(x, y, evts) {
+    var div = document.createElement("div");
+
+    evts = ("fillInfoBubble" in evts) ? [evts] : evts;
+    for (var i = 0; i < evts.length; i++) {
+        var div2 = document.createElement("div");
+        div.appendChild(div2);
+
+        evts[i].fillInfoBubble(div2, this._params.theme, this._band.getLabeller());
+    }
+
+    SimileAjax.WindowManager.cancelPopups();
+    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, this._params.theme.event.bubble.width);
+};
+
+Timeline.CompactEventPainter.prototype._fireOnSelect = function(eventIDs) {
+    for (var i = 0; i < this._onSelectListeners.length; i++) {
+        this._onSelectListeners[i](eventIDs);
+    }
+};
+/*==================================================
+ *  Span Highlight Decorator
+ *==================================================
+ */
+
+Timeline.SpanHighlightDecorator = function(params) {
+    // When evaluating params, test against null. Not "p in params". Testing against
+    // null enables caller to explicitly request the default. Testing against "in" means
+    // that the param has to be ommitted to get the default.
+    this._unit = params.unit != null ? params.unit : SimileAjax.NativeDateUnit;
+    this._startDate = (typeof params.startDate == "string") ?
+        this._unit.parseFromObject(params.startDate) : params.startDate;
+    this._endDate = (typeof params.endDate == "string") ?
+        this._unit.parseFromObject(params.endDate) : params.endDate;
+    this._startLabel = params.startLabel != null ? params.startLabel : ""; // not null!
+    this._endLabel   = params.endLabel   != null ? params.endLabel   : ""; // not null!
+    this._color = params.color;
+    this._cssClass = params.cssClass != null ? params.cssClass : null;
+    this._opacity = params.opacity != null ? params.opacity : 100;
+         // Default z is 10, behind everything but background grid.
+         // If inFront, then place just behind events, in front of everything else
+    this._zIndex = (params.inFront != null && params.inFront) ? 113 : 10;
+};
+
+Timeline.SpanHighlightDecorator.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+
+    this._layerDiv = null;
+};
+
+Timeline.SpanHighlightDecorator.prototype.paint = function() {
+    if (this._layerDiv != null) {
+        this._band.removeLayerDiv(this._layerDiv);
+    }
+    this._layerDiv = this._band.createLayerDiv(this._zIndex);
+    this._layerDiv.setAttribute("name", "span-highlight-decorator"); // for debugging
+    this._layerDiv.style.display = "none";
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    if (this._unit.compare(this._startDate, maxDate) < 0 &&
+        this._unit.compare(this._endDate, minDate) > 0) {
+
+        minDate = this._unit.later(minDate, this._startDate);
+        maxDate = this._unit.earlier(maxDate, this._endDate);
+
+        var minPixel = this._band.dateToPixelOffset(minDate);
+        var maxPixel = this._band.dateToPixelOffset(maxDate);
+
+        var doc = this._timeline.getDocument();
+
+        var createTable = function() {
+            var table = doc.createElement("table");
+            table.insertRow(0).insertCell(0);
+            return table;
+        };
+
+        var div = doc.createElement("div");
+        div.className='timeline-highlight-decorator'
+        if(this._cssClass) {
+        	  div.className += ' ' + this._cssClass;
+        }
+        if(this._color != null) {
+        	  div.style.backgroundColor = this._color;
+        }
+        if (this._opacity < 100) {
+            SimileAjax.Graphics.setOpacity(div, this._opacity);
+        }
+        this._layerDiv.appendChild(div);
+
+        var tableStartLabel = createTable();
+        tableStartLabel.className = 'timeline-highlight-label timeline-highlight-label-start'
+        var tdStart =  tableStartLabel.rows[0].cells[0]
+        tdStart.innerHTML = this._startLabel;
+        if (this._cssClass) {
+        	  tdStart.className = 'label_' + this._cssClass;
+        }
+        this._layerDiv.appendChild(tableStartLabel);
+
+        var tableEndLabel = createTable();
+        tableEndLabel.className = 'timeline-highlight-label timeline-highlight-label-end'
+        var tdEnd = tableEndLabel.rows[0].cells[0]
+        tdEnd.innerHTML = this._endLabel;
+        if (this._cssClass) {
+        	   tdEnd.className = 'label_' + this._cssClass;
+        }
+        this._layerDiv.appendChild(tableEndLabel);
+
+        if (this._timeline.isHorizontal()){
+            div.style.left = minPixel + "px";
+            div.style.width = (maxPixel - minPixel) + "px";
+
+            tableStartLabel.style.right = (this._band.getTotalViewLength() - minPixel) + "px";
+            tableStartLabel.style.width = (this._startLabel.length) + "em";
+
+            tableEndLabel.style.left = maxPixel + "px";
+            tableEndLabel.style.width = (this._endLabel.length) + "em";
+
+        } else {
+            div.style.top = minPixel + "px";
+            div.style.height = (maxPixel - minPixel) + "px";
+
+            tableStartLabel.style.bottom = minPixel + "px";
+            tableStartLabel.style.height = "1.5px";
+
+            tableEndLabel.style.top = maxPixel + "px";
+            tableEndLabel.style.height = "1.5px";
+        }
+    }
+    this._layerDiv.style.display = "block";
+};
+
+Timeline.SpanHighlightDecorator.prototype.softPaint = function() {
+};
+
+/*==================================================
+ *  Point Highlight Decorator
+ *==================================================
+ */
+
+Timeline.PointHighlightDecorator = function(params) {
+    this._unit = params.unit != null ? params.unit : SimileAjax.NativeDateUnit;
+    this._date = (typeof params.date == "string") ?
+        this._unit.parseFromObject(params.date) : params.date;
+    this._width = params.width != null ? params.width : 10;
+      // Since the width is used to calculate placements (see minPixel, below), we
+      // specify width here, not in css.
+    this._color = params.color;
+    this._cssClass = params.cssClass != null ? params.cssClass : '';
+    this._opacity = params.opacity != null ? params.opacity : 100;
+};
+
+Timeline.PointHighlightDecorator.prototype.initialize = function(band, timeline) {
+    this._band = band;
+    this._timeline = timeline;
+    this._layerDiv = null;
+};
+
+Timeline.PointHighlightDecorator.prototype.paint = function() {
+    if (this._layerDiv != null) {
+        this._band.removeLayerDiv(this._layerDiv);
+    }
+    this._layerDiv = this._band.createLayerDiv(10);
+    this._layerDiv.setAttribute("name", "span-highlight-decorator"); // for debugging
+    this._layerDiv.style.display = "none";
+
+    var minDate = this._band.getMinDate();
+    var maxDate = this._band.getMaxDate();
+
+    if (this._unit.compare(this._date, maxDate) < 0 &&
+        this._unit.compare(this._date, minDate) > 0) {
+
+        var pixel = this._band.dateToPixelOffset(this._date);
+        var minPixel = pixel - Math.round(this._width / 2);
+
+        var doc = this._timeline.getDocument();
+
+        var div = doc.createElement("div");
+        div.className='timeline-highlight-point-decorator';
+        div.className += ' ' + this._cssClass;
+
+        if(this._color != null) {
+        	  div.style.backgroundColor = this._color;
+        }
+        if (this._opacity < 100) {
+            SimileAjax.Graphics.setOpacity(div, this._opacity);
+        }
+        this._layerDiv.appendChild(div);
+
+        if (this._timeline.isHorizontal()) {
+            div.style.left = minPixel + "px";
+            div.style.width = this._width;
+        } else {
+            div.style.top = minPixel + "px";
+            div.style.height = this._width;
+        }
+    }
+    this._layerDiv.style.display = "block";
+};
+
+Timeline.PointHighlightDecorator.prototype.softPaint = function() {
+};
+/*==================================================
+ *  Default Unit
+ *==================================================
+ */
+
+Timeline.NativeDateUnit = new Object();
+
+Timeline.NativeDateUnit.createLabeller = function(locale, timeZone) {
+    return new Timeline.GregorianDateLabeller(locale, timeZone);
+};
+
+Timeline.NativeDateUnit.makeDefaultValue = function() {
+    return new Date();
+};
+
+Timeline.NativeDateUnit.cloneValue = function(v) {
+    return new Date(v.getTime());
+};
+
+Timeline.NativeDateUnit.getParser = function(format) {
+    if (typeof format == "string") {
+        format = format.toLowerCase();
+    }
+    return (format == "iso8601" || format == "iso 8601") ?
+        Timeline.DateTime.parseIso8601DateTime : 
+        Timeline.DateTime.parseGregorianDateTime;
+};
+
+Timeline.NativeDateUnit.parseFromObject = function(o) {
+    return Timeline.DateTime.parseGregorianDateTime(o);
+};
+
+Timeline.NativeDateUnit.toNumber = function(v) {
+    return v.getTime();
+};
+
+Timeline.NativeDateUnit.fromNumber = function(n) {
+    return new Date(n);
+};
+
+Timeline.NativeDateUnit.compare = function(v1, v2) {
+    var n1, n2;
+    if (typeof v1 == "object") {
+        n1 = v1.getTime();
+    } else {
+        n1 = Number(v1);
+    }
+    if (typeof v2 == "object") {
+        n2 = v2.getTime();
+    } else {
+        n2 = Number(v2);
+    }
+
+    return n1 - n2;
+};
+
+Timeline.NativeDateUnit.earlier = function(v1, v2) {
+    return Timeline.NativeDateUnit.compare(v1, v2) < 0 ? v1 : v2;
+};
+
+Timeline.NativeDateUnit.later = function(v1, v2) {
+    return Timeline.NativeDateUnit.compare(v1, v2) > 0 ? v1 : v2;
+};
+
+Timeline.NativeDateUnit.change = function(v, n) {
+    return new Date(v.getTime() + n);
+};
+
+/*==================================================
+ *  Common localization strings
+ *==================================================
+ */
+
+Timeline.strings["fr"] = {
+    wikiLinkLabel:  "Discute"
+};
+
+/*==================================================
+ *  Localization of labellers.js
+ *==================================================
+ */
+
+Timeline.GregorianDateLabeller.monthNames["fr"] = [
+    "jan", "fev", "mar", "avr", "mai", "jui", "jui", "aou", "sep", "oct", "nov", "dec"
+];
+/*==================================================
+ *  Common localization strings
+ *==================================================
+ */
+
+Timeline.strings["en"] = {
+    wikiLinkLabel:  "Discuss"
+};
+
+/*==================================================
+ *  Localization of labellers.js
+ *==================================================
+ */
+
+Timeline.GregorianDateLabeller.monthNames["en"] = [
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+];
+
+Timeline.GregorianDateLabeller.dayNames["en"] = [
+    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+];
--- a/web/data/cubicweb.timeline-ext.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.timeline-ext.js	Thu May 14 12:50:34 2009 +0200
@@ -9,7 +9,7 @@
 /* provide our own custom date parser since the default
  * one only understands iso8601 and gregorian dates
  */
-Timeline.NativeDateUnit.getParser = function(format) {
+SimileAjax.NativeDateUnit.getParser = Timeline.NativeDateUnit.getParser = function(format) {
     if (typeof format == "string") {
 	if (format.indexOf('%') != -1) {
 	    return function(datestring) {
--- a/web/data/cubicweb.widgets.js	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/cubicweb.widgets.js	Thu May 14 12:50:34 2009 +0200
@@ -222,46 +222,46 @@
 
     __init__ : function(wdgnode) {
 	this.variables = getNodeAttribute(wdgnode, 'cubicweb:variables').split(',');
-	this.options = {'name' : wdgnode.getAttribute('cubicweb:inputname'),
-			'id'   : wdgnode.getAttribute('cubicweb:inputid'),
+	this.options = {'name'   : wdgnode.getAttribute('cubicweb:inputid'),
 			'rows' : wdgnode.getAttribute('cubicweb:rows') || 40,
 			'cols' : wdgnode.getAttribute('cubicweb:cols') || 80
 		       };
 	// this.variableRegexp = /%\((\w+)\)s/;
-	this.parentnode = wdgnode;
-    },
-
-    show : function(parentnode) {
-	parentnode = parentnode || this.parentnode;
-	this.errorField = DIV({'class' : "textfieldErrors"});
+	this.errorField = DIV({'class' : "errorMessage"});
 	this.textField = TEXTAREA(this.options);
-	connect(this.textField, 'onkeyup', this, this.highlightInvalidVariables);
-	appendChildNodes(parentnode, this.textField, this.errorField);
-	appendChildNodes(parentnode, this.textField);
+	jQuery(this.textField).bind('keyup', {'self': this}, this.highlightInvalidVariables);
+	jQuery('#substitutions').prepend(this.errorField);
+	jQuery('#substitutions .errorMessage').hide();
+	wdgnode.appendChild(this.textField);
     },
 
     /* signal callbacks */
 
-    highlightInvalidVariables : function() {
-	var text = this.textField.value;
+    highlightInvalidVariables : function(event) {
+	var self = event.data.self;
+	var text = self.textField.value;
 	var unknownVariables = [];
-	var it=0;
+	var it = 0;
 	var group = null;
 	var variableRegexp = /%\((\w+)\)s/g;
 	// emulates rgx.findAll()
 	while ( group=variableRegexp.exec(text) ) {
-	    if ( !this.variables.contains(group[1]) ) {
+	    if ( !self.variables.contains(group[1]) ) {
 		unknownVariables.push(group[1]);
 	    }
 	    it++;
-	    if (it > 5)
+	    if (it > 5) {
 		break;
+	    }
 	}
 	var errText = '';
 	if (unknownVariables.length) {
 	    errText = "Detected invalid variables : " + ", ".join(unknownVariables);
+	    jQuery('#substitutions .errorMessage').show();
+	} else {
+	    jQuery('#substitutions .errorMessage').hide();
 	}
-	this.errorField.innerHTML = errText;
+	self.errorField.innerHTML = errText;
     }
 
 });
@@ -282,14 +282,14 @@
 	  this.eid_to = name[1];
           this.etype_to = wdgnode.getAttribute('cubicweb:etype_to');
           this.etype_from = wdgnode.getAttribute('cubicweb:etype_from');
-     	  var d = async_remote_exec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val');
+     	  var d = asyncRemoteExec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val');
           d.addCallback(function (eid) {
-          jQuery(wdgnode).find("option[selected]").removeAttr("selected");
-          var new_option = OPTION({'value':eid, 'selected':'selected'}, value=new_val);
-          wdgnode.appendChild(new_option);
+	      jQuery(wdgnode).find("option[selected]").removeAttr("selected");
+              var new_option = OPTION({'value':eid, 'selected':'selected'}, value=new_val);
+              wdgnode.appendChild(new_option);
           });
           d.addErrback(function (xxx) {
-             log('xxx =', xxx);
+              log('xxx =', xxx);
           });
      });
    }
--- a/web/data/external_resources	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/external_resources	Thu May 14 12:50:34 2009 +0200
@@ -52,3 +52,4 @@
 DOWNLOAD_ICON = DATADIR/download.gif
 UPLOAD_ICON = DATADIR/upload.gif
 GMARKER_ICON = DATADIR/gmap_blue_marker.png
+UP_ICON = DATADIR/up.gif
--- a/web/data/timeline-bundle.css	Thu May 14 12:50:14 2009 +0200
+++ b/web/data/timeline-bundle.css	Thu May 14 12:50:34 2009 +0200
@@ -1,171 +1,175 @@
-
-
-/*------------------- Horizontal / Vertical lines ----------------*/
-
-/* style for ethers */
-.timeline-ether-lines{border-color:#666; border-style:dotted; position:absolute;}
-
-.timeline-horizontal .timeline-ether-lines{border-width:0 0 0 1px; height:100%; top: 0; width: 1px;}
-
-.timeline-vertical .timeline-ether-lines{border-width:1px 0 0; height:1px; left: 0; width: 100%;}
-
-
-
-
-/*---------------- Weekends ---------------------------*/
-
-.timeline-ether-weekends{
-	position:absolute;
-	background-color:#FFFFE0;
-}
-
-.timeline-vertical .timeline-ether-weekends{left:0;width:100%;}
-
-.timeline-horizontal .timeline-ether-weekends{top:0; height:100%;}
-
-
-
-/*-------------------------- HIGHLIGHT DECORATORS -------------------*/
-.timeline-highlight-decorator,
-.timeline-highlight-point-decorator{
-	position:absolute;
-	overflow:hidden;
-}
-.timeline-horizontal .timeline-highlight-point-decorator,
-.timeline-horizontal .timeline-highlight-decorator{
-	width:10px;
-	top:0;
-    height:100%;
-}
-
-.timeline-vertical .timeline-highlight-point-decorator,
-.timeline-vertical .timeline-highlight-decorator{
-	height:10px;
-	width:100%;
-	left:0;
-}
-
-.timeline-highlight-decorator{background-color:#FFC080;}
-.timeline-highlight-point-decorator{background-color:#ff5;}
-
-
-
-/*---------------------------- LABELS -------------------------*/
-.timeline-highlight-label{position:absolute;overflow:hidden;font-size:200%;font-weight:bold;color:#999;}
-
-
-/*---------------- VERTICAL LABEL -------------------*/
-.timeline-horizontal .timeline-highlight-label{top:0;height:100%;}
-.timeline-horizontal .timeline-highlight-label td{vertical-align:middle;}
-.timeline-horizontal .timeline-highlight-label-start{text-align:right;}
-.timeline-horizontal .timeline-highlight-label-end{text-align:left;}
-
-
-/*---------------- HORIZONTAL LABEL -------------------*/
-.timeline-vertical .timeline-highlight-label{left:0;width:100%;}
-.timeline-vertical .timeline-highlight-label td{vertical-align:top;}
-.timeline-vertical .timeline-highlight-label-start{text-align:center;}
-.timeline-vertical .timeline-highlight-label-end{text-align:center;}
-
-
-
-/*-------------------------------- DATE LABELS --------------------------------*/
-.timeline-date-label{position:absolute; border:solid #aaa; color:#aaa;	width:5em; height:1.5em;}
-.timeline-date-label-em{color:#000;}
-
-/* horizontal */
-.timeline-horizontal .timeline-date-label{padding-left:2px;}
-.timeline-horizontal .timeline-date-label{border-width:0 0 0 1px;}
-.timeline-horizontal .timeline-date-label-em{height:2em}
+div.simileAjax-bubble-container {
+    margin:     0px;
+    padding:    0px;
+    border:     none;
+    position:   absolute;
+    z-index:    1000;
+}
+
+div.simileAjax-bubble-innerContainer {
+    margin:     0px;
+    padding:    0px;
+    border:     none;
+    position:   relative;
+    width:      100%;
+    height:     100%;
+    overflow:   visible;
+}
 
-/* vertical */
-.timeline-vertical .timeline-date-label{padding-top:2px;}
-.timeline-vertical .timeline-date-label{border-width:1px 0 0;}
-.timeline-vertical .timeline-date-label-em{width:7em}
-
-/*------------------------------- Ether.highlight -------------------------*/
-.timeline-ether-highlight{position:absolute; background-color:#fff;}
-.timeline-horizontal .timeline-ether-highlight{top:2px;}
-.timeline-vertical .timeline-ether-highlight{left:2px;}
-
-
-
-/*------------------------------ EVENTS ------------------------------------*/
-.timeline-event-icon, .timeline-event-label,.timeline-event-tape{
-	position:absolute;
-	cursor:pointer;
-}
-
-.timeline-event-tape,
-.timeline-small-event-tape,
-.timeline-small-event-icon{
-	background-color:#58A0DC;
-	overflow:hidden;
-}
-
-.timeline-small-event-tape,
-.timeline-small-event-icon{
-	position:absolute;
-}
-
-.timeline-event-tape{height:4px;}
-
-.timeline-small-event-tape{height:2px;}
-.timeline-small-event-icon{width:1px; height:6px;}
- 
- 
-
-/*--------------------------------- TIMELINE-------------------------*/
-.timeline-ether-bg{width:100%; height:100%;}
-.timeline-band-0 .timeline-ether-bg{background-color:#eee}
-.timeline-band-1 .timeline-ether-bg{background-color:#ddd}
-.timeline-band-2 .timeline-ether-bg{background-color:#ccc}
-.timeline-band-3 .timeline-ether-bg{background-color:#aaa}
-.timeline-duration-event {
-    position: absolute;
-    overflow: hidden;
-    border: 1px solid blue;
-}
-
-.timeline-instant-event2 {
-    position: absolute;
-    overflow: hidden;
-    border-left: 1px solid blue;
-    padding-left: 2px;
-}
-
-.timeline-instant-event {
-    position: absolute;
-    overflow: hidden;
-}
-
-.timeline-event-bubble-title {
-    font-weight: bold;
-    border-bottom: 1px solid #888;
-    margin-bottom: 0.5em;
-}
-
-.timeline-event-bubble-body {
-}
-
-.timeline-event-bubble-wiki {
-    margin:     0.5em;
-    text-align: right;
-    color:      #A0A040;
-}
-.timeline-event-bubble-wiki a {
-    color:      #A0A040;
-}
-
-.timeline-event-bubble-time {
-    color: #aaa;
-}
-
-.timeline-event-bubble-image {
-    float: right;
-    padding-left: 5px;
-    padding-bottom: 5px;
-}.timeline-container {
+div.simileAjax-bubble-contentContainer {
+    margin:     0px;
+    padding:    0px;
+    border:     none;
+    position:   absolute;
+    left:       0px;
+    top:        0px;
+    width:      100%;
+    height:     100%;
+    overflow:   auto;
+    background: white;
+}
+
+div.simileAjax-bubble-border-left {
+    position:   absolute;
+    left:       -50px;
+    top:        0px;
+    width:      50px;
+    height:     100%;
+}
+div.simileAjax-bubble-border-left-pngTranslucent {
+    background: url(../images/bubble-left.png) top right repeat-y;
+}
+
+div.simileAjax-bubble-border-right {
+    position:   absolute;
+    right:      -50px;
+    top:        0px;
+    width:      50px;
+    height:     100%;
+}
+.simileAjax-bubble-border-right-pngTranslucent {
+    background: url(../images/bubble-right.png) top left repeat-y;
+}
+
+div.simileAjax-bubble-border-top {
+    position:   absolute;
+    top:        -50px;
+    left:       0px;
+    width:      100%;
+    height:     50px;
+}
+.simileAjax-bubble-border-top-pngTranslucent {
+    background: url(../images/bubble-top.png) bottom left repeat-x;
+}
+
+div.simileAjax-bubble-border-bottom {
+    position:   absolute;
+    bottom:     -50px;
+    left:       0px;
+    width:      100%;
+    height:     50px;
+}
+.simileAjax-bubble-border-bottom-pngTranslucent {
+    background: url(../images/bubble-bottom.png) top left repeat-x;
+}
+
+div.simileAjax-bubble-border-top-left {
+    position:   absolute;
+    top:        -50px;
+    left:       -50px;
+    width:      50px;
+    height:     50px;
+}
+.simileAjax-bubble-border-top-left-pngTranslucent {
+    background: url(../images/bubble-top-left.png) bottom right no-repeat;
+}
+
+div.simileAjax-bubble-border-top-right {
+    position:   absolute;
+    top:        -50px;
+    right:      -50px;
+    width:      50px;
+    height:     50px;
+}
+.simileAjax-bubble-border-top-right-pngTranslucent {
+    background: url(../images/bubble-top-right.png) bottom left no-repeat;
+}
+
+div.simileAjax-bubble-border-bottom-left {
+    position:   absolute;
+    bottom:     -50px;
+    left:       -50px;
+    width:      50px;
+    height:     50px;
+}
+.simileAjax-bubble-border-bottom-left-pngTranslucent {
+    background: url(../images/bubble-bottom-left.png) top right no-repeat;
+}
+
+div.simileAjax-bubble-border-bottom-right {
+    position:   absolute;
+    bottom:     -50px;
+    right:      -50px;
+    width:      50px;
+    height:     50px;
+}
+.simileAjax-bubble-border-bottom-right-pngTranslucent {
+    background: url(../images/bubble-bottom-right.png) top left no-repeat;
+}
+
+div.simileAjax-bubble-arrow-point-left {
+    position:   absolute;
+    left:       -100px;
+    width:      100px;
+    height:     49px;
+}
+.simileAjax-bubble-arrow-point-left-pngTranslucent {
+    background: url(../images/bubble-arrow-point-left.png) center right no-repeat;
+}
+
+div.simileAjax-bubble-arrow-point-right {
+    position:   absolute;
+    right:      -100px;
+    width:      100px;
+    height:     49px;
+}
+.simileAjax-bubble-arrow-point-right-pngTranslucent {
+    background: url(../images/bubble-arrow-point-right.png) center left no-repeat;
+}
+
+div.simileAjax-bubble-arrow-point-up {
+    position:   absolute;
+    top:        -100px;
+    width:      49px;
+    height:     100px;
+}
+.simileAjax-bubble-arrow-point-up-pngTranslucent {
+    background: url(../images/bubble-arrow-point-up.png) bottom center no-repeat;
+}
+
+div.simileAjax-bubble-arrow-point-down {
+    position:   absolute;
+    bottom:     -100px;
+    width:      49px;
+    height:     100px;
+}
+.simileAjax-bubble-arrow-point-down-pngTranslucent {
+    background: url(../images/bubble-arrow-point-down.png) bottom center no-repeat;
+}
+
+
+div.simileAjax-bubble-close {
+    position:   absolute;
+    right:      -10px;
+    top:        -12px;
+    width:      16px;
+    height:     16px;
+    cursor:     pointer;
+}
+.simileAjax-bubble-close-pngTranslucent {
+    background: url(../images/close-button.png) no-repeat;
+}
+.timeline-container {
     position: relative;
     overflow: hidden;
 }
@@ -230,3 +234,168 @@
     height:     100%;
 }
 
+
+
+/*------------------- Horizontal / Vertical lines ----------------*/
+
+/* style for ethers */
+.timeline-ether-lines{border-color:#666; border-style:dotted; position:absolute;}
+.timeline-horizontal .timeline-ether-lines{border-width:0 0 0 1px; height:100%; top: 0; width: 1px;}
+.timeline-vertical .timeline-ether-lines{border-width:1px 0 0; height:1px; left: 0; width: 100%;}
+
+
+
+/*---------------- Weekends ---------------------------*/
+.timeline-ether-weekends{
+	position:absolute;
+	background-color:#FFFFE0;
+}
+
+.timeline-vertical .timeline-ether-weekends{left:0;width:100%;}
+.timeline-horizontal .timeline-ether-weekends{top:0; height:100%;}
+
+
+/*-------------------------- HIGHLIGHT DECORATORS -------------------*/
+/* Used for decorators, not used for Timeline Highlight              */
+.timeline-highlight-decorator,
+.timeline-highlight-point-decorator{
+	position:absolute;
+	overflow:hidden;
+}
+
+/* Width of horizontal decorators and Height of vertical decorators is
+   set in the decorator function params */
+.timeline-horizontal .timeline-highlight-point-decorator,
+.timeline-horizontal .timeline-highlight-decorator{
+	top:0;
+  height:100%;
+}
+
+.timeline-vertical .timeline-highlight-point-decorator,
+.timeline-vertical .timeline-highlight-decorator{
+	width:100%;
+	left:0;
+}
+
+.timeline-highlight-decorator{background-color:#FFC080;}
+.timeline-highlight-point-decorator{background-color:#ff5;}
+
+
+/*---------------------------- LABELS -------------------------*/
+.timeline-highlight-label {
+  position:absolute; overflow:hidden; font-size:200%;
+  font-weight:bold; color:#999; }
+
+
+/*---------------- VERTICAL LABEL -------------------*/
+.timeline-horizontal .timeline-highlight-label {top:0; height:100%;}
+.timeline-horizontal .timeline-highlight-label td {vertical-align:middle;}
+.timeline-horizontal .timeline-highlight-label-start {text-align:right;}
+.timeline-horizontal .timeline-highlight-label-end {text-align:left;}
+
+
+/*---------------- HORIZONTAL LABEL -------------------*/
+.timeline-vertical .timeline-highlight-label {left:0;width:100%;}
+.timeline-vertical .timeline-highlight-label td {vertical-align:top;}
+.timeline-vertical .timeline-highlight-label-start {text-align:center;}
+.timeline-vertical .timeline-highlight-label-end {text-align:center;}
+
+
+/*-------------------------------- DATE LABELS --------------------------------*/
+.timeline-date-label {
+  position: absolute;
+  border: solid #aaa;
+  color: #aaa;
+  width: 5em;
+  height: 1.5em;}
+.timeline-date-label-em {color: #000;}
+
+/* horizontal */
+.timeline-horizontal .timeline-date-label{padding-left:2px;}
+.timeline-horizontal .timeline-date-label{border-width:0 0 0 1px;}
+.timeline-horizontal .timeline-date-label-em{height:2em}
+
+/* vertical */
+.timeline-vertical .timeline-date-label{padding-top:2px;}
+.timeline-vertical .timeline-date-label{border-width:1px 0 0;}
+.timeline-vertical .timeline-date-label-em{width:7em}
+
+
+/*------------------------------- Ether.highlight -------------------------*/
+.timeline-ether-highlight{position:absolute; background-color:#fff;}
+.timeline-horizontal .timeline-ether-highlight{top:2px;}
+.timeline-vertical .timeline-ether-highlight{left:2px;}
+
+
+/*------------------------------ EVENTS ------------------------------------*/
+.timeline-event-icon, .timeline-event-label,.timeline-event-tape{
+	position:absolute;
+	cursor:pointer;
+}
+
+.timeline-event-tape,
+.timeline-small-event-tape,
+.timeline-small-event-icon{
+	background-color:#58A0DC;
+	overflow:hidden;
+}
+
+.timeline-small-event-tape,
+.timeline-small-event-icon{
+	position:absolute;
+}
+
+.timeline-small-event-icon{width:1px; height:6px;}
+
+  
+/*--------------------------------- TIMELINE-------------------------*/
+.timeline-ether-bg{width:100%; height:100%;}
+.timeline-band-0 .timeline-ether-bg{background-color:#eee}
+.timeline-band-1 .timeline-ether-bg{background-color:#ddd}
+.timeline-band-2 .timeline-ether-bg{background-color:#ccc}
+.timeline-band-3 .timeline-ether-bg{background-color:#aaa}
+.timeline-duration-event {
+    position: absolute;
+    overflow: hidden;
+    border: 1px solid blue;
+}
+
+.timeline-instant-event2 {
+    position: absolute;
+    overflow: hidden;
+    border-left: 1px solid blue;
+    padding-left: 2px;
+}
+
+.timeline-instant-event {
+    position: absolute;
+    overflow: hidden;
+}
+
+.timeline-event-bubble-title {
+    font-weight: bold;
+    border-bottom: 1px solid #888;
+    margin-bottom: 0.5em;
+}
+
+.timeline-event-bubble-body {
+}
+
+.timeline-event-bubble-wiki {
+    margin:     0.5em;
+    text-align: right;
+    color:      #A0A040;
+}
+.timeline-event-bubble-wiki a {
+    color:      #A0A040;
+}
+
+.timeline-event-bubble-time {
+    color: #aaa;
+}
+
+.timeline-event-bubble-image {
+    float: right;
+    padding-left: 5px;
+    padding-bottom: 5px;
+}
\ No newline at end of file
Binary file web/data/up.gif has changed
--- a/web/facet.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/facet.py	Thu May 14 12:50:34 2009 +0200
@@ -19,10 +19,8 @@
 from rql import parse, nodes
 
 from cubicweb import Unauthorized, typed_eid
-from cubicweb.common.selectors import match_context_prop, one_has_relation
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.utils import AcceptMixIn
+from cubicweb.selectors import match_context_prop, partial_relation_possible
+from cubicweb.appobject import AppRsetObject
 from cubicweb.web.htmlwidgets import HTMLWidget
 
 ## rqlst manipulation functions used by facets ################################
@@ -119,12 +117,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 +137,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 +174,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,8 +237,7 @@
 
 
 ## base facet classes #########################################################
-class AbstractFacet(AcceptMixIn, AppRsetObject):
-    __registerer__ = priority_registerer
+class AbstractFacet(AppRsetObject):
     __abstract__ = True
     __registry__ = 'facets'
     property_defs = {
@@ -334,8 +336,8 @@
 
 
 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'
     target_attr = 'eid'
@@ -411,7 +413,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 +427,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 +445,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 +523,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/form.py	Thu May 14 12:50:34 2009 +0200
@@ -1,65 +1,35 @@
 """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 import formrenderers
+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)
-
-
-class FormMixIn(object):
-    """abstract form mix-in"""
+class FormViewMixIn(object):
+    """abstract form view mix-in"""
     category = 'form'
     controller = 'edit'
-    domid = 'entityForm'
-    
     http_cache_manager = NoHTTPCacheManager
     add_to_breadcrumbs = False
-    skip_relations = set()
-    
-    def __init__(self, req, rset):
-        super(FormMixIn, self).__init__(req, rset)
-        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')
-        # get validation session data which may have been previously set.
-        # deleting validation errors here breaks form reloading (errors are
-        # no more available), they have to be deleted by application's publish
-        # method on successful commit
-        formurl = req.url()
-        forminfo = req.get_session_data(formurl)
-        if forminfo:
-            req.data['formvalues'] = forminfo['values']
-            req.data['formerrors'] = errex = forminfo['errors']
-            req.data['displayederrors'] = set()
-            # if some validation error occured on entity creation, we have to
-            # get the original variable name from its attributed eid
-            foreid = errex.entity
-            for var, eid in forminfo['eidmap'].items():
-                if foreid == eid:
-                    errex.eid = var
-                    break
-            else:
-                errex.eid = foreid
-        
+
     def html_headers(self):
         """return a list of html headers (eg something to be inserted between
         <head> and </head> of the returned page
@@ -67,18 +37,109 @@
         by default forms are neither indexed nor followed
         """
         return [NOINDEX, NOFOLLOW]
-        
+
     def linkable(self):
         """override since forms are usually linked by an action,
         so we don't want them to be listed by appli.possible_views
         """
         return False
 
-    @property
-    def limit(self):
-        if self.force_display:
-            return None
-        return self.maxrelitems + 1
+
+# XXX should disappear
+class FormMixIn(object):
+    """abstract form mix-in
+    XXX: you should inherit from this FIRST (obscure pb with super call)
+    """
+
+    def session_key(self):
+        """return the key that may be used to store / retreive data about a
+        previous post which failed because of a validation error
+        """
+        return '%s#%s' % (self.req.url(), self.domid)
+
+    def __init__(self, req, rset, **kwargs):
+        super(FormMixIn, self).__init__(req, rset, **kwargs)
+        self.restore_previous_post(self.session_key())
+
+    def restore_previous_post(self, sessionkey):
+        # get validation session data which may have been previously set.
+        # deleting validation errors here breaks form reloading (errors are
+        # no more available), they have to be deleted by application's publish
+        # method on successful commit
+        forminfo = self.req.get_session_data(sessionkey, pop=True)
+        if forminfo:
+            # XXX remove req.data assigment once cw.web.widget is killed
+            self.req.data['formvalues'] = self.form_previous_values = forminfo['values']
+            self.req.data['formerrors'] = self.form_valerror = forminfo['errors']
+            self.req.data['displayederrors'] = self.form_displayed_errors = set()
+            # if some validation error occured on entity creation, we have to
+            # get the original variable name from its attributed eid
+            foreid = self.form_valerror.entity
+            for var, eid in forminfo['eidmap'].items():
+                if foreid == eid:
+                    self.form_valerror.eid = var
+                    break
+            else:
+                self.form_valerror.eid = foreid
+        else:
+            self.form_previous_values = {}
+            self.form_valerror = None
+
+    # XXX deprecated with new form system. Should disappear
+
+    domid = 'entityForm'
+    category = 'form'
+    controller = 'edit'
+    http_cache_manager = NoHTTPCacheManager
+    add_to_breadcrumbs = False
+
+    def html_headers(self):
+        """return a list of html headers (eg something to be inserted between
+        <head> and </head> of the returned page
+
+        by default forms are neither indexed nor followed
+        """
+        return [NOINDEX, NOFOLLOW]
+
+    def linkable(self):
+        """override since forms are usually linked by an action,
+        so we don't want them to be listed by appli.possible_views
+        """
+        return False
+
+
+    def button(self, label, klass='validateButton', tabindex=None, **kwargs):
+        if tabindex is None:
+            tabindex = self.req.next_tabindex()
+        return tags.input(value=label, klass=klass, **kwargs)
+
+    def action_button(self, label, onclick=None, __action=None, **kwargs):
+        if onclick is None:
+            onclick = "postForm('__action_%s', \'%s\', \'%s\')" % (
+                __action, label, self.domid)
+        return self.button(label, onclick=onclick, **kwargs)
+
+    def button_ok(self, label=None, type='submit', name='defaultsubmit',
+                  **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
+        return self.button(label, name=name, type=type, **kwargs)
+
+    def button_apply(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
+        return self.action_button(label, __action='apply', type=type, **kwargs)
+
+    def button_delete(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
+        return self.action_button(label, __action='delete', type=type, **kwargs)
+
+    def button_cancel(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
+        return self.action_button(label, __action='cancel', type=type, **kwargs)
+
+    def button_reset(self, label=None, type='reset', name='__action_cancel',
+                     **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
+        return self.button(label, type=type, **kwargs)
 
     def need_multipart(self, entity, categories=('primary', 'secondary')):
         """return a boolean indicating if form's enctype should be multipart
@@ -102,16 +163,16 @@
 
         This method should be called once inlined field errors has been consumed
         """
-        errex = self.req.data.get('formerrors')
+        errex = self.req.data.get('formerrors') or self.form_valerror
         # get extra errors
         if errex is not None:
             errormsg = self.req._('please correct the following errors:')
-            displayed = self.req.data['displayederrors']
+            displayed = self.req.data.get('displayederrors') or self.form_displayed_errors
             errors = sorted((field, err) for field, err in errex.errors.items()
                             if not field in displayed)
             if errors:
                 if len(errors) > 1:
-                    templstr = '<li>%s</li>\n' 
+                    templstr = '<li>%s</li>\n'
                 else:
                     templstr = '&nbsp;%s\n'
                 for field, err in errors:
@@ -123,131 +184,490 @@
                     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
+
+
+###############################################################################
+
+class metafieldsform(type):
+    """metaclass for FieldsForm to retrieve fields defined as class attributes
+    and put them into a single ordered list: '_fields_'.
+    """
+    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 FieldNotFound(Exception):
+    """raised by field_by_name when a field with the given name has not been
+    found
+    """
+
+class FieldsForm(FormMixIn, AppRsetObject):
+    __metaclass__ = metafieldsform
+    __registry__ = 'forms'
+    __select__ = yes()
+
+    renderer_cls = formrenderers.FormRenderer
+    is_subform = False
+
+    # attributes overrideable through __init__
+    internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
+    needs_js = ('cubicweb.ajax.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
+    redirect_path = None
+    set_error_url = True
+    copy_nav_params = False
+    form_buttons = None # form buttons (button widgets instances)
+
+    def __init__(self, req, rset=None, row=None, col=None, submitmsg=None,
+                 **kwargs):
+        super(FieldsForm, self).__init__(req, rset, row=row, col=col)
+        self.fields = list(self.__class__._fields_)
+        for key, val in kwargs.items():
+            if key in NAV_FORM_PARAMETERS:
+                self.form_add_hidden(key, val)
             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
+                assert hasattr(self.__class__, key) and not key[0] == '_', key
+                setattr(self, key, val)
+        if self.set_error_url:
+            self.form_add_hidden('__errorurl', self.session_key())
+        if self.copy_nav_params:
+            for param in NAV_FORM_PARAMETERS:
+                if not param in kwargs:
+                    value = req.form.get(param)
+                    if value:
+                        self.form_add_hidden(param, value)
+        if submitmsg is not None:
+            self.form_add_hidden('__message', submitmsg)
+        self.context = None
+        if 'domid' in kwargs:# session key changed
+            self.restore_previous_post(self.session_key())
+
+    @iclassmethod
+    def field_by_name(cls_or_self, name, role='subject'):
+        """return field with the given name and role"""
+        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 FieldNotFound(name)
+
+    @iclassmethod
+    def remove_field(cls_or_self, field):
+        """remove a field from form class or instance"""
+        if isinstance(cls_or_self, type):
+            fields = cls_or_self._fields_
+        else:
+            fields = cls_or_self.fields
+        fields.remove(field)
+
+    @iclassmethod
+    def append_field(cls_or_self, field):
+        """append a field to form class or instance"""
+        if isinstance(cls_or_self, type):
+            fields = cls_or_self._fields_
+        else:
+            fields = cls_or_self.fields
+        fields.append(field)
+
+    @property
+    def form_needs_multipart(self):
+        """true if the form needs enctype=multipart/form-data"""
+        return any(field.needs_multipart for field in self.fields)
+
+    def form_add_hidden(self, name, value=None, **kwargs):
+        """add an hidden field to the form"""
+        field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
+                            **kwargs)
+        if 'id' in kwargs:
+            # by default, hidden input don't set id attribute. If one is
+            # explicitly specified, ensure it will be set
+            field.widget.setdomid = True
+        self.append_field(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):
+        """render this form, using the renderer given in args or the default
+        FormRenderer()
+        """
+        renderer = values.pop('renderer', self.renderer_cls())
+        return renderer.render(self, values)
+
+    def form_build_context(self, rendervalues=None):
+        """build form context values (the .context attribute which is a
+        dictionary with field instance as key associated to a dictionary
+        containing field 'name' (qualified), 'id', 'value' (for display, always
+        a string).
+
+        rendervalues is an optional dictionary containing extra kwargs given to
+        form_render()
         """
-        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
+        self.context = context = {}
+        # ensure rendervalues is a dict
+        if rendervalues is None:
+            rendervalues = {}
+        for field in self.fields:
+            for field in field.actual_fields(self):
+                field.form_init(self)
+                value = self.form_field_display_value(field, rendervalues)
+                context[field] = {'value': value,
+                                  'name': self.form_field_name(field),
+                                  'id': self.form_field_id(field),
+                                  }
+
+    def form_field_display_value(self, field, rendervalues, load_bytes=False):
+        """return field's *string* value to use for display
+
+        looks in
+        1. previously submitted form values if any (eg on validation error)
+        2. req.form
+        3. extra kw args given to render_form
+        4. field's typed value
+
+        values found in 1. and 2. are expected te be already some 'display'
+        value while those found in 3. and 4. are expected to be correctly typed.
+        """
+        value = self._req_display_value(field)
+        if value is None:
+            if field.name in rendervalues:
+                value = rendervalues[field.name]
+            else:
+                value = self.form_field_value(field, load_bytes)
+                if callable(value):
+                    value = value(self)
+            if value != INTERNAL_FIELD_VALUE:
+                value = field.format_value(self.req, value)
+        return value
+
+    def _req_display_value(self, field):
+        qname = self.form_field_name(field)
+        if qname in self.form_previous_values:
+            return self.form_previous_values[qname]
+        if qname in self.req.form:
+            return self.req.form[qname]
+        return None
+
+    def form_field_value(self, field, load_bytes=False):
+        """return field's *typed* value"""
+        value = field.initial
+        if callable(value):
+            value = value(self)
+        return value
+
+    def form_field_error(self, field):
+        """return validation error for widget's field, if any"""
+        if self._field_has_error(field):
+            self.form_displayed_errors.add(field.name)
+            return u'<span class="error">%s</span>' % self.form_valerror.errors[field.name]
+        return u''
+
+    def form_field_format(self, field):
+        """return MIME type used for the given (text or bytes) field"""
+        return self.req.property_value('ui.default-text-format')
+
+    def form_field_encoding(self, field):
+        """return encoding used for the given (text) field"""
+        return self.req.encoding
+
+    def form_field_name(self, field):
+        """return qualified name for the given field"""
+        return field.name
+
+    def form_field_id(self, field):
+        """return dom id for the given field"""
+        return field.id
+
+    def form_field_vocabulary(self, field, limit=None):
+        """return vocabulary for the given field. Should be overriden in
+        specific forms using fields which requires some vocabulary
+        """
+        raise NotImplementedError
+
+    def _field_has_error(self, field):
+        """return true if the field has some error in given validation exception
+        """
+        return self.form_valerror and field.name in self.form_valerror.errors
 
 
-    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
+class EntityFieldsForm(FieldsForm):
+    __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
+
+    internal_fields = FieldsForm.internal_fields + ('__type', 'eid', '__maineid')
+    domid = 'entityForm'
+
+    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 or 0, self.col or 0)
+        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)
+        # in case of direct instanciation
+        self.schema = self.edited_entity.schema
+        self.vreg = self.edited_entity.vreg
+
+    def _field_has_error(self, field):
+        """return true if the field has some error in given validation exception
+        """
+        return super(EntityFieldsForm, self)._field_has_error(field) \
+               and self.form_valerror.eid == self.edited_entity.eid
+
+    def _relation_vocabulary(self, rtype, targettype, role,
+                            limit=None, done=None):
+        """return unrelated entities for a given relation and target entity type
+        for use in vocabulary
         """
-        rparams = redirect_params(self.req.form)
-        if rparams:
-            return self.build_url('view', **rparams)
-        return self.reset_url(entity)
+        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 _req_display_value(self, field):
+        value = super(EntityFieldsForm, self)._req_display_value(field)
+        if value is None:
+            value = self.edited_entity.linked_to(field.name, field.role) or None
+        return value
 
-    def reset_url(self, entity):
-        raise NotImplementedError('implement me in concrete classes')
+    def _form_field_default_value(self, field, load_bytes):
+        defaultattr = 'default_%s' % field.name
+        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)
+            if callable(value):
+                value = value()
+        else:
+            value = super(EntityFieldsForm, self).form_field_value(field,
+                                                                   load_bytes)
+        return value
+
+    def form_build_context(self, values=None):
+        """overriden to add edit[s|o] hidden fields and to ensure schema fields
+        have eidparam set to True
 
-    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"/>'
+        edit[s|o] hidden fields are used to indicate the value for the
+        associated field before the (potential) modification made when
+        submitting the form.
+        """
+        eschema = self.edited_entity.e_schema
+        for field in self.fields[:]:
+            for field in field.actual_fields(self):
+                fieldname = field.name
+                if fieldname != 'eid' and (
+                    (eschema.has_subject_relation(fieldname) or
+                     eschema.has_object_relation(fieldname))):
+                    field.eidparam = True
+                    self.fields.append(HiddenInitialValueField(field))
+        return super(EntityFieldsForm, self).form_build_context(values)
+
+    def form_field_value(self, field, load_bytes=False):
+        """return field's *typed* value
 
-    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 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)
+        overriden to deal with
+        * special eid / __type / edits- / edito- fields
+        * lookup for values on edited entities
+        """
+        attr = field.name
+        entity = self.edited_entity
+        if attr == 'eid':
+            return entity.eid
+        if not field.eidparam:
+            return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
+        if attr.startswith('edits-') or attr.startswith('edito-'):
+            # edit[s|o]- fieds must have the actual value stored on the entity
+            assert hasattr(field, 'visible_field')
+            vfield = field.visible_field
+            assert vfield.eidparam
+            if entity.has_eid():
+                return self.form_field_value(vfield)
+            return INTERNAL_FIELD_VALUE
+        if attr == '__type':
+            return entity.id
+        if self.schema.rschema(attr).is_final():
+            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 entity.has_eid() or attr in entity:
+                value = getattr(entity, attr)
+            else:
+                value = self._form_field_default_value(field, load_bytes)
+            return value
+        # non final relation field
+        if entity.has_eid() or entity.relation_cached(attr, field.role):
+            value = [r[0] for r in entity.related(attr, field.role)]
+        else:
+            value = self._form_field_default_value(field, load_bytes)
+        return value
+
+    def form_field_format(self, field):
+        """return MIME type used for the given (text or bytes) 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.attr_metadata(field.name, 'format')
+        return self.req.property_value('ui.default-text-format')
+
+    def form_field_encoding(self, field):
+        """return encoding used for the given (text) 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.attr_metadata(field.name, 'encoding')
+        return super(EntityFieldsForm, self).form_field_encoding(field)
+
+    def form_field_name(self, field):
+        """return qualified name for the given field"""
+        if field.eidparam:
+            return eid_param(field.name, self.edited_entity.eid)
+        return field.name
+
+    def form_field_id(self, field):
+        """return dom id for the given field"""
+        if field.eidparam:
+            return eid_param(field.id, self.edited_entity.eid)
+        return field.id
 
-    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 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 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 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):
+        """return vocabulary for the given field"""
+        role, rtype = field.role, field.name
+        method = '%s_%s_vocabulary' % (role, rtype)
+        try:
+            vocabfunc = getattr(self, method)
+        except AttributeError:
+            try:
+                # XXX bw compat, <role>_<rtype>_vocabulary on the entity
+                vocabfunc = getattr(self.edited_entity, method)
+            except AttributeError:
+                vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+            else:
+                warn('found %s on %s, should be set on a specific form'
+                     % (method, self.edited_entity.id), DeprecationWarning)
+        # 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)
+        """
+        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 subject_in_state_vocabulary(self, rtype, 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"""
 
-    form_params = ()
+    def __init__(self, *args, **kwargs):
+        super(CompositeForm, self).__init__(*args, **kwargs)
+        self.forms = []
 
-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):
+        """mark given form as a subform and append it"""
+        subform.is_subform = True
+        self.forms.append(subform)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/formfields.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,502 @@
+"""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 forms. It makes
+    the link between something to edit and its display in the form. Actual
+    display is handled by a widget associated to the field.
+
+    Attributes
+    ----------
+    all the attributes described below have sensible default value which may be
+    overriden by value given to field's constructor.
+
+    :name:
+       name of the field (basestring), should be unique in a form.
+    :id:
+       dom identifier (default to the same value as `name`), should be unique in
+       a form.
+    :label:
+       label of the field (default to the same value as `name`).
+    :help:
+       help message about this field.
+    :widget:
+       widget associated to the field. Each field class has a default widget
+       class which may be overriden per instance.
+    :required:
+       bool flag telling if the field is required or not.
+    :initial:
+       initial value, used when no value specified by other means.
+    :choices:
+       static vocabulary for this field. May be a list of values or a list of
+       (label, value) tuples if specified.
+    :sort:
+       bool flag telling if the vocabulary (either static vocabulary specified
+       in `choices` or dynamic vocabulary fetched from the form) should be
+       sorted on label.
+    :internationalizable:
+       bool flag telling if the vocabulary labels should be translated using the
+       current request language.
+    :eidparam:
+       bool flag telling if this field is linked to a specific entity
+    :role:
+       when the field is linked to an entity attribute or relation, tells the
+       role of the entity in the relation (eg 'subject' or 'object')
+
+    """
+    # default widget associated to this class of fields. May be overriden per
+    # instance
+    widget = TextInput
+    # does this field requires a multipart form
+    needs_multipart = False
+    # class attribute used for ordering of fields in a form
+    __creation_rank = 0
+
+    def __init__(self, name=None, id=None, label=None, help=None,
+                 widget=None, required=False, initial=None,
+                 choices=None, sort=True, internationalizable=False,
+                 eidparam=False, role='subject'):
+        self.name = name
+        self.id = id or name
+        self.label = label or name
+        self.help = help
+        self.required = required
+        if widget is not None:
+            self.widget = widget
+        if isinstance(self.widget, type):
+            self.widget = self.widget()
+        self.initial = initial
+        self.choices = choices
+        self.sort = sort
+        self.internationalizable = internationalizable
+        self.eidparam = eidparam
+        self.role = role
+        # ordering number for this field instance
+        self.creation_rank = Field.__creation_rank
+        Field.__creation_rank += 1
+
+    def __unicode__(self):
+        return u'<%s name=%r label=%r id=%r initial=%r visible=%r @%x>' % (
+            self.__class__.__name__, self.name, self.label,
+            self.id, self.initial, self.is_visible(), id(self))
+
+    def __repr__(self):
+        return self.__unicode__().encode('utf-8')
+
+    def set_name(self, name):
+        """automatically set .id and .label when name is set"""
+        assert name
+        self.name = name
+        if not self.id:
+            self.id = name
+        if not self.label:
+            self.label = name
+
+    def is_visible(self):
+        """return true if the field is not an hidden field"""
+        return not isinstance(self.widget, HiddenInput)
+
+    def actual_fields(self, form):
+        """return actual fields composing this field in case of a compound
+        field, usually simply return self
+        """
+        yield self
+
+    def format_value(self, req, value):
+        """return value suitable for display where value may be a list or tuple
+        of values
+        """
+        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):
+        """return value suitable for display"""
+        if value is None or value is False:
+            return u''
+        if value is True:
+            return u'1'
+        return unicode(value)
+
+    def get_widget(self, form):
+        """return the widget instance associated to this field"""
+        return self.widget
+
+    def example_format(self, req):
+        """return a sample string describing what can be given as input for this
+        field
+        """
+        return u''
+
+    def render(self, form, renderer):
+        """render this field, which is part of form, using the given form
+        renderer
+        """
+        return self.get_widget(form).render(form, self)
+
+    def vocabulary(self, form):
+        """return vocabulary for this field. This method will be called by
+        widgets which desire it."""
+        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]
+        else:
+            vocab = form.form_field_vocabulary(self)
+        if self.internationalizable:
+            vocab = [(form.req._(label), value) for label, value in vocab]
+        if self.sort:
+            vocab = sorted(vocab)
+        return vocab
+
+    def form_init(self, form):
+        """method called before by form_build_context to trigger potential field
+        initialization requiring the form instance
+        """
+        pass
+
+class StringField(Field):
+    def __init__(self, max_length=None, **kwargs):
+        super(StringField, self).__init__(**kwargs)
+        self.max_length = max_length
+        if isinstance(self.widget, TextArea):
+            self.init_text_area(self.widget)
+
+    def init_text_area(self, widget):
+        if self.max_length < 513:
+            widget.attrs.setdefault('cols', 60)
+            widget.attrs.setdefault('rows', 5)
+
+
+class RichTextField(StringField):
+    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()
+            widget = TextArea()
+            self.init_text_area(widget)
+            return widget
+        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)]
+                widget.attrs['size'] = 1
+            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:
+            # XXX we want both fields to remain vertically aligned
+            format_field.widget.attrs['style'] = 'display: block'
+            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
+        if isinstance(self.widget, TextInput):
+            self.widget.attrs.setdefault('size', 5)
+            self.widget.attrs.setdefault('maxlength', 15)
+
+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'
+    widget = TextInput
+
+class HiddenInitialValueField(Field):
+    def __init__(self, visible_field):
+        name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
+        super(HiddenInitialValueField, self).__init__(
+            name=name, widget=HiddenInput, eidparam=True)
+        self.visible_field = visible_field
+
+
+class RelationField(Field):
+    def __init__(self, **kwargs):
+        kwargs.setdefault('sort', False)
+        super(RelationField, self).__init__(**kwargs)
+
+    @staticmethod
+    def fromcardinality(card, **kwargs):
+        kwargs.setdefault('widget', Select(multiple=card in '*+'))
+        return RelationField(**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 = []
+        vocab = res + form.form_field_vocabulary(self) + relatedvocab
+        if self.sort:
+            vocab = sorted(vocab)
+        return vocab
+
+    def format_single_value(self, req, value):
+        return value
+
+
+def guess_field(eschema, 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
+    if role == 'subject':
+        targetschema = rschema.objects(eschema)[0]
+        card = rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+        help = rschema.rproperty(eschema, targetschema, 'description')
+        if rschema.is_final():
+            if rschema.rproperty(eschema, targetschema, 'internationalizable'):
+                kwargs['internationalizable'] = True
+            def get_default(form, es=eschema, rs=rschema):
+                return es.default(rs)
+            kwargs['initial'] = get_default
+    else:
+        targetschema = rschema.subjects(eschema)[0]
+        card = rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+        help = rschema.rproperty(targetschema, eschema, 'description')
+    kwargs['required'] = card in '1+'
+    kwargs['name'] = rschema.type
+    kwargs['help'] = help
+    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')
+                return RichTextField(**kwargs)
+            constraints = rschema.rproperty(eschema, targetschema, 'constraints')
+            # init StringField parameters according to constraints
+            for cstr in constraints:
+                if isinstance(cstr, StaticVocabularyConstraint):
+                    kwargs.setdefault('widget', Select())
+                    kwargs.setdefault('choices', cstr.vocabulary)
+                    if card in '?1':
+                        kwargs['widget'].attrs.setdefault('size', 1)
+                if isinstance(cstr, SizeConstraint) and cstr.max is not None:
+                    if cstr.max < 257:
+                        kwargs.setdefault('widget', TextInput())
+                    kwargs['max_length'] = cstr.max
+            kwargs.setdefault('widget', TextArea())
+            return StringField(**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(eschema, 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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,474 @@
+"""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
+from cubicweb.web.formfields import HiddenInitialValueField
+
+
+class FormRenderer(object):
+    """basic renderer displaying fields in a two columns table label | value
+
+    +--------------+--------------+
+    | field1 label | field1 input |
+    +--------------+--------------+
+    | field1 label | field2 input |
+    +--------------+--------------+
+    +---------+
+    | buttons |
+    +---------+
+    """
+    _options = ('display_fields', 'display_label', 'display_help',
+                'display_progress_div', 'table_class', 'button_bar_class')
+    display_fields = None # None -> all fields
+    display_label = True
+    display_help = True
+    display_progress_div = True
+    table_class = u'attributeForm'
+    button_bar_class = u'formButtonBar'
+
+    def __init__(self, **kwargs):
+        if self._set_options(kwargs):
+            raise ValueError('unconsumed arguments %s' % kwargs)
+
+    def _set_options(self, kwargs):
+        for key in self._options:
+            try:
+                setattr(self, key, kwargs.pop(key))
+            except KeyError:
+                continue
+        return kwargs
+
+    # renderer interface ######################################################
+
+    def render(self, form, values):
+        self._set_options(values)
+        form.add_media()
+        data = []
+        w = data.append
+        w(self.open_form(form, values))
+        if self.display_progress_div:
+            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 = form.form_valerror
+        # get extra errors
+        if errex is not None:
+            errormsg = req._('please correct the following errors:')
+            displayed = form.form_displayed_errors
+            errors = sorted((field, err) for field, err in errex.errors.items()
+                            if not field in displayed)
+            if errors:
+                if len(errors) > 1:
+                    templstr = '<li>%s</li>\n'
+                else:
+                    templstr = '&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" enctype="%s"' % (
+            html_escape(action or '#'), enctype))
+        if form.domid:
+            tag += ' id="%s"' % form.domid
+        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):
+        if isinstance(field, HiddenInitialValueField):
+            field = field.visible_field
+        return (self.display_fields is None
+                or field.name in form.internal_fields
+                or (field.name, field.role) in self.display_fields
+                or (field.name, field.role) in form.internal_fields)
+
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
+        fields = self._render_hidden_fields(w, form)
+        if fields:
+            self._render_fields(fields, w, form)
+        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):
+        w(u'<table class="%s">' % self.table_class)
+        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">')
+                w(error)
+            else:
+                w(u'<td>')
+            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.render(form))
+        w(u'</tr></table>')
+
+
+class HTableFormRenderer(FormRenderer):
+    """display fields horizontally in a table
+
+    +--------------+--------------+---------+
+    | field1 label | field2 label |         |
+    +--------------+--------------+---------+
+    | field1 input | field2 input | buttons
+    +--------------+--------------+---------+
+    """
+    display_help = False
+    def _render_fields(self, fields, w, form):
+        w(u'<table border="0">')
+        w(u'<tr>')
+        for field in fields:
+            if self.display_label:
+                w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+            if self.display_help:
+                w(self.render_help(form, field))
+        # empty slot for buttons
+        w(u'<th class="labelCol">&nbsp;</th>')
+        w(u'</tr>')
+        w(u'<tr>')
+        for field in fields:
+            error = form.form_field_error(field)
+            if error:
+                w(u'<td class="error">')
+                w(error)
+            else:
+                w(u'<td>')
+            w(field.render(form, self))
+            w(u'</td>')
+        w(u'<td>')
+        for button in form.form_buttons:
+            w(button.render(form))
+        w(u'</td>')
+        w(u'</tr>')
+        w(u'</table>')
+
+    def render_buttons(self, w, form):
+        pass
+
+
+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):
+        if form.is_subform:
+            entity = form.edited_entity
+            values = form.form_previous_values
+            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)"""
+    _options = FormRenderer._options + ('display_relations_form',)
+    display_relations_form = True
+
+    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() and self.display_relations_form:
+            self.relations_form(w, form)
+
+    def _render_fields(self, fields, w, form):
+        if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
+            super(EntityFormRenderer, self)._render_fields(fields, w, form)
+
+    def render_buttons(self, w, form):
+        if len(form.form_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(button.render(form) for button in form.form_buttons))
+        else:
+            super(EntityFormRenderer, self).render_buttons(w, form)
+
+    def relations_form(self, w, form):
+        srels_by_cat = form.srelations_by_category('generic', '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.inlined_relations():
+            # 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(form.view('inline-edition', existant, rtype=rschema, role=role,
+                                ptype=entity.e_schema, peid=entity.eid))
+                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(form.view('inline-creation', None, etype=targettype,
+                                peid=entity.eid, ptype=entity.e_schema,
+                                rtype=rschema, role=role))
+                # 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')" % (
+                        entity.eid, 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></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)
+        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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,432 @@
+"""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
+from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
+
+
+class FieldWidget(object):
+    """abstract widget class"""
+    # javascript / css files required by the widget
+    needs_js = ()
+    needs_css = ()
+    # automatically set id and tabindex attributes ?
+    setdomid = True
+    settabindex = True
+
+    def __init__(self, attrs=None, setdomid=None, settabindex=None):
+        if attrs is None:
+            attrs = {}
+        self.attrs = attrs
+        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):
+        """render the widget for the given `field` of `form`.
+        To override in concrete class
+        """
+        raise NotImplementedError
+
+    def _render_attrs(self, form, field):
+        """return html tag name, attributes and a list of values for the 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 and not 'tabindex' in attrs:
+            attrs['tabindex'] = form.req.next_tabindex()
+        return name, values, attrs
+
+
+class Input(FieldWidget):
+    """abstract widget class for <input> tag based widgets"""
+    type = None
+
+    def render(self, form, field):
+        """render the widget for the given `field` of `form`.
+
+        Generate one <input> tag for each field's value
+        """
+        self.add_media(form)
+        name, values, attrs = self._render_attrs(form, field)
+        # ensure something is rendered
+        if not values:
+            values = (INTERNAL_FIELD_VALUE,)
+        inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
+                  for value in values]
+        return u'\n'.join(inputs)
+
+
+# basic html widgets ###########################################################
+
+class TextInput(Input):
+    """<input type='text'>"""
+    type = 'text'
+
+
+class PasswordInput(Input):
+    """<input type='password'> and its confirmation field (using
+    <field's name>-confirm as name)
+    """
+    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')
+        try:
+            confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
+        except TypeError:
+            confirmname = '%s-confirm' % name
+        inputs = [tags.input(name=name, value=values[0], type=self.type, id=id, **attrs),
+                  '<br/>',
+                  tags.input(name=confirmname, value=values[0], type=self.type, **attrs),
+                  '&nbsp;', tags.span(form.req._('confirm password'),
+                                      **{'class': 'emphasis'})]
+        return u'\n'.join(inputs)
+
+
+class FileInput(Input):
+    """<input type='file'>"""
+    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):
+    """<input type='hidden'>"""
+    type = 'hidden'
+    setdomid = False # by default, don't set id attribute on hidden input
+    settabindex = False
+
+
+class ButtonInput(Input):
+    """<input type='button'>
+
+    if you want a global form button, look at the Button, SubmitButton,
+    ResetButton and ImgButton classes below.
+    """
+    type = 'button'
+
+
+class TextArea(FieldWidget):
+    """<textarea>"""
+    def render(self, form, field):
+        name, values, attrs = self._render_attrs(form, field)
+        attrs.setdefault('onkeypress', 'autogrow(this)')
+        attrs.setdefault('cols', 80)
+        attrs.setdefault('rows', 20)
+        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):
+    """FCKEditor enabled <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):
+    """<select>, for field having a specific vocabulary"""
+    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)
+        if not 'size' in attrs and self._multiple:
+            attrs['size'] = '5'
+        options = []
+        optgroup_opened = False
+        for label, value in field.vocabulary(form):
+            if value is None:
+                # handle separator
+                if optgroup_opened:
+                    options.append(u'</optgroup>')
+                options.append(u'<optgroup label="%s">' % (label or ''))
+                optgroup_opened = True
+            elif value in curvalues:
+                options.append(tags.option(label, value=value, selected='selected'))
+            else:
+                options.append(tags.option(label, value=value))
+        if optgroup_opened:
+            options.append(u'</optgroup>')
+        return tags.select(name=name, multiple=self._multiple,
+                           options=options, **attrs)
+
+
+class CheckBox(Input):
+    """<input type='checkbox'>, for field having a specific vocabulary. One
+    input will be generated for each possible value.
+    """
+    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):
+    """<input type='radio'>, for field having a specific vocabulary. One
+    input will be generated for each possible value.
+    """
+    type = 'radio'
+
+    def render(self, form, field):
+        name, curvalues, attrs = self._render_attrs(form, field)
+        domid = attrs.pop('id', None)
+        options = []
+        for i, (label, value) in enumerate(field.vocabulary(form)):
+            iattrs = attrs.copy()
+            if i == 0 and domid is not None:
+                iattrs['id'] = domid
+            if value in curvalues:
+                iattrs['checked'] = u'checked'
+            tag = tags.input(name=name, type=self.type, value=value, **iattrs)
+            options.append(tag + label + '<br/>')
+        return '\n'.join(options)
+
+
+# javascript widgets ###########################################################
+
+class DateTimePicker(TextInput):
+    """<input type='text' + javascript date/time picker for date or datetime
+    fields
+    """
+    monthnames = ('january', 'february', 'march', 'april',
+                  'may', 'june', 'july', 'august',
+                  'september', 'october', 'november', 'december')
+    daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
+                'friday', 'saturday', 'sunday')
+
+    needs_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):
+        value = form.form_field_value(field)
+        if not value:
+            value = date.today()
+        inputid = form.context[field]['id']
+        helperid = '%shelper' % inputid
+        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,
+                   form.req.external_resource('CALENDAR_ICON'),
+                   form.req._('calendar'), helperid) )
+
+
+
+# ajax widgets ################################################################
+
+def init_ajax_attributes(attrs, wdgtype, loadtype=u'auto'):
+    try:
+        attrs['klass'] += u' widget'
+    except KeyError:
+        attrs['klass'] = u'widget'
+    attrs.setdefault('cubicweb:wdgtype', wdgtype)
+    attrs.setdefault('cubicweb:loadtype', loadtype)
+
+
+class AjaxWidget(FieldWidget):
+    """simple <div> based ajax widget"""
+    def __init__(self, wdgtype, inputid=None, **kwargs):
+        super(AjaxWidget, self).__init__(**kwargs)
+        init_ajax_attributes(self.attrs, 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)
+
+
+class AutoCompletionWidget(TextInput):
+    """ajax widget for StringField, proposing matching existing values as you
+    type.
+    """
+    needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
+    needs_css = ('jquery.autocomplete.css',)
+    wdgtype = 'SuggestField'
+    loadtype = 'auto'
+
+    def _render_attrs(self, form, field):
+        name, values, attrs = super(AutoCompletionWidget, self)._render_attrs(form, field)
+        if not values[0]:
+            values = (INTERNAL_FIELD_VALUE,)
+        init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
+        # XXX entity form specific
+        attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity)
+        return name, values, attrs
+
+    def _get_url(self, entity):
+        return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
+                                pageid=entity.req.pageid, mode='remote')
+
+
+class StaticFileAutoCompletionWidget(AutoCompletionWidget):
+    """XXX describe me"""
+    wdgtype = 'StaticFileSuggestField'
+
+    def _get_url(self, entity):
+        return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
+
+
+class RestrictedAutoCompletionWidget(AutoCompletionWidget):
+    """XXX describe me"""
+    wdgtype = 'RestrictedSuggestField'
+
+
+class AddComboBoxWidget(Select):
+    def _render_attrs(self, form, field):
+        name, values, attrs = super(AddComboBoxWidget, self)._render_attrs(form, field)
+        init_ajax_attributes(self.attrs, 'AddComboBox')
+        # XXX entity form specific
+        entity = form.edited_entity
+        attrs['cubicweb:etype_to'] = entity.e_schema
+        etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
+        attrs['cubicweb:etype_from'] = etype_from
+
+    def render(self, form, field):
+        return super(AddComboBoxWidget, self).render(form, field) + u'''
+<div id="newvalue">
+  <input type="text" id="newopt" />
+  <a href="javascript:noop()" id="add_newopt">&nbsp;</a></div>
+'''
+
+# buttons ######################################################################
+
+class Button(Input):
+    """<input type='button'>, base class for global form buttons
+
+    note label is a msgid which will be translated at form generation time, you
+    should not give an already translated string.
+    """
+    type = 'button'
+    def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
+                 setdomid=None, settabindex=None,
+                 name='', value='', onclick=None, cwaction=None):
+        super(Button, self).__init__(attrs, setdomid, settabindex)
+        self.label = label
+        self.name = name
+        self.value = ''
+        self.onclick = onclick
+        self.cwaction = cwaction
+        self.attrs.setdefault('klass', 'validateButton')
+
+    def render(self, form, field=None):
+        label = form.req._(self.label)
+        attrs = self.attrs.copy()
+        if self.cwaction:
+            assert self.onclick is None
+            attrs['onclick'] = "postForm('__action_%s', \'%s\', \'%s\')" % (
+                self.cwaction, self.label, form.domid)
+        elif self.onclick:
+            attrs['onclick'] = self.onclick
+        if self.name:
+            attrs['name'] = name
+            if self.setdomid:
+                attrs['id'] = self.name
+        if self.settabindex and not 'tabindex' in attrs:
+            attrs['tabindex'] = form.req.next_tabindex()
+        return tags.input(value=label, type=self.type, **attrs)
+
+
+class SubmitButton(Button):
+    """<input type='submit'>, main button to submit a form"""
+    type = 'submit'
+
+
+class ResetButton(Button):
+    """<input type='reset'>, main button to reset a form.
+    You usually don't want this.
+    """
+    type = 'reset'
+
+
+class ImgButton(object):
+    """<img> wrapped into a <a> tag with href triggering something (usually a
+    javascript call)
+
+    note label is a msgid which will be translated at form generation time, you
+    should not give an already translated string.
+    """
+    def __init__(self, domid, href, label, imgressource):
+        self.domid = domid
+        self.href = href
+        self.imgressource = imgressource
+        self.label = label
+
+    def render(self, form, field=None):
+        label = form.req._(self.label)
+        imgsrc = form.req.external_resource(self.imgressource)
+        return '<a id="%(domid)s" href="%(href)s">'\
+               '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % {
+            'label': label, 'imgsrc': imgsrc,
+            'domid': self.domid, 'href': self.href}
+
+
+
+# XXX EntityLinkComboBoxWidget, [Raw]DynamicComboBoxWidget
--- a/web/htmlwidgets.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/htmlwidgets.py	Thu May 14 12:50:34 2009 +0200
@@ -4,13 +4,13 @@
 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
 """
 
 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,
@@ -59,7 +59,7 @@
 
     main_div_class = 'boxContent'
     listing_class = 'boxListing'
-    
+
     def box_begin_content(self):
         self.w(u'<div class="%s">\n' % self.main_div_class)
         if self.islist:
@@ -71,12 +71,12 @@
         self.w(u'</div>\n')
         if self.shadow:
             self.w(u'<div class="shadow">&nbsp;</div>')
-        
+
     def _render(self):
         if self.id:
             self.w(u'<div class="%s" id="%s">' % (self._class, self.id))
         else:
-            self.w(u'<div class="%s">' % self._class)            
+            self.w(u'<div class="%s">' % self._class)
         if self.title:
             if self.escape:
                 title = '<span>%s</span>' % html_escape(self.title)
@@ -100,7 +100,7 @@
         super(SideBoxWidget, self).__init__(title, id=id, _class='sideBox',
                                             shadow=False)
 
-                                            
+
 class MenuWidget(BoxWidget):
     main_div_class = 'menuContent'
     listing_class = 'menuListing'
@@ -109,7 +109,7 @@
         if self.islist:
             self.w(u'</ul>\n')
         self.w(u'</div>\n')
-    
+
 
 class RawBoxItem(HTMLWidget):
     """a simpe box item displaying raw data"""
@@ -122,9 +122,9 @@
             return u'<li>'
         else:
             return u'<li class="%s">' % self.liclass
-        
+
         return self.label
-    
+
     def _render(self):
         self.w(u'%s%s</li>' % (self._start_li(), self.label))
 
@@ -132,7 +132,7 @@
 class BoxMenu(RawBoxItem):
     """a menu in a box"""
     link_class = 'boxMenu'
-    
+
     def __init__(self, label, items=None, isitem=True, liclass=None, ident=None,
                  link_class=None):
         super(BoxMenu, self).__init__(label, liclass)
@@ -141,7 +141,7 @@
         self.ident = ident or u'boxmenu_%s' % label.replace(' ', '_').replace("'", '')
         if link_class:
             self.link_class = link_class
-            
+
     def append(self, item):
         self.items.append(item)
 
@@ -150,7 +150,7 @@
 
     def _end_menu(self):
         self.w(u'</ul>')
-        
+
     def _render(self):
         if self.isitem:
             self.w(self._start_li())
@@ -183,15 +183,15 @@
     def __init__(self, label, value):
         self.label = label
         self.value = value
-    
+
     def _render(self):
         self.w(u'<li><div><span class="label">%s</span>&nbsp;'
-               u'<span class="value">%s</span></div></li>' 
+               u'<span class="value">%s</span></div></li>'
                % (self.label, self.value))
 
 class BoxSeparator(HTMLWidget):
     """a menu separator"""
-    
+
     def _render(self):
         self.w(u'</ul><hr class="boxSeparator"/><ul>')
 
@@ -249,7 +249,7 @@
 
     highlight = "onmouseover=\"addElementClass(this, 'highlighted');\" " \
                 "onmouseout=\"removeElementClass(this, 'highlighted');\""
-    
+
     def __init__(self, model):
         self.model = model
         self.columns = []
@@ -278,7 +278,7 @@
                 attrs = ('%s="%s"' % (name, value) for name, value in attrs.iteritems())
                 self.w(u'<td %s>' % (' '.join(attrs)))
                 for cellvid, colindex in column.cellrenderers:
-                    self.model.render(cellvid, rowindex, colindex, w=self.w)
+                    self.model.render_cell(cellvid, rowindex, colindex, w=self.w)
                 self.w(u'</td>')
             self.w(u'</tr>')
         self.w(u'</tbody>')
--- a/web/httpcache.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/httpcache.py	Thu May 14 12:50:34 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"""
@@ -42,14 +42,14 @@
 
     def etag(self):
         return self.view.id + '/' + ','.join(sorted(self.req.user.groups))
-    
+
     def max_age(self):
         # 0 to actually force revalidation
         return 0
-    
+
     def last_modified(self):
         return self.view.last_modified()
-    
+
     def set_headers(self):
         req = self.req
         try:
@@ -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	Thu May 14 12:50:14 2009 +0200
+++ b/web/request.py	Thu May 14 12:50:34 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"
@@ -17,15 +17,18 @@
 from rql.utils import rqlvar_maker
 
 from logilab.common.decorators import cached
+from logilab.common.deprecation import obsolete
 
-# XXX move _MARKER here once AppObject.external_resource has been removed
+from logilab.mtconverter import html_escape
+
 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.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, RequestError,
-                       StatusResponse)
+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):
@@ -56,8 +59,8 @@
 
 
 class CubicWebRequestBase(DBAPIRequest):
-    """abstract HTTP request, should be extended according to the HTTP backend"""    
-    
+    """abstract HTTP request, should be extended according to the HTTP backend"""
+
     def __init__(self, vreg, https, form=None):
         super(CubicWebRequestBase, self).__init__(vreg)
         self.message = None
@@ -73,7 +76,7 @@
         self.data = {}
         # search state: 'normal' or 'linksearch' (eg searching for an object
         # to create a relation with another)
-        self.search_state = ('normal',) 
+        self.search_state = ('normal',)
         # tabindex generator
         self.tabindexgen = count()
         self.next_tabindex = self.tabindexgen.next
@@ -106,27 +109,27 @@
                     return
         # 3. default language
         self.set_default_language(vreg)
-            
+
     def set_language(self, lang):
         self._ = self.__ = self.translations[lang]
         self.lang = lang
         self.debug('request language: %s', lang)
-        
+
     # input form parameters management ########################################
-    
+
     # common form parameters which should be protected against html values
     # XXX can't add 'eid' for instance since it may be multivalued
     # dont put rql as well, if query contains < and > it will be corrupted!
-    no_script_form_params = set(('vid', 
-                                 'etype', 
+    no_script_form_params = set(('vid',
+                                 'etype',
                                  'vtitle', 'title',
                                  '__message',
                                  '__redirectvid', '__redirectrql'))
-        
+
     def setup_params(self, params):
         """WARNING: we're intentionaly leaving INTERNAL_FIELD_VALUE here
 
-        subclasses should overrides to 
+        subclasses should overrides to
         """
         if params is None:
             params = {}
@@ -146,7 +149,7 @@
                 del self.form[k]
             else:
                 self.form[k] = v
-    
+
     def no_script_form_param(self, param, default=None, value=None):
         """ensure there is no script in a user form param
 
@@ -167,11 +170,11 @@
                 value = value[0]
             return remove_html_tags(value)
         return value
-        
+
     def list_form_param(self, param, form=None, pop=False):
         """get param from form parameters and return its value as a list,
         skipping internal markers if any
-        
+
         * if the parameter isn't defined, return an empty list
         * if the parameter is a single (unicode) value, return a list
           containing that value
@@ -182,8 +185,8 @@
         """
         if form is None:
             form = self.form
-        return list_form_param(form, param, pop)            
-    
+        return list_form_param(form, param, pop)
+
 
     def reset_headers(self):
         """used by AutomaticWebTest to clear html headers between tests on
@@ -193,11 +196,11 @@
         return self
 
     # web state helpers #######################################################
-    
+
     def set_message(self, msg):
         assert isinstance(msg, unicode)
         self.message = msg
-    
+
     def update_search_state(self):
         """update the current search state"""
         searchstate = self.form.get('__mode')
@@ -215,6 +218,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')
@@ -234,7 +250,7 @@
     def register_onetime_callback(self, func, *args):
         cbname = 'cb_%s' % (
             sha.sha('%s%s%s%s' % (time.time(), func.__name__,
-                                  random.random(), 
+                                  random.random(),
                                   self.user.login)).hexdigest())
         def _cb(req):
             try:
@@ -242,12 +258,12 @@
             except TypeError:
                 from warnings import warn
                 warn('user callback should now take request as argument')
-                ret = func(*args)            
+                ret = func(*args)
             self.unregister_callback(self.pageid, cbname)
             return ret
         self.set_page_data(cbname, _cb)
         return cbname
-    
+
     def unregister_callback(self, pageid, cbname):
         assert pageid is not None
         assert cbname.startswith('cb_')
@@ -260,14 +276,17 @@
             callbacks = [key for key in sessdata if key.startswith('cb_')]
             for callback in callbacks:
                 self.del_session_data(callback)
-    
+
     # web edition helpers #####################################################
-    
+
     @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"""
@@ -314,7 +333,7 @@
             print eid, params
             raise RequestError(self._('missing parameters for entity %s') % eid)
         return params
-    
+
     def get_pending_operations(self, entity, relname, role):
         operations = {'insert' : [], 'delete' : []}
         for optype in ('insert', 'delete'):
@@ -326,7 +345,7 @@
                     if role == 'object' and entity.eid == eidto:
                         operations[optype].append(eidfrom)
         return operations
-    
+
     def get_pending_inserts(self, eid=None):
         """shortcut to access req's pending_insert entry
 
@@ -361,7 +380,7 @@
         """
         self.del_session_data(errorurl)
         self.remove_pending_operations()
-    
+
     # high level methods for HTTP headers management ##########################
 
     # must be cached since login/password are popped from the form dictionary
@@ -379,7 +398,7 @@
                 return None, None
         else:
             return self.header_authorization()
-    
+
     def get_cookie(self):
         """retrieve request cookies, returns an empty cookie if not found"""
         try:
@@ -407,7 +426,7 @@
         morsel['Max-Age'] = 0
         # The only way to set up cookie age for IE is to use an old "expired"
         # syntax. IE doesn't support Max-Age there is no library support for
-        # managing 
+        # managing
         # ===> Do _NOT_ comment this line :
         morsel['expires'] = 'Thu, 01-Jan-1970 00:00:00 GMT'
         self.add_header('Set-Cookie', morsel.OutputString())
@@ -460,9 +479,26 @@
             if localfile:
                 cssfile = self.datadir_url + cssfile
             add_css(cssfile, media)
-    
+
+    def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace',
+                               **extraparams):
+        """builds an ajax url that will replace `nodeid`s content
+        :param nodeid: the dom id of the node to replace
+        :param rql: rql to execute
+        :param vid: the view to apply on the resultset
+        :param replacemode: defines how the replacement should be done.
+        Possible values are :
+         - 'replace' to replace the node's content with the generated HTML
+         - 'swap' to replace the node itself with the generated HTML
+         - 'append' to append the generated HTML to the node's content
+        """
+        url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
+                             **extraparams)
+        return "javascript: loadxhtml('%s', '%s', '%s')" % (
+            nodeid, html_escape(url), replacemode)
+
     # urls/path management ####################################################
-    
+
     def url(self, includeparams=True):
         """return currently accessed url"""
         return self.base_url() + self.relative_path(includeparams)
@@ -470,7 +506,7 @@
     def _datadir_url(self):
         """return url of the application's data directory"""
         return self.base_url() + 'data%s/' % self.vreg.config.instance_md5_version()
-    
+
     def selected(self, url):
         """return True if the url is equivalent to currently accessed url"""
         reqpath = self.relative_path().lower()
@@ -486,7 +522,7 @@
     def base_url_path(self):
         """returns the absolute path of the base url"""
         return urlsplit(self.base_url())[2]
-        
+
     @cached
     def from_controller(self):
         """return the id (string) of the controller issuing the request"""
@@ -496,7 +532,7 @@
         if controller in registered_controllers:
             return controller
         return 'view'
-    
+
     def external_resource(self, rid, default=_MARKER):
         """return a path to an external resource, using its identifier
 
@@ -525,9 +561,9 @@
         self._validate_cache()
         if self.http_method() == 'HEAD':
             raise StatusResponse(200, '')
-        
+
     # abstract methods to override according to the web front-end #############
-        
+
     def http_method(self):
         """returns 'POST', 'GET', 'HEAD', etc."""
         raise NotImplementedError()
@@ -537,7 +573,7 @@
         exists and is still usable
         """
         raise NotImplementedError()
-        
+
     def relative_path(self, includeparams=True):
         """return the normalized path of the request (ie at least relative
         to the application's root, but some other normalization may be needed
@@ -561,11 +597,11 @@
     def add_header(self, header, value):
         """add an output HTTP header"""
         raise NotImplementedError()
-    
+
     def remove_header(self, header):
         """remove an output HTTP header"""
         raise NotImplementedError()
-        
+
     def header_authorization(self):
         """returns a couple (auth-type, auth-value)"""
         auth = self.get_header("Authorization", None)
@@ -582,42 +618,54 @@
                            auth, ex.__class__.__name__, ex)
         return None, None
 
+    @obsolete("use parse_accept_header('Accept-Language')")
     def header_accept_language(self):
         """returns an ordered list of preferred languages"""
-        acceptedlangs = self.get_header('Accept-Language', '')
-        langs = []
-        for langinfo in acceptedlangs.split(','):
+        return [value.split('-')[0] for value in
+                self.parse_accept_header('Accept-Language')]
+
+    def parse_accept_header(self, header):
+        """returns an ordered list of preferred languages"""
+        accepteds = self.get_header(header, '')
+        values = []
+        for info in accepteds.split(','):
             try:
-                lang, score = langinfo.split(';')
-                score = float(score[2:]) # remove 'q='
+                value, scores = info.split(';', 1)
             except ValueError:
-                lang = langinfo
+                value = info
                 score = 1.0
-            lang = lang.split('-')[0]
-            langs.append( (score, lang) )
-        langs.sort(reverse=True)
-        return (lang for (score, lang) in langs)
+            else:
+                for score in scores.split(';'):
+                    try:
+                        scorekey, scoreval = score.split('=')
+                        if scorekey == 'q': # XXX 'level'
+                            score = float(score[2:]) # remove 'q='
+                    except ValueError:
+                        continue
+            values.append((score, value))
+        values.sort(reverse=True)
+        return (value for (score, value) in values)
 
     def header_if_modified_since(self):
         """If the HTTP header If-modified-since is set, return the equivalent
         mx date time value (GMT), else return None
         """
         raise NotImplementedError()
-    
+
     # page data management ####################################################
 
     def get_page_data(self, key, default=None):
         """return value associated to `key` in curernt page data"""
         page_data = self.cnx.get_session_data(self.pageid, {})
         return page_data.get(key, default)
-        
+
     def set_page_data(self, key, value):
         """set value associated to `key` in current page data"""
         self.html_headers.add_unload_pagedata()
         page_data = self.cnx.get_session_data(self.pageid, {})
         page_data[key] = value
         return self.cnx.set_session_data(self.pageid, page_data)
-        
+
     def del_page_data(self, key=None):
         """remove value associated to `key` in current page data
         if `key` is None, all page data will be cleared
@@ -638,16 +686,21 @@
     def ie_browser(self):
         useragent = self.useragent()
         return useragent and 'MSIE' in useragent
-    
+
     def xhtml_browser(self):
         useragent = self.useragent()
-        # MSIE does not support xml content-type
-        # quick fix: Opera supports xhtml and handles namespaces
-        # properly but it breaks jQuery.attr()
+        # * MSIE/Konqueror does not support xml content-type
+        # * Opera supports xhtml and handles namespaces properly but it breaks
+        #   jQuery.attr()
         if useragent and ('MSIE' in useragent or 'KHTML' in useragent
                           or 'Opera' in useragent):
             return False
         return True
 
+    def html_content_type(self):
+        if self.xhtml_browser():
+            return 'application/xhtml+xml'
+        return 'text/html'
+
 from cubicweb import set_log_methods
 set_log_methods(CubicWebRequestBase, LOGGER)
--- a/web/test/data/schema/relations.rel	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/data/schema/relations.rel	Thu May 14 12:50:34 2009 +0200
@@ -1,2 +1,2 @@
 Personne travaille Societe
-EUser connait Personne
+CWUser connait Personne
--- a/web/test/data/schema/testschema.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/data/schema/testschema.py	Thu May 14 12:50:34 2009 +0200
@@ -1,14 +1,14 @@
 class Salesterm(EntityType):
     described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
     amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
-    
+
 class tags(RelationDefinition):
     subject = 'Tag'
-    object = ('BlogEntry', 'EUser')
+    object = ('BlogEntry', 'CWUser')
 
 class checked_by(RelationType):
     subject = 'BlogEntry'
-    object = 'EUser'
+    object = 'CWUser'
     cardinality = '?*'
     permissions = {
         'add': ('managers',),
--- a/web/test/data/views.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/data/views.py	Thu May 14 12:50:34 2009 +0200
@@ -21,6 +21,6 @@
             pass
         assert req.user.login == login
     return orig_publish(self, path, req)
-    
+
 orig_publish = CubicWebPublisher.main_publish
 CubicWebPublisher.main_publish = auto_login_publish
--- a/web/test/runtests.py	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/test_views.py	Thu May 14 12:50:34 2009 +0200
@@ -1,14 +1,12 @@
 """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',
-    'Any L,F,E WHERE E is EUser, E login L, E firstname F',
-    'Any COUNT(X) WHERE X is EUser',
+    'Any L,F WHERE E is CWUser, E login L, E firstname F',
+    'Any L,F,E WHERE E is CWUser, E login L, E firstname F',
+    'Any COUNT(X) WHERE X is CWUser',
     ]
 
 class ComposityCopy(WebTest):
@@ -17,14 +15,14 @@
         """regression test: make sure we can ask a copy of a
         composite entity
         """
-        rset = self.execute('EUser X WHERE X login "admin"')
+        rset = self.execute('CWUser X WHERE X login "admin"')
         self.view('copy', rset)
 
 
 
 class SomeView(AnyRsetView):
     id = 'someview'
-    
+
     def call(self):
         self.req.add_js('spam.js')
         self.req.add_js('spam.js')
@@ -35,40 +33,34 @@
         self.auto_populate(10)
 
     def test_manual_tests(self):
-        rset = self.execute('Any P,F,S WHERE P is EUser, P firstname F, P surname S')
+        rset = self.execute('Any P,F,S WHERE P is CWUser, 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)
-        
 
     def test_sortable_js_added(self):
-        rset = self.execute('EUser X')
+        rset = self.execute('CWUser X')
         # sortable.js should not be included by default
         self.failIf('jquery.tablesorter.js' in self.view('oneline', rset))
         # but should be included by the tableview
-        rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
+        rset = self.execute('Any P,F,S LIMIT 1 WHERE P is CWUser, P firstname F, P surname S')
         self.failUnless('jquery.tablesorter.js' in self.view('table', rset))
 
     def test_js_added_only_once(self):
+        self.vreg._loadedmods[__name__] = {}
         self.vreg.register_vobject_class(SomeView)
-        rset = self.execute('EUser X')
+        rset = self.execute('CWUser X')
         source = self.view('someview', rset).source
         self.assertEquals(source.count('spam.js'), 1)
 
 
 
 class ExplicitViewsTest(WebTest):
-    
+
     def test_unrelateddivs(self):
-        rset = self.execute('Any X WHERE X is EUser, X login "admin"')
-        group = self.add_entity('EGroup', name=u'R&D')
+        rset = self.execute('Any X WHERE X is CWUser, X login "admin"')
+        group = self.add_entity('CWGroup', name=u'R&D')
         req = self.request(relation='in_group_subject')
         self.view('unrelateddivs', rset, req)
-        
-        
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/web/test/unittest_application.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_application.py	Thu May 14 12:50:34 2009 +0200
@@ -44,7 +44,7 @@
 class RequestBaseTC(TestCase):
     def setUp(self):
         self.req = FakeRequest()
-        
+
 
     def test_list_arg(self):
         """tests the list_arg() function"""
@@ -73,13 +73,13 @@
         self.assertEquals(req.relative_path(False), 'login')
         self.assertEquals(req.from_controller(), 'login')
 
-        
+
 class UtilsTC(TestCase):
     """test suite for misc application utilities"""
 
     def setUp(self):
         self.ctrl = FakeController()
-    
+
     #def test_which_mapping(self):
     #    """tests which mapping is used (application or core)"""
     #    init_mapping()
@@ -95,7 +95,7 @@
         self.assertEquals(self.ctrl.execute_linkto(), None)
         self.assertEquals(self.ctrl._cursor.executed,
                           [])
-        
+
         self.ctrl.set_form({'__linkto' : 'works_for:12_13_14:object',
                               'eid': 8})
         self.ctrl.execute_linkto()
@@ -110,7 +110,7 @@
         self.assertEquals(self.ctrl._cursor.executed,
                           ['SET X works_for Y WHERE X eid 8, Y eid %s' % i
                            for i in (12, 13, 14)])
-        
+
 
         self.ctrl.new_cursor()
         self.ctrl.req.form = {'__linkto' : 'works_for:12_13_14:object'}
@@ -152,10 +152,10 @@
             return path, params
         else:
             self.fail('expected a Redirect exception')
-        
+
     def expect_redirect_publish(self, req, path='view'):
         return self.expect_redirect(lambda x: self.publish(x, path), req)
-    
+
     def test_cnx_user_groups_sync(self):
         user = self.user()
         self.assertEquals(user.groups, set(('managers',)))
@@ -168,22 +168,22 @@
         # cleanup
         self.execute('DELETE X in_group G WHERE X eid %s, G name "guests"' % user.eid)
         self.commit()
-    
+
     def test_nonregr_publish1(self):
-        req = self.request(u'EEType X WHERE X final FALSE, X meta FALSE')
+        req = self.request(u'CWEType X WHERE X final FALSE, X meta FALSE')
         self.app.publish('view', req)
-        
+
     def test_nonregr_publish2(self):
         req = self.request(u'Any count(N) WHERE N todo_by U, N is Note, U eid %s'
                            % self.user().eid)
         self.app.publish('view', req)
-        
+
     def test_publish_validation_error(self):
         req = self.request()
         user = self.user()
         req.form = {
             'eid':       `user.eid`,
-            '__type:'+`user.eid`:    'EUser',
+            '__type:'+`user.eid`:    'CWUser',
             'login:'+`user.eid`:     '', # ERROR: no login specified
             'edits-login:'+`user.eid`: unicode(user.login),
              # just a sample, missing some necessary information for real life
@@ -204,12 +204,12 @@
 
     def test_validation_error_dont_loose_subentity_data(self):
         """test creation of two linked entities
-        """        
+        """
         req = self.request()
         form = {'eid': ['X', 'Y'],
-                '__type:X': 'EUser',
+                '__type:X': 'CWUser',
                 # missing required field
-                'login:X': u'', 'edits-login:X': '', 
+                'login:X': u'', 'edits-login:X': '',
                 'surname:X': u'Mr Ouaoua', 'edits-surname:X': '',
                 '__type:Y': 'EmailAddress',
                 # but email address is set
@@ -229,13 +229,13 @@
         self.assertEquals(forminfo['errors'].errors, {'login': 'required attribute',
                                                       'upassword': 'required attribute'})
         self.assertEquals(forminfo['values'], form)
-        
+
     def _test_cleaned(self, kwargs, injected, cleaned):
         req = self.request(**kwargs)
         page = self.app.publish('view', req)
         self.failIf(injected in page, (kwargs, injected))
         self.failUnless(cleaned in page, (kwargs, cleaned))
-        
+
     def test_nonregr_script_kiddies(self):
         """test against current script injection"""
         injected = '<i>toto</i>'
@@ -245,7 +245,7 @@
                        {'vtitle': injected},
                        ):
             yield self._test_cleaned, kwargs, injected, cleaned
-        
+
     def test_site_wide_eproperties_sync(self):
         # XXX work in all-in-one configuration but not in twisted for instance
         # in which case we need a kindof repo -> http server notification
@@ -253,7 +253,7 @@
         vreg = self.app.vreg
         # default value
         self.assertEquals(vreg.property_value('ui.language'), 'en')
-        self.execute('INSERT EProperty X: X value "fr", X pkey "ui.language"')
+        self.execute('INSERT CWProperty X: X value "fr", X pkey "ui.language"')
         self.assertEquals(vreg.property_value('ui.language'), 'en')
         self.commit()
         self.assertEquals(vreg.property_value('ui.language'), 'fr')
@@ -261,7 +261,7 @@
         self.assertEquals(vreg.property_value('ui.language'), 'fr')
         self.commit()
         self.assertEquals(vreg.property_value('ui.language'), 'de')
-        self.execute('DELETE EProperty X WHERE X pkey "ui.language"')
+        self.execute('DELETE CWProperty X WHERE X pkey "ui.language"')
         self.assertEquals(vreg.property_value('ui.language'), 'de')
         self.commit()
         self.assertEquals(vreg.property_value('ui.language'), 'en')
@@ -278,7 +278,7 @@
         self.failIf(req.cnx is origcnx)
         self.assertEquals(req.user.login, 'turlututu')
         self.failUnless('turlututu' in page, page)
-        
+
     # authentication tests ####################################################
 
     def _init_auth(self, authmode, anonuser=None):
@@ -289,7 +289,7 @@
         req.cnx = None
         sh = self.app.session_handler
         # not properly cleaned between tests
-        self.open_sessions = sh.session_manager._sessions = {} 
+        self.open_sessions = sh.session_manager._sessions = {}
         return req, origcnx
 
     def _test_auth_succeed(self, req, origcnx):
@@ -299,16 +299,16 @@
         self.assertEquals(len(self.open_sessions), 1, self.open_sessions)
         self.assertEquals(cnx.login, origcnx.login)
         self.assertEquals(cnx.password, origcnx.password)
-        self.assertEquals(cnx.anonymous_connection, False) 
+        self.assertEquals(cnx.anonymous_connection, False)
         self.assertEquals(path, 'view')
         self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login})
-    
+
     def _test_auth_fail(self, req):
         self.assertRaises(AuthenticationError, self.app.connect, req)
         self.assertEquals(req.cnx, None)
-        self.assertEquals(len(self.open_sessions), 0) 
+        self.assertEquals(len(self.open_sessions), 0)
         clear_cache(req, 'get_authorization')
-        
+
     def test_http_auth_no_anon(self):
         req, origcnx = self._init_auth('http')
         self._test_auth_fail(req)
@@ -318,7 +318,7 @@
         req._headers['Authorization'] = 'basic %s' % authstr
         self._test_auth_succeed(req, origcnx)
         self.assertRaises(AuthenticationError, self.publish, req, 'logout')
-        self.assertEquals(len(self.open_sessions), 0) 
+        self.assertEquals(len(self.open_sessions), 0)
 
     def test_cookie_auth_no_anon(self):
         req, origcnx = self._init_auth('cookie')
@@ -331,17 +331,37 @@
         req.form['__password'] = origcnx.password
         self._test_auth_succeed(req, origcnx)
         self.assertRaises(AuthenticationError, self.publish, req, 'logout')
-        self.assertEquals(len(self.open_sessions), 0) 
+        self.assertEquals(len(self.open_sessions), 0)
+
+    def test_login_by_email(self):
+        login = self.request().user.login
+        address = login + u'@localhost'
+        self.execute('INSERT EmailAddress X: X address %(address)s, U primary_email X '
+                     'WHERE U login %(login)s', {'address': address, 'login': login})
+        self.commit()
+        # option allow-email-login not set
+        req, origcnx = self._init_auth('cookie')
+        req.form['__login'] = address
+        req.form['__password'] = origcnx.password
+        self._test_auth_fail(req)
+        # option allow-email-login set
+        self.set_option('allow-email-login', True)
+        req, origcnx = self._init_auth('cookie')
+        req.form['__login'] = address
+        req.form['__password'] = origcnx.password
+        self._test_auth_succeed(req, origcnx)
+        self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+        self.assertEquals(len(self.open_sessions), 0)
 
     def _test_auth_anon(self, req):
         self.app.connect(req)
         acnx = req.cnx
         self.assertEquals(len(self.open_sessions), 1)
-        self.assertEquals(acnx.login, 'anon') 
-        self.assertEquals(acnx.password, 'anon') 
+        self.assertEquals(acnx.login, 'anon')
+        self.assertEquals(acnx.password, 'anon')
         self.failUnless(acnx.anonymous_connection)
         self._reset_cookie(req)
-        
+
     def _reset_cookie(self, req):
         # preparing the suite of the test
         # set session id in cookie
@@ -351,15 +371,15 @@
         clear_cache(req, 'get_authorization')
         # reset cnx as if it was a new incoming request
         req.cnx = None
-        
+
     def _test_anon_auth_fail(self, req):
-        self.assertEquals(len(self.open_sessions), 1) 
+        self.assertEquals(len(self.open_sessions), 1)
         self.app.connect(req)
         self.assertEquals(req.message, 'authentication failure')
         self.assertEquals(req.cnx.anonymous_connection, True)
-        self.assertEquals(len(self.open_sessions), 1) 
+        self.assertEquals(len(self.open_sessions), 1)
         self._reset_cookie(req)
-        
+
     def test_http_auth_anon_allowed(self):
         req, origcnx = self._init_auth('http', 'anon')
         self._test_auth_anon(req)
@@ -370,8 +390,8 @@
         req._headers['Authorization'] = 'basic %s' % authstr
         self._test_auth_succeed(req, origcnx)
         self.assertRaises(AuthenticationError, self.publish, req, 'logout')
-        self.assertEquals(len(self.open_sessions), 0) 
-        
+        self.assertEquals(len(self.open_sessions), 0)
+
     def test_cookie_auth_anon_allowed(self):
         req, origcnx = self._init_auth('cookie', 'anon')
         self._test_auth_anon(req)
@@ -382,10 +402,8 @@
         req.form['__password'] = origcnx.password
         self._test_auth_succeed(req, origcnx)
         self.assertRaises(AuthenticationError, self.publish, req, 'logout')
-        self.assertEquals(len(self.open_sessions), 0) 
+        self.assertEquals(len(self.open_sessions), 0)
 
-    
 
-        
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_controller.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_controller.py	Thu May 14 12:50:34 2009 +0200
@@ -2,20 +2,29 @@
 
 """
 
-from mx.DateTime import DateTimeType, DateTimeDeltaType
+from datetime import datetime, date, time
 
 from logilab.common.testlib import unittest_main
 
 from cubicweb.devtools import apptest
 
 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)
+
+    def test_parse_datetime_ok(self):
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18'),
+                              datetime)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24'),
+                              date)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18', 'Datetime'),
+                              datetime)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Datetime'),
+                              datetime)
+        self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Date'),
+                              date)
+        self.assertIsInstance(self.ctrl.parse_datetime('12:18', 'Time'),
+                              time)
+
+    def test_parse_datetime_ko(self):
         self.assertRaises(ValueError,
                           self.ctrl.parse_datetime, '2006/06/24 12:188', 'Datetime')
         self.assertRaises(ValueError,
@@ -28,6 +37,6 @@
                           self.ctrl.parse_datetime, '2006/06/240', 'Date')
         self.assertRaises(ValueError,
                           self.ctrl.parse_datetime, '12:188', 'Time')
-        
+
 if __name__ == '__main__':
     unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_form.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,183 @@
+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, FieldsForm
+from cubicweb.web.formrenderers import 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 FieldsFormTC(WebTest):
+
+    def test_form_field_format(self):
+        form = FieldsForm(self.request(), None)
+        self.assertEquals(form.form_field_format(None), 'text/html')
+        self.execute('INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
+        self.commit()
+        self.assertEquals(form.form_field_format(None), 'text/rest')
+
+
+class EntityFieldsFormTC(WebTest):
+
+    def setUp(self):
+        super(EntityFieldsFormTC, self).setUp()
+        self.req = self.request()
+        self.entity = self.user(self.req)
+        self.renderer = FormRenderer()
+
+    def test_form_field_vocabulary_unrelated(self):
+        b = self.add_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller')
+        t = self.add_entity('Tag', name=u'x')
+        form1 = EntityFieldsForm(self.request(), None, entity=t)
+        unrelated = [reid for rview, reid in form1.subject_relation_vocabulary('tags')]
+        self.failUnless(b.eid in unrelated, unrelated)
+        form2 = EntityFieldsForm(self.request(), None, entity=b)
+        unrelated = [reid for rview, reid in form2.object_relation_vocabulary('tags')]
+        self.failUnless(t.eid in unrelated, unrelated)
+        self.execute('SET X tags Y WHERE X is Tag, Y is BlogEntry')
+        unrelated = [reid for rview, reid in form1.subject_relation_vocabulary('tags')]
+        self.failIf(b.eid in unrelated, unrelated)
+        unrelated = [reid for rview, reid in form2.object_relation_vocabulary('tags')]
+        self.failIf(t.eid in unrelated, unrelated)
+
+    def test_form_field_vocabulary_new_entity(self):
+        e = self.etype_instance('CWUser')
+        form = EntityFieldsForm(self.request(), None, entity=e)
+        unrelated = [rview for rview, reid in form.subject_relation_vocabulary('in_group')]
+        # should be default groups but owners, i.e. managers, users, guests
+        self.assertEquals(unrelated, [u'guests', u'managers', u'users'])
+
+    def test_subject_in_state_vocabulary(self):
+        # on a new entity
+        e = self.etype_instance('CWUser')
+        form = EntityFieldsForm(self.request(), None, entity=e)
+        states = list(form.subject_in_state_vocabulary('in_state'))
+        self.assertEquals(len(states), 1)
+        self.assertEquals(states[0][0], u'activated') # list of (combobox view, state eid)
+        # on an existant entity
+        e = self.user()
+        form = EntityFieldsForm(self.request(), None, entity=e)
+        states = list(form.subject_in_state_vocabulary('in_state'))
+        self.assertEquals(len(states), 1)
+        self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
+
+
+
+    # form view tests #########################################################
+
+    def test_massmailing_formview(self):
+        self.execute('INSERT EmailAddress X: X address L + "@cubicweb.org", '
+                     'U use_email X WHERE U is CWUser, U login L')
+        rset = self.execute('CWUser X')
+        self.view('massmailing', rset, template=None)
+
+
+    # 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'')
+
+    # fields tests ############################################################
+
+    def _render_entity_field(self, name, form):
+        form.form_build_context({})
+        return form.field_by_name(name).render(form, self.renderer)
+
+    def _test_richtextfield(self, expected):
+        class RTFForm(EntityFieldsForm):
+            description = RichTextField()
+        state = self.execute('State X WHERE X name "activated", X state_of ET, ET name "CWUser"').get_entity(0, 0)
+        form = RTFForm(self.req, redirect_path='perdu.com', entity=state)
+        # make it think it can use fck editor anyway
+        form.form_field_format = lambda x: 'text/html'
+        self.assertTextEquals(self._render_entity_field('description', form),
+                              expected % {'eid': state.eid})
+
+
+    def test_richtextfield_1(self):
+        self.req.use_fckeditor = lambda: False
+        self._test_richtextfield('''<select id="description_format:%(eid)s" name="description_format:%(eid)s" size="1" style="display: block" tabindex="0">
+<option value="text/cubicweb-page-template">text/cubicweb-page-template</option>
+<option value="text/html">text/html</option>
+<option value="text/plain">text/plain</option>
+<option selected="selected" value="text/rest">text/rest</option>
+</select><textarea cols="60" id="description:%(eid)s" name="description:%(eid)s" onkeypress="autogrow(this)" rows="5" tabindex="1"/>''')
+
+
+    def test_richtextfield_2(self):
+        self.req.use_fckeditor = lambda: True
+        self._test_richtextfield('<input name="description_format:%(eid)s" style="display: block" type="hidden" value="text/rest"/><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeypress="autogrow(this)" rows="20" tabindex="0"/>')
+
+
+    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_encoding=u'UTF-8',
+                               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" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+<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" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+</div>
+<br/>
+<input name="data:%(eid)s__detach" type="checkbox"/>
+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_encoding=u'UTF-8', 
+                               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" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+<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" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/><br/>
+</div>
+<br/>
+<input name="data:%(eid)s__detach" type="checkbox"/>
+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 cols="80" name="data:%(eid)s" onkeypress="autogrow(this)" rows="20" tabindex="3">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" name="upassword:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+<br/>
+<input name="upassword-confirm:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+&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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,84 @@
+"""unittests for cw.web.formfields"""
+
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.web.formwidgets import PasswordInput, TextArea
+from cubicweb.web.formfields import *
+from cubicweb.entities.wfobjs import State
+from cubicweb.entities.authobjs import CWUser
+from cubes.file.entities import File
+
+config = TestServerConfiguration('data')
+config.bootstrap_cubes()
+schema = config.load_schema()
+state_schema = schema['State']
+cwuser_schema = schema['CWUser']
+file_schema = schema['File']
+
+class GuessFieldTC(TestCase):
+
+    def test_state_fields(self):
+        title_field = guess_field(state_schema, schema['name'])
+        self.assertIsInstance(title_field, StringField)
+        self.assertEquals(title_field.required, True)
+
+#         synopsis_field = guess_field(state_schema, schema['synopsis'])
+#         self.assertIsInstance(synopsis_field, StringField)
+#         self.assertIsInstance(synopsis_field.widget, TextArea)
+#         self.assertEquals(synopsis_field.required, False)
+#         self.assertEquals(synopsis_field.help, 'an abstract for this state')
+
+        description_field = guess_field(state_schema, schema['description'])
+        self.assertIsInstance(description_field, RichTextField)
+        self.assertEquals(description_field.required, False)
+        self.assertEquals(description_field.format_field, None)
+
+        description_format_field = guess_field(state_schema, schema['description_format'])
+        self.assertEquals(description_format_field, None)
+
+        description_format_field = guess_field(state_schema, schema['description_format'], skip_meta_attr=False)
+        self.assertEquals(description_format_field.internationalizable, True)
+        self.assertEquals(description_format_field.sort, True)
+        self.assertEquals(description_format_field.initial(None), 'text/rest')
+
+#         wikiid_field = guess_field(state_schema, schema['wikiid'])
+#         self.assertIsInstance(wikiid_field, StringField)
+#         self.assertEquals(wikiid_field.required, False)
+
+
+    def test_euser_fields(self):
+        upassword_field = guess_field(cwuser_schema, 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(cwuser_schema, schema['last_login_time'])
+        self.assertIsInstance(last_login_time_field, DateTimeField)
+        self.assertEquals(last_login_time_field.required, False)
+
+        in_group_field = guess_field(cwuser_schema, schema['in_group'])
+        self.assertIsInstance(in_group_field, RelationField)
+        self.assertEquals(in_group_field.required, True)
+        self.assertEquals(in_group_field.role, 'subject')
+        self.assertEquals(in_group_field.help, 'groups grant permissions to the user')
+
+        owned_by_field = guess_field(cwuser_schema, 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, schema['data_format'])
+        self.assertEquals(data_format_field, None)
+        data_encoding_field = guess_field(file_schema, schema['data_encoding'])
+        self.assertEquals(data_encoding_field, None)
+
+        data_field = guess_field(file_schema, 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_magicsearch.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_magicsearch.py	Thu May 14 12:50:34 2009 +0200
@@ -11,7 +11,7 @@
 
 
 translations = {
-    u'EUser' : u"Utilisateur",
+    u'CWUser' : u"Utilisateur",
 #    u'Workcase' : u"Affaire",
     u'EmailAddress' : u"Adresse",
 #    u'Division' : u"Division",
@@ -33,7 +33,7 @@
 
 class QueryTranslatorTC(EnvBasedTC):
     """test suite for QueryTranslatorTC"""
-    
+
     def setUp(self):
         super(QueryTranslatorTC, self).setUp()
         self.req = self.env.create_request()
@@ -54,7 +54,7 @@
         self.assertEquals(rql, "Any P WHERE P use_email C, C is EmailAddress, C alias 'Logilab'")
         rql = "Any P WHERE P is Utilisateur, P adel C, P nom 'Smith'"
         rql, = self.proc.preprocess_query(rql, self.req)
-        self.assertEquals(rql, "Any P WHERE P is EUser, P use_email C, P surname 'Smith'")
+        self.assertEquals(rql, "Any P WHERE P is CWUser, P use_email C, P surname 'Smith'")
 
 
 class QSPreProcessorTC(EnvBasedTC):
@@ -79,11 +79,11 @@
     def test_attribute_translation(self):
         """tests QSPreProcessor._get_attribute_name"""
         translate = self.proc._get_attribute_name
-        eschema = self.schema.eschema('EUser')
+        eschema = self.schema.eschema('CWUser')
         self.assertEquals(translate(u'prénom', eschema), "firstname")
         self.assertEquals(translate(u'nom', eschema), 'surname')
         #self.assert_(translate(u'nom') in ('name', 'surname'))
-        eschema = self.schema.eschema('EmailAddress')        
+        eschema = self.schema.eschema('EmailAddress')
         self.assertEquals(translate(u'adresse', eschema), "address")
         self.assertEquals(translate(u'nom', eschema), 'alias')
         # should fail if the name is not an attribute for the given entity schema
@@ -95,10 +95,10 @@
         transform = self.proc._one_word_query
         self.assertEquals(transform('123'),
                           ('Any X WHERE X eid %(x)s', {'x': 123}, 'x'))
-        self.assertEquals(transform('EUser'),
-                          ('EUser E',))
+        self.assertEquals(transform('CWUser'),
+                          ('CWUser C',))
         self.assertEquals(transform('Utilisateur'),
-                          ('EUser E',))
+                          ('CWUser C',))
         self.assertEquals(transform('Adresse'),
                           ('EmailAddress E',))
         self.assertEquals(transform('adresse'),
@@ -108,39 +108,39 @@
     def test_two_words_query(self):
         """tests the 'two words shortcut queries'"""
         transform = self.proc._two_words_query
-        self.assertEquals(transform('EUser', 'E'),
-                          ("EUser E",))
-        self.assertEquals(transform('EUser', 'Smith'),
-                          ('EUser E WHERE E has_text %(text)s', {'text': 'Smith'}))
+        self.assertEquals(transform('CWUser', 'E'),
+                          ("CWUser E",))
+        self.assertEquals(transform('CWUser', 'Smith'),
+                          ('CWUser C WHERE C has_text %(text)s', {'text': 'Smith'}))
         self.assertEquals(transform('utilisateur', 'Smith'),
-                          ('EUser E WHERE E has_text %(text)s', {'text': 'Smith'}))
+                          ('CWUser C WHERE C has_text %(text)s', {'text': 'Smith'}))
         self.assertEquals(transform(u'adresse', 'Logilab'),
                           ('EmailAddress E WHERE E has_text %(text)s', {'text': 'Logilab'}))
         self.assertEquals(transform(u'adresse', 'Logi%'),
                           ('EmailAddress E WHERE E alias LIKE %(text)s', {'text': 'Logi%'}))
         self.assertRaises(BadRQLQuery, transform, "pers", "taratata")
-        #self.assertEquals(transform('EUser', '%mi'), 'EUser E WHERE P surname LIKE "%mi"')
+        #self.assertEquals(transform('CWUser', '%mi'), 'CWUser E WHERE P surname LIKE "%mi"')
 
     def test_three_words_query(self):
         """tests the 'three words shortcut queries'"""
         transform = self.proc._three_words_query
         self.assertEquals(transform('utilisateur', u'prénom', 'cubicweb'),
-                          ('EUser E WHERE E firstname %(text)s', {'text': 'cubicweb'}))
+                          ('CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'}))
         self.assertEquals(transform('utilisateur', 'nom', 'cubicweb'),
-                          ('EUser E WHERE E surname %(text)s', {'text': 'cubicweb'}))
+                          ('CWUser C WHERE C surname %(text)s', {'text': 'cubicweb'}))
         self.assertEquals(transform(u'adresse', 'nom', 'cubicweb'),
                           ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
         self.assertEquals(transform('EmailAddress', 'nom', 'cubicweb'),
-                          ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'})) 
+                          ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
         self.assertEquals(transform('utilisateur', u'prénom', 'cubicweb%'),
-                          ('EUser E WHERE E firstname LIKE %(text)s', {'text': 'cubicweb%'}))
+                          ('CWUser C WHERE C firstname LIKE %(text)s', {'text': 'cubicweb%'}))
         # expanded shortcuts
-        self.assertEquals(transform('EUser', 'use_email', 'Logilab'),
-                          ('EUser E WHERE E use_email E1, E1 has_text %(text)s', {'text': 'Logilab'}))
-        self.assertEquals(transform('EUser', 'use_email', '%Logilab'),
-                          ('EUser E WHERE E use_email E1, E1 alias LIKE %(text)s', {'text': '%Logilab'}))
+        self.assertEquals(transform('CWUser', 'use_email', 'Logilab'),
+                          ('CWUser C WHERE C use_email C1, C1 has_text %(text)s', {'text': 'Logilab'}))
+        self.assertEquals(transform('CWUser', 'use_email', '%Logilab'),
+                          ('CWUser C WHERE C use_email C1, C1 alias LIKE %(text)s', {'text': '%Logilab'}))
         self.assertRaises(BadRQLQuery, transform, 'word1', 'word2', 'word3')
-        
+
     def test_multiple_words_query(self):
         """tests multiple_words_query()"""
         self.assertEquals(self.proc._multiple_words_query(['a', 'b', 'c', 'd', 'e']),
@@ -150,33 +150,33 @@
         """tests how quoted queries are handled"""
         queries = [
             (u'Adresse "My own EmailAddress"', ('EmailAddress E WHERE E has_text %(text)s', {'text': u'My own EmailAddress'})),
-            (u'Utilisateur prénom "Jean Paul"', ('EUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})),
-            (u'Utilisateur firstname "Jean Paul"', ('EUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})),
-            (u'EUser firstname "Jean Paul"', ('EUser E WHERE E firstname %(text)s', {'text': 'Jean Paul'})),
+            (u'Utilisateur prénom "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
+            (u'Utilisateur firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
+            (u'CWUser firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
             ]
         transform = self.proc._quoted_words_query
         for query, expected in queries:
             self.assertEquals(transform(query), expected)
         self.assertRaises(BadRQLQuery, transform, "unquoted rql")
         self.assertRaises(BadRQLQuery, transform, 'pers "Jean Paul"')
-        self.assertRaises(BadRQLQuery, transform, 'EUser firstname other "Jean Paul"')
-    
+        self.assertRaises(BadRQLQuery, transform, 'CWUser firstname other "Jean Paul"')
+
     def test_process_query(self):
         """tests how queries are processed"""
         queries = [
-            (u'Utilisateur', (u"EUser E",)),
-            (u'Utilisateur P', (u"EUser P",)),
-            (u'Utilisateur cubicweb', (u'EUser E WHERE E has_text %(text)s', {'text': u'cubicweb'})),
-            (u'EUser prénom cubicweb', (u'EUser E WHERE E firstname %(text)s', {'text': 'cubicweb'},)),
+            (u'Utilisateur', (u"CWUser C",)),
+            (u'Utilisateur P', (u"CWUser P",)),
+            (u'Utilisateur cubicweb', (u'CWUser C WHERE C has_text %(text)s', {'text': u'cubicweb'})),
+            (u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
             (u'Any X WHERE X is Something', (u"Any X WHERE X is Something",)),
             ]
         for query, expected in queries:
             self.assertEquals(self.proc.preprocess_query(query, self.req), expected)
-        
+
 
 
 ## Processor Chains tests ############################################
-        
+
 
 class ProcessorChainTC(EnvBasedTC):
     """test suite for magic_search's processor chains"""
@@ -195,11 +195,11 @@
             # XXX this sounds like a language translator test...
             # and it fail
             (u'Utilisateur Smith',
-             ('EUser E WHERE E has_text %(text)s', {'text': u'Smith'})),
+             ('CWUser C WHERE C has_text %(text)s', {'text': u'Smith'})),
             (u'utilisateur nom Smith',
-             ('EUser E WHERE E surname %(text)s', {'text': u'Smith'})),
+             ('CWUser C WHERE C surname %(text)s', {'text': u'Smith'})),
             (u'Any P WHERE P is Utilisateur, P nom "Smith"',
-             ('Any P WHERE P is EUser, P surname "Smith"', None)),
+             ('Any P WHERE P is CWUser, P surname "Smith"', None)),
             ]
         for query, expected in queries:
             rset = self.proc.process_query(query, self.req)
@@ -213,12 +213,12 @@
 
     def test_explicit_component(self):
         self.assertRaises(RQLSyntaxError,
-                          self.proc.process_query, u'rql: EUser E WHERE E noattr "Smith",', self.req)
+                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",', self.req)
         self.assertRaises(BadRQLQuery,
-                          self.proc.process_query, u'rql: EUser E WHERE E noattr "Smith"', self.req)
+                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"', self.req)
         rset = self.proc.process_query(u'text: utilisateur Smith', self.req)
         self.assertEquals(rset.rql, 'Any X WHERE X has_text %(text)s')
         self.assertEquals(rset.args, {'text': u'utilisateur Smith'})
-                          
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_owl.py	Thu May 14 12:50:14 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_urlpublisher.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_urlpublisher.py	Thu May 14 12:50:34 2009 +0200
@@ -21,11 +21,11 @@
         b = self.add_entity('BlogEntry', title=u'hell\'o', content=u'blabla')
         c = self.add_entity('Tag', name=u'yo') # take care: Tag's name normalized to lower case
         self.execute('SET C tags B WHERE C eid %(c)s, B eid %(b)s', {'c':c.eid, 'b':b.eid}, 'b')
-                          
+
     def process(self, url):
         req = self.req = self.request()
         return self.env.app.url_resolver.process(req, url)
-        
+
     def test_raw_path(self):
         """tests raw path resolution'"""
         self.assertEquals(self.process('view'), ('view', None))
@@ -41,32 +41,32 @@
 
     def test_rest_path(self):
         """tests the rest path resolution"""
-        ctrl, rset = self.process('EUser')
+        ctrl, rset = self.process('CWUser')
         self.assertEquals(ctrl, 'view')
-        self.assertEquals(rset.description[0][0], 'EUser')
+        self.assertEquals(rset.description[0][0], 'CWUser')
         self.assertEquals(rset.printable_rql(),
-                          "Any X,AA,AB,AC,AD ORDERBY AA WHERE X is EUser, X login AA, X firstname AB, X surname AC, X modification_date AD")
-        ctrl, rset = self.process('EUser/login/admin')
+                          "Any X,AA,AB,AC,AD ORDERBY AA WHERE X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD")
+        ctrl, rset = self.process('CWUser/login/admin')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
-        self.assertEquals(rset.description[0][0], 'EUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is EUser, X login "admin"')
-        ctrl, rset = self.process('euser/admin')
+        self.assertEquals(rset.description[0][0], 'CWUser')
+        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"')
+        ctrl, rset = self.process('cwuser/admin')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
-        self.assertEquals(rset.description[0][0], 'EUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is EUser, X login "admin"')
-        ctrl, rset = self.process('euser/eid/%s'%rset[0][0])
+        self.assertEquals(rset.description[0][0], 'CWUser')
+        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"')
+        ctrl, rset = self.process('cwuser/eid/%s'%rset[0][0])
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
-        self.assertEquals(rset.description[0][0], 'EUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is EUser, X eid 5')
+        self.assertEquals(rset.description[0][0], 'CWUser')
+        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X eid 5')
         # test non-ascii paths
-        ctrl, rset = self.process('EUser/login/%C3%BFsa%C3%BFe')
+        ctrl, rset = self.process('CWUser/login/%C3%BFsa%C3%BFe')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
-        self.assertEquals(rset.description[0][0], 'EUser')
-        self.assertEquals(rset.printable_rql(), u'Any X WHERE X is EUser, X login "ÿsaÿe"')
+        self.assertEquals(rset.description[0][0], 'CWUser')
+        self.assertEquals(rset.printable_rql(), u'Any X WHERE X is CWUser, X login "ÿsaÿe"')
         # test quoted paths
         ctrl, rset = self.process('BlogEntry/title/hell%27o')
         self.assertEquals(ctrl, 'view')
@@ -74,10 +74,10 @@
         self.assertEquals(rset.description[0][0], 'BlogEntry')
         self.assertEquals(rset.printable_rql(), u'Any X WHERE X is BlogEntry, X title "hell\'o"')
         # errors
-        self.assertRaises(NotFound, self.process, 'EUser/eid/30000')
+        self.assertRaises(NotFound, self.process, 'CWUser/eid/30000')
         self.assertRaises(NotFound, self.process, 'Workcases')
-        self.assertRaises(NotFound, self.process, 'EUser/inexistant_attribute/joe')
-    
+        self.assertRaises(NotFound, self.process, 'CWUser/inexistant_attribute/joe')
+
     def test_action_path(self):
         """tests the action path resolution"""
         self.assertRaises(Redirect, self.process, '1/edit')
@@ -85,14 +85,14 @@
         self.assertRaises(Redirect, self.process, 'Tag/yo/edit')
         self.assertRaises(NotFound, self.process, 'view/edit')
         self.assertRaises(NotFound, self.process, '1/non_action')
-        self.assertRaises(NotFound, self.process, 'EUser/login/admin/non_action')
+        self.assertRaises(NotFound, self.process, 'CWUser/login/admin/non_action')
 
 
     def test_regexp_path(self):
         """tests the regexp path resolution"""
         ctrl, rset = self.process('add/Task')
         self.assertEquals(ctrl, 'view')
-        self.assertEquals(rset, None) 
+        self.assertEquals(rset, None)
         self.assertEquals(self.req.form, {'etype' : "Task", 'vid' : "creation"})
         self.assertRaises(NotFound, self.process, 'add/foo/bar')
 
@@ -103,11 +103,11 @@
         try:
             path = str(FakeRequest().url_quote(u'été'))
             ctrl, rset = self.process(path)
-            self.assertEquals(rset, None) 
+            self.assertEquals(rset, None)
             self.assertEquals(self.req.form, {'vid' : "foo"})
         finally:
             SimpleReqRewriter.rules = oldrules
-            
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_urlrewrite.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_urlrewrite.py	Thu May 14 12:50:34 2009 +0200
@@ -28,14 +28,14 @@
             ('/manage', dict(vid='manage')),
             ('/notfound', {'vid': '404'}),
             ('/error', {'vid': 'error'}),
-            ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is EEType, X name "\1"', 'vid': 'eschema'}),
+            ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'eschema'}),
             ('/add/([^/]+?)/?$' , dict(vid='creation', etype=r'\1')),
             ('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
             ('/doc/?$', dict(fid='main', vid='wdoc')),
             ('/doc/(.+?)/?$', dict(fid='\\1', vid='wdoc')),
             ('/changelog/?$', dict(vid='changelog')),
             # now in SchemaBasedRewriter
-            #('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')), 
+            #('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')),
             ])
 
 
@@ -94,8 +94,8 @@
         pmid, rset = rewriter.rewrite(req, u'/DaLToN/JoE')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset[0][0], self.p1.eid)
-        
-    
+
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_actions.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_actions.py	Thu May 14 12:50:34 2009 +0200
@@ -4,11 +4,11 @@
 
 class ActionsTC(EnvBasedTC):
     def test_view_action(self):
-        req = self.request(__message='bla bla bla', vid='rss', rql='EUser X')
-        rset = self.execute('EUser X')
+        req = self.request(__message='bla bla bla', vid='rss', rql='CWUser X')
+        rset = self.execute('CWUser X')
         vaction = [action for action in self.vreg.possible_vobjects('actions', req, rset)
                    if action.id == 'view'][0]
-        self.assertEquals(vaction.url(), 'http://testing.fr/cubicweb/view?rql=EUser%20X')
+        self.assertEquals(vaction.url(), 'http://testing.fr/cubicweb/view?rql=CWUser%20X')
 
     def test_sendmail_action(self):
         req = self.request()
@@ -20,6 +20,6 @@
         rset = self.execute('Any X WHERE X login "anon"', req=req)
         self.failIf([action for action in self.vreg.possible_vobjects('actions', req, rset)
                      if action.id == 'sendemail'])
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_basecontrollers.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Thu May 14 12:50:34 2009 +0200
@@ -11,34 +11,34 @@
 from cubicweb.common.uilib import rql_for_eid
 
 from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
-from cubicweb.web.views.basecontrollers import xmlize
+from cubicweb.web.views.basecontrollers import xhtml_wrap
 
-from cubicweb.entities.authobjs import EUser
+from cubicweb.entities.authobjs import CWUser
 
 
 class EditControllerTC(ControllerTC):
     def setUp(self):
         ControllerTC.setUp(self)
-        self.failUnless('users' in self.schema.eschema('EGroup').get_groups('read'))
-        
+        self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
+
     def tearDown(self):
         ControllerTC.tearDown(self)
-        self.failUnless('users' in self.schema.eschema('EGroup').get_groups('read'))
-        
+        self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
+
     def test_noparam_edit(self):
         """check behaviour of this controller without any form parameter
         """
-        
+
         self.req.form = {}
         self.assertRaises(ValidationError, self.publish, self.req)
-        
+
     def test_validation_unique(self):
         """test creation of two linked entities
-        """        
+        """
         user = self.user()
-        self.req.form = {'eid': 'X', '__type:X': 'EUser',
-                         'login:X': u'admin', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
+        self.req.form = {'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': u'admin', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
@@ -47,13 +47,13 @@
         """checking that a manager user can edit itself
         """
         user = self.user()
-        basegroups = [str(eid) for eid, in self.execute('EGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
-        groupeids = [eid for eid, in self.execute('EGroup G WHERE G name in ("managers", "users")')]
+        basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
         groups = [str(eid) for eid in groupeids]
         stateeid = [eid for eid, in self.execute('State S WHERE S name "activated"')][0]
         self.req.form = {
             'eid':       `user.eid`,
-            '__type:'+`user.eid`:    'EUser',
+            '__type:'+`user.eid`:    'CWUser',
             'login:'+`user.eid`:     unicode(user.login),
             'firstname:'+`user.eid`: u'Th\xe9nault',
             'surname:'+`user.eid`:   u'Sylvain',
@@ -78,10 +78,10 @@
         user = self.create_user('user')
         cnx = self.login('user')
         req = self.request()
-        #self.assertEquals(self.ctrl.schema['EUser']._groups['read'],
+        #self.assertEquals(self.ctrl.schema['CWUser']._groups['read'],
         #                  ('managers', 'users'))
         req.form = {
-            'eid': `user.eid`, '__type:'+`user.eid`: 'EUser',
+            'eid': `user.eid`, '__type:'+`user.eid`: 'CWUser',
             '__maineid' : str(user.eid),
             'upassword:'+`user.eid`: 'tournicoton',
             'upassword-confirm:'+`user.eid`: 'tournicoton',
@@ -89,7 +89,7 @@
             }
         path, params = self.expect_redirect_publish(req)
         cnx.commit() # commit to check we don't get late validation error for instance
-        self.assertEquals(path, 'euser/user')
+        self.assertEquals(path, 'cwuser/user')
         self.failIf('vid' in params)
 
     def testr_user_editing_itself_no_relation(self):
@@ -97,10 +97,10 @@
         relations (meaning no changes)
         """
         user = self.user()
-        groupeids = [eid for eid, in self.execute('EGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
         self.req.form = {
             'eid':       `user.eid`,
-            '__type:'+`user.eid`:    'EUser',
+            '__type:'+`user.eid`:    'CWUser',
             'login:'+`user.eid`:     unicode(user.login),
             'firstname:'+`user.eid`: u'Th\xe9nault',
             'surname:'+`user.eid`:   u'Sylvain',
@@ -117,42 +117,42 @@
         self.assertEquals([g.eid for g in e.in_group], groupeids)
         stateeids = [eid for eid, in self.execute('State S WHERE S name "activated"')]
         self.assertEquals([s.eid for s in e.in_state], stateeids)
-        
-        
+
+
     def test_create_multiple_linked(self):
-        gueid = self.execute('EGroup G WHERE G name "users"')[0][0]
+        gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
         self.req.form = {'eid': ['X', 'Y'],
-                         
-                         '__type:X': 'EUser',
+
+                         '__type:X': 'CWUser',
                          '__maineid' : 'X',
-                         'login:X': u'adim', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
+                         'login:X': u'adim', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
                          'surname:X': u'Di Mascio', 'edits-surname:X': '',
 
-                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE, 
-                         
+                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE, 
+                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
                          }
         path, params = self.expect_redirect_publish()
         # should be redirected on the created person
-        self.assertEquals(path, 'euser/adim')
+        self.assertEquals(path, 'cwuser/adim')
         e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
         self.assertEquals(e.surname, 'Di Mascio')
         email = e.use_email[0]
         self.assertEquals(email.address, 'dima@logilab.fr')
-        
+
     def test_edit_multiple_linked(self):
         peid = self.create_user('adim').eid
         self.req.form = {'eid': [`peid`, 'Y'],
-                         '__type:%s'%peid: 'EUser',
+                         '__type:%s'%peid: 'CWUser',
                          'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: '',
-                         
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
                          'use_email:%s'%peid: 'Y', 'edits-use_email:%s'%peid: INTERNAL_FIELD_VALUE,
-                         
+
                          '__redirectrql': 'Any X WHERE X eid %s'%peid,
                          }
         path, params = self.expect_redirect_publish()
@@ -162,14 +162,14 @@
         self.assertEquals(e.surname, 'Di Masci')
         email = e.use_email[0]
         self.assertEquals(email.address, 'dima@logilab.fr')
-        
+
         emaileid = email.eid
         self.req.form = {'eid': [`peid`, `emaileid`],
-                         '__type:%s'%peid: 'EUser',
+                         '__type:%s'%peid: 'CWUser',
                          'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: 'Di Masci',
                          '__type:%s'%emaileid: 'EmailAddress',
                          'address:%s'%emaileid: u'adim@logilab.fr', 'edits-address:%s'%emaileid: 'dima@logilab.fr',
-                         'use_email:%s'%peid: `emaileid`, 'edits-use_email:%s'%peid: `emaileid`, 
+                         'use_email:%s'%peid: `emaileid`, 'edits-use_email:%s'%peid: `emaileid`,
                          '__redirectrql': 'Any X WHERE X eid %s'%peid,
                          }
         path, params = self.expect_redirect_publish()
@@ -180,21 +180,21 @@
         email = e.use_email[0]
         self.assertEquals(email.address, 'adim@logilab.fr')
 
-        
+
     def test_password_confirm(self):
         """test creation of two linked entities
-        """        
+        """
         user = self.user()
         self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'EUser',
-                         'login:X': u'toto', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'edits-upassword:X': u'', 
+                         'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': u'toto', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
         self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'EUser',
-                         'login:X': u'toto', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'', 
+                         'eid': 'X', '__type:X': 'CWUser',
+                         'login:X': u'toto', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'',
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
@@ -220,14 +220,14 @@
                          'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
                          }
         self.expect_redirect_publish()
-        # should be redirected on the created 
+        # should be redirected on the created
         #eid = params['rql'].split()[-1]
         e = self.execute('Salesterm X').get_entity(0, 0)
         self.assertEquals(e.amount, 10)
 
     def test_req_pending_insert(self):
         """make sure req's pending insertions are taken into account"""
-        tmpgroup = self.add_entity('EGroup', name=u"test")
+        tmpgroup = self.add_entity('CWGroup', name=u"test")
         user = self.user()
         self.req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
         path, params = self.expect_redirect_publish()
@@ -240,7 +240,7 @@
     def test_req_pending_delete(self):
         """make sure req's pending deletions are taken into account"""
         user = self.user()
-        groupeid = self.execute('INSERT EGroup G: G name "test", U in_group G WHERE U eid %(x)s',
+        groupeid = self.execute('INSERT CWGroup G: G name "test", U in_group G WHERE U eid %(x)s',
                                 {'x': user.eid})[0][0]
         usergroups = [gname for gname, in
                       self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
@@ -258,13 +258,13 @@
         def custom_login_edit(self, formparams, value, relations):
             formparams['login'] = value.upper()
             relations.append('X login %(login)s')
-        EUser.custom_login_edit = custom_login_edit
+        CWUser.custom_login_edit = custom_login_edit
         try:
             user = self.user()
             eid = repr(user.eid)
             self.req.form = {
                 'eid': eid,
-                '__type:'+eid:  'EUser',
+                '__type:'+eid:  'CWUser',
                 'login:'+eid: u'foo',
                 'edits-login:'+eid:  unicode(user.login),
                 }
@@ -272,8 +272,8 @@
             rset = self.execute('Any L WHERE X eid %(x)s, X login L', {'x': user.eid}, 'x')
             self.assertEquals(rset[0][0], 'FOO')
         finally:
-            del EUser.custom_login_edit
-        
+            del CWUser.custom_login_edit
+
     def test_redirect_apply_button(self):
         redirectrql = rql_for_eid(4012) # whatever
         self.req.form = {
@@ -329,7 +329,7 @@
         self.req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress',
                          '__action_delete': ''}
         path, params = self.expect_redirect_publish()
-        self.assertEquals(path, 'euser/admin')
+        self.assertEquals(path, 'cwuser/admin')
         self.assertEquals(params, {u'__message': u'entity deleted'})
         eid1 = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid
         eid2 = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid
@@ -342,74 +342,74 @@
         self.assertEquals(params, {u'__message': u'entities deleted'})
 
     def test_nonregr_egroup_etype_editing(self):
-        """non-regression test checking that a manager user can edit a EEType entity (EGroup)
+        """non-regression test checking that a manager user can edit a CWEType entity (CWGroup)
         """
-        groupeids = [eid for eid, in self.execute('EGroup G WHERE G name "managers"')]
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name "managers"')]
         groups = [str(eid) for eid in groupeids]
-        eeetypeeid = self.execute('EEType X WHERE X name "EGroup"')[0][0]
-        basegroups = [str(eid) for eid, in self.execute('EGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
+        eeetypeeid = self.execute('CWEType X WHERE X name "CWGroup"')[0][0]
+        basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
         self.req.form = {
                 'eid':      `eeetypeeid`,
-                '__type:'+`eeetypeeid`:   'EEType',
-                'name:'+`eeetypeeid`:     u'EGroup',
+                '__type:'+`eeetypeeid`:   'CWEType',
+                'name:'+`eeetypeeid`:     u'CWGroup',
                 'final:'+`eeetypeeid`:    False,
                 'meta:'+`eeetypeeid`:     True,
-                'description:'+`eeetypeeid`:     u'users group', 
+                'description:'+`eeetypeeid`:     u'users group',
                 'read_permission:'+`eeetypeeid`:  groups,
                 #
-                'edits-name:'+`eeetypeeid`:     u'EGroup',
+                'edits-name:'+`eeetypeeid`:     u'CWGroup',
                 'edits-final:'+`eeetypeeid`:    False,
                 'edits-meta:'+`eeetypeeid`:     True,
-                'edits-description:'+`eeetypeeid`:     u'users group', 
+                'edits-description:'+`eeetypeeid`:     u'users group',
                 'edits-read_permission:'+`eeetypeeid`:  basegroups,
                 }
         try:
             path, params = self.expect_redirect_publish()
             e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
-            self.assertEquals(e.name, 'EGroup')
+            self.assertEquals(e.name, 'CWGroup')
             self.assertEquals([g.eid for g in e.read_permission], groupeids)
         finally:
             # restore
-            self.execute('SET X read_permission Y WHERE X name "EGroup", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
+            self.execute('SET X read_permission Y WHERE X name "CWGroup", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
             self.commit()
-            
+
     def test_nonregr_eetype_etype_editing(self):
-        """non-regression test checking that a manager user can edit a EEType entity (EEType)
+        """non-regression test checking that a manager user can edit a CWEType entity (CWEType)
         """
-        groupeids = sorted(eid for eid, in self.execute('EGroup G WHERE G name in ("managers", "users")'))
+        groupeids = sorted(eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")'))
         groups = [str(eid) for eid in groupeids]
-        eeetypeeid = self.execute('EEType X WHERE X name "EEType"')[0][0]
-        basegroups = [str(eid) for eid, in self.execute('EGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
+        eeetypeeid = self.execute('CWEType X WHERE X name "CWEType"')[0][0]
+        basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
         self.req.form = {
                 'eid':      `eeetypeeid`,
-                '__type:'+`eeetypeeid`:  'EEType',
-                'name:'+`eeetypeeid`:     u'EEType',
+                '__type:'+`eeetypeeid`:  'CWEType',
+                'name:'+`eeetypeeid`:     u'CWEType',
                 'final:'+`eeetypeeid`:    False,
                 'meta:'+`eeetypeeid`:     True,
-                'description:'+`eeetypeeid`:     u'users group', 
+                'description:'+`eeetypeeid`:     u'users group',
                 'read_permission:'+`eeetypeeid`:  groups,
 
-                'edits-name:'+`eeetypeeid`:     u'EEType',
+                'edits-name:'+`eeetypeeid`:     u'CWEType',
                 'edits-final:'+`eeetypeeid`:    False,
                 'edits-meta:'+`eeetypeeid`:     True,
-                'edits-description:'+`eeetypeeid`:     u'users group', 
+                'edits-description:'+`eeetypeeid`:     u'users group',
                 'edits-read_permission:'+`eeetypeeid`:  basegroups,
                 }
         try:
             path, params = self.expect_redirect_publish()
             e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
-            self.assertEquals(e.name, 'EEType')
+            self.assertEquals(e.name, 'CWEType')
             self.assertEquals(sorted(g.eid for g in e.read_permission), groupeids)
         finally:
             # restore
-            self.execute('SET X read_permission Y WHERE X name "EEType", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
+            self.execute('SET X read_permission Y WHERE X name "CWEType", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
             self.commit()
-        
+
     def test_nonregr_strange_text_input(self):
         """non-regression test checking text input containing "13:03:43"
 
         this seems to be postgres (tsearch?) specific
-        """        
+        """
         self.req.form = {
                          'eid': 'A', '__type:A': 'BlogEntry',
                          '__maineid' : 'A',
@@ -424,32 +424,32 @@
 
 
     def test_nonregr_multiple_empty_email_addr(self):
-        gueid = self.execute('EGroup G WHERE G name "users"')[0][0]
+        gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
         self.req.form = {'eid': ['X', 'Y'],
-                         
-                         '__type:X': 'EUser',
-                         'login:X': u'adim', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
-                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE, 
-                         
+
+                         '__type:X': 'CWUser',
+                         'login:X': u'adim', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
+                         'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+
                          '__type:Y': 'EmailAddress',
                          'address:Y': u'', 'edits-address:Y': '',
                          'alias:Y': u'', 'edits-alias:Y': '',
-                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE, 
+                         'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
                          }
         self.assertRaises(ValidationError, self.publish, self.req)
 
     def test_nonregr_copy(self):
         user = self.user()
         self.req.form = {'__cloned_eid:X': user.eid,
-                         'eid': 'X', '__type:X': 'EUser',
+                         'eid': 'X', '__type:X': 'CWUser',
                          '__maineid' : 'X',
-                         'login:X': u'toto', 'edits-login:X': u'', 
-                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'', 
+                         'login:X': u'toto', 'edits-login:X': u'',
+                         'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
                          }
         path, params = self.expect_redirect_publish()
-        self.assertEquals(path, 'euser/toto')
-        e = self.execute('Any X WHERE X is EUser, X login "toto"').get_entity(0, 0)
+        self.assertEquals(path, 'cwuser/toto')
+        e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
         self.assertEquals(e.login, 'toto')
         self.assertEquals(e.in_group[0].name, 'managers')
 
@@ -464,8 +464,8 @@
             self.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',
                          {'p' : p.eid, 'e' : e.eid})
             self.req.form = {'__cloned_eid:X': p.eid,
-                             'eid': 'X', '__type:X': 'EUser',
-                             'login': u'dodo', 'edits-login': u'dodo', 
+                             'eid': 'X', '__type:X': 'CWUser',
+                             'login': u'dodo', 'edits-login': u'dodo',
                              'surname:X': u'Boom', 'edits-surname:X': u'',
                              '__errorurl' : "whatever but required",
                              }
@@ -479,7 +479,7 @@
                 self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
                 self.req.form['vid'] = 'copy'
                 self.env.app.publish('view', self.req)
-            rset = self.execute('EUser P WHERE P surname "Boom"')
+            rset = self.execute('CWUser P WHERE P surname "Boom"')
             self.assertEquals(len(rset), 0)
         finally:
             p.__class__.skip_copy_for = old_skips
@@ -510,7 +510,7 @@
         self.login('anon')
         req = self.request()
         self.assertRaises(Unauthorized, self.env.app.select_controller, 'sendmail', req)
-   
+
 
 
 class JSONControllerTC(EnvBasedTC):
@@ -527,42 +527,45 @@
 
     ## tests ##################################################################
     def test_simple_exec(self):
-        ctrl = self.ctrl(self.request(rql='EUser P WHERE P login "John"',
-                                      pageid='123'))
+        req = self.request(rql='CWUser P WHERE P login "John"',
+                           pageid='123', fname='view')
+        ctrl = self.ctrl(req)
+        rset = self.john.as_rset()
+        rset.req = req
         self.assertTextEquals(ctrl.publish(),
-                              xmlize(self.john.view('primary')))
+                              xhtml_wrap(ctrl.view('primary', rset)))
 
-    def test_json_exec(self):
-        rql = 'Any T,N WHERE T is Tag, T name N'
-        ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
-        self.assertEquals(ctrl.publish(),
-                          simplejson.dumps(self.execute(rql).rows))
+#     def test_json_exec(self):
+#         rql = 'Any T,N WHERE T is Tag, T name N'
+#         ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
+#         self.assertEquals(ctrl.publish(),
+#                           simplejson.dumps(self.execute(rql).rows))
 
     def test_remote_add_existing_tag(self):
         self.remote_call('tag_entity', self.john.eid, ['python'])
         self.assertUnorderedIterableEquals([tname for tname, in self.execute('Any N WHERE T is Tag, T name N')],
                              ['python', 'cubicweb'])
-        self.assertEquals(self.execute('Any N WHERE T tags P, P is EUser, T name N').rows,
+        self.assertEquals(self.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
                           [['python']])
-    
+
     def test_remote_add_new_tag(self):
         self.remote_call('tag_entity', self.john.eid, ['javascript'])
         self.assertUnorderedIterableEquals([tname for tname, in self.execute('Any N WHERE T is Tag, T name N')],
                              ['python', 'cubicweb', 'javascript'])
-        self.assertEquals(self.execute('Any N WHERE T tags P, P is EUser, T name N').rows,
+        self.assertEquals(self.execute('Any N WHERE T tags P, P is CWUser, T name N').rows,
                           [['javascript']])
 
     def test_edit_field(self):
-        nbusers = len(self.execute('EUser P'))
+        nbusers = len(self.execute('CWUser P'))
         eid = self.john.eid
         self.remote_call('edit_field', 'apply',
                          ('eid', 'firstname:%s' % eid, '__maineid', '__type:%s'% eid, 'edits-firstname:%s' % eid ),
-                         (str(eid), u'Remi', str(eid), 'EUser', self.john.firstname),
+                         (str(eid), u'Remi', str(eid), 'CWUser', self.john.firstname),
                          'firstname',
                          eid)
         self.commit()
-        rset = self.execute('EUser P')
-        # make sure we did not insert a new euser here
+        rset = self.execute('CWUser P')
+        # make sure we did not insert a new cwuser here
         self.assertEquals(len(rset), nbusers)
         john = self.execute('Any X WHERE X eid %(x)s', {'x': self.john.eid}, 'x').get_entity(0, 0)
         self.assertEquals(john.eid, self.john.eid)
@@ -570,12 +573,12 @@
 
 
     def test_pending_insertion(self):
-        res, req = self.remote_call('add_pending_insert', ['12', 'tags', '13'])
+        res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '13']])
         deletes = req.get_pending_deletes()
         self.assertEquals(deletes, [])
         inserts = req.get_pending_inserts()
         self.assertEquals(inserts, ['12:tags:13'])
-        res, req = self.remote_call('add_pending_insert', ['12', 'tags', '14'])
+        res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
         deletes = req.get_pending_deletes()
         self.assertEquals(deletes, [])
         inserts = req.get_pending_inserts()
@@ -609,7 +612,7 @@
 
     def test_remove_pending_operations(self):
         self.remote_call('add_pending_delete', ['12', 'tags', '13'])
-        _, req = self.remote_call('add_pending_insert', ['12', 'tags', '14'])
+        _, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
         inserts = req.get_pending_inserts()
         self.assertEquals(inserts, ['12:tags:14'])
         deletes = req.get_pending_deletes()
@@ -617,7 +620,7 @@
         req.remove_pending_operations()
         self.assertEquals(req.get_pending_deletes(), [])
         self.assertEquals(req.get_pending_inserts(), [])
-        
+
 
     def test_add_inserts(self):
         res, req = self.remote_call('add_pending_inserts',
@@ -625,7 +628,7 @@
         inserts = req.get_pending_inserts()
         self.assertEquals(inserts, ['12:tags:13', '12:tags:14'])
         req.remove_pending_operations()
-        
+
 
     # silly tests
     def test_external_resource(self):
@@ -636,11 +639,11 @@
                           simplejson.dumps(['bimboom']))
 
     def test_format_date(self):
-        self.assertEquals(self.remote_call('format_date', '"2007-01-01 12:00:00"')[0],
+        self.assertEquals(self.remote_call('format_date', '2007-01-01 12:00:00')[0],
                           simplejson.dumps('2007/01/01'))
 
-        
+
 
-        
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_baseforms.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_baseforms.py	Thu May 14 12:50:34 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):
@@ -32,7 +31,7 @@
 
     def setup_database(self):
         self.create_user('joe')
-        
+
     def _build_creation_form(self, etype):
         req = self.request()
         req.next_tabindex()
@@ -45,12 +44,12 @@
         view.w = buffer.write
         view.edit_form(entity, {})
         return buffer.getvalue()
-    
+
     def _test_view_for(self, etype, expected):
         self.assertTextEquals(expected, cleanup_text(self._build_creation_form(etype)))
-        
+
     def test_base(self):
-        self._test_view_for('EGroup', '''\
+        self._test_view_for('CWGroup', '''\
 <form id="entityForm" class="entityForm" cubicweb:target="eformframe"
 method="post" onsubmit="return freezeFormButtons('entityForm')" enctype="application/x-www-form-urlencoded" action="http://testing.fr/cubicweb/validateform">
 <div class="formTitle"><span>egroup (creation)</span></div>
@@ -58,7 +57,7 @@
 <div class="iformTitle"><span>main informations</span></div>
 <div class="formBody"><fieldset>
 <input type="hidden" name="eid" value="A" />
-<input type="hidden" name="__type:A" value="EGroup" />
+<input type="hidden" name="__type:A" value="CWGroup" />
 <input type="hidden" name="__maineid" value="A" />
 <input id="errorurl" type="hidden" name="__errorurl" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop" />
 <input type="hidden" name="__form_id" value="edition" />
@@ -89,14 +88,14 @@
 
     def test_with_inline_view(self):
         activated = self.execute('Any X WHERE X is State, X name "activated"')[0][0]
-        self._test_view_for('EUser', '''<form id="entityForm" class="entityForm" cubicweb:target="eformframe"
+        self._test_view_for('CWUser', '''<form id="entityForm" class="entityForm" cubicweb:target="eformframe"
 method="post" onsubmit="return freezeFormButtons('entityForm')" enctype="application/x-www-form-urlencoded" action="http://testing.fr/cubicweb/validateform">
 <div class="formTitle"><span>euser (creation)</span></div>
 <div id="progress">validating...</div>
 <div class="iformTitle"><span>main informations</span></div>
 <div class="formBody"><fieldset>
 <input type="hidden" name="eid" value="A" />
-<input type="hidden" name="__type:A" value="EUser" />
+<input type="hidden" name="__type:A" value="CWUser" />
 <input type="hidden" name="__maineid" value="A" />
 <input id="errorurl" type="hidden" name="__errorurl" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop" />
 <input type="hidden" name="__form_id" value="edition" />
@@ -160,7 +159,7 @@
 </table>
 <div id="inlineuse_emailslot">
 <div class="inlinedform" id="addNewEmailAddressuse_emailsubject:A" cubicweb:limit="true">
-<a class="addEntity" id="adduse_email:Alink" href="javascript: addInlineCreationForm('A', 'EUser', 'EmailAddress', 'use_email', 'subject')" >+ add a EmailAddress.</a>
+<a class="addEntity" id="adduse_email:Alink" href="javascript: addInlineCreationForm('A', 'CWUser', 'EmailAddress', 'use_email', 'subject')" >+ add a EmailAddress.</a>
 </div>
 <div class="trame_grise">&nbsp;</div>
 </div>
@@ -180,7 +179,7 @@
 
     def test_redirection_after_creation(self):
         req = self.request()
-        req.form['etype'] = 'EUser'
+        req.form['etype'] = 'CWUser'
         view = self.vreg.select_view('creation', req, None)
         self.assertEquals(view.redirect_url(), 'http://testing.fr/cubicweb/euser')
         req.form['__redirectrql'] = 'Any X WHERE X eid 3012'
@@ -199,7 +198,7 @@
         entity = vreg.etype_class('Salesterm')(req, None, None)
         view = vreg.select_view('creation', req, None)
         self.failUnless(view.need_multipart(entity))
-        
+
 
 
     def test_nonregr_check_add_permission_on_relation(self):
@@ -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)
@@ -214,7 +214,7 @@
         self.login('joe')
         html = self._build_creation_form('BlogEntry')
         self.failIf('name="edits-checked_by:A"' in html)
-        
+
 from cubicweb.devtools.testlib import WebTest
 from cubicweb.devtools.htmlparser import DTDValidator
 
@@ -229,7 +229,7 @@
 
 
     def test_cloned_elements_in_copy_form(self):
-        rset = self.execute('EUser P WHERE P login "Doe"')
+        rset = self.execute('CWUser P WHERE P login "Doe"')
         output = self.view('copy', rset)
         clones = [attrs for _, attrs in output.input_tags
                   if attrs.get('name', '').startswith('__cloned_eid')]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_views_basetemplates.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,16 @@
+from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.htmlparser import DTDValidator
+
+
+class LogFormTemplateTC(WebTest):
+
+    def _login_labels(self):
+        valid = self.content_type_validators.get('text/html', DTDValidator)()
+        page = valid.parse_string(self.vreg.main_template(self.request(), 'login'))
+        return page.find_tag('label')
+
+    def test_label(self):
+        self.set_option('allow-email-login', 'yes')
+        self.assertEquals(self._login_labels(), ['login or email', 'password'])
+        self.set_option('allow-email-login', 'no')
+        self.assertEquals(self._login_labels(), ['login', 'password'])
--- a/web/test/unittest_views_baseviews.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_baseviews.py	Thu May 14 12:50:34 2009 +0200
@@ -6,17 +6,17 @@
 from cubicweb.devtools.apptest import EnvBasedTC
 
 from cubicweb.web.htmlwidgets import TableWidget
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.web.views import vid_from_rset
 
 def loadjson(value):
     return loads(html_unescape(value))
 
 class VidFromRsetTC(EnvBasedTC):
-    
+
     def test_no_rset(self):
         req = self.request()
         self.assertEquals(vid_from_rset(req, None, self.schema), 'index')
-    
+
     def test_no_entity(self):
         req = self.request()
         rset = self.execute('Any X WHERE X login "blabla"')
@@ -36,38 +36,38 @@
         req = self.request()
         rset = self.execute('Any X WHERE X eid 1')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'primary')
-        
+
     def test_more_than_one_entity(self):
         req = self.request()
-        rset = self.execute('Any X WHERE X is EUser')
+        rset = self.execute('Any X WHERE X is CWUser')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'list')
         rset = self.execute('Any X, L WHERE X login L')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'list')
-    
+
     def test_more_than_one_entity_by_row(self):
         req = self.request()
         rset = self.execute('Any X, G WHERE X in_group G')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
-    
+
     def test_more_than_one_entity_by_row_2(self):
         req = self.request()
         rset = self.execute('Any X, GN WHERE X in_group G, G name GN')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
-    
+
     def test_aggregat(self):
         req = self.request()
         rset = self.execute('Any X, COUNT(T) GROUPBY X WHERE X is T')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
-        rset = self.execute('Any MAX(X) WHERE X is EUser')
+        rset = self.execute('Any MAX(X) WHERE X is CWUser')
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
 
     def test_subquery(self):
         rset = self.execute(
 'DISTINCT Any X,N ORDERBY N '
 'WITH X,N BEING ('
-'     (DISTINCT Any P,N WHERE P is EUser, P login N)'
+'     (DISTINCT Any P,N WHERE P is CWUser, P login N)'
 '       UNION'
-'     (DISTINCT Any W,N WHERE W is EGroup, W name N))')
+'     (DISTINCT Any W,N WHERE W is CWGroup, W name N))')
         req = self.request()
         self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
 
@@ -81,7 +81,7 @@
         req = self.request()
         view = self.vreg.select_view('table', req, rset)
         return e, rset, view
-      
+
     def test_headers(self):
         self.skip('implement me')
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_views_editforms.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,153 @@
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import WebTest
+from cubicweb.web.views.autoform import AutomaticEntityForm as AEF
+from cubicweb.web.formwidgets import AutoCompletionWidget
+def rbc(entity, category):
+    return [(rschema.type, x) for rschema, tschemas, x in AEF.erelations_by_category(entity, category)]
+
+class AutomaticEntityFormTC(EnvBasedTC):
+
+    def test_custom_widget(self):
+        AEF.rfields_kwargs.tag_subject_of(('CWUser', 'login', '*'),
+                                          {'widget':AutoCompletionWidget})
+        form = self.vreg.select_object('forms', 'edition', self.request(), None,
+                                       entity=self.user())
+        field = form.field_by_name('login')
+        self.assertIsInstance(field.widget, AutoCompletionWidget)
+        AEF.rfields_kwargs.del_rtag('CWUser', 'login', '*', 'subject')
+
+
+    def test_euser_relations_by_category(self):
+        #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
+        #    if rtype == 'tags':
+        #        print rtype, role, stype, otype, ':', tag
+        e = self.etype_instance('CWUser')
+        # see custom configuration in views.euser
+        self.assertEquals(rbc(e, 'primary'),
+                          [('login', 'subject'),
+                           ('upassword', 'subject'),
+                           ('in_group', 'subject'),
+                           ('in_state', 'subject'),
+                           ('eid', 'subject'),
+                           ])
+        self.assertListEquals(rbc(e, 'secondary'),
+                              [('firstname', 'subject'),
+                               ('surname', 'subject')
+                               ])
+        self.assertListEquals(rbc(e, 'metadata'),
+                              [('last_login_time', 'subject'),
+                               ('created_by', 'subject'),
+                               ('creation_date', 'subject'),
+                               ('modification_date', 'subject'),
+                               ('owned_by', 'subject'),
+                               ('bookmarked_by', 'object'),
+                               ])
+        self.assertListEquals(rbc(e, 'generic'),
+                              [('primary_email', 'subject'),
+                               ('connait', 'subject'),
+                               ('checked_by', 'object'),
+                               ])
+        # owned_by is defined both as subject and object relations on CWUser
+        self.assertListEquals(rbc(e, 'generated'),
+                              [('use_email', 'subject'),
+                               ('has_text', 'subject'),
+                               ('identity', 'subject'),
+                               ('is', 'subject'),
+                               ('is_instance_of', 'subject'),
+                               ('tags', 'object'),
+                               ('for_user', 'object'),
+                               ('created_by', 'object'),
+                               ('wf_info_for', 'object'),
+                               ('owned_by', 'object'),
+                               ('identity', 'object'),
+                               ])
+
+    def test_inlined_view(self):
+        self.failUnless(AEF.rinlined.etype_get('CWUser', 'use_email', 'subject'))
+        self.failIf(AEF.rinlined.etype_get('CWUser', 'primary_email', 'subject'))
+
+    def test_personne_relations_by_category(self):
+        e = self.etype_instance('Personne')
+        self.assertListEquals(rbc(e, 'primary'),
+                              [('nom', 'subject'),
+                               ('eid', 'subject')
+                               ])
+        self.assertListEquals(rbc(e, 'secondary'),
+                              [('prenom', 'subject'),
+                               ('sexe', 'subject'),
+                               ('promo', 'subject'),
+                               ('titre', 'subject'),
+                               ('ass', 'subject'),
+                               ('web', 'subject'),
+                               ('tel', 'subject'),
+                               ('fax', 'subject'),
+                               ('datenaiss', 'subject'),
+                               ('test', 'subject'),
+                               ('description', 'subject'),
+                               ('salary', 'subject')
+                               ])
+        self.assertListEquals(rbc(e, 'metadata'),
+                              [('created_by', 'subject'),
+                               ('creation_date', 'subject'),
+                               ('modification_date', 'subject'),
+                               ('owned_by', 'subject'),
+                               ])
+        self.assertListEquals(rbc(e, 'generic'),
+                              [('travaille', 'subject'),
+                               ('connait', 'object')
+                               ])
+        self.assertListEquals(rbc(e, 'generated'),
+                              [('has_text', 'subject'),
+                               ('identity', 'subject'),
+                               ('is', 'subject'),
+                               ('is_instance_of', 'subject'),
+                               ('identity', 'object'),
+                               ])
+
+    def test_edition_form(self):
+        rset = self.execute('CWUser 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(), None,
+                                entity=rset.get_entity(0, 0))
+        self.failIf(any(f for f in form.fields if f is None))
+
+
+class FormViewsTC(WebTest):
+    def test_delete_conf_formview(self):
+        rset = self.execute('CWGroup X')
+        self.view('deleteconf', rset, template=None).source
+
+    def test_automatic_edition_formview(self):
+        rset = self.execute('CWUser X')
+        self.view('edition', rset, row=0, template=None).source
+
+    def test_automatic_edition_formview(self):
+        rset = self.execute('CWUser X')
+        self.view('copy', rset, row=0, template=None).source
+
+    def test_automatic_creation_formview(self):
+        self.view('creation', None, etype='CWUser', template=None).source
+
+    def test_automatic_muledit_formview(self):
+        rset = self.execute('CWUser X')
+        self.view('muledit', rset, template=None).source
+
+    def test_automatic_reledit_formview(self):
+        rset = self.execute('CWUser X')
+        self.view('reledit', rset, row=0, rtype='login', template=None).source
+
+    def test_automatic_inline_edit_formview(self):
+        geid = self.execute('CWGroup X LIMIT 1')[0][0]
+        rset = self.execute('CWUser 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('CWGroup X LIMIT 1')[0][0]
+        self.view('inline-creation', None, etype='CWUser', rtype='in_group', peid=geid, template=None).source
+
+
+if __name__ == '__main__':
+    unittest_main()
--- a/web/test/unittest_views_navigation.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_navigation.py	Thu May 14 12:50:34 2009 +0200
@@ -9,7 +9,7 @@
 BreadCrumbEntityVComponent.visible = True
 
 class NavigationTC(EnvBasedTC):
-    
+
     def test_navigation_selection(self):
         rset = self.execute('Any X,N WHERE X name N')
         req = self.request()
@@ -39,29 +39,29 @@
         req.set_search_state('W:X:Y:Z')
         navcomp = self.vreg.select_component('navigation', req, rset)
         self.assertIsInstance(navcomp, SortedNavigation)
-        
-        
+
+
     def test_sorted_navigation(self):
         rset = self.execute('Any X,N ORDERBY N WHERE X name N')
         req = self.request()
         req.set_search_state('W:X:Y:Z')
         navcomp = self.vreg.select_component('navigation', rset.req, rset)
-        html = navcomp.dispatch()
+        html = navcomp.render()
         rset = self.execute('Any RDEF ORDERBY RT WHERE RDEF relation_type RT')
         navcomp = self.vreg.select_component('navigation', req, rset)
-        html = navcomp.dispatch()
+        html = navcomp.render()
         rset = self.execute('Any RDEF ORDERBY RDEF WHERE RDEF relation_type RT')
         navcomp = self.vreg.select_component('navigation', req, rset)
-        html = navcomp.dispatch()
-        rset = self.execute('EFRDef RDEF ORDERBY RDEF')
+        html = navcomp.render()
+        rset = self.execute('CWAttribute RDEF ORDERBY RDEF')
         navcomp = self.vreg.select_component('navigation', req, rset)
-        html = navcomp.dispatch()
+        html = navcomp.render()
         rset = self.execute('Any RDEF ORDERBY N WHERE RDEF relation_type RT, RT name N')
         navcomp = self.vreg.select_component('navigation', req, rset)
-        html = navcomp.dispatch()
+        html = navcomp.render()
         rset = self.execute('Any N, COUNT(RDEF) GROUPBY N ORDERBY N WHERE RDEF relation_type RT, RT name N')
         navcomp = self.vreg.select_component('navigation', rset.req, rset)
-        html = navcomp.dispatch()
+        html = navcomp.render()
 
 
 
@@ -69,7 +69,7 @@
 
     def test_component_context(self):
         view = mock_object(is_primary=lambda x: True)
-        rset = self.execute('EUser X LIMIT 1')
+        rset = self.execute('CWUser X LIMIT 1')
         req = self.request()
         objs = self.vreg.possible_vobjects('contentnavigation', req, rset,
                                            view=view, context='navtop')
@@ -81,21 +81,21 @@
         # breadcrumbs should _NOT_ be in footers by default
         clsids = set(obj.id for obj in objs)
         self.failIf('breadcrumbs' in clsids)
-        self.execute('INSERT EProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+        self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
                      'P value "navbottom"')
         # breadcrumbs should now be in footers
         req.cnx.commit()
         objs = self.vreg.possible_vobjects('contentnavigation', req, rset,
                                           view=view, context='navbottom')
-        
+
         clsids = [obj.id for obj in objs]
         self.failUnless('breadcrumbs' in clsids)
         objs = self.vreg.possible_vobjects('contentnavigation', req, rset,
                                           view=view, context='navtop')
-        
+
         clsids = [obj.id for obj in objs]
         self.failIf('breadcrumbs' in clsids)
-        
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_searchrestriction.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Thu May 14 12:50:34 2009 +0200
@@ -15,7 +15,7 @@
         mainvar = prepare_facets_rqlst(rqlst)[0]
         insert_attr_select_relation(rqlst.children[0], mainvar, rel, role, attr)
         return rqlst.as_string()
-        
+
     @property
     def select(self):
         return self.parse('Any B,(NOW - CD),S,V,U,GROUP_CONCAT(TN),VN,P,CD,BMD '
@@ -24,30 +24,30 @@
                           'B modification_date BMD, T? tags B, T name TN, '
                           'V? bookmarked_by B, V title VN, B created_by U?, '
                           'B in_group P, P name "managers"')
-    
+
     def test_1(self):
         self.assertEquals(self._generate(self.select, 'in_state', 'subject', 'name'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "B in_state A, A name C, B is EUser")
-        
+                          "B in_state A, A name C, B is CWUser")
+
     def test_2(self):
         self.assertEquals(self._generate(self.select, 'tags', 'object', 'name'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "A tags B, A name C, B is EUser")
-        
+                          "A tags B, A name C, B is CWUser")
+
     def test_3(self):
         self.assertEquals(self._generate(self.select, 'created_by', 'subject', 'login'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "B created_by A, A login C, B is EUser")
-        
+                          "B created_by A, A login C, B is CWUser")
+
     def test_4(self):
-        self.assertEquals(self._generate(self.parse('Any X WHERE X is EUser'), 'created_by', 'subject', 'login'),
-                          "DISTINCT Any A,B ORDERBY B WHERE X is EUser, X created_by A, A login B")
-        
+        self.assertEquals(self._generate(self.parse('Any X WHERE X is CWUser'), 'created_by', 'subject', 'login'),
+                          "DISTINCT Any A,B ORDERBY B WHERE X is CWUser, X created_by A, A login B")
+
     def test_5(self):
-        self.assertEquals(self._generate(self.parse('Any X,L WHERE X is EUser, X login L'), 'created_by', 'subject', 'login'),
-                          "DISTINCT Any A,B ORDERBY B WHERE X is EUser, X created_by A, A login B")
-        
+        self.assertEquals(self._generate(self.parse('Any X,L WHERE X is CWUser, X login L'), 'created_by', 'subject', 'login'),
+                          "DISTINCT Any A,B ORDERBY B WHERE X is CWUser, X created_by A, A login B")
+
     def test_nonregr1(self):
         select = self.parse('Any T,V WHERE T bookmarked_by V?, '
                             'V in_state VS, VS name "published", T created_by U')
@@ -57,14 +57,14 @@
 
     def test_nonregr2(self):
         #'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
-        select = self.parse('DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is EUser,'
+        select = self.parse('DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is CWUser,'
                             'NOT V in_state VS, VS name "published", V login L')
         rschema = self.schema['connait']
         for s, o in rschema.iter_rdefs():
             rschema.set_rproperty(s, o, 'cardinality', '++')
         try:
             self.assertEquals(self._generate(select, 'in_state', 'subject', 'name'),
-                              "DISTINCT Any A,B ORDERBY B WHERE V is EUser, "
+                              "DISTINCT Any A,B ORDERBY B WHERE V is CWUser, "
                               "NOT V in_state VS, VS name 'published', "
                               "V in_state A, A name B")
         finally:
@@ -73,11 +73,11 @@
 
     def test_nonregr3(self):
         #'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
-        select = self.parse('DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is EUser, Y is Bookmark, X in_group A')
+        select = self.parse('DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is CWUser, Y is Bookmark, X in_group A')
         self.assertEquals(self._generate(select, 'in_group', 'subject', 'name'),
-                          "DISTINCT Any B,C ORDERBY C WHERE X is EUser, X in_group B, B name C")
+                          "DISTINCT Any B,C ORDERBY C WHERE X is CWUser, X in_group B, B name C")
 
-        
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/web/test/unittest_viewselector.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_viewselector.py	Thu May 14 12:50:34 2009 +0200
@@ -1,32 +1,29 @@
 # -*- 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 cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
-from cubicweb.common.selectors import match_user_group
+from logilab.common.testlib import unittest_main
 
-from cubicweb.web._exceptions import NoSelectableObject
+from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
+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
+from cubicweb.web.views import (primary, baseviews, tableview, editforms,
+                                calendar, management, embedding, actions,
+                                startup, cwuser, schema, xbel, vcard, owl,
+                                treeview, idownloadable, wdoc, debug,
+                                cwproperties, workflow, xmlrss, csvexport)
 
 USERACTIONS = [('myprefs', actions.UserPreferencesAction),
                ('myinfos', actions.UserInfoAction),
                ('logout', actions.LogoutAction)]
 SITEACTIONS = [('siteconfig', actions.SiteConfigurationAction),
                ('manage', actions.ManageAction),
-               ('schema', actions.ViewSchemaAction)]        
+               ('schema', schema.ViewSchemaAction)]
 
 
 class ViewSelectorTC(EnvBasedTC):
@@ -34,7 +31,6 @@
     def setup_database(self):
         self.add_entity('BlogEntry', title=u"une news !", content=u"cubicweb c'est beau")
         self.add_entity('Bookmark', title=u"un signet !", path=u"view?vid=index")
-        self.add_entity('Card', title=u'mandatory', content=u"DoC !")
         self.add_entity('EmailAddress', address=u"devel@logilab.fr", alias=u'devel')
         self.add_entity('Tag', name=u'x')
 
@@ -63,132 +59,131 @@
             print 'no more', [v for v in expected if not v in content.keys()]
             print 'missing', [v for v in content.keys() if not v in expected]
             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', cwproperties.EPropertiesForm),
                               ('index', startup.IndexView),
                               ('info', management.ProcessInformationView),
                               ('manage', startup.ManageView),
                               ('owl', owl.OWLView),
                               ('schema', startup.SchemaView),
-                              ('systemepropertiesform', management.SystemEpropertiesForm)])
-        # no entity but etype
+                              ('systemepropertiesform', cwproperties.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
-        rset, req = self.env.get_rset_and_req('EGroup X WHERE X name "managers"')
+                             [])
+
+    def test_possible_views_one_egroup(self):
+        rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
         self.assertListEqual(self.pviews(req, rset),
-                             [('csvexport', baseviews.CSVRsetView),
-                              ('ecsvexport', baseviews.CSVEntityView),
+                             [('csvexport', csvexport.CSVRsetView),
+                              ('ecsvexport', csvexport.CSVEntityView),
                               ('editable-table', tableview.EditableTableView),
                               ('filetree', treeview.FileTreeView),
                               ('list', baseviews.ListView),
                               ('oneline', baseviews.OneLineView),
                               ('owlabox', owl.OWLABOXView),
-                              ('primary', baseviews.PrimaryView),
-                              ('rsetxml', baseviews.XMLRsetView),
-                              ('rss', baseviews.RssView),
+                              ('primary', primary.PrimaryView),
+                              ('rsetxml', xmlrss.XMLRsetView),
+                              ('rss', xmlrss.RSSView),
+                              ('secondary', baseviews.SecondaryView),
+                              ('security', management.SecurityManagementView),
+                              ('table', tableview.TableView),
+                              ('text', baseviews.TextView),
+                              ('treeview', treeview.TreeView),
+                              ('xbel', xbel.XbelView),
+                              ('xml', xmlrss.XMLView),
+                              ])
+
+    def test_possible_views_multiple_egroups(self):
+        rset, req = self.env.get_rset_and_req('CWGroup X')
+        self.assertListEqual(self.pviews(req, rset),
+                             [('csvexport', csvexport.CSVRsetView),
+                              ('ecsvexport', csvexport.CSVEntityView),
+                              ('editable-table', tableview.EditableTableView),
+                              ('filetree', treeview.FileTreeView),
+                              ('list', baseviews.ListView),
+                              ('oneline', baseviews.OneLineView),
+                              ('owlabox', owl.OWLABOXView),
+                              ('primary', primary.PrimaryView),
+                              ('rsetxml', xmlrss.XMLRsetView),
+                              ('rss', xmlrss.RSSView),
                               ('secondary', baseviews.SecondaryView),
                               ('security', management.SecurityManagementView),
                               ('table', tableview.TableView),
                               ('text', baseviews.TextView),
                               ('treeview', treeview.TreeView),
                               ('xbel', xbel.XbelView),
-                              ('xml', baseviews.XmlView),
+                              ('xml', xmlrss.XMLView),
                               ])
-        # list of entities of the same type
-        rset, req = self.env.get_rset_and_req('EGroup X')
+
+    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),
-                              ('ecsvexport', baseviews.CSVEntityView),
+                             [('csvexport', csvexport.CSVRsetView),
+                              ('ecsvexport', csvexport.CSVEntityView),
                               ('editable-table', tableview.EditableTableView),
                               ('filetree', treeview.FileTreeView),
                               ('list', baseviews.ListView),
                               ('oneline', baseviews.OneLineView),
                               ('owlabox', owl.OWLABOXView),
-                              ('primary', baseviews.PrimaryView),
-                              ('rsetxml', baseviews.XMLRsetView),
-                              ('rss', baseviews.RssView),
+                              ('primary', primary.PrimaryView),
+                              ('rsetxml', xmlrss.XMLRsetView),
+                              ('rss', xmlrss.RSSView),
                               ('secondary', baseviews.SecondaryView),
                               ('security', management.SecurityManagementView),
                               ('table', tableview.TableView),
                               ('text', baseviews.TextView),
                               ('treeview', treeview.TreeView),
                               ('xbel', xbel.XbelView),
-                              ('xml', baseviews.XmlView),
+                              ('xml', xmlrss.XMLView),
                               ])
-        # list of entities of different types
-        rset, req = self.env.get_rset_and_req('Any X')
+
+    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),
-                              ('ecsvexport', baseviews.CSVEntityView),
+                             [('csvexport', csvexport.CSVRsetView),
+                              ('editable-table', tableview.EditableTableView),
+                              ('rsetxml', xmlrss.XMLRsetView),
+                              ('table', tableview.TableView),
+                              ])
+
+    def test_possible_views_multiple_eusers(self):
+        rset, req = self.env.get_rset_and_req('CWUser X')
+        self.assertListEqual(self.pviews(req, rset),
+                             [('csvexport', csvexport.CSVRsetView),
+                              ('ecsvexport', csvexport.CSVEntityView),
                               ('editable-table', tableview.EditableTableView),
                               ('filetree', treeview.FileTreeView),
+                              ('foaf', cwuser.FoafView),
                               ('list', baseviews.ListView),
                               ('oneline', baseviews.OneLineView),
                               ('owlabox', owl.OWLABOXView),
-                              ('primary', baseviews.PrimaryView),
-                              ('rsetxml', baseviews.XMLRsetView),
-                              ('rss', baseviews.RssView),
+                              ('primary', primary.PrimaryView),
+                              ('rsetxml', xmlrss.XMLRsetView),
+                              ('rss', xmlrss.RSSView),
                               ('secondary', baseviews.SecondaryView),
                               ('security', management.SecurityManagementView),
                               ('table', tableview.TableView),
                               ('text', baseviews.TextView),
                               ('treeview', treeview.TreeView),
+                              ('vcard', vcard.VCardCWUserView),
                               ('xbel', xbel.XbelView),
-                              ('xml', baseviews.XmlView),
-                              ])
-        # whatever
-        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),
-                              ('editable-table', tableview.EditableTableView),
-                              ('rsetxml', baseviews.XMLRsetView),
-                              ('table', tableview.TableView),
+                              ('xml', xmlrss.XMLView),
                               ])
-        # list of euser entities
-        rset, req = self.env.get_rset_and_req('EUser X')
-        self.assertListEqual(self.pviews(req, rset),
-                             [('csvexport', baseviews.CSVRsetView),
-                              ('ecsvexport', baseviews.CSVEntityView),
-                              ('editable-table', tableview.EditableTableView),
-                              ('filetree', treeview.FileTreeView),
-                              ('foaf', euser.FoafView),
-                              ('list', baseviews.ListView),
-                              ('oneline', baseviews.OneLineView),
-                              ('owlabox', owl.OWLABOXView),
-                              ('primary', euser.EUserPrimaryView),
-                              ('rsetxml', baseviews.XMLRsetView),
-                              ('rss', baseviews.RssView),
-                              ('secondary', baseviews.SecondaryView),
-                              ('security', management.SecurityManagementView),
-                              ('table', tableview.TableView),
-                              ('text', baseviews.TextView),
-                              ('treeview', treeview.TreeView),
-                              ('vcard', vcard.VCardEUserView),
-                              ('xbel', xbel.XbelView),
-                              ('xml', baseviews.XmlView),
-                              ])
-        
+
     def test_possible_actions_none_rset(self):
         req = self.request()
         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,8 +191,9 @@
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               })
+
     def test_possible_actions_same_type_entities(self):
-        rset, req = self.env.get_rset_and_req('EGroup X')
+        rset, req = self.env.get_rset_and_req('CWGroup X')
         self.assertDictEqual(self.pactions(req, rset),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
@@ -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,53 +209,44 @@
                               '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),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS})
-        
-    def test_possible_actions_eetype_euser_entity(self):
-        rset, req = self.env.get_rset_and_req('EEType X WHERE X name "EUser"')
+
+    def test_possible_actions_eetype_cwuser_entity(self):
+        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWUser"')
         self.assertDictEqual(self.pactions(req, rset),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction),
-                                              ('workflow', schemaentities.ViewWorkflowAction),],
-                              'moreactions': [('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction)],
+                                              ('workflow', workflow.ViewWorkflowAction),],
+                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('delete', actions.DeleteAction),
+                                              ('copy', actions.CopyAction),
+                                              ],
                               })
 
-    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):
         rset = None
         req = self.request()
         # creation form
-        req.form['etype'] = 'EGroup'
+        req.form['etype'] = 'CWGroup'
         self.assertIsInstance(self.vreg.select_view('creation', req, rset),
-                                  baseforms.CreationForm)
+                              editforms.CreationFormView)
         del req.form['etype']
         # custom creation form
-        class EUserCreationForm(baseforms.CreationForm):
-            accepts = ('EUser',)
-        self.vreg.register_vobject_class(EUserCreationForm)
-        req.form['etype'] = 'EUser'
+        class CWUserCreationForm(editforms.CreationFormView):
+            __select__ = specified_etype_implements('CWUser')
+        self.vreg._loadedmods[__name__] = {}
+        self.vreg.register_vobject_class(CWUserCreationForm)
+        req.form['etype'] = 'CWUser'
         self.assertIsInstance(self.vreg.select_view('creation', req, rset),
-                              EUserCreationForm)
-            
+                              CWUserCreationForm)
+
     def test_select_view(self):
         # no entity
         rset = None
@@ -269,7 +257,7 @@
                              self.vreg.select_view, 'primary', req, rset)
         self.failUnlessRaises(NoSelectableObject,
                              self.vreg.select_view, 'table', req, rset)
-        
+
         # no entity
         rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
         self.failUnlessRaises(NoSelectableObject,
@@ -281,13 +269,13 @@
         self.failUnlessRaises(NoSelectableObject,
                              self.vreg.select_view, 'table', req, rset)
         # one entity
-        rset, req = self.env.get_rset_and_req('EGroup X WHERE X name "managers"')
+        rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
         self.assertIsInstance(self.vreg.select_view('primary', req, rset),
-                             baseviews.PrimaryView)
+                             primary.PrimaryView)
         self.assertIsInstance(self.vreg.select_view('list', req, rset),
                              baseviews.ListView)
         self.assertIsInstance(self.vreg.select_view('edition', req, rset),
-                             baseforms.EditionForm)
+                             editforms.EditionFormView)
         self.assertIsInstance(self.vreg.select_view('table', req, rset),
                              tableview.TableView)
         self.failUnlessRaises(NoSelectableObject,
@@ -295,9 +283,9 @@
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg.select_view, 'index', req, rset)
         # list of entities of the same type
-        rset, req = self.env.get_rset_and_req('EGroup X')
+        rset, req = self.env.get_rset_and_req('CWGroup X')
         self.assertIsInstance(self.vreg.select_view('primary', req, rset),
-                             baseviews.PrimaryView)
+                             primary.PrimaryView)
         self.assertIsInstance(self.vreg.select_view('list', req, rset),
                              baseviews.ListView)
         self.assertIsInstance(self.vreg.select_view('table', req, rset),
@@ -307,7 +295,7 @@
         # list of entities of different types
         rset, req = self.env.get_rset_and_req('Any X')
         self.assertIsInstance(self.vreg.select_view('primary', req, rset),
-                                  baseviews.PrimaryView)
+                                  primary.PrimaryView)
         self.assertIsInstance(self.vreg.select_view('list', req, rset),
                                   baseviews.ListView)
         self.assertIsInstance(self.vreg.select_view('table', req, rset),
@@ -331,19 +319,13 @@
         self.failUnlessRaises(NoSelectableObject,
                              self.vreg.select_view, 'edition', req, rset)
         # mixed query
-        rset, req = self.env.get_rset_and_req('Any U,G WHERE U is EUser, G is EGroup')
+        rset, req = self.env.get_rset_and_req('Any U,G WHERE U is CWUser, G is CWGroup')
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg.select_view, 'edition', req, rset)
         self.failUnlessRaises(NoSelectableObject,
                               self.vreg.select_view, 'creation', req, rset)
         self.assertIsInstance(self.vreg.select_view('table', req, rset),
                               tableview.TableView)
-        # euser primary view priority
-        rset, req = self.env.get_rset_and_req('EUser X WHERE X login "admin"')
-        self.assertIsInstance(self.vreg.select_view('primary', req, rset),
-                             euser.EUserPrimaryView)
-        self.assertIsInstance(self.vreg.select_view('text', req, rset),
-                             baseviews.TextView)
 
     def test_interface_selector(self):
         image = self.add_entity('Image', name=u'bim.png', data=Binary('bim'))
@@ -351,8 +333,8 @@
         rset, req = self.env.get_rset_and_req('Image X WHERE X name "bim.png"')
         self.assertIsInstance(self.vreg.select_view('primary', req, rset),
                               idownloadable.IDownloadablePrimaryView)
-        
-        
+
+
     def test_score_entity_selector(self):
         image = self.add_entity('Image', name=u'bim.png', data=Binary('bim'))
         # image primary view priority
@@ -363,9 +345,9 @@
         # image primary view priority
         rset, req = self.env.get_rset_and_req('File X WHERE X name "bim.txt"')
         self.assertRaises(NoSelectableObject, self.vreg.select_view, 'image', req, rset)
-        
-        
-        
+
+
+
     def _test_view(self, vid, rql, args):
         if rql is None:
             rset = None
@@ -381,9 +363,9 @@
     def test_form(self):
         for vid, rql, args in (
             #('creation', 'Any X WHERE X eid 999999', {}),
-            ('edition', 'EGroup X WHERE X name "managers"', {}),
-            ('copy', 'EGroup X WHERE X name "managers"', {}),
-            ('muledit', 'EGroup X', {}),
+            ('edition', 'CWGroup X WHERE X name "managers"', {}),
+            ('copy', 'CWGroup X WHERE X name "managers"', {}),
+            ('muledit', 'CWGroup X', {}),
             #('muledit', 'Any X', {}),
             ):
             self._test_view(vid, rql, args)
@@ -403,78 +385,49 @@
         self.assertEquals(self.vreg.property_value('boxes.possible_views_box.visible'), False)
         self.assertEquals(self.vreg.property_value('boxes.possible_views_box.order'), 10)
         self.assertRaises(KeyError, self.vreg.property_value, 'boxes.actions_box')
-        
+
+
 
 
-    def test_owners_match_user_group(self):
-        """tests usage of 'owners' group with match_user_group"""
-        class SomeAction(Action):
-            id = 'yo'
-            category = 'foo'
-            __selectors__ = (match_user_group,)
-            require_groups = ('owners', )            
-        self.vreg.register_vobject_class(SomeAction)
-        self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
-        try:
-            # login as a simple user
-            self.create_user('john')
-            self.login('john')
-            # it should not be possible to use SomeAction not owned objects
-            rset, req = self.env.get_rset_and_req('Any G WHERE G is EGroup, G name "managers"')
-            self.failIf('foo' in self.pactions(req, rset))
-            # insert a new card, and check that we can use SomeAction on our object
-            self.execute('INSERT Card C: C title "zoubidou"')
-            self.commit()
-            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
-            self.failUnless('foo' in self.pactions(req, rset))
-            # make sure even managers can't use the action
-            self.restore_connection()
-            rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
-            self.failIf('foo' in self.pactions(req, rset))
-        finally:
-            del self.vreg[SomeAction.__registry__][SomeAction.id]
-
-
-        
-
-
-from cubicweb.web.action import EntityAction
-
-class EETypeRQLAction(EntityAction):
+class CWETypeRQLAction(Action):
     id = 'testaction'
-    accepts = ('EEType',)
-    condition = 'X name "EEType"'
+    __select__ = implements('CWEType') & rql_condition('X name "CWEType"')
     title = 'bla'
 
 class RQLActionTC(ViewSelectorTC):
-            
+
     def setUp(self):
         super(RQLActionTC, self).setUp()
-        self.vreg.register_vobject_class(EETypeRQLAction)
-        
+        self.vreg._loadedmods[__name__] = {}
+        self.vreg.register_vobject_class(CWETypeRQLAction)
+
     def tearDown(self):
-        super(RQLActionTC, self).tearDown()        
+        super(RQLActionTC, self).tearDown()
         del self.vreg._registries['actions']['testaction']
-        
+
     def test(self):
-        rset, req = self.env.get_rset_and_req('EEType X WHERE X name "EEType"')
+        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWEType"')
         self.assertDictEqual(self.pactions(req, rset),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction)],
-                              'moreactions': [('delete', actions.DeleteAction),
+                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('delete', actions.DeleteAction),
                                               ('copy', actions.CopyAction),
-                                              ('testaction', EETypeRQLAction)],
+                                              ('testaction', CWETypeRQLAction),
+                                              ],
                               })
-        rset, req = self.env.get_rset_and_req('EEType X WHERE X name "ERType"')
+        rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWRType"')
         self.assertDictEqual(self.pactions(req, rset),
                              {'useractions': USERACTIONS,
                               'siteactions': SITEACTIONS,
                               'mainactions': [('edit', actions.ModifyAction)],
-                              'moreactions': [('delete', actions.DeleteAction),
-                                              ('copy', actions.CopyAction)],
+                              'moreactions': [('managepermission', actions.ManagePermissionsAction),
+                                              ('delete', actions.DeleteAction),
+                                              ('copy', actions.CopyAction),
+                                              ],
                               })
-        
+
 
 
 if __name__ == '__main__':
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_web.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,18 @@
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.web import ajax_replace_url as  arurl
+class AjaxReplaceUrlTC(TestCase):
+
+    def test_ajax_replace_url(self):
+        # NOTE: for the simplest use cases, we could use doctest
+        self.assertEquals(arurl('foo', 'Person P'),
+                          "javascript: replacePageChunk('foo', 'Person%20P');")
+        self.assertEquals(arurl('foo', 'Person P', 'oneline'),
+                          "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');")
+        self.assertEquals(arurl('foo', 'Person P', 'oneline', name='bar', age=12),
+                          'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'oneline\', {"age": 12, "name": "bar"});')
+        self.assertEquals(arurl('foo', 'Person P', name='bar', age=12),
+                          'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'null\', {"age": 12, "name": "bar"});')
+
+
+if __name__ == '__main__':
+    unittest_main()
--- a/web/test/unittest_webconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_webconfig.py	Thu May 14 12:50:34 2009 +0200
@@ -10,7 +10,7 @@
         self.config = ApptestConfiguration('data')
         self.config._cubes = ['file']
         self.config.load_configuration()
-        
+
     def test_nonregr_print_css_as_list(self):
         """make sure PRINT_CSS *must* is a list"""
         config = self.config
@@ -26,8 +26,8 @@
         self.failUnless('file' in self.config.locate_resource(rname).split(os.sep))
         cubicwebcsspath = self.config.locate_resource('cubicweb.css').split(os.sep)
         self.failUnless('web' in cubicwebcsspath or 'shared' in cubicwebcsspath) # 'shared' if tests under apycot
-        
+
 if __name__ == '__main__':
     unittest_main()
 
-    
+
--- a/web/test/unittest_widgets.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/test/unittest_widgets.py	Thu May 14 12:50:34 2009 +0200
@@ -1,21 +1,21 @@
-"""cubicweb.common.widget unit tests
+"""cubicweb.common.widget unit tests"""
 
-"""
+from datetime import datetime
+NOW = datetime.now()
 
-from mx.DateTime import now
-NOW = 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
 
 
 class WidgetsTC(EnvBasedTC):
-        
+
     def get_widget(self, etype, rname, rtype):
         rschema = self.schema[rname]
         return widget(self.vreg, etype, rschema, rtype, role='subject')
-    
+
 
     def test_hidden_widget(self):
         w = self.get_widget('State', 'eid', 'Int')
@@ -30,7 +30,7 @@
                            u'<input type="hidden" name="eid" value="X" />')
 
     def test_textarea_widget(self):
-        self.add_entity('EProperty', pkey=u'ui.fckeditor', value=u'')
+        self.add_entity('CWProperty', pkey=u'ui.fckeditor', value=u'')
         self.commit()
         w = self.get_widget('State', 'description', 'String')
         self.assertEquals(w.name, 'description')
@@ -41,6 +41,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,12 +52,11 @@
 <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'')
+        self.add_entity('CWProperty', pkey=u'ui.fckeditor', value=u'')
         self.commit()
         w = self.get_widget('State', 'description', 'String')
         req = self.request()
@@ -62,6 +65,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 +76,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 +185,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')))
+        example = '%s, or without time: %s' % (
+            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 +219,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')
@@ -224,12 +229,12 @@
         self.assertEquals(w.edit_render(entity),
                           u'<input type="hidden" name="edits-salary:X" value="__cubicweb_internal_field__"/>\n'
                           '<input type="text" name="salary:X" value="" accesskey="s" id="salary:X" maxlength="15" size="5" tabindex="0"/>')
-                          
-                          
+
+
     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()
@@ -277,11 +282,11 @@
 
 
     def test_password_widget(self):
-        w = self.get_widget('EUser', 'upassword', 'Password')
+        w = self.get_widget('CWUser', 'upassword', 'Password')
         self.assertEquals(w.name, 'upassword')
         self.assertEquals(w.render_example(self.request()), '')
         self.assertDictEquals(w.attrs, {'accesskey': 'u'})
-        entity = self.etype_instance('EUser')
+        entity = self.etype_instance('CWUser')
         entity.eid = 'X'
         self.assertEquals(w.required(entity), True)
         self.assertEquals(w.render(entity), '')
@@ -307,7 +312,7 @@
             self.assertTextEquals(w.edit_render(entity),
                                   u'<input type="hidden" name="edits-nom:X" value="__cubicweb_internal_field__"/>\n'
                                   u'<input type="text" name="nom:X" value="" cubicweb:dataurl="http://testing.fr/cubicweb/json?pageid=None&amp;mode=remote&amp;fname=getnames" class="widget required" id="nom:X" tabindex="0" cubicweb:loadtype="auto" cubicweb:wdgtype="SuggestField"  cubicweb:accesskey="n" cubicweb:maxlength="64" cubicweb:size="40" />')
-                                  
+
         finally:
             del entity.widgets['nom']
 
@@ -330,7 +335,7 @@
             self.assertTextEquals(w.edit_render(entity),
                                   u'<input type="hidden" name="edits-nom:X" value="__cubicweb_internal_field__"/>\n'
                                   u'<input type="text" name="nom:X" value="a name" cubicweb:dataurl="http://testing.fr/cubicweb/json?pageid=None&amp;mode=remote&amp;fname=getnames" class="widget required" id="nom:X" tabindex="0" cubicweb:loadtype="auto" cubicweb:wdgtype="SuggestField"  cubicweb:accesskey="n" cubicweb:maxlength="64" cubicweb:size="40" />')
-            
+
         finally:
             del entity.widgets['nom']
 
@@ -338,7 +343,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()
@@ -358,7 +363,7 @@
         entity.autocomplete_initfuncs = {'nom' : 'getnames'}
         w = self.get_widget(entity, 'travaille', 'Societe')
         self.failUnless(isinstance(w, AutoCompletionWidget))
-        
-        
+
+
 if __name__ == '__main__':
     unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/uicfg.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,281 @@
+# :organization: Logilab
+# :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""This module regroups a set of structures that may be used to configure
+various places of the generated web interface.
+
+Primary view configuration
+``````````````````````````
+:primaryview_section:
+   where to display a relation in primary view. Value may be one of:
+   * 'attributes', display in the attributes section
+   * 'relations', display in the relations section (below attributes)
+   * 'sideboxes', display in the side boxes (beside attributes)
+   * 'hidden', don't display
+
+:primaryview_display_ctrl:
+
+   how to display a relation in primary view. Values are dict with some of the
+   following keys:
+
+   :vid:
+      identifier of a view to use to display the result set. Defaults depends on
+      the section:
+      * 'attributes' section: 'reledit' view
+      * 'relations' section: 'autolimited' view
+      * 'sideboxes' section: 'sidebox' view
+
+   :label:
+     label for the relations section or side box
+
+   :limit:
+      boolean telling if the results should be limited according to the
+      configuration
+
+   :filter:
+      callback taking the related result set as argument and returning it
+      filtered
+
+   :order:
+      int used to control order within a section. When not specified,
+      automatically set according to order in which tags are added.
+
+   Notice those values are only considered if the relation is in a displayed
+   section (controlled by :attr:`primaryview_section`)
+
+
+Index view configuration
+````````````````````````
+:indexview_etype_section:
+   entity type category in the index/manage page. May be one of
+   * 'application'
+   * 'system'
+   * 'schema'
+   * 'subobject' (not displayed by default)
+
+
+Actions box configuration
+`````````````````````````
+:actionbox_appearsin_addmenu:
+  simple boolean relation tags used to control the "add entity" submenu.
+  Relations whose rtag is True will appears, other won't.
+
+Automatic form configuration
+````````````````````````````
+
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.rtags import RelationTags, RelationTagsBool, RelationTagsSet
+from cubicweb.web import formwidgets
+
+# primary view configuration ##################################################
+
+def dual_role(role):
+    return 'object' if role == 'subject' else 'subject'
+
+def card_from_role(card, role):
+    if role == 'subject':
+        return card[0]
+    assert role in ('object', 'sobject'), repr(role)
+    return card[1]
+
+def init_primaryview_section(rtag, sschema, rschema, oschema, role):
+    if rtag.get(sschema, rschema, oschema, role) is None:
+        card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role)
+        composed = rschema.rproperty(sschema, oschema, 'composite') == dual_role(role)
+        if rschema.is_final():
+            if rschema.meta or oschema.type in ('Password', 'Bytes'):
+                section = 'hidden'
+            else:
+                section = 'attributes'
+        elif card in '1+':
+            section = 'attributes'
+        elif composed:
+            section = 'relations'
+        else:
+            section = 'sideboxes'
+        rtag.tag_relation((sschema, rschema, oschema, role), section)
+
+primaryview_section = RelationTags(init_primaryview_section,
+                                    frozenset(('attributes', 'relations',
+                                               'sideboxes', 'hidden')))
+for rtype in ('eid', 'creation_date', 'modification_date',
+              'is', 'is_instance_of', 'identity',
+              'owned_by', 'created_by',
+              'in_state', 'wf_info_for', 'require_permission',
+              'from_entity', 'to_entity',
+              'see_also'):
+    primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
+    primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
+
+for attr in ('name', 'meta', 'final'):
+    primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
+for attr in ('name', 'meta', 'final', 'symetric', 'inlined'):
+    primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
+
+
+class DisplayCtrlRelationTags(RelationTags):
+    def __init__(self, *args, **kwargs):
+        super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
+        self._counter = 0
+
+    def tag_relation(self, key, tag):
+        assert isinstance(tag, dict)
+        super(DisplayCtrlRelationTags, self).tag_relation(key, tag)
+        self._counter += 1
+        tag.setdefault('order', self._counter)
+
+
+def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
+    if role == 'subject':
+        oschema = '*'
+        label = rschema.type
+    else:
+        sschema = '*'
+        label = '%s_%s' % (rschema, role)
+    displayinfo = rtag.get(sschema, rschema, oschema, role)
+    if displayinfo is None:
+        displayinfo = {}
+        rtag.tag_relation((sschema, rschema, oschema, role), displayinfo)
+    displayinfo.setdefault('label', label)
+
+primaryview_display_ctrl = DisplayCtrlRelationTags(init_primaryview_display_ctrl)
+
+
+# index view configuration ####################################################
+# entity type section in the index/manage page. May be one of
+# * 'application'
+# * 'system'
+# * 'schema'
+# * 'subobject' (not displayed by default)
+
+indexview_etype_section = {'EmailAddress': 'subobject'}
+
+
+# autoform.AutomaticEntityForm configuration ##################################
+
+# relations'section (eg primary/secondary/generic/metadata/generated)
+
+def init_autoform_section(rtag, sschema, rschema, oschema, role):
+    if rtag.get(sschema, rschema, oschema, role) is None:
+        if role == 'subject':
+            card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
+            composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+        else:
+            card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
+            composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
+        if sschema.is_metadata(rschema):
+            section = 'generated'
+        elif card in '1+':
+            if not rschema.is_final() and composed:
+                section = 'generated'
+            else:
+                section = 'primary'
+        elif rschema.is_final():
+            section = 'secondary'
+        else:
+            section = 'generic'
+        rtag.tag_relation((sschema, rschema, oschema, role), section)
+
+autoform_section = RelationTags(init_autoform_section,
+                                set(('primary', 'secondary', 'generic',
+                                     'metadata', 'generated')))
+# use primary and not generated for eid since it has to be an hidden
+autoform_section.tag_attribute(('*', 'eid'), 'primary')
+autoform_section.tag_attribute(('*', 'description'), 'secondary')
+autoform_section.tag_attribute(('*', 'creation_date'), 'metadata')
+autoform_section.tag_attribute(('*', 'modification_date'), 'metadata')
+autoform_section.tag_attribute(('*', 'has_text'), 'generated')
+autoform_section.tag_subject_of(('*', 'in_state', '*'), 'primary')
+autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata')
+autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata')
+autoform_section.tag_subject_of(('*', 'is', '*'), 'generated')
+autoform_section.tag_object_of(('*', 'is', '*'), 'generated')
+autoform_section.tag_subject_of(('*', 'is_instance_of', '*'), 'generated')
+autoform_section.tag_object_of(('*', 'is_instance_of', '*'), 'generated')
+autoform_section.tag_subject_of(('*', 'identity', '*'), 'generated')
+autoform_section.tag_object_of(('*', 'identity', '*'), 'generated')
+autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated')
+autoform_section.tag_subject_of(('*', 'wf_info_for', '*'), 'generated')
+autoform_section.tag_object_of(('*', 'wf_info_for', '*'), 'generated')
+autoform_section.tag_subject_of(('*', 'for_user', '*'), 'generated')
+autoform_section.tag_object_of(('*', 'for_user', '*'), 'generated')
+autoform_section.tag_subject_of(('CWPermission', 'require_group', '*'), 'primary')
+autoform_section.tag_attribute(('CWEType', 'final'), 'generated')
+autoform_section.tag_attribute(('CWRType', 'final'), 'generated')
+autoform_section.tag_attribute(('CWUser', 'firstname'), 'secondary')
+autoform_section.tag_attribute(('CWUser', 'surname'), 'secondary')
+autoform_section.tag_attribute(('CWUser', 'last_login_time'), 'metadata')
+autoform_section.tag_subject_of(('CWUser', 'in_group', '*'), 'primary')
+autoform_section.tag_object_of(('*', 'owned_by', 'CWUser'), 'generated')
+autoform_section.tag_object_of(('*', 'created_by', 'CWUser'), 'generated')
+autoform_section.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'metadata')
+autoform_section.tag_attribute(('Bookmark', 'path'), 'primary')
+autoform_section.tag_subject_of(('*', 'use_email', '*'), 'generated') # inlined actually
+autoform_section.tag_subject_of(('*', 'primary_email', '*'), 'generic')
+
+
+# relations'field class
+autoform_field = RelationTags()
+
+# relations'field explicit kwargs (given to field's __init__)
+autoform_field_kwargs = RelationTags()
+autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
+                                    {'widget': formwidgets.TextInput})
+autoform_field_kwargs.tag_attribute(('Bookmark', 'path'),
+                                    {'widget': formwidgets.TextInput})
+
+
+
+# inlined view flag for non final relations: when True for an entry, the
+# entity(ies) at the other end of the relation will be editable from the
+# form of the edited entity
+autoform_is_inlined = RelationTagsBool()
+autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
+autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
+autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
+autoform_is_inlined.tag_subject_of(('CWRelation', 'to_entity', '*'), True)
+
+
+# set of tags of the form <action>_on_new on relations. <action> is a
+# schema action (add/update/delete/read), and when such a tag is found
+# permissions checking is by-passed and supposed to be ok
+autoform_permissions_overrides = RelationTagsSet()
+
+
+# boxes.EditBox configuration #################################################
+
+# 'link' / 'create' relation tags, used to control the "add entity" submenu
+def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
+    if rtag.get(sschema, rschema, oschema, role) is None:
+        card = rschema.rproperty(sschema, oschema, 'cardinality')[role == 'object']
+        if not card in '?1' and \
+               rschema.rproperty(sschema, oschema, 'composite') == role:
+            rtag.tag_relation((sschema, rschema, oschema, role), True)
+
+actionbox_appearsin_addmenu = RelationTagsBool(init_actionbox_appearsin_addmenu)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'is', '*'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'is', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'is_instance_of', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'identity', '*'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'identity', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'owned_by', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'created_by', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
+actionbox_appearsin_addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'state_of', 'CWEType'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'transition_of', 'CWEType'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'owned_by', 'CWUser'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'created_by', 'CWUser'), False)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
+actionbox_appearsin_addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
+actionbox_appearsin_addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
+actionbox_appearsin_addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
--- a/web/views/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/__init__.py	Thu May 14 12:50:34 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
@@ -48,11 +52,16 @@
             return True
     return False
 
-
+VID_BY_MIMETYPE = {'text/xml': 'xml',
+                   # XXX rss, owl...
+                  }
 def vid_from_rset(req, rset, schema):
     """given a result set, return a view id"""
     if rset is None:
         return 'index'
+    for mimetype in req.parse_accept_header('Accept'):
+        if mimetype in VID_BY_MIMETYPE:
+            return VID_BY_MIMETYPE[mimetype]
     nb_rows = len(rset)
     # empty resultset
     if nb_rows == 0 :
@@ -68,29 +77,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/actions.py	Thu May 14 12:50:34 2009 +0200
@@ -6,71 +6,109 @@
 """
 __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,
-                                       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.autoform 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',)
+    id = 'select'
+    __select__ = match_search_state('linksearch') & match_searched_etype()
+
+    title = _('select')
+    category = 'mainactions'
     order = 0
-    
-    id = 'select'
-    title = _('select')
-    
-    @classmethod
-    def accept_rset(cls, req, rset, row, col):
-        return linksearch_match(req, rset)
-    
+
     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'
+    __select__ = (match_search_state('normal') &
+                  match_user_groups('users', 'managers') &
+                  view_is_not_default_view() &
+                  non_final_entity())
+
     title = _('view')
-    
-    @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
-    
+    category = 'mainactions'
+    order = 0
+
     def url(self):
         params = self.req.form.copy()
         params.pop('vid', None)
@@ -79,91 +117,82 @@
                               **params)
 
 
-class ModifyAction(EntityAction):
-    category = 'mainactions'
-    __selectors__ = (one_line_rset, searchstate_accept)
-    #__selectors__ = searchstate_accept,
-    schema_action = 'update'
-    order = 10
-    
+class ModifyAction(Action):
     id = 'edit'
+    __select__ = (match_search_state('normal') &
+                  one_line_rset() &
+                  (has_permission('update') | has_editable_relation('add')))
+
     title = _('modify')
-    
-    @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
+    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',)
-    category = 'moreactions'
-    id = 'addpermission'
+class ManagePermissionsAction(Action):
+    id = 'managepermission'
+    __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
+
     title = _('manage permissions')
-    order = 100
+    category = 'moreactions'
+    order = 15
 
-    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', 'CWPermission',
+                                                   action='add')))
+        return super(ManagePermissionsAction, cls).registered(vreg)
+
     def url(self):
-        return self.rset.get_entity(0, 0).absolute_url(vid='security')
+        return self.rset.get_entity(self.row or 0, self.col or 0).absolute_url(vid='security')
+
 
-    
-class DeleteAction(EntityAction):
-    category = 'moreactions' 
-    __selectors__ = (searchstate_accept,)
-    schema_action = 'delete'
+class DeleteAction(Action):
+    id = 'delete'
+    __select__ = has_permission('delete')
+
+    title = _('delete')
+    category = 'moreactions'
     order = 20
-    
-    id = 'delete'
-    title = _('delete')
-    
+
     def url(self):
         if len(self.rset) == 1:
-            entity = self.rset.get_entity(0, 0)
+            entity = self.rset.get_entity(self.row or 0, self.col or 0)
             return self.build_url(entity.rest_path(), vid='deleteconf')
         return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
-    
-        
-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')
@@ -173,34 +202,14 @@
     """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):
@@ -219,98 +228,66 @@
 # 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')
+        return self.build_url('cwuser/%s'%self.req.user.login, vid='edition')
 
 
 class LogoutAction(Action):
-    category = 'useractions'
-    __selectors__ = authenticated_user,
-    order = 30
-    
     id = 'logout'
-    title = _('logout')
+    __select__ = authenticated_user()
 
-    def url(self):
-        return self.build_url(self.id)
-
-    
-# site actions ################################################################
-
-class ManagersAction(Action):
-    category = 'siteactions'
-    __abstract__ = True
-    __selectors__ = match_user_group,
-    require_groups = ('managers',)
+    title = _('logout')
+    category = 'useractions'
+    order = 30
 
     def url(self):
         return self.build_url(self.id)
 
-    
-class SiteConfigurationAction(ManagersAction):
-    order = 10
-    id = 'siteconfig'
-    title = _('site configuration')
+
+# site actions ################################################################
 
-    
-class ManageAction(ManagersAction):
-    order = 20
-    id = 'manage'
-    title = _('manage')
-
+class ManagersAction(Action):
+    __abstract__ = True
+    __select__ = match_user_groups('managers')
 
-class ViewSchemaAction(Action):
     category = 'siteactions'
-    id = 'schema'
-    title = _("site schema")
-    __selectors__ = yes,
-    order = 30
-    
+
     def url(self):
         return self.build_url(self.id)
 
 
-# content type specific actions ###############################################
+class SiteConfigurationAction(ManagersAction):
+    id = 'siteconfig'
+    title = _('site configuration')
+    order = 10
 
-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()
 
-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')
+class ManageAction(ManagersAction):
+    id = 'manage'
+    title = _('manage')
+    order = 20
 
+
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views.bookmark import FollowAction
+FollowAction = class_moved(FollowAction)
+
--- a/web/views/ajaxedit.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/ajaxedit.py	Thu May 14 12:50:34 2009 +0200
@@ -1,13 +1,13 @@
 """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 import role
+from cubicweb.selectors import match_form_params, match_kwargs
 from cubicweb.web.box import EditRelationBoxTemplate
 
 class AddRelationView(EditRelationBoxTemplate):
@@ -18,13 +18,14 @@
     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')
 
     build_js = EditRelationBoxTemplate.build_reload_js_call
-    
+
     def cell_call(self, row, col, rtype=None, target=None, etype=None):
         self.rtype = rtype or self.req.form['rtype']
         self.target = target or self.req.form['target']
@@ -41,7 +42,7 @@
         fakebox = []
         self.w(u'<div id="%s">' % self.id)
         self.w(u'<h1>%s</h1>' % self.req._('relation %(relname)s of %(ent)s')
-               % {'relname': rschema.display_name(self.req, self.xtarget()[0]),
+               % {'relname': rschema.display_name(self.req, role(self)),
                   'ent': entity.view('incontext')})
         self.w(u'<ul>')
         self.w_unrelated(fakebox, entity)
@@ -55,15 +56,16 @@
         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 getattr(self, 'etype', None):
-            rset = entity.unrelated(self.rtype, self.etype, x, ordermethod='fetch_order')
+            rset = entity.unrelated(self.rtype, self.etype, role(self),
+                                    ordermethod='fetch_order')
             self.pagination(self.req, rset, w=self.w)
             return rset.entities()
         # in other cases, use vocabulary functions
         entities = []
-        for _, eid in entity.vocabulary(self.rtype, x):
+        # XXX to update for 3.2
+        for _, eid in entity.vocabulary(self.rtype, role(self)):
             if eid is not None:
                 rset = self.req.eid_rset(eid)
                 entities.append(rset.get_entity(0, 0))
--- a/web/views/apacherewrite.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/apacherewrite.py	Thu May 14 12:50:34 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'):
@@ -28,7 +28,7 @@
     def match(self, **kwargs):
         self._match = self.condition.match(kwargs[self.match_part])
         return not self._match is None
-    
+
     def action_rewrite(self, path):
         for rgx, replace in self.rules:
             if not rgx.match(path) is None:
@@ -45,8 +45,8 @@
     def action_stop(self, path):
         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 :
@@ -69,24 +69,24 @@
         RewriteRule ^/(data/.*) http://localhost:8080/$1 [L,P]
         RewriteRule ^/(json.*) http://localhost:8080/$1 [L,P]
         RewriteRule ^/(.*) http://localhost:8080/m_%1/$1 [L,P]
-    
+
     could be written (considering that no "host rewritting" is necessary):
 
-      class MyAppRules(ApacheURLRewrite): 
+      class MyAppRules(ApacheURLRewrite):
         rules = [
           RewriteCond('logilab\.fr', match='host',
                       rules=[('/(.*)', r'http://www.logilab.fr/\1')],
                       action='redirect'),
           RewriteCond('(www)\.logilab\.fr', match='host', action='stop'),
           RewriteCond('/(data|json)/', match='path', action='stop'),
-          RewriteCond('(?P<cat>.*)\.logilab\.fr', match='host', 
+          RewriteCond('(?P<cat>.*)\.logilab\.fr', match='host',
                       rules=[('/(.*)', r'/m_%(cat)s/\1')]),
         ]
     """
     __abstract__ = True
     id = 'urlrewriter'
     rules = []
-        
+
     def rewrite(self, host, path):
         for cond in self.rules:
             if cond.match(host=host, path=path):
--- a/web/views/authentication.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/authentication.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """user authentication component
 
 :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"
@@ -12,11 +12,11 @@
 from cubicweb.dbapi import repo_connect, ConnectionProperties
 from cubicweb.web import ExplicitLogin, InvalidSession
 from cubicweb.web.application import AbstractAuthenticationManager
-    
+
 
 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
     """authenticate user associated to a request and check session validity"""
-    
+
     def __init__(self):
         self.repo = self.config.repository(self.vreg)
         self.log_queries = self.config['query-log-file']
@@ -51,10 +51,22 @@
         # associate the connection to the current request
         req.set_connection(cnx, user)
         return cnx
-        
+
+    def login_from_email(self, login):
+        # XXX should not be called from web interface
+        session = self.repo.internal_session()
+        try:
+            rset = session.execute('Any L WHERE U login L, U primary_email M, '
+                                   'M address %(login)s', {'login': login})
+            if rset.rowcount == 1:
+                login = rset[0][0]
+        finally:
+            session.close()
+        return login
+
     def authenticate(self, req, _login=None, _password=None):
         """authenticate user and return corresponding user object
-        
+
         :raise ExplicitLogin: if authentication is required (no authentication
         info found or wrong user/password)
 
@@ -66,6 +78,8 @@
             login, password = _login, _password
         else:
             login, password = req.get_authorization()
+        if self.vreg.config['allow-email-login'] and '@' in (login or u''):
+            login = self.login_from_email(login)
         if not login:
             # No session and no login -> try anonymous
             login, password = self.vreg.config.anonymous_user()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/autoform.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,305 @@
+"""The automatic entity form.
+
+: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"
+_ = unicode
+
+from logilab.common.decorators import iclassmethod
+
+from cubicweb import typed_eid
+from cubicweb.web import stdmsgs, uicfg
+from cubicweb.web.form import FieldNotFound, EntityFieldsForm
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formwidgets import Button, SubmitButton
+from cubicweb.web.views.editforms import toggleable_relation_link, relation_id
+
+
+class AutomaticEntityForm(EntityFieldsForm):
+    """base automatic form to edit any entity.
+
+    Designed to be fully generated from schema but highly configurable through:
+    * rtags (rcategories, rfields, rwidgets, inlined, rpermissions)
+    * various standard form parameters
+
+    You can also easily customise it by adding/removing fields in
+    AutomaticEntityForm instances.
+    """
+    id = 'edition'
+
+    cwtarget = 'eformframe'
+    cssclass = 'entityForm'
+    copy_nav_params = True
+    form_buttons = [SubmitButton(stdmsgs.BUTTON_OK),
+                    Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
+                    Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+    attrcategories = ('primary', 'secondary')
+    # class attributes below are actually stored in the uicfg module since we
+    # don't want them to be reloaded
+    rcategories = uicfg.autoform_section
+    rfields = uicfg.autoform_field
+    rfields_kwargs = uicfg.autoform_field_kwargs
+    rinlined = uicfg.autoform_is_inlined
+    rpermissions_overrides = uicfg.autoform_permissions_overrides
+
+    @classmethod
+    def erelations_by_category(cls, entity, categories=None, permission=None,
+                               rtags=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
+        if rtags is None:
+            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):
+            # check category first, potentially lower cost than checking
+            # permission which may imply rql queries
+            if categories is not None:
+                targetschemas = [tschema for tschema in targetschemas
+                                 if rtags.etype_get(eschema, rschema, role, tschema) in categories]
+                if not targetschemas:
+                    continue
+            if permission is not None:
+                # tag allowing to hijack the permission machinery when
+                # permission is not verifiable until the entity is actually
+                # created...
+                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+                    yield (rschema, targetschemas, role)
+                    continue
+                if rschema.is_final():
+                    if not rschema.has_perm(entity.req, permission, eid):
+                        continue
+                elif role == 'subject':
+                    if not ((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', eschema=None):
+        """return field with the given name and role. If field is not explicitly
+        defined for the form but `eclass` is specified, guess_field will be
+        called.
+        """
+        try:
+            return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role)
+        except FieldNotFound: # XXX should raise more explicit exception
+            if eschema is None or not name in cls_or_self.schema:
+                raise
+            rschema = cls_or_self.schema.rschema(name)
+            # XXX use a sample target type. Document this.
+            tschemas = rschema.targets(eschema, role)
+            fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role, tschemas[0])
+            kwargs = cls_or_self.rfields_kwargs.etype_get(eschema, rschema, role, tschemas[0])
+            if kwargs is None:
+                kwargs = {}
+            if fieldcls:
+                if not isinstance(fieldcls, type):
+                    return fieldcls # already and instance
+                return fieldcls(name=name, role=role, eidparam=True, **kwargs)
+            field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
+            if field is None:
+                raise
+            return field
+
+    def __init__(self, *args, **kwargs):
+        super(AutomaticEntityForm, self).__init__(*args, **kwargs)
+        entity = self.edited_entity
+        if entity.has_eid():
+            entity.complete()
+        for rschema, role in self.editable_attributes():
+            try:
+                self.field_by_name(rschema.type, role)
+                continue # explicitly specified
+            except FieldNotFound:
+                # has to be guessed
+                try:
+                    field = self.field_by_name(rschema.type, role,
+                                               eschema=entity.e_schema)
+                    self.fields.append(field)
+                except FieldNotFound:
+                    # meta attribute such as <attr>_format
+                    continue
+        self.maxrelitems = self.req.property_value('navigation.related-limit')
+        self.force_display = bool(self.req.form.get('__force_display'))
+
+    @property
+    def related_limit(self):
+        if self.force_display:
+            return None
+        return self.maxrelitems + 1
+
+    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 inlined_relations(self):
+        """return a list of (relation schema, target schemas, role) matching
+        given category(ies) and permission
+        """
+        # we'll need an initialized varmaker if there are some inlined relation
+        self.initialize_varmaker()
+        return self.erelations_by_category(self.edited_entity, True, 'add', self.rinlined)
+
+    def srelations_by_category(self, categories=None, permission=None):
+        """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 editable_attributes(self):
+        """return a list of (relation schema, role) to edit for the entity"""
+        return [(rschema, role) for rschema, _, role in self.relations_by_category(
+                self.attrcategories, 'add') if rschema != 'eid']
+
+    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.related_limit)
+            if rschema.has_perm(self.req, 'delete'):
+                toggleable_rel_link_func = toggleable_relation_link
+            else:
+                toggleable_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 = toggleable_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_get(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 '+*'
+
+
+def etype_relation_field(etype, rtype, role='subject'):
+    eschema = AutomaticEntityForm.schema.eschema(etype)
+    return AutomaticEntityForm.field_by_name(rtype, role, eschema)
--- a/web/views/basecomponents.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/basecomponents.py	Thu May 14 12:50:34 2009 +0200
@@ -2,35 +2,34 @@
 
 * 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.common.uilib import html_escape, toggle_action
-from cubicweb.common.selectors import yes, non_final_entity, one_line_rset
+from cubicweb.selectors import yes, two_etypes_rset, match_form_params
 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.common.uilib import html_escape, toggle_action
+from cubicweb.web import component
+from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
+                                      BoxLink)
 
 _ = unicode
 
+VISIBLE_PROP_DEF = {
+    _('visible'):  dict(type='Boolean', default=False,
+                        help=_('display the component or not')),
+    }
 
-class RQLInputForm(SingletonVComponent):
+class RQLInputForm(component.Component):
     """build the rql input form, usually displayed in the header"""
     id = 'rqlinput'
-    visible = False
-       
+    property_defs = VISIBLE_PROP_DEF
+
     def call(self, view=None):
         if hasattr(view, 'filter_box_context_info'):
             rset = view.filter_box_context_info()[0]
@@ -55,30 +54,36 @@
         self.w(u'</form></div>')
 
 
-class ApplLogo(SingletonVComponent):
+class ApplLogo(component.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
+    property_defs = VISIBLE_PROP_DEF
+    # don't want user to hide this component using an cwproperty
+    site_wide = True
+
     def call(self):
         self.w(u'<a href="%s"><img class="logo" src="%s" alt="logo"/></a>'
                % (self.req.base_url(), self.req.external_resource('LOGO')))
 
 
-class ApplHelp(SingletonVComponent):
+class ApplHelp(component.Component):
     """build the help button, usually displayed in the header"""
     id = 'help'
+    property_defs = VISIBLE_PROP_DEF
     def call(self):
         self.w(u'<a href="%s" class="help" title="%s">&nbsp;</a>'
                % (self.build_url(_restpath='doc/main'),
                   self.req._(u'help'),))
 
 
-class UserLink(SingletonVComponent):
+class UserLink(component.Component):
     """if the user is the anonymous user, build a link to login
     else a link to the connected user object with a loggout link
     """
+    property_defs = VISIBLE_PROP_DEF
+    # don't want user to hide this component using an cwproperty
+    site_wide = True
     id = 'loggeduserlink'
-    site_wide = True # don't want user to hide this component using an eproperty
 
     def call(self):
         if not self.req.cnx.anonymous_connection:
@@ -98,25 +103,32 @@
             box.render(w=self.w)
         else:
             self.anon_user_link()
-            
+
     def anon_user_link(self):
         if self.config['auth-mode'] == 'cookie':
             self.w(self.req._('anonymous'))
             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.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
+    property_defs = VISIBLE_PROP_DEF
+    # don't want user to hide this component using an cwproperty
+    site_wide = True
 
     def call(self):
         msgs = [msg for msg in (self.req.get_shared_data('sources_error', pop=True),
@@ -129,72 +141,42 @@
         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')
+class ApplicationName(component.Component):
+    """display the application name"""
+    id = 'appliname'
+    property_defs = VISIBLE_PROP_DEF
+    # don't want user to hide this component using an cwproperty
+    site_wide = True
 
-    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)
+    def call(self):
+        self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
+            self.req.base_url(), self.req.property_value('ui.site-title')))
 
 
-class ApplicationName(SingletonVComponent):
-    """display the application name"""
-    id = 'appliname'
-
-    def call(self):
-        self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (self.req.base_url(),
-                                                         self.req.property_value('ui.site-title')))
-        
-
-class SeeAlsoVComponent(RelatedObjectsVComponent):
+class SeeAlsoVComponent(component.RelatedObjectsVComponent):
     """display any entity's see also"""
     id = 'seealso'
     context = 'navcontentbottom'
     rtype = 'see_also'
-    target = 'object'
+    role = 'subject'
     order = 40
     # register msg not generated since no entity use see_also in cubicweb itself
     title = _('contentnavigation_seealso')
     help = _('contentnavigation_seealso_description')
 
-    
-class EtypeRestrictionComponent(SingletonVComponent):
+
+class EtypeRestrictionComponent(component.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')
+    property_defs = VISIBLE_PROP_DEF
+    # don't want user to hide this component using an cwproperty
+    site_wide = True
     visible = False # disabled by default
-    
+
     def call(self):
         _ = self.req._
         self.w(u'<div id="etyperestriction">')
@@ -233,20 +215,9 @@
             html.insert(0, u'<span class="selected">%s</span>' % _('Any'))
         self.w(u'&nbsp;|&nbsp;'.join(html))
         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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/basecontrollers.py	Thu May 14 12:50:34 2009 +0200
@@ -13,16 +13,15 @@
 
 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
 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 import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
+from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset
 try:
@@ -31,8 +30,43 @@
     HAS_SEARCH_RESTRICTION = True
 except ImportError: # gae
     HAS_SEARCH_RESTRICTION = False
-    
-    
+
+
+def xhtml_wrap(source):
+    head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE
+    return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
+
+def jsonize(func):
+    """decorator to sets correct content_type and calls `simplejson.dumps` on
+    results
+    """
+    def wrapper(self, *args, **kwargs):
+        self.req.set_content_type('application/json')
+        return json_dumps(func(self, *args, **kwargs))
+    wrapper.__name__ = func.__name__
+    return wrapper
+
+def xhtmlize(func):
+    """decorator to sets correct content_type and calls `xmlize` on results"""
+    def wrapper(self, *args, **kwargs):
+        self.req.set_content_type(self.req.html_content_type())
+        result = func(self, *args, **kwargs)
+        return xhtml_wrap(result)
+    wrapper.__name__ = func.__name__
+    return wrapper
+
+def check_pageid(func):
+    """decorator which checks the given pageid is found in the
+    user's session data
+    """
+    def wrapper(self, *args, **kwargs):
+        data = self.req.get_session_data(self.req.pageid)
+        if data is None:
+            raise RemoteCallFailed(self.req._('pageid-not-found'))
+        return func(self, *args, **kwargs)
+    return wrapper
+
+
 class LoginController(Controller):
     id = 'login'
 
@@ -45,25 +79,72 @@
             # Cookie authentication
             return self.appli.need_login_content(self.req)
 
-    
+
 class LogoutController(Controller):
     id = 'logout'
-    
+
     def publish(self, rset=None):
         """logout from the application"""
         return self.appli.session_handler.logout(self.req)
 
 
 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
@@ -85,7 +166,7 @@
             else:
                 rql = 'SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s' % rtype
             for teid in eids:
-                req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) 
+                req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y'))
 
 
 class FormValidatorController(Controller):
@@ -132,135 +213,46 @@
         except AttributeError:
             eid = err.entity
         return (False, (eid, err.errors))
-        
-def xmlize(source):
-    head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE % CW_XHTML_EXTENSIONS
-    return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
 
-def jsonize(func):
-    """sets correct content_type and calls `simplejson.dumps` on results
-    """
-    def wrapper(self, *args, **kwargs):
-        self.req.set_content_type('application/json')
-        result = func(self, *args, **kwargs)
-        return simplejson.dumps(result)
-    return wrapper
-
-
-def check_pageid(func):
-    """decorator which checks the given pageid is found in the
-    user's session data
-    """
-    def wrapper(self, *args, **kwargs):
-        data = self.req.get_session_data(self.req.pageid)
-        if data is None:
-            raise RemoteCallFailed(self.req._('pageid-not-found'))
-        return func(self, *args, **kwargs)
-    return wrapper
-    
 
 class JSonController(Controller):
     id = 'json'
-    template = 'main'
 
     def publish(self, rset=None):
-        mode = self.req.form.get('mode', 'html')
-        self.req.pageid = self.req.form.get('pageid')
-        try:
-            func = getattr(self, '%s_exec' % mode)
-        except AttributeError, ex:
-            self.error('json controller got an unknown mode %r', mode)
-            self.error('\t%s', ex)
-            result = u''
-        else:
-            try:
-                result = func(rset)
-            except RemoteCallFailed:
-                raise
-            except Exception, ex:
-                self.exception('an exception occured on json request(rset=%s): %s',
-                               rset, ex)
-                raise RemoteCallFailed(repr(ex))
-        return result.encode(self.req.encoding)
+        """call js_* methods. Expected form keys:
 
-    def _exec(self, rql, args=None, eidkey=None, rocheck=True):
-        """json mode: execute RQL and return resultset as json"""
-        if rocheck:
-            self.ensure_ro_rql(rql)
-        try:
-            return self.req.execute(rql, args, eidkey)
-        except Exception, ex:
-            self.exception("error in _exec(rql=%s): %s", rql, ex)
-            return None
-        return None
-
-    @jsonize
-    def json_exec(self, rset=None):
-        """json mode: execute RQL and return resultset as json"""
-        rql = self.req.form.get('rql')
-        if rset is None and rql:
-            rset = self._exec(rql)
-        return rset and rset.rows or []
+        :fname: the method name without the js_ prefix
+        :args: arguments list (json)
 
-    def _set_content_type(self, vobj, data):
-        """sets req's content type according to vobj's content type
-        (and xmlize data if needed)
+        note: it's the responsability of js_* methods to set the correct
+        response content type
         """
-        content_type = vobj.content_type
-        if content_type == 'application/xhtml+xml':
-            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"""
-        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)
+        self.req.pageid = self.req.form.get('pageid')
+        fname = self.req.form['fname']
         try:
-            view = self.vreg.select_view(vid, req, rset)
-        except NoSelectableObject:
-            vid = req.form.get('fallbackvid', 'noresult')
-            view = self.vreg.select_view(vid, req, rset)
-        divid = req.form.get('divid', 'pageContent')
-        # we need to call pagination before with the stream set
-        stream = view.set_stream()
-        if req.form.get('paginate'):
-            if divid == 'pageContent':
-                # mimick main template behaviour
-                stream.write(u'<div id="pageContent">')
-                vtitle = self.req.form.get('vtitle')
-                if vtitle:
-                    stream.write(u'<h1 class="vtitle">%s</h1>\n' % vtitle)
-            view.pagination(req, rset, view.w, not view.need_navigation)
-            if divid == 'pageContent':
-                stream.write(u'<div id="contentmain">')
-        view.dispatch()
-        if req.form.get('paginate') and divid == 'pageContent':
-            stream.write(u'</div></div>')
-        source = stream.getvalue()
-        return self._set_content_type(view, source)
-
-    def rawremote_exec(self, rset=None):
-        """like remote_exec but doesn't change content type"""
+            func = getattr(self, 'js_%s' % fname)
+        except AttributeError:
+            raise RemoteCallFailed('no %s method' % fname)
         # no <arg> attribute means the callback takes no argument
         args = self.req.form.get('arg', ())
         if not isinstance(args, (list, tuple)):
             args = (args,)
-        fname = self.req.form['fname']
         args = [simplejson.loads(arg) for arg in args]
         try:
-            func = getattr(self, 'js_%s' % fname)
-        except AttributeError:
-            self.exception('rawremote_exec fname=%s', fname)
-            return u""
-        return func(*args)
+            result = func(*args)
+        except RemoteCallFailed:
+            raise
+        except Exception, ex:
+            self.exception('an exception occured while calling js_%s(%s): %s',
+                           fname, args, ex)
+            raise RemoteCallFailed(repr(ex))
+        if result is None:
+            return ''
+        # get unicode on @htmlize methods, encoded string on @jsonize methods
+        elif isinstance(result, unicode):
+            return result.encode(self.req.encoding)
+        return result
 
-    remote_exec = jsonize(rawremote_exec)
-        
     def _rebuild_posted_form(self, names, values, action=None):
         form = {}
         for name, value in zip(names, values):
@@ -280,8 +272,98 @@
         if action:
             form['__action_%s' % action] = u'whatever'
         return form
-    
+
+    def _exec(self, rql, args=None, eidkey=None, rocheck=True):
+        """json mode: execute RQL and return resultset as json"""
+        if rocheck:
+            self.ensure_ro_rql(rql)
+        try:
+            return self.req.execute(rql, args, eidkey)
+        except Exception, ex:
+            self.exception("error in _exec(rql=%s): %s", rql, ex)
+            return None
+        return None
+
+    @xhtmlize
+    def js_view(self):
+        # XXX try to use the page-content template
+        req = self.req
+        rql = req.form.get('rql')
+        if rql:
+            rset = self._exec(rql)
+        else:
+            rset = None
+        vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
+        try:
+            view = self.vreg.select_view(vid, req, rset)
+        except NoSelectableObject:
+            vid = req.form.get('fallbackvid', 'noresult')
+            view = self.vreg.select_view(vid, req, rset)
+        divid = req.form.get('divid', 'pageContent')
+        # we need to call pagination before with the stream set
+        stream = view.set_stream()
+        if req.form.get('paginate'):
+            if divid == 'pageContent':
+                # mimick main template behaviour
+                stream.write(u'<div id="pageContent">')
+                vtitle = self.req.form.get('vtitle')
+                if vtitle:
+                    stream.write(u'<h1 class="vtitle">%s</h1>\n' % vtitle)
+            view.pagination(req, rset, view.w, not view.need_navigation)
+            if divid == 'pageContent':
+                stream.write(u'<div id="contentmain">')
+        view.render()
+        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>')
+        return stream.getvalue()
+
+    @xhtmlize
+    def js_prop_widget(self, propkey, varname, tabindex=None):
+        """specific method for CWProperty handling"""
+        entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
+        entity.eid = varname
+        entity['pkey'] = propkey
+        form = self.vreg.select_object('forms', 'edition', self.req, None,
+                                       entity=entity)
+        form.form_build_context()
+        vfield = form.field_by_name('value')
+        renderer = FormRenderer()
+        return vfield.render(form, renderer, tabindex=tabindex) \
+               + renderer.render_help(form, vfield)
+
+    @xhtmlize
+    def js_component(self, compid, rql, registry='components', extraargs=None):
+        if rql:
+            rset = self._exec(rql)
+        else:
+            rset = None
+        comp = self.vreg.select_object(registry, compid, self.req, rset)
+        if extraargs is None:
+            extraargs = {}
+        else: # we receive unicode keys which is not supported by the **syntax
+            extraargs = dict((str(key), value)
+                             for key, value in extraargs.items())
+        extraargs = extraargs or {}
+        return comp.render(**extraargs)
+
+    @check_pageid
+    @xhtmlize
+    def js_inline_creation_form(self, peid, ttype, rtype, role):
+        view = self.vreg.select_view('inline-creation', self.req, None,
+                                     etype=ttype, peid=peid, rtype=rtype,
+                                     role=role)
+        return view.render(etype=ttype, peid=peid, rtype=rtype, role=role)
+
+    @jsonize
     def js_validate_form(self, action, names, values):
+        return self.validate_form(action, names, values)
+
+    def validate_form(self, action, names, values):
         # XXX this method (and correspoding js calls) should use the new
         #     `RemoteCallFailed` mechansim
         self.req.form = self._rebuild_posted_form(names, values, action)
@@ -300,64 +382,61 @@
             else:
                 eid = err.entity.eid
             return (False, (eid, err.errors))
-        except Redirect, err:
-            return (True, err.location)
+        except Redirect, redir:
+            return (True, redir.location)
         except Exception, err:
             self.req.cnx.rollback()
             self.exception('unexpected error in js_validateform')
-            return (False, self.req._(str(err)))
+            return (False, self.req._(str(err).decode('utf-8')))
         return (False, '???')
 
-    def js_edit_field(self, action, names, values, rtype, eid):
-        success, args = self.js_validate_form(action, names, values)
+    @jsonize
+    def js_edit_field(self, action, names, values, rtype, eid, default):
+        success, args = self.validate_form(action, names, values)
         if success:
+            # Any X,N where we don't seem to use N is an optimisation
+            # printable_value won't need to query N again
             rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
                                     {'x': eid}, 'x')
             entity = rset.get_entity(0, 0)
-            return (success, args, entity.printable_value(rtype))
+            value = entity.printable_value(rtype)
+            return (success, args, value or default)
         else:
             return (success, args, None)
-            
-    def js_rql(self, rql):
-        rset = self._exec(rql)
-        return rset and rset.rows or []
-    
+
+    @jsonize
+    def js_edit_relation(self, action, names, values,
+                         rtype, role, eid, vid, default):
+        success, args = self.validate_form(action, names, values)
+        if success:
+            entity = self.req.eid_rset(eid).get_entity(0, 0)
+            rset = entity.related(rtype, role)
+            if rset:
+                output = self.view(vid, rset)
+            else:
+                output = default
+            return (success, args, output)
+        else:
+            return (success, args, None)
+
+    @jsonize
     def js_i18n(self, msgids):
         """returns the translation of `msgid`"""
         return [self.req._(msgid) for msgid in msgids]
 
+    @jsonize
     def js_format_date(self, strdate):
         """returns the formatted date for `msgid`"""
-        date = DateFromString(strdate)
+        date = strptime(strdate, '%Y-%m-%d %H:%M:%S')
         return self.format_date(date)
 
+    @jsonize
     def js_external_resource(self, resource):
         """returns the URL of the external resource named `resource`"""
         return self.req.external_resource(resource)
 
-    def js_prop_widget(self, propkey, varname, tabindex=None):
-        """specific method for EProperty handling"""
-        w = self.vreg.property_value_widget(propkey, req=self.req)
-        entity = self.vreg.etype_class('EProperty')(self.req, None, None)
-        entity.eid = varname
-        self.req.form['value'] = self.vreg.property_info(propkey)['default']
-        return w.edit_render(entity, tabindex, includehelp=True)
-
-    def js_component(self, compid, rql, registry='components', extraargs=None):
-        if rql:
-            rset = self._exec(rql)
-        else:
-            rset = None
-        comp = self.vreg.select_object(registry, compid, self.req, rset)
-        if extraargs is None:
-            extraargs = {}
-        else: # we receive unicode keys which is not supported by the **syntax
-            extraargs = dict((str(key), value)
-                             for key, value in extraargs.items())
-        extraargs = extraargs or {}
-        return self._set_content_type(comp, comp.dispatch(**extraargs))
-
     @check_pageid
+    @jsonize
     def js_user_callback(self, cbname):
         page_data = self.req.get_session_data(self.req.pageid, {})
         try:
@@ -365,54 +444,16 @@
         except KeyError:
             return None
         return cb(self.req)
-    
-    def js_unregister_user_callback(self, cbname):
-        self.req.unregister_callback(self.req.pageid, cbname)
-
-    def js_unload_page_data(self):
-        self.req.del_session_data(self.req.pageid)
-        
-    def js_cancel_edition(self, errorurl):
-        """cancelling edition from javascript
-
-        We need to clear associated req's data :
-          - errorurl
-          - pending insertions / deletions
-        """
-        self.req.cancel_edition(errorurl)
-    
-    @check_pageid
-    def js_inline_creation_form(self, peid, ptype, ttype, rtype, role):
-        view = self.vreg.select_view('inline-creation', self.req, None,
-                                     etype=ttype, ptype=ptype, peid=peid,
-                                     rtype=rtype, role=role)
-        source = view.dispatch(etype=ttype, ptype=ptype, peid=peid, rtype=rtype,
-                               role=role)
-        return self._set_content_type(view, source)
-
-    def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
-        self._remove_pending(eidfrom, rel, eidto, 'insert')
-        
-    def js_add_pending_insert(self, (eidfrom, rel, eidto)):
-        self._add_pending(eidfrom, rel, eidto, 'insert')
-        
-    def js_add_pending_inserts(self, tripletlist):
-        for eidfrom, rel, eidto in tripletlist:
-            self._add_pending(eidfrom, rel, eidto, 'insert')
-        
-    def js_remove_pending_delete(self, (eidfrom, rel, eidto)):
-        self._remove_pending(eidfrom, rel, eidto, 'delete')
-    
-    def js_add_pending_delete(self, (eidfrom, rel, eidto)):
-        self._add_pending(eidfrom, rel, eidto, 'delete')
 
     if HAS_SEARCH_RESTRICTION:
+        @jsonize
         def js_filter_build_rql(self, names, values):
             form = self._rebuild_posted_form(names, values)
             self.req.form = form
             builder = FilterRQLBuilder(self.req)
             return builder.build_rql()
 
+        @jsonize
         def js_filter_select_content(self, facetids, rql):
             rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet
             mainvar = prepare_facets_rqlst(rqlst)[0]
@@ -422,13 +463,33 @@
                 update_map[facetid] = facet.possible_values()
             return update_map
 
+    def js_unregister_user_callback(self, cbname):
+        self.req.unregister_callback(self.req.pageid, cbname)
+
+    def js_unload_page_data(self):
+        self.req.del_session_data(self.req.pageid)
+
+    def js_cancel_edition(self, errorurl):
+        """cancelling edition from javascript
+
+        We need to clear associated req's data :
+          - errorurl
+          - pending insertions / deletions
+        """
+        self.req.cancel_edition(errorurl)
+
     def js_delete_bookmark(self, beid):
-        try:
-            rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
-            self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
-        except Exception, ex:
-            self.exception(unicode(ex))
-            return self.req._('Problem occured')
+        rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
+        self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
+
+    def js_set_cookie(self, cookiename, cookievalue):
+        # XXX we should consider jQuery.Cookie
+        cookiename, cookievalue = str(cookiename), str(cookievalue)
+        cookies = self.req.get_cookie()
+        cookies[cookiename] = cookievalue
+        self.req.set_cookie(cookies, cookiename)
+
+    # relations edition stuff ##################################################
 
     def _add_pending(self, eidfrom, rel, eidto, kind):
         key = 'pending_%s' % kind
@@ -437,15 +498,26 @@
         self.req.set_session_data(key, pendings)
 
     def _remove_pending(self, eidfrom, rel, eidto, kind):
-        key = 'pending_%s' % kind        
-        try:
-            pendings = self.req.get_session_data(key)
-            pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
-        except:
-            self.exception('while removing pending eids')
-        else:
-            self.req.set_session_data(key, pendings)
+        key = 'pending_%s' % kind
+        pendings = self.req.get_session_data(key)
+        pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
+        self.req.set_session_data(key, pendings)
+
+    def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
+        self._remove_pending(eidfrom, rel, eidto, 'insert')
 
+    def js_add_pending_inserts(self, tripletlist):
+        for eidfrom, rel, eidto in tripletlist:
+            self._add_pending(eidfrom, rel, eidto, 'insert')
+
+    def js_remove_pending_delete(self, (eidfrom, rel, eidto)):
+        self._remove_pending(eidfrom, rel, eidto, 'delete')
+
+    def js_add_pending_delete(self, (eidfrom, rel, eidto)):
+        self._add_pending(eidfrom, rel, eidto, 'delete')
+
+    # XXX specific code. Kill me and my AddComboBox friend
+    @jsonize
     def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from):
         # create a new entity
         eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
@@ -453,16 +525,10 @@
         rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
         return eid_from
 
-    def js_set_cookie(self, cookiename, cookievalue):
-        # XXX we should consider jQuery.Cookie
-        cookiename, cookievalue = str(cookiename), str(cookievalue)
-        cookies = self.req.get_cookie()
-        cookies[cookiename] = cookievalue
-        self.req.set_cookie(cookies, cookiename)
 
 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"""
@@ -494,12 +560,12 @@
         msg = format_mail({'email' : self.req.user.get_email(),
                            'name' : self.req.user.dc_title(),},
                           [recipient], body, subject)
-        self.smtp.sendmail(helo_addr, [recipient], msg.as_string())    
+        self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
 
     def publish(self, rset=None):
         # XXX this allow anybody with access to an cubicweb application to use it as a mail relay
         body = self.req.form['mailbody']
-        subject = self.req.form['mailsubject']
+        subject = self.req.form['subject']
         for recipient in self.recipients():
             text = body % recipient.as_email_context()
             self.sendmail(recipient.get_email(), subject, text)
@@ -510,11 +576,11 @@
 
 class MailBugReportController(SendMailController):
     id = 'reportbug'
-    __selectors__ = (yes,)
+    __select__ = yes()
 
     def publish(self, rset=None):
         body = self.req.form['description']
         self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body)
         url = self.build_url(__message=self.req._('bug report sent'))
         raise Redirect(url)
-    
+
--- a/web/views/baseforms.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/baseforms.py	Thu May 14 12:50:34 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.autoform 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
@@ -220,14 +34,14 @@
     name of the attribute being edited. You may use this feature to compute
     dynamic default values such as the 'tomorrow' date or the user's login
     being connected
-    """    
-    __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 = set()
+
     EDITION_BODY = u'''\
  %(errormsg)s
 <form id="%(formid)s" class="entityForm" cubicweb:target="eformframe"
@@ -256,8 +70,7 @@
 '''
 
     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)
 
@@ -322,7 +135,7 @@
     @property
     def formid(self):
         return self.id
-    
+
     def action_title(self, entity):
         """form's title"""
         ptitle = self.req._(self.title)
@@ -342,7 +155,7 @@
                 output.append(u'<input type="hidden" name="%s" value="%s" />'
                               % (name, value))
         return u'\n'.join(output)
-                
+
     def add_hidden_web_behaviour_params(self, entity):
         """inserts hidden params controlling how errors and redirection
         should be handled
@@ -361,8 +174,8 @@
             self._hiddens.append( ('__linkto', linkto, '') )
             msg = '%s %s' % (msg, self.req._('and linked'))
         self._hiddens.append( ('__message', msg, '') )
-        
-    
+
+
     def attributes_form(self, entity, kwargs, include_eid=True):
         """create a form to edit entity's attributes"""
         html = []
@@ -395,7 +208,7 @@
         # XXX both (add, delete)
         return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
                 if rschema != 'eid']
-    
+
     def relations_form(self, entity, kwargs):
         srels_by_cat = entity.srelations_by_category(('generic', 'metadata'), 'add')
         if not srels_by_cat:
@@ -454,7 +267,7 @@
         w(u'</table>')
         w(u'</fieldset>')
         return '\n'.join(html)
-        
+
     def inline_entities_form(self, entity, kwargs):
         """create a form to edit entity's inlined relations"""
         result = []
@@ -473,7 +286,7 @@
                 existant = entity.has_eid() and entity.related(rschema)
                 if existant:
                     # display inline-edition view for all existing related entities
-                    result.append(self.view('inline-edition', existant, 
+                    result.append(self.view('inline-edition', existant,
                                             ptype=entity.e_schema, peid=entity.eid,
                                             rtype=rschema, role=x, **kwargs))
                 if x == 'subject':
@@ -505,19 +318,20 @@
         return '\n'.join(result)
 
     # 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 AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
+                                                      targettype)
 
     def should_display_inline_relation_form(self, rschema, existant, card):
         return not existant and card in '1+'
 
     def should_display_add_inline_relation_link(self, rschema, existant, card):
         return not existant or card in '+*'
-    
+
     def reset_url(self, entity):
         return entity.absolute_url()
-    
+
     def on_submit(self, entity):
         return u'return freezeFormButtons(\'%s\')' % (self.domid)
 
@@ -525,22 +339,26 @@
         return self.req._('element edited')
 
 
-    
+
 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):
@@ -567,16 +385,16 @@
     @property
     def formid(self):
         return 'edition'
-    
+
     def relations_form(self, entity, kwargs):
         return u''
 
     def reset_url(self, entity=None):
         return self.build_url(self.req.form.get('etype', '').lower())
-    
+
     def submited_message(self):
         return self.req._('element created')
-    
+
     def url(self):
         """return the url associated with this view"""
         return self.create_url(self.req.form.get('etype'))
@@ -587,25 +405,26 @@
     @cached
     def card(self, etype):
         return self.rschema.rproperty(self.parent_schema, etype, 'cardinality')[0]
-    
+
     def action_title(self, entity):
         return self.rschema.display_name(self.req, self.role)
-        
+
     def add_hidden_web_behaviour_params(self, entity):
         pass
-    
+
     def edit_form(self, entity, ptype, peid, rtype,
                   role='subject', **kwargs):
         self.rschema = self.schema.rschema(rtype)
-        self.role = role        
+        self.role = role
         self.parent_schema = self.schema.eschema(ptype)
         self.parent_eid = peid
         super(InlineFormMixIn, self).edit_form(entity, kwargs)
-    
+
     def should_inline_relation_form(self, entity, rschema, targettype, role):
         if rschema == self.rschema:
             return False
-        return entity.rtags.is_inlined(rschema, targettype, role)
+        return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
+                                                      targettype)
 
     @cached
     def keep_entity(self, entity):
@@ -638,112 +457,15 @@
         return ctx
 
 
-class InlineEntityCreationForm(InlineFormMixIn, CreationForm):
-    id = 'inline-creation'
-    __selectors__ = (match_kwargs, accept_etype)
-    expected_kwargs = ('ptype', 'peid', 'rtype')
-    
-    EDITION_BODY = u'''\
-<div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
- <div class="iformBody">
- <div class="iformTitle"><span>%(title)s</span> #<span class="icounter">1</span> [<a href="javascript: removeInlineForm('%(parenteid)s', '%(rtype)s', '%(eid)s'); noop();">%(removemsg)s</a>]</div>
- <fieldset class="subentity">
- %(attrform)s
- %(relattrform)s
- </fieldset>
- </div>
- <fieldset class="hidden" id="fs-%(parenteid)s-%(rtype)s-%(eid)s">
-%(base)s
- <input type="hidden" value="%(novalue)s" name="edit%(so)s-%(rtype)s:%(parenteid)s" />
- <input id="rel-%(parenteid)s-%(rtype)s-%(eid)s" type="hidden" value="%(eid)s" name="%(rtype)s:%(parenteid)s" />
- </fieldset>
-</div>''' # do not insert trailing space or \n here !
-
-    def call(self, etype, ptype, peid, rtype, role='subject', **kwargs):
-        """
-        :param etype: the entity type being created in the inline form
-        :param parent: the parent entity hosting the inline form
-        :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')
-    
-    EDITION_BODY = u'''\
-<div onclick="restoreInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s')" id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">   
-<div id="notice-%(parenteid)s-%(rtype)s-%(eid)s" class="notice">%(notice)s</div>
-<div class="iformTitle"><span>%(title)s</span>  #<span class="icounter">%(count)s</span> [<a href="javascript: removeInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s'); noop();">%(removemsg)s</a>]</div>
- <div class="iformBody">
- <fieldset class="subentity">
- %(attrform)s
- </fieldset>
- %(relattrform)s
- </div>
- <fieldset id="fs-%(parenteid)s-%(rtype)s-%(eid)s">
-%(base)s
- <input type="hidden" value="%(eid)s" name="edit%(so)s-%(rtype)s:%(parenteid)s" />
- %(rinput)s
- </fieldset>
-</div>''' # do not insert trailing space or \n here !
-
-    rel_input = u'''<input id="rel-%(parenteid)s-%(rtype)s-%(eid)s" type="hidden" value="%(eid)s" name="%(rtype)s:%(parenteid)s" />'''
- 
-    def call(self, **kwargs):
-        """redefine default View.call() method to avoid automatic
-        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)
-
-    def cell_call(self, row, col, ptype, peid, rtype, role='subject', **kwargs):
-        """
-        :param parent: the parent entity hosting the inline form
-        :param rtype: the relation bridging `etype` and `parent`
-        :param role: the role played by the `parent` in the relation
-        """
-        entity = self.entity(row, col)
-        self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
-
-
-    def form_context(self, entity, kwargs):
-        ctx = super(InlineEntityEditionForm, self).form_context(entity, kwargs)
-        if self.keep_entity(entity):
-            ctx['rinput'] = self.rel_input % ctx
-            ctx['todelete'] = u''
-        else:
-            ctx['rinput'] = u''
-            ctx['todelete'] = u'checked="checked"'
-        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. 
+        # request's cache.
         self.newentity = copy(entity)
         self.copying = self.newentity.eid
         self.newentity.eid = None
@@ -760,25 +482,24 @@
     @property
     def formid(self):
         return 'edition'
-        
+
     def relations_form(self, entity, kwargs):
         return u''
 
     def reset_url(self, entity):
         return self.build_url('view', rql='Any X WHERE X eid %s' % self.copying)
-    
+
     def attributes_form(self, entity, kwargs, include_eid=True):
         # we don't want __clone_eid on inlined edited entities
         if entity.eid == self.newentity.eid:
             self._hiddens.append((eid_param('__cloned_eid', entity.eid), self.copying, ''))
         return EditionForm.attributes_form(self, entity, kwargs, include_eid)
-    
+
     def submited_message(self):
         return self.req._('element copied')
-       
-    
+
 
-class TableEditForm(EntityForm):
+class TableEditForm(FormMixIn, EntityView):
     id = 'muledit'
     title = _('multiple edit')
 
@@ -805,7 +526,7 @@
       </td>
     </tr>
   </table>
-  </fieldset>    
+  </fieldset>
 </form>
 '''
 
@@ -814,15 +535,13 @@
   %(error)s
   <div>%(widget)s</div>
 </td>'''
-    
+
     def call(self, **kwargs):
         """a view to edit multiple entities of the same type
         the first column should be the eid
         """
         req = self.req
         form = req.form
-        req.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])
@@ -841,13 +560,13 @@
                'oktitle': _('validate modifications on selected items').capitalize(),
                'cancelvalue': _('button_reset').capitalize(),
                'canceltitle': _('revert changes').capitalize(),
-               }        
+               }
         self.w(self.EDITION_BODY % ctx)
-        
-        
+
+
     def reset_url(self, entity=None):
         self.build_url('view', rql=self.rset.printable_rql())
-        
+
     def edit_form(self, entity):
         html = []
         w = html.append
@@ -878,128 +597,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/basetemplates.py	Thu May 14 12:50:34 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, STRICT_DOCTYPE
+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,76 @@
                 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()
+        view.set_stream()
+        xhtml_wrap = (self.req.form.has_key('__notemplate') and view.templatable
+                      and view.content_type == self.req.html_content_type())
+        if xhtml_wrap:
+            view.w(u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE)
+            view.w(u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
+        # have to replace our unicode stream using view's binary stream
+        view.render()
+        if xhtml_wrap:
+            view.w(u'</div>')
+        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'
+    id = 'main-template'
+    __select__ = templatable_view()
 
-    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
-    
-    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.render(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.render(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,38 +141,31 @@
         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')
         rqlcomp = self.vreg.select_component('rqlinput', self.req, self.rset)
         if rqlcomp:
-            rqlcomp.dispatch(w=self.w, view=view)
+            rqlcomp.render(w=self.w, view=view)
         msgcomp = self.vreg.select_component('applmessages', self.req, self.rset)
         if msgcomp:
-            msgcomp.dispatch(w=self.w)
+            msgcomp.render(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):
@@ -219,15 +174,15 @@
         if boxes:
             self.w(u'<td class="navcol"><div class="navboxes">\n')
             for box in boxes:
-                box.dispatch(w=self.w, view=view)
+                box.render(w=self.w, view=view)
             self.w(u'</div></td>\n')
 
     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 +190,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()
@@ -244,9 +199,9 @@
         view = self.vreg.select_view('error', self.req, self.rset)
         self.template_header(self.content_type, view, self.req._('an error occured'),
                              [NOINDEX, NOFOLLOW])
-        view.dispatch(w=self.w)
+        view.render(w=self.w)
         self.template_footer(view)
-    
+
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         w = self.whead
         lang = self.req.lang
@@ -254,7 +209,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 +220,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 +230,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')
@@ -288,7 +243,7 @@
         if boxes:
             w(u'<div class="navboxes">\n')
             for box in boxes:
-                box.dispatch(w=w)
+                box.render(w=w)
             self.w(u'</div>\n')
         w(u'</td>')
         w(u'<td id="contentcol" rowspan="2">')
@@ -296,20 +251,20 @@
         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>')
-        self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+        self.vreg.select_component('logo', self.req, self.rset).render(w=self.w)
         self.w(u'</td>\n')
         self.w(u'</tr></table>\n')
 
 # page parts templates ########################################################
 
-class HTMLHeader(Template):
+class HTMLHeader(View):
     """default html headers"""
     id = 'htmlheader'
-    
+
     def call(self, **kwargs):
         self.favicon()
         self.stylesheets()
@@ -321,7 +276,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 +286,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 +302,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,38 +317,38 @@
         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')
         self.w(u'<td id="firstcolumn">')
-        self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+        self.vreg.select_component('logo', self.req, self.rset).render(w=self.w)
         self.w(u'</td>\n')
         # appliname and breadcrumbs
         self.w(u'<td id="headtext">')
         comp = self.vreg.select_component('appliname', self.req, self.rset)
         if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w)
+            comp.render(w=self.w)
         comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
         if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w, view=view)
+            comp.render(w=self.w, view=view)
         self.w(u'</td>')
         # logged user and help
         self.w(u'<td>\n')
         comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
-        comp.dispatch(w=self.w)
+        comp.render(w=self.w)
         self.w(u'</td><td>')
         helpcomp = self.vreg.select_component('help', self.req, self.rset)
         if helpcomp: # may not be available if Card is not defined in the schema
-            helpcomp.dispatch(w=self.w)
+            helpcomp.render(w=self.w)
         self.w(u'</td>')
         # lastcolumn
         self.w(u'<td id="lastcolumn">')
         self.w(u'</td>\n')
         self.w(u'</tr></table>\n')
-        self.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 +358,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 +384,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',
@@ -444,16 +399,16 @@
         if components:
             self.w(u'<div id="contentheader">')
             for comp in components:
-                comp.dispatch(w=self.w, view=view)
+                comp.render(w=self.w, view=view)
             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,
@@ -461,12 +416,14 @@
         if components:
             self.w(u'<div id="contentfooter">')
             for comp in components:
-                comp.dispatch(w=self.w, view=view)
+                comp.render(w=self.w, view=view)
             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):
@@ -475,7 +432,7 @@
         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 +448,15 @@
         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'))
+        msg = (self.config['allow-email-login'] and _('login or email')) or _('login')
+        self.w(u'<td><label for="__login">%s</label></td>' % msg)
         self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
         self.w(u'</tr><tr>\n')
         self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
@@ -510,7 +468,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 +476,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/baseviews.py	Thu May 14 12:50:34 2009 +0200
@@ -4,7 +4,6 @@
 * primary, sidebox
 * secondary, oneline, incontext, outofcontext, text
 * list
-* xml, rss
 
 
 :organization: Logilab
@@ -15,39 +14,31 @@
 
 __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 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 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):
         self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
                % self.req._('No result matching query'))
@@ -55,7 +46,7 @@
 
 class FinalView(AnyRsetView):
     """display values without any transformation (i.e. get a number for
-    entities) 
+    entities)
     """
     id = 'final'
     # record generated i18n catalog messages
@@ -73,10 +64,11 @@
     _('%d hours')
     _('%d minutes')
     _('%d seconds')
-            
+
     def cell_call(self, row, col, props=None, displaytime=False, format='text/html'):
         etype = self.rset.description[row][col]
         value = self.rset.rows[row][col]
+
         if etype == 'String':
             entity, rtype = self.rset.related_entity(row, col)
             if entity is not None:
@@ -84,7 +76,7 @@
                 self.w(entity.printable_value(rtype, value, format=format))
                 return
         if etype in ('Time', 'Interval'):
-            # value is DateTimeDelta but we have no idea about what is the 
+            # value is DateTimeDelta but we have no idea about what is the
             # reference date here, so we can only approximate years and months
             if format == 'text/html':
                 space = '&nbsp;'
@@ -98,262 +90,20 @@
                 self.w(self.req.__('%%d%sweeks' % space) % (value.days // 7))
             elif value.days > 2:
                 self.w(self.req.__('%%d%sdays' % space) % int(value.days))
-            elif value.hours > 2:
-                self.w(self.req.__('%%d%shours' % space) % int(value.hours))
-            elif value.minutes >= 2:
-                self.w(self.req.__('%%d%sminutes' % space) % int(value.minutes))
+            elif value.seconds > 3600:
+                self.w(self.req.__('%%d%shours' % space) % int(value.seconds // 3600))
+            elif value.seconds >= 120:
+                self.w(self.req.__('%%d%sminutes' % space) % int(value.seconds // 60))
             else:
                 self.w(self.req.__('%%d%sseconds' % space) % int(value.seconds))
             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', 
-                         'in_state', 'wf_info_for', 'require_permission',
-                         'from_entity', 'to_entity',
-                         'see_also'])
-
-class PrimaryView(EntityView):
-    """the full view of an non final entity"""
-    id = 'primary'
-    title = _('primary')
-    show_attr_label = True
-    show_rel_label = True
-    skip_none = True
-    skip_attrs = ('eid', 'creation_date', 'modification_date')
-    skip_rels = ()
-    main_related_section = True
-
-    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 primary views are indexed
-        """
-        return []
-    
-    def cell_call(self, row, col):        
-        self.row = row
-        # XXX move render_entity implementation here
-        self.render_entity(self.complete_entity(row, col))
-    
-    def render_entity(self, entity):
-        """return html to display the given entity"""
-        siderelations = []
-        self.render_entity_title(entity)
-        self.render_entity_metadata(entity)
-        # entity's attributes and relations, excluding meta data
-        # if the entity isn't meta itself
-        self.w(u'<div>')
-        self.w(u'<div class="mainInfo">')
-        self.render_entity_attributes(entity, siderelations)
-        self.w(u'</div>')
-        self.content_navigation_components('navcontenttop')
-        if self.main_related_section:
-            self.render_entity_relations(entity, siderelations)
-        self.w(u'</div>')
-        # side boxes
-        self.w(u'<div class="primaryRight">')
-        self.render_side_related(entity, siderelations)
-        self.w(u'</div>')
-        self.w(u'<div class="clear"></div>')          
-        self.content_navigation_components('navcontentbottom')
-
-    def content_navigation_components(self, context):
-        self.w(u'<div class="%s">' % context)
-        for comp in self.vreg.possible_vobjects('contentnavigation',
-                                                self.req, self.rset, row=self.row,
-                                                view=self, context=context):
-            try:
-                comp.dispatch(w=self.w, row=self.row, view=self)
-            except NotImplementedError:
-                warn('component %s doesnt implement cell_call, please update'
-                     % comp.__class__, DeprecationWarning)
-                comp.dispatch(w=self.w, view=self)
-        self.w(u'</div>')
-        
-    def iter_attributes(self, entity):
-        for rschema, targetschema in entity.e_schema.attribute_definitions():
-            attr = rschema.type
-            if attr in self.skip_attrs:
-               continue
-            yield rschema, targetschema
-            
-    def iter_relations(self, entity):
-        skip = set(self.skip_rels)
-        skip.update(PRIMARY_SKIP_RELS)
-        for rschema, targetschemas, x in entity.e_schema.relation_definitions():
-            if rschema.type in skip:
-                continue
-            yield rschema, targetschemas, x
-
-    def render_entity_title(self, entity):
-        title = self.content_title(entity) # deprecate content_title?
-        if title:
-            self.w(u'<h1><span class="etype">%s</span> %s</h1>'
-                   % (entity.dc_type().capitalize(), title))
-    
-    def content_title(self, entity):
-        """default implementation return an empty string"""
-        return u''
-            
-    def render_entity_metadata(self, entity):
-        entity.view('metadata', w=self.w)
-        summary = self.summary(entity) # deprecate summary?
-        if summary:
-            self.w(u'<div class="summary">%s</div>' % summary)
-    
-    def summary(self, entity):
-        """default implementation return an empty string"""
-        return u''    
-               
-    def render_entity_attributes(self, entity, siderelations):
-        for rschema, targetschema in self.iter_attributes(entity):
-            attr = rschema.type
-            if targetschema.type in ('Password', 'Bytes'):
-                continue
-            try:
-                wdg = entity.get_widget(attr)
-            except Exception, ex:
-                value = entity.printable_value(attr, entity[attr], targetschema.type)
-            else:
-                value = wdg.render(entity)
-            if self.skip_none and (value is None or value == ''):
-                continue
-            if rschema.meta:
-                continue
-            self._render_related_entities(entity, rschema, value)
-
-    def render_entity_relations(self, entity, siderelations):
-        if hasattr(self, 'get_side_boxes_defs'):
-            return
-        eschema = entity.e_schema
-        maxrelated = self.req.property_value('navigation.related-limit')
-        for rschema, targetschemas, x in self.iter_relations(entity):
-            try:
-                related = entity.related(rschema.type, x, limit=maxrelated+1)
-            except Unauthorized:
-                continue
-            if not related:
-                continue
-            if self.is_side_related(rschema, eschema):
-                siderelations.append((rschema, related, x))
-                continue
-            self._render_related_entities(entity, rschema, related, x)
-
-    def render_side_related(self, entity, siderelations):
-        """display side related relations:
-        non-meta in a first step, meta in a second step
-        """
-        if hasattr(self, 'get_side_boxes_defs'):
-            sideboxes = [(label, rset) for label, rset in self.get_side_boxes_defs(entity)
-                         if rset]
-            if sideboxes:
-                for label, rset in sideboxes:
-                    self.w(u'<div class="sideRelated">')
-                    self.wview('sidebox', rset, title=label)
-                    self.w(u'</div>')
-        elif siderelations:
-            self.w(u'<div class="sideRelated">')
-            for relatedinfos in siderelations:
-                # if not relatedinfos[0].meta:
-                #    continue
-                self._render_related_entities(entity, *relatedinfos)
-            self.w(u'</div>')
-        boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
-                                                 row=self.row, view=self,
-                                                 context='incontext'))
-        if boxes:
-            for box in boxes:
-                try:
-                    box.dispatch(w=self.w, row=self.row)
-                except NotImplementedError:
-                    # much probably a context insensitive box, which only implements
-                    # .call() and not cell_call()
-                    box.dispatch(w=self.w)               
-                
-    def is_side_related(self, rschema, eschema):
-        return rschema.meta and \
-               not rschema.schema_relation() == eschema.schema_entity()
-
-    def _render_related_entities(self, entity, rschema, related,
-                                 role='subject'):
-        if rschema.is_final():
-            value = related
-            show_label = self.show_attr_label
-        else:
-            if not related:
-                return
-            show_label = self.show_rel_label
-            # if not too many entities, show them all in a list
-            maxrelated = self.req.property_value('navigation.related-limit')
-            if related.rowcount <= maxrelated:
-                if related.rowcount == 1:
-                    value = self.view('incontext', related, row=0)
-                elif 1 < related.rowcount <= 5:
-                    value = self.view('csv', related)
-                else:
-                    value = '<div>' + self.view('simplelist', related) + '</div>'
-            # else show links to display related entities
-            else:
-                rql = related.printable_rql()
-                related.limit(maxrelated)
-                value = '<div>' + self.view('simplelist', related)
-                value += '[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
-                                                    self.req._('see them all'))
-                value +=  '</div>'
-        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'
     title = _('secondary')
-    
+
     def cell_call(self, row, col):
         """the secondary view for an entity
         secondary = icon + view(oneline)
@@ -362,9 +112,10 @@
         self.w(u'&nbsp;')
         self.wview('oneline', self.rset, row=row, col=col)
 
+
 class OneLineView(EntityView):
     id = 'oneline'
-    title = _('oneline') 
+    title = _('oneline')
 
     def cell_call(self, row, col):
         """the one line view for an entity: linked text view
@@ -374,12 +125,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
@@ -394,7 +146,7 @@
             self.wview(self.id, rset, row=i, **kwargs)
             if len(rset) > 1:
                 self.w(u"\n")
-    
+
     def cell_call(self, row, col=0, **kwargs):
         entity = self.entity(row, col)
         self.w(cut(entity.dc_title(),
@@ -404,9 +156,8 @@
 class MetaDataView(EntityView):
     """paragraph view of some metadata"""
     id = 'metadata'
-    accepts = 'Any',
     show_eid = True
-    
+
     def cell_call(self, row, col):
         _ = self.req._
         entity = self.entity(row, col)
@@ -415,15 +166,15 @@
             self.w(u'#%s - ' % entity.eid)
         if entity.modification_date != entity.creation_date:
             self.w(u'<span>%s</span> ' % _('latest update on'))
-            self.w(u'<span class="value">%s</span>,&nbsp;'
+            self.w(u'<span class="value">%s</span>, '
                    % self.format_date(entity.modification_date))
         # entities from external source may not have a creation date (eg ldap)
-        if entity.creation_date: 
+        if entity.creation_date:
             self.w(u'<span>%s</span> ' % _('created on'))
             self.w(u'<span class="value">%s</span>'
                    % self.format_date(entity.creation_date))
         if entity.creator:
-            self.w(u'&nbsp;<span>%s</span> ' % _('by'))
+            self.w(u' <span>%s</span> ' % _('by'))
             self.w(u'<span class="value">%s</span>' % entity.creator.name())
         self.w(u'</div>')
 
@@ -437,7 +188,8 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         self.w(entity.dc_title())
-        
+
+
 class OutOfContextTextView(InContextTextView):
     id = 'textoutofcontext'
 
@@ -457,7 +209,7 @@
         self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
         self.w(u'</a>')
 
-        
+
 class OutOfContextView(EntityView):
     id = 'outofcontext'
 
@@ -466,29 +218,17 @@
         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 views ##################################################################
 
-            
-# list and table related views ################################################
-    
 class ListView(EntityView):
     id = 'list'
     title = _('list')
     item_vid = 'listitem'
-        
+
     def call(self, klass=None, title=None, subvid=None, listid=None, **kwargs):
         """display a list of entities by calling their <item_vid> view
-        
+
         :param listid: the DOM id to use for the root element
         """
         if subvid is None and 'subvid' in self.req.form:
@@ -535,16 +275,16 @@
             return self.build_url(entity.rest_path(), vid=self.id)
         return self.build_url(rql=self.rset.printable_rql(), vid=self.id)
 
- 
+
 class ListItemView(EntityView):
     id = 'listitem'
-    
+
     @property
     def redirect_vid(self):
         if self.req.search_state[0] == 'normal':
             return 'outofcontext'
         return 'outofcontext-search'
-        
+
     def cell_call(self, row, col, vid=None, **kwargs):
         if not vid:
             vid = self.redirect_vid
@@ -565,7 +305,7 @@
 class CSVView(SimpleListView):
     id = 'csv'
     redirect_vid = 'incontext'
-        
+
     def call(self, **kwargs):
         rset = self.rset
         for i in xrange(len(rset)):
@@ -575,397 +315,10 @@
 
 
 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 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'''<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)))
-        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' % html_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'</rss>')       
-
-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' % html_escape(entity.absolute_url()))
-        self.render_title_link(entity)
-        self._marker('description', entity.dc_date(self.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.w(u'<author>')
-            self._marker('name', entity.creator.name())
-            email = entity.creator.get_email()
-            if email:
-                self._marker('email', 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>')
-
+        self.wview('incontext', self.rset, row=row, col=col)
 
 class TextSearchResultView(EntityView):
     """this view is used to display full-text search
@@ -976,7 +329,6 @@
     """
     id = 'tsearch'
 
-
     def cell_call(self, row, col, **kwargs):
         entity = self.complete_entity(row, col)
         self.w(entity.view('incontext'))
@@ -987,7 +339,7 @@
         highlighted = '<b>%s</b>' % searched
         for attr in entity.e_schema.indexable_attributes():
             try:
-                value = html_escape(entity.printable_value(attr, format='text/plain').lower())
+                value = xml_escape(entity.printable_value(attr, format='text/plain').lower())
             except TransformError, ex:
                 continue
             except:
@@ -1000,36 +352,32 @@
                     else:
                         contexts.append(ctx)
                 value = u'\n' + highlighted.join(contexts)
-                self.w(value.replace('\n', '<br/>'))            
+                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, primary
+PrimaryView = class_moved(primary.PrimaryView)
+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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/bookmark.py	Thu May 14 12:50:34 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,14 +9,26 @@
 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.box import UserRQLBoxTemplate
-from cubicweb.web.views.baseviews import PrimaryView
+from cubicweb.web import action, box
+from cubicweb.web.views import primary
 
 
-class BookmarkPrimaryView(PrimaryView):
-    accepts = ('Bookmark',)
-        
+class FollowAction(action.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(primary.PrimaryView):
+    __select__ = implements('Bookmark')
+
     def cell_call(self, row, col):
         """the primary view for bookmark entity"""
         entity = self.complete_entity(row, col)
@@ -32,7 +44,7 @@
         self.w(u'</div>')
 
 
-class BookmarksBox(UserRQLBoxTemplate):
+class BookmarksBox(box.UserRQLBoxTemplate):
     """display a box containing all user's bookmarks"""
     id = 'bookmarks_box'
     order = 40
@@ -42,8 +54,8 @@
            'U eid %(x)s')
     etype = 'Bookmark'
     rtype = 'bookmarked_by'
-    
-    
+
+
     def call(self, **kwargs):
         req = self.req
         ueid = req.user.eid
--- a/web/views/boxes.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/boxes.py	Thu May 14 12:50:34 2009 +0200
@@ -3,7 +3,6 @@
 
 * actions box
 * possible views box
-* rss icon
 
 additional (disabled by default) boxes
 * schema box
@@ -17,24 +16,30 @@
 
 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 import uicfg
+from cubicweb.web.box import BoxTemplate
 
 _ = unicode
 
-
 class EditBox(BoxTemplate):
     """
     box with all actions impacting the entity displayed: edit, copy, delete
     change state, add related entities
     """
-    __selectors__ = (any_rset,) + BoxTemplate.__selectors__
     id = 'edit_box'
+    __select__ = BoxTemplate.__select__ & non_final_entity()
+
     title = _('actions')
     order = 2
+    # class attributes below are actually stored in the uicfg module since we
+    # don't want them to be reloaded
+    appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
 
-    def call(self, **kwargs):
+    def call(self, view=None, **kwargs):
         _ = self.req._
         title = _(self.title)
         if self.rset:
@@ -45,7 +50,7 @@
                 title = u'%s - %s' % (title, etypelabel.lower())
         box = BoxWidget(title, self.id, _class="greyBoxFrame")
         # build list of actions
-        actions = self.vreg.possible_actions(self.req, self.rset)
+        actions = self.vreg.possible_actions(self.req, self.rset, view=view)
         add_menu = BoxMenu(_('add')) # 'addrelated' category
         other_menu = BoxMenu(_('more actions')) # 'moreactions' category
         searchstate = self.req.search_state[0]
@@ -86,22 +91,53 @@
             box.append(boxlink)
         elif submenu.items:
             box.append(submenu)
-        
+
     def schema_actions(self, entity):
         user = self.req.user
         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)
+                label = '%s %s %s %s' % (eschema, rschema, teschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'object')
             else:
-                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
+                label = '%s %s %s %s' % (teschema, rschema, eschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'subject')
             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
+                # XXX consider autoform_permissions_overrides?
+                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.appearsin_addmenu.etype_get(eschema, rschema,
+                                                            role, teschema):
+                        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:
@@ -133,10 +169,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" />
@@ -147,7 +183,6 @@
 </td></tr></table>
 </form>"""
 
-
     def call(self, view=None, **kwargs):
         req = self.req
         if req.form.pop('__fromsearchbox', None):
@@ -159,7 +194,7 @@
         title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
         box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
         box.append(BoxHtml(form))
-        box.render(self.w)            
+        box.render(self.w)
 
 
 # boxes disabled by default ###################################################
@@ -167,11 +202,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)
@@ -185,22 +220,6 @@
         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"""
@@ -214,7 +233,36 @@
         for view in self.vreg.possible_views(self.req, None):
             if view.category == 'startupview':
                 box.append(self.box_action(view))
-        
+
         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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/calendar.py	Thu May 14 12:50:34 2009 +0200
@@ -1,63 +1,93 @@
 """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 vobject import iCalendar, icalendar
+from datetime import datetime, date, timedelta
 
 from logilab.mtconverter import html_escape
 
 from cubicweb.interfaces import ICalendarable
-from cubicweb.common.utils import date_range
-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")
+from cubicweb.selectors import implements
+from cubicweb.utils import strptime, date_range, todate, todatetime
+from cubicweb.view import EntityView
 
 _ = 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) 
+# Calendar views ##############################################################
+
+try:
+    from vobject import iCalendar
+
+    class iCalView(EntityView):
+        """A calendar view that generates a iCalendar file (RFC 2445)
+
+        Does apply to ICalendarable compatible entities
+        """
+        __select__ = implements(ICalendarable)
+        need_navigation = False
+        content_type = 'text/calendar'
+        title = _('iCalendar')
+        templatable = False
+        id = 'ical'
+
+        def call(self):
+            ical = iCalendar()
+            for i in range(len(self.rset.rows)):
+                task = self.complete_entity(i)
+                event = ical.add('vevent')
+                event.add('summary').value = task.dc_title()
+                event.add('description').value = task.dc_description()
+                if task.start:
+                    event.add('dtstart').value = task.start
+                if task.stop:
+                    event.add('dtend').value = task.stop
+
+            buff = ical.serialize()
+            if not isinstance(buff, unicode):
+                buff = unicode(buff, self.req.encoding)
+            self.w(buff)
+
+except ImportError:
+    pass
+
+class hCalView(EntityView):
+    """A calendar view that generates a hCalendar file
+
+    Does apply to ICalendarable compatible entities
+    """
+    id = 'hcal'
+    __select__ = implements(ICalendarable)
+    need_navigation = False
+    title = _('hCalendar')
+    #templatable = False
+
+    def call(self):
+        self.w(u'<div class="hcalendar">')
+        for i in range(len(self.rset.rows)):
+            task = self.complete_entity(i)
+            self.w(u'<div class="vevent">')
+            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>' % (task.start.isoformat(), self.format_date(task.start)))
+            if task.stop:
+                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self.format_date(task.stop)))
+            self.w(u'</div>')
+        self.w(u'</div>')
 
 
 class CalendarItemView(EntityView):
@@ -68,74 +98,14 @@
         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/>' % 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'
-        
-#################
-# 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,)
-    need_navigation = False
-    content_type = 'text/calendar'
-    title = _('iCalendar')
-    templatable = False
-    id = 'ical'
-
-    def call(self):
-        ical = iCalendar()
-        for i in range(len(self.rset.rows)):
-            task = self.complete_entity(i)
-            event = ical.add('vevent')
-            event.add('summary').value = task.dc_title()
-            event.add('description').value = task.dc_description()
-            if task.start:
-                event.add('dtstart').value = mkdt(task.start)
-            if task.stop:
-                event.add('dtend').value = mkdt(task.stop)
 
-        buff = ical.serialize()
-        if not isinstance(buff, unicode):
-            buff = unicode(buff, self.req.encoding)
-        self.w(buff)
 
-class hCalView(EntityView):
-    """A calendar view that generates a hCalendar file
-
-    Does apply to ICalendarable compatible entities
-    """
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
-    need_navigation = False
-    title = _('hCalendar')
-    #templatable = False
-    id = 'hcal'
-
-    def call(self):
-        self.w(u'<div class="hcalendar">')
-        for i in range(len(self.rset.rows)):
-            task = self.complete_entity(i)
-            self.w(u'<div class="vevent">')
-            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)))
-            if task.stop:
-                self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (iso(task.stop), self.format_date(task.stop)))
-            self.w(u'</div>')
-        self.w(u'</div>')
-
-    
 class _TaskEntry(object):
     def __init__(self, task, color, index=0):
         self.task = task
@@ -143,20 +113,29 @@
         self.index = index
         self.length = 1
 
+    def in_working_hours(self):
+        """predicate returning True is the task is in working hours"""
+        if todatetime(self.task.start).hour > 7 and todatetime(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 +146,62 @@
         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)
-            if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'EUser':
-                user = self.rset.get_entity(row,1)
+            task = self.rset.get_entity(row, 0)
+            if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'CWUser':
+                user = self.rset.get_entity(row, 1)
             else:
                 user = None
             the_dates = []
-            if task.start:
-                if task.start > lastday:
-                    continue
-                the_dates = [task.start]
-            if task.stop:
-                if task.stop < firstday:
+            tstart = task.start
+            if tstart:
+                tstart = todate(task.start)
+                if tstart > lastday:
                     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 = [tstart]
+            tstop = task.stop
+            if tstop:
+                tstop = todate(tstop)
+                if tstop < firstday:
+                    continue
+                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 +217,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]
@@ -262,43 +247,45 @@
         # output header
         self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
                tuple(self.req._(day) for day in WEEKDAYS))
-        
+
         # 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
-        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)
+        prevdate = curdate - timedelta(31)
+        nextdate = curdate + timedelta(31)
+        rql = self.rset.printable_rql()
+        prevlink = self.req.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
+                                                   year=prevdate.year,
+                                                   month=prevdate.month)
+        nextlink = self.req.build_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 +299,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 +317,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,47 +334,50 @@
         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 = task.start
+            tstop = task.stop
+            if tstart:
+                tstart = todate(tstart)
+                if tstart > lastday:
                     continue
-                the_dates = [task.start]
-            if task.stop:
-                if task.stop < firstday:
+                the_dates = [tstart]
+            if tstop:
+                tstop = todate(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
-                task_descr = _TaskEntry(task, task_colors[task])  
+                day = d.weekday()
+                task_descr = _TaskEntry(task, task_colors[task])
                 dates[day].append(task_descr)
-            
+
         self.w(u'<div id="oneweekcalid">')
         # build schedule
         self.w(u'<table class="omcalendar" id="week">')
@@ -399,22 +386,21 @@
         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>')
         self.w(u'<td style="width:5em;">') # column for hours
@@ -422,52 +408,43 @@
         for h in range(8, 20):
             self.w(u'<div class="hour" %s>'%extra)
             self.w(u'%02d:00'%h)
-            self.w(u'</div>')            
+            self.w(u'</div>')
         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)
             for h in range(8, 20):
                 self.w(u'<div class="hourline" style="top:%sex;">'%((h-7)*8))
-                self.w(u'</div>')            
+                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>')
         self.w(u'</table></div>')
         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,15 +475,15 @@
             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
-                    
+
             height = 100.0*(stop_hour+stop_min/60.0-start_hour-start_min/60.0)/(20-8)
             top = 100.0*(start_hour+start_min/60.0-8)/(20-8)
             left = width*task_desc.index
@@ -516,8 +493,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
                                  )
 
@@ -536,14 +513,16 @@
                 self.w(u'</div>')
             self.w(u'</div>')
 
-            
+
     def _prevnext_links(self, curdate):
-        prevdate = curdate - RelativeDateTime(days=7)
-        nextdate = curdate + RelativeDateTime(days=7)
-        rql = self.rset.rql
-        prevlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
-                                    year=prevdate.year, week=prevdate.iso_week[1])
-        nextlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
-                                    year=nextdate.year, week=nextdate.iso_week[1])
+        prevdate = curdate - timedelta(7)
+        nextdate = curdate + timedelta(7)
+        rql = self.rset.printable_rql()
+        prevlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+                                                   year=prevdate.year,
+                                                   week=prevdate.isocalendar()[1])
+        nextlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+                                                   year=nextdate.year,
+                                                   week=nextdate.isocalendar()[1])
         return prevlink, nextlink
 
--- a/web/views/card.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-"""Specific views for cards
-
-: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.web.views import baseviews
-from logilab.mtconverter import html_escape
-
-_ = unicode
-
-class CardPrimaryView(baseviews.PrimaryView):
-    accepts = ('Card',)
-    skip_attrs = baseviews.PrimaryView.skip_attrs + ('title', 'synopsis', 'wikiid')
-    show_attr_label = False
-
-    def content_title(self, entity):
-        return html_escape(entity.dc_title())
-    
-    def summary(self, entity):
-        return html_escape(entity.dc_description())
-
-
-class CardInlinedView(CardPrimaryView):
-    """hide card title and summary"""
-    id = 'inlined'
-    title = _('inlined view')
-    main_related_section = False
-    
-    def render_entity_title(self, entity):
-        pass
-    
-    def render_entity_metadata(self, entity):
-        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/csvexport.py	Thu May 14 12:50:34 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([[], []])
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/cwproperties.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,343 @@
+"""Specific views for CWProperty
+
+:organization: Logilab
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from logilab.mtconverter import html_escape
+
+from logilab.common.decorators import cached
+
+from cubicweb import UnknownProperty
+from cubicweb.selectors import (one_line_rset, none_rset, implements,
+                                match_user_groups)
+from cubicweb.view import StartupView
+from cubicweb.web import uicfg, stdmsgs
+from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
+from cubicweb.web.formfields import FIELDS, StringField
+from cubicweb.web.formwidgets import Select, Button, SubmitButton
+from cubicweb.web.views import primary
+
+
+# some string we want to be internationalizable for nicer display of eproperty
+# groups
+_('navigation')
+_('ui')
+_('actions')
+_('boxes')
+_('components')
+_('contentnavigation')
+_('navigation.combobox-limit')
+_('navigation.page-size')
+_('navigation.related-limit')
+_('navigation.short-line-size')
+_('ui.date-format')
+_('ui.datetime-format')
+_('ui.default-text-format')
+_('ui.fckeditor')
+_('ui.float-format')
+_('ui.language')
+_('ui.time-format')
+_('open all')
+_('ui.main-template')
+_('ui.site-title')
+_('ui.encoding')
+_('category')
+
+
+def make_togglable_link(nodeid, label, cookiename):
+    """builds a HTML link that switches the visibility & remembers it"""
+    action = u"javascript: toggleVisibility('%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 CWPropertyPrimaryView(primary.PrimaryView):
+    __select__ = implements('CWProperty')
+    skip_none = False
+
+
+class SystemEPropertiesForm(FormViewMixIn, StartupView):
+    id = 'systemepropertiesform'
+    __select__ = none_rset() & match_user_groups('managers')
+
+    title = _('site configuration')
+    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'):
+        """return css class name 'hidden' (collapsed), or '' (open)"""
+        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.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, True)
+        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))
+        # we don't want this in each sub-forms
+        w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+        for label, group, form in sorted((_(g), g, f)
+                                         for g, f in mainopts.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))
+            w(form)
+            w(u'</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 CWProperty, 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('CWProperty')(self.req, None, None)
+            entity.eid = self.req.varmaker.next()
+            entity['pkey'] = key
+            entity['value'] = self.vreg.property_value(key)
+        return entity
+
+    def form(self, keys, splitlabel=False):
+        buttons = [SubmitButton(),
+                   Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+        form = CompositeForm(self.req, domid=None, action=self.build_url(),
+                             form_buttons=buttons,
+                             submitmsg=self.req._('changes applied'))
+        path = self.req.relative_path()
+        if '?' in path:
+            path, params = path.split('?', 1)
+            form.form_add_hidden('__redirectparams', params)
+        form.form_add_hidden('__redirectpath', path)
+        for key in keys:
+            self.form_row(form, key, splitlabel)
+        return form.form_render(display_progress_div=False)
+
+    def form_row(self, form, key, splitlabel):
+        entity = self.entity_for_key(key)
+        if splitlabel:
+            label = key.split('.')[-1]
+        else:
+            label = key
+        subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False)
+        subform.append_field(PropertyValueField(name='value', label=label,
+                                                eidparam=True))
+        subform.vreg = self.vreg
+        subform.form_add_hidden('pkey', key, eidparam=True)
+        form.form_add_subform(subform)
+        return subform
+
+
+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 CWProperty, P pkey K, P value V,'
+                                'P for_user U, U eid %(x)s', {'x': self.user.eid})
+
+    def form_row(self, form, key, splitlabel):
+        subform = super(EPropertiesForm, self).form_row(form, key, splitlabel)
+        # if user is in the managers group and the property is being created,
+        # we have to set for_user explicitly
+        if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
+            subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
+
+
+# eproperty form objects ######################################################
+
+class PlaceHolderWidget(object):
+
+    def render(self, form, field):
+        domid = form.context[field]['id']
+        # empty span as well else html validation fail (label is refering to
+        # this id)
+        return '<div id="div:%s"><span id="%s">%s</span></div>' % (
+            domid, domid, form.req._('select a key first'))
+
+
+class NotEditableWidget(object):
+    def __init__(self, value, msg=None):
+        self.value = value
+        self.msg = msg
+
+    def render(self, form, field):
+        domid = form.context[field]['id']
+        value = '<span class="value" id="%s">%s</span>' % (domid, self.value)
+        if self.msg:
+            value + '<div class="helper">%s</div>' % self.msg
+        return value
+
+
+class PropertyKeyField(StringField):
+    """specific field for CWProperty.pkey to set the value widget according to
+    the selected key
+    """
+    widget = Select
+
+    def render(self, form, renderer):
+        wdg = self.get_widget(form)
+        wdg.attrs['tabindex'] = form.req.next_tabindex()
+        wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
+            form.edited_entity.eid, form.req.next_tabindex())
+        return wdg.render(form, self)
+
+    def vocabulary(self, form):
+        entity = form.edited_entity
+        _ = form.req._
+        if entity.has_eid():
+            return [(_(entity.pkey), entity.pkey)]
+        # key beginning with 'system.' should usually not be edited by hand
+        choices = entity.vreg.user_property_keys()
+        return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
+
+
+class PropertyValueField(StringField):
+    """specific field for CWProperty.value  which will be different according to
+    the selected key type and vocabulary information
+    """
+    widget = PlaceHolderWidget
+
+    def render(self, form, renderer=None, tabindex=None):
+        wdg = self.get_widget(form)
+        if tabindex is not None:
+            wdg.attrs['tabindex'] = tabindex
+        return wdg.render(form, self)
+
+    def form_init(self, form):
+        entity = form.edited_entity
+        if not (entity.has_eid() or 'pkey' in entity):
+            # no key set yet, just include an empty div which will be filled
+            # on key selection
+            return
+        try:
+            pdef = form.vreg.property_info(entity.pkey)
+        except UnknownProperty, ex:
+            self.warning('%s (you should probably delete that property '
+                         'from the database)', ex)
+            msg = form.req._('you should probably delete that property')
+            self.widget = NotEditableWidget(entity.printable_value('value'),
+                                            '%s (%s)' % (msg, ex))
+        if entity.pkey.startswith('system.'):
+            msg = form.req._('value associated to this key is not editable '
+                             'manually')
+            self.widget = NotEditableWidget(entity.printable_value('value'), msg)
+        # XXX race condition when used from CWPropertyForm, should not rely on
+        # instance attributes
+        self.initial = pdef['default']
+        self.help = pdef['help']
+        vocab = pdef['vocabulary']
+        if vocab is not None:
+            if callable(vocab):
+                # list() just in case its a generator function
+                self.choices = list(vocab(form.req))
+            else:
+                self.choices = vocab
+            wdg = Select()
+        else:
+            wdg = FIELDS[pdef['type']].widget()
+            if pdef['type'] == 'Boolean':
+                self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')]
+            elif pdef['type'] in ('Float', 'Int'):
+                wdg.attrs.setdefault('size', 3)
+        self.widget = wdg
+
+
+uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
+uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/cwuser.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,64 @@
+"""Specific views for users
+
+: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 cubicweb.selectors import one_line_rset, implements, match_user_groups
+from cubicweb.view import EntityView
+from cubicweb.web import action
+from cubicweb.web.views import primary
+
+
+class UserPreferencesEntityAction(action.Action):
+    id = 'prefs'
+    __select__ = (one_line_rset() & implements('CWUser') &
+                  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('cwuser/%s'%login, vid='epropertiesform')
+
+
+class FoafView(EntityView):
+    id = 'foaf'
+    __select__ = implements('CWUser')
+
+    title = _('foaf')
+    templatable = False
+    content_type = 'text/xml'
+
+    def call(self):
+        self.w(u'''<?xml version="1.0" encoding="%s"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:rdfs="http://www.w3org/2000/01/rdf-schema#"
+         xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self.req.encoding)
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</rdf:RDF>\n')
+
+    def cell_call(self, row, col):
+        entity = self.complete_entity(row, col)
+        self.w(u'''<foaf:PersonalProfileDocument rdf:about="">
+                      <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:
+            self.w(u'<foaf:family_name>%s</foaf:family_name>\n'
+                   % html_escape(entity.surname))
+        if entity.firstname:
+            self.w(u'<foaf:givenname>%s</foaf:givenname>\n'
+                   % html_escape(entity.firstname))
+        emailaddr = entity.get_email()
+        if emailaddr:
+            self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
+        self.w(u'</foaf:Person>\n')
--- a/web/views/debug.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/debug.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/editcontroller.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """The edit controller, handling form submitting.
 
 :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"
@@ -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()
@@ -68,7 +65,7 @@
         if self._pending_relations:
             for rschema, formparams, x, entity in self._pending_relations:
                 self.handle_relation(rschema, formparams, x, entity, True)
-            
+
         # XXX this processes *all* pending operations of *all* entities
         if form.has_key('__delete'):
             todelete += req.list_form_param('__delete', form, pop=True)
@@ -79,7 +76,7 @@
         if toinsert:
             self.insert_relations(parse_relations_descr(toinsert))
         self.req.remove_pending_operations()
-        
+
     def edit_entity(self, formparams, multiple=False):
         """edit / create / copy an entity and return its eid"""
         etype = formparams['__type']
@@ -87,7 +84,7 @@
         entity.eid = eid = self._get_eid(formparams['eid'])
         edited = self.req.form.get('__maineid') == formparams['eid']
         # let a chance to do some entity specific stuff.
-        entity.pre_web_edit() 
+        entity.pre_web_edit()
         # create a rql query from parameters
         self.relations = []
         self.restrictions = []
@@ -102,14 +99,14 @@
                 self.handle_inlined_relation(rschema, formparams, entity)
         execute = self.req.execute
         if eid is None: # creation or copy
-            if self.relations: 
+            if self.relations:
                 rql = 'INSERT %s X: %s' % (etype, ','.join(self.relations))
             else:
                 rql = 'INSERT %s X' % etype
             if self.restrictions:
                 rql += ' WHERE %s' % ','.join(self.restrictions)
             try:
-                # get the new entity (in some cases, the type might have 
+                # get the new entity (in some cases, the type might have
                 # changed as for the File --> Image mutation)
                 entity = execute(rql, formparams).get_entity(0, 0)
                 eid = entity.eid
@@ -155,7 +152,7 @@
     def _action_apply(self):
         self._default_publish()
         self.reset()
-            
+
     def _action_cancel(self):
         errorurl = self.req.form.get('__errorurl')
         if errorurl:
@@ -166,16 +163,16 @@
         self.delete_entities(self.req.edited_eids(withtype=True))
         return self.reset()
 
-    def _needs_edition(self, rtype, formparams):
+    def _needs_edition(self, rtype, formparams, entity):
         """returns True and and the new value if `rtype` was edited"""
         editkey = 'edits-%s' % rtype
         if not editkey in formparams:
             return False, None # not edited
         value = formparams.get(rtype) or None
-        if (formparams.get(editkey) or None) == value:
+        if entity.has_eid() and (formparams.get(editkey) or None) == value:
             return False, None # not modified
         if value == INTERNAL_FIELD_VALUE:
-            value = None        
+            value = None
         return True, value
 
     def handle_attribute(self, entity, rschema, formparams):
@@ -183,7 +180,7 @@
         attribute described by the given schema if necessary
         """
         attr = rschema.type
-        edition_needed, value = self._needs_edition(attr, formparams)
+        edition_needed, value = self._needs_edition(attr, formparams, entity)
         if not edition_needed:
             return
         # test if entity class defines a special handler for this attribute
@@ -199,27 +196,33 @@
             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)))
-            else:
-                # (filename, mimetype, stream)
+                encoding = entity.attr_metadata(attr, 'encoding')
+                value = Binary(value.encode(encoding))
+            elif value:
+                # value is a  3-uple (filename, mimetype, stream)
                 val = Binary(value[2].read())
                 if not val.getvalue(): # usually an unexistant file
                     value = None
                 else:
+                    val.filename = value[0]
+                    # ignore browser submitted MIME type since it may be buggy
+                    # XXX add a config option to tell if we should consider it
+                    # or not?
+                    #if entity.e_schema.has_metadata(attr, 'format'):
+                    #    key = '%s_format' % attr
+                    #    formparams[key] = value[1]
+                    #    self.relations.append('X %s_format %%(%s)s'
+                    #                          % (attr, key))
                     # XXX suppose a File compatible schema
-                    val.filename = value[0]
-                    if entity.has_format(attr):
-                        key = '%s_format' % attr
-                        formparams[key] = value[1]
-                        self.relations.append('X %s_format %%(%s)s'
-                                              % (attr, key))
                     if entity.e_schema.has_subject_relation('name') \
                            and not formparams.get('name'):
                         formparams['name'] = value[0]
@@ -281,7 +284,7 @@
             self.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
         elif entity.has_eid():
             self.handle_relation(rschema, formparams, 'subject', entity, late)
-        
+
     def handle_relation(self, rschema, formparams, x, entity, late=False):
         """handle edition for the (rschema, x) relation of the given entity
         """
@@ -305,15 +308,17 @@
         if x == 'object' or not rschema.inlined or not values:
             # this is not an inlined relation or no values specified,
             # explicty remove relations
+            rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+                subjvar, rschema, objvar)
             for reid in origvalues.difference(values):
-                rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
-                    subjvar, rschema, objvar)
                 self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
-        rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
-            subjvar, rschema, objvar)
-        for reid in values.difference(origvalues):
-            self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
-    
+        seteids = values.difference(origvalues)
+        if seteids:
+            rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+                subjvar, rschema, objvar)
+            for reid in seteids:
+                self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
+
     def _get_eid(self, eid):
         # should be either an int (existant entity) or a variable (to be
         # created entity)
@@ -343,5 +348,5 @@
                 raise Exception('duh')
             result.add(eid)
         return result
-        
 
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editforms.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,404 @@
+"""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.mtconverter import html_escape
+
+from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
+                                specified_etype_implements, yes)
+from cubicweb.utils import make_uid
+from cubicweb.view import EntityView
+from cubicweb.common import tags
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
+from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
+from cubicweb.web.formfields import RelationField
+from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton, Select
+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 toggleable_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(FormViewMixIn, EntityView):
+    """form used to confirm deletion of some entities"""
+    id = 'deleteconf'
+    title = _('delete')
+    # 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', copy_nav_params=True,
+                             action=self.build_url('edit'), onsubmit=None,
+                             form_buttons=[Button(stdmsgs.YES, cwaction='delete'),
+                                           Button(stdmsgs.NO, cwaction='cancel')])
+        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 ClickAndEditFormView(FormViewMixIn, 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 inlineValidateAttributeForm('%(divid)s-form', '%(rtype)s', "
+                "'%(eid)s', '%(divid)s', %(reload)s, '%(default)s');")
+    ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+
+    def cell_call(self, row, col, rtype=None, role='subject', reload=False,
+                  vid='textoutofcontext', default=None):
+        """display field to edit entity's `rtype` relation on double-click"""
+        rschema = self.schema.rschema(rtype)
+        entity = self.entity(row, col)
+        if not default:
+            default = self.req._('not specified')
+        if rschema.is_final():
+            if getattr(entity, rtype) is None:
+                value = default
+            else:
+                value = entity.printable_value(rtype)
+        else:
+            rset = entity.related(rtype, role)
+            # XXX html_escape but that depends of the actual vid
+            value = html_escape(self.view(vid, rset, 'null') or default)
+        if not entity.has_perm('update'):
+            self.w(value)
+            return
+        if rschema.is_final():
+            form = self._build_attribute_form(entity, value, rtype, role, reload, row, col, default)
+        else:
+            form = self._build_relation_form(entity, value, rtype, role, row, col, vid, default)
+        form.form_add_hidden(u'__maineid', entity.eid)
+        renderer = FormRenderer(display_label=False, display_help=False,
+                                display_fields=[(rtype, role)],
+                                table_class='', button_bar_class='buttonbar',
+                                display_progress_div=False)
+        self.w(form.form_render(renderer=renderer))
+
+    def _build_relation_form(self, entity, value, rtype, role, row, col, vid, default):
+        entity = self.entity(row, col)
+        divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
+        event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : vid,
+                      'default' : default, 'role' : role}
+        form = EntityFieldsForm(self.req, None, entity=entity, action='#',
+                                domid='%s-form' % divid,
+                                cssstyle='display: none',
+                                onsubmit=("return inlineValidateRelationForm('%(divid)s-form', '%(rtype)s', "
+                                          "'%(role)s', '%(eid)s', '%(divid)s', '%(vid)s', '%(default)s');" %
+                                          event_data),
+                                form_buttons=[SubmitButton(),
+                                              Button(stdmsgs.BUTTON_CANCEL,
+                                                     onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" %\
+                                                         (entity.eid, rtype, divid))])
+        form.append_field(RelationField(name=rtype, role=role, sort=True,
+                                        widget=Select(),
+                                        label=u' '))
+        self.w(tags.div(value, klass='editableField', id=divid,
+                        ondblclick=self.ondblclick % event_data))
+        return form
+
+    def _build_attribute_form(self, entity, value, rtype, role, reload, row, col, default):
+        eid = entity.eid
+        divid = 'd%s' % make_uid('%s-%s' % (rtype, eid))
+        event_data = {'divid' : divid, 'eid' : eid, 'rtype' : rtype,
+                      'reload' : dumps(reload), 'default' : default}
+        buttons = [SubmitButton(stdmsgs.BUTTON_OK),
+                   Button(stdmsgs.BUTTON_CANCEL,
+                          onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (
+                              eid, rtype, divid))]
+        form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                       row=row, col=col, form_buttons=buttons,
+                                       domid='%s-form' % divid, action='#',
+                                       cssstyle='display: none',
+                                       onsubmit=self.onsubmit % event_data)
+        self.w(tags.div(value, klass='editableField', id=divid,
+                        ondblclick=self.ondblclick % event_data))
+        return form
+
+
+class EditionFormView(FormViewMixIn, 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')
+    renderer = EntityFormRenderer()
+
+    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, entity.rset,
+                                       row=entity.row, col=entity.col, entity=entity,
+                                       submitmsg=self.submited_message())
+        self.init_form(form, entity)
+        self.w(form.form_render(renderer=self.renderer, 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)
+        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.
+        entity.complete()
+        self.newentity = copy(entity)
+        self.copying = entity
+        self.initialize_varmaker()
+        self.newentity.eid = self.varmaker.next()
+        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(self.newentity)
+        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(eid_param('__cloned_eid', entity.eid),
+                                 self.copying.eid)
+        for rschema, _, role in form.relations_by_category(form.attrcategories,
+                                                           'add'):
+            if not rschema.is_final():
+                # ensure relation cache is filed
+                rset = self.copying.related(rschema, role)
+                self.newentity.set_related_cache(rschema, role, rset)
+
+    def submited_message(self):
+        """return the message that will be displayed on successful edition"""
+        return self.req._('entity copied')
+
+
+class TableEditForm(CompositeForm):
+    id = 'muledit'
+    domid = 'entityForm'
+    onsubmit = "return validateForm('%s', null);" % domid
+    form_buttons = [SubmitButton(_('validate modifications on selected items')),
+                    ResetButton(_('revert changes'))]
+
+    def __init__(self, req, rset, **kwargs):
+        kwargs.setdefault('__redirectrql', rset.printable_rql())
+        super(TableEditForm, self).__init__(req, rset, **kwargs)
+        for row in xrange(len(self.rset)):
+            form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                           row=row, attrcategories=('primary',),
+                                           set_error_url=False)
+            # XXX rely on the EntityCompositeFormRenderer to put the eid input
+            form.remove_field(form.field_by_name('eid'))
+            self.form_add_subform(form)
+
+
+class TableEditFormView(FormViewMixIn, 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)
+        self.w(form.form_render(renderer=EntityCompositeFormRenderer()))
+
+
+class InlineEntityEditionFormView(FormViewMixIn, 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"""
+        form = self.vreg.select_object('forms', 'edition', self.req, None,
+                                       entity=entity, set_error_url=False)
+        self.add_hiddens(form, entity, peid, rtype, role)
+        divid = '%s-%s-%s' % (peid, rtype, entity.eid)
+        title = self.schema.rschema(rtype).display_name(self.req, role)
+        removejs = self.removejs % (peid, rtype,entity.eid)
+        self.w(form.form_render(renderer=EntityInlinedFormRenderer(), divid=divid,
+                                title=title, removejs=removejs,**kwargs))
+
+    def add_hiddens(self, form, entity, peid, rtype, role):
+        # to ease overriding (see cubes.vcsfile.views.forms for instance)
+        if self.keep_entity(form, 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))
+
+    def keep_entity(self, form, entity, peid, rtype):
+        if not entity.has_eid():
+            return True
+        # are we regenerating form because of a validation error ?
+        if form.form_previous_values:
+            cdvalues = self.req.list_form_param(eid_param(rtype, peid),
+                                                form.form_previous_values)
+            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'))
+    removejs = "removeInlineForm('%s', '%s', '%s')"
+
+    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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,206 @@
+"""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.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')
+
+    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,
+                                       self.rset, entity=entity)
+        field = form.field_by_name(rschema, target, entity.e_schema)
+        limit = self.req.property_value('navigation.combobox-limit')
+        for eview, reid in form.form_field_vocabulary(field, limit):
+            if reid is None:
+                options.append('<option class="separator">-- %s --</option>'
+                               % 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/emailaddress.py	Thu May 14 12:50:34 2009 +0200
@@ -1,24 +1,25 @@
 """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 import baseviews, primary
 
-class EmailAddressPrimaryView(baseviews.PrimaryView):
-    accepts = ('EmailAddress',)
-    
+class EmailAddressPrimaryView(primary.PrimaryView):
+    __select__ = implements('EmailAddress')
+
     def cell_call(self, row, col, skipeids=None):
         self.skipeids = skipeids
         super(EmailAddressPrimaryView, self).cell_call(row, col)
-        
-    def render_entity_attributes(self, entity, siderelations):
+
+    def render_entity_attributes(self, entity):
         self.w(u'<h3>')
         entity.view('oneline', w=self.w)
         if not entity.canonical:
@@ -51,7 +52,7 @@
             emailofstr = ', '.join(e.view('oneline') for e in emailof)
             self.field(display_name(self.req, 'use_email', 'object'), emailofstr)
 
-    def render_entity_relations(self, entity, siderelations):
+    def render_entity_relations(self, entity):
         for i, email in enumerate(entity.related_emails(self.skipeids)):
             self.w(u'<div class="%s">' % (i%2 and 'even' or 'odd'))
             email.view('oneline', w=self.w, contexteid=entity.eid)
@@ -59,18 +60,19 @@
 
 
 class EmailAddressShortPrimaryView(EmailAddressPrimaryView):
-    accepts = ('EmailAddress',)
+    __select__ = implements('EmailAddress')
     id = 'shortprimary'
     title = None # hidden view
-    def render_entity_attributes(self, entity, siderelations):
+
+    def render_entity_attributes(self, entity):
         self.w(u'<h5>')
         entity.view('oneline', w=self.w)
         self.w(u'</h5>')
 
-    
+
 class EmailAddressOneLineView(baseviews.OneLineView):
-    accepts = ('EmailAddress',)
-    
+    __select__ = implements('EmailAddress')
+
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
         if entity.reverse_primary_email:
@@ -89,8 +91,8 @@
     'mailto:'"""
 
     id = 'mailto'
-    accepts = ('EmailAddress',)
-    
+    __select__ = implements('EmailAddress')
+
     def cell_call(self, row, col, **kwargs):
         entity = self.entity(row, col)
         if entity.reverse_primary_email:
@@ -105,15 +107,15 @@
             mailto = "mailto:%s" % entity.display_address()
         self.w(u'<a href="%s">%s</a>' % (html_escape(mailto),
                                          html_escape(entity.display_address())))
-            
+
         if entity.alias:
             self.w(u'&gt;\n')
         if entity.reverse_primary_email:
             self.w(u'</b>')
 
-    
+
 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/embedding.py	Thu May 14 12:50:34 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
@@ -29,11 +29,10 @@
     """template embeding an external web pages into CubicWeb web interface
     """
     id = 'external'
-    
+
     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,26 +91,13 @@
     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)
         url = urljoin(self.req.base_url(), entity.embeded_url())
@@ -121,7 +119,7 @@
     def __init__(self, prefix, custom_css=None):
         self.prefix = prefix
         self.custom_css = custom_css
-        
+
     def __call__(self, match):
         original_url = match.group(1)
         url = self.prefix + urlquote(original_url, safe='')
@@ -132,12 +130,13 @@
                 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
         self.tag = tag
         self.custom_css = custom_css
-    
+
     def __call__(self, match):
         original_url = match.group(1)
         if '://' in original_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	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-"""Specific views for EProperty
-
-
-:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.web.views import baseviews
-
-class EPropertyPrimaryView(baseviews.PrimaryView):
-    accepts = ('EProperty',)
-    skip_none = False
--- a/web/views/error.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/error.py	Thu May 14 12:50:34 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'
@@ -24,5 +24,3 @@
         _ = self.req._
         self.w(u"<h1>%s</h1>" %
                _('an error occured, the request cannot be fulfilled'))
-    
-
--- a/web/views/euser.py	Thu May 14 12:50:14 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-"""Specific views for users
-
-: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.decorators import cached
-from logilab.mtconverter import html_escape
-
-from cubicweb.schema import display_name
-from cubicweb.web import INTERNAL_FIELD_VALUE
-from cubicweb.web.form import EntityForm
-from cubicweb.web.views.baseviews import PrimaryView, EntityView
-
-class EUserPrimaryView(PrimaryView):
-    accepts = ('EUser',)
-    skip_attrs = ('firstname', 'surname')
-    
-    def iter_relations(self, entity):
-        # don't want to display user's entities
-        for rschema, targetschemas, x in super(EUserPrimaryView, self).iter_relations(entity):
-            if x == 'object' and rschema.type in ('owned_by', 'for_user'):
-                continue
-            yield rschema, targetschemas, x
-
-    def content_title(self, entity):
-        return entity.name()
-
-    def is_side_related(self, rschema, eschema):
-        return  rschema.type in ['interested_in', 'tags', 
-                                 'todo_by', 'bookmarked_by',
-                                 ]
-class FoafView(EntityView):
-    id = 'foaf'
-    accepts = ('EUser',)
-    title = _('foaf')
-    templatable = False
-    content_type = 'text/xml'
-
-    def call(self):
-        self.w(u'''<?xml version="1.0" encoding="%s"?>
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-         xmlns:rdfs="http://www.w3org/2000/01/rdf-schema#"
-         xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self.req.encoding)
-        for i in xrange(self.rset.rowcount):
-            self.cell_call(i, 0)
-        self.w(u'</rdf:RDF>\n')
-
-    def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
-        self.w(u'''<foaf:PersonalProfileDocument rdf:about="">
-                      <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:
-            self.w(u'<foaf:family_name>%s</foaf:family_name>\n'
-                   % html_escape(entity.surname))
-        if entity.firstname:
-            self.w(u'<foaf:givenname>%s</foaf:givenname>\n'
-                   % html_escape(entity.firstname))
-        emailaddr = entity.get_email()
-        if emailaddr:
-            self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
-        self.w(u'</foaf:Person>\n')
-                   
-class EditGroups(EntityForm):
-    """displays a simple euser / egroups editable table"""
-    
-    id = 'editgroups'
-    accepts = ('EUser',)
-    
-    def call(self):
-        self.req.add_css('cubicweb.acl.css')            
-        _ = self.req._
-        self.w(u'<form id="editgroup" method="post" action="edit">')
-        self.w(u'<table id="groupedit">\n')
-        self.w(u'<tr>')
-        self.w(u'<th>%s</th>' % display_name(self.req, 'EUser'))
-        self.w(u''.join(u'<th>%s</th>' % _(gname) for geid, gname in self.egroups))
-        self.w(u'</tr>')
-        for row in xrange(len(self.rset)):
-            self.build_table_line(row)
-        self.w(u'</table>')
-        self.w(u'<fieldset>')
-        self.w(self.button_cancel())
-        self.w(self.button_ok())
-        self.w(u'</fieldset>')
-        self.w(u'</form>')
-
-
-    def build_table_line(self, row):
-        euser = self.entity(row)
-        euser_groups = [group.name for group in euser.in_group]
-        if euser_groups:
-            self.w(u'<tr>')
-        else:
-            self.w(u'<tr class="nogroup">')
-        self.w(u'<th><fieldset>')
-        self.w(u'<input type="hidden" name="eid" value="%s" />' % euser.eid)
-        self.w(u'<input type="hidden" name="__type:%s" value="EUser" />' % euser.eid)
-        # this should not occur (for now) since in_group relation is mandatory
-        if not euser_groups:
-            self.w(u'<input type="hidden" name="edits-in_group:%s" value="%s">' %
-                   (euser.eid, INTERNAL_FIELD_VALUE))
-        self.w(euser.dc_title())
-        self.w(u'</fieldset></th>')
-        for geid, gname in self.egroups:
-            self.w(u'<td><fieldset>')
-            if gname in euser_groups:
-                self.w(u'<input type="hidden" name="edits-in_group:%s" value="%s" />' %
-                       (euser.eid, geid))
-                self.w(u'<input type="checkbox" name="in_group:%s" value="%s" checked="checked" />' %
-                       (euser.eid, geid))
-            else:
-                self.w(u'<input type="checkbox" name="in_group:%s" value="%s" />' %
-                       (euser.eid, geid))
-            self.w(u'</fieldset></td>')
-        self.w(u'</tr>\n')
-
-        
-    @property
-    @cached
-    def egroups(self):
-        groups = self.req.execute('Any G, N ORDERBY N WHERE G is EGroup, G name N')
-        return [(geid, gname) for geid, gname in groups.rows if gname != 'owners']
-                
-        
--- a/web/views/facets.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/facets.py	Thu May 14 12:50:34 2009 +0200
@@ -10,26 +10,27 @@
 
 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)():
         return 1
-    return 0    
+    return 0
 
 
 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
@@ -41,7 +42,7 @@
         be used by the facet
         """
         return {}
-        
+
     def _get_context(self, view):
         context = getattr(view, 'filter_box_context_info', lambda: None)()
         if context:
@@ -51,14 +52,14 @@
             vid, divid = None, 'pageContent'
             paginate = view and view.need_navigation
         return rset, vid, divid, paginate
-        
+
     def call(self, view=None):
         req = self.req
         req.add_js( ('cubicweb.ajax.js', 'cubicweb.formfilter.js') )
         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:
@@ -108,7 +109,7 @@
         return self.vreg.possible_vobjects('facets', self.req, rset,
                                            context='facetbox',
                                            filtered_variable=mainvar)
-        
+
 # facets ######################################################################
 
 class CreatedByFacet(RelationFacet):
@@ -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'
@@ -143,7 +144,7 @@
         """
         etypes = self.rset.column_types(0)
         return sorted((self.req._(etype), etype) for etype in etypes)
-    
+
     def add_rql_restrictions(self):
         """add restriction for this facet into the rql syntax tree"""
         value = self.req.form.get(self.id)
@@ -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'
@@ -161,7 +162,7 @@
     @property
     def title(self):
         return self.req._('has_text')
-    
+
     def get_widget(self):
         """return the widget instance to use to display this facet
 
--- a/web/views/ibreadcrumbs.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/ibreadcrumbs.py	Thu May 14 12:50:34 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
@@ -22,15 +21,14 @@
 def bc_title(entity):
     textsize = entity.req.property_value('navigation.short-line-size')
     return html_escape(cut(entity.dc_title(), textsize))
-    
+
 
 class BreadCrumbEntityVComponent(EntityVComponent):
     id = 'breadcrumbs'
     # 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
@@ -54,7 +52,7 @@
                 self.w(u"\n")
                 self.wpath_part(parent, entity, i == len(path) - 1)
             self.w(u'</span>')
-            
+
     def wpath_part(self, part, contextentity, last=False):
         if isinstance(part, Entity):
             if last and part.eid == contextentity.eid:
@@ -69,11 +67,11 @@
         else:
             textsize = self.req.property_value('navigation.short-line-size')
             self.w(cut(unicode(part), textsize))
-        
+
 
 class BreadCrumbComponent(BreadCrumbEntityVComponent):
     __registry__ = 'components'
-    __selectors__ = (one_line_rset, implement_interface)
+    __select__ = (one_line_rset() & implements(IBreadCrumbs))
     visible = True
 
 
--- a/web/views/idownloadable.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/idownloadable.py	Thu May 14 12:50:34 2009 +0200
@@ -1,21 +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"
+_ = unicode
 
 from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape
 
+from cubicweb.view import EntityView
+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
+from cubicweb.web.views import primary, 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
@@ -32,34 +39,26 @@
     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)
         download_box(self.w, entity, title, label)
 
 
-class DownloadView(baseviews.EntityView):
-    """this view is replacing the deprecated 'download' controller and allow downloading
-    of entities providing the necessary interface
+class DownloadView(EntityView):
+    """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'
@@ -83,34 +82,24 @@
         self.w(self.complete_entity(0).download_data())
 
 
-class DownloadLinkView(baseviews.EntityView):
+class DownloadLinkView(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)
         url = html_escape(entity.download_url())
         self.w(u'<a href="%s">%s</a>' % (url, html_escape(title or entity.dc_title())))
 
 
-                                                                                
-class IDownloadablePrimaryView(baseviews.PrimaryView):
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (IDownloadable,)
-    # XXX File/Image attributes but this is not specified in the IDownloadable interface
-    skip_attrs = baseviews.PrimaryView.skip_attrs + ('data', 'name')
+class IDownloadablePrimaryView(primary.PrimaryView):
+    __select__ = implements(IDownloadable)
 
-    def render_entity_title(self, entity):
-        self.w(u'<h1>%s %s</h1>'
-               % (entity.dc_type().capitalize(),
-                  html_escape(entity.dc_title())))
-    
-    def render_entity_attributes(self, entity, siderelations):
-        super(IDownloadablePrimaryView, self).render_entity_attributes(entity, siderelations)
+    def render_entity_attributes(self, entity):
+        super(IDownloadablePrimaryView, self).render_entity_attributes(entity)
         self.w(u'<div class="content">')
         contenttype = entity.download_content_type()
         if contenttype.startswith('image/'):
@@ -126,17 +115,10 @@
                 msg = self.req._("can't display data, unexpected error: %s") % ex
                 self.w('<div class="error">%s</div>' % msg)
         self.w(u'</div>')
-            
-    def is_side_related(self, rschema, eschema):
-        """display all relations as side related"""
-        return True
 
 
 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"""
@@ -148,12 +130,12 @@
                (url, name, durl, self.req._('download')))
 
 
-class ImageView(baseviews.EntityView):
-    __selectors__ = (implement_interface, score_entity_selector)
+class ImageView(EntityView):
     id = 'image'
+    __select__ = implements(IDownloadable) & score_entity(is_image)
+
     title = _('image')
-    accepts_interfaces = (IDownloadable,)
-    
+
     def call(self):
         rset = self.rset
         for i in xrange(len(rset)):
@@ -161,13 +143,6 @@
             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)
         #if entity.data_format.startswith('image/'):
--- a/web/views/igeocodable.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/igeocodable.py	Thu May 14 12:50:34 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/iprogress.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """Specific views for entities implementing IProgress
 
 :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
 """
 
@@ -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
 
 
@@ -32,12 +32,10 @@
 
     header_for_COLNAME methods allow to customize header's label
     """
-    
+
     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'),
@@ -89,7 +87,7 @@
     def header_for_milestone(self, ecls):
         """use entity's type as label"""
         return display_name(self.req, ecls.id)
-    
+
     def table_header(self, ecls):
         """builds the table's header"""
         self.w(u'<thead><tr>')
@@ -103,7 +101,7 @@
             self.w(u'<th>%s</th>' % html_escape(colname))
         self.w(u'</tr></thead>\n')
 
-    
+
     ## cell management ########################################################
     def build_project_cell(self, entity):
         """``project`` column cell renderer"""
@@ -119,7 +117,7 @@
     def build_state_cell(self, entity):
         """``state`` column cell renderer"""
         return html_escape(self.req._(entity.state))
-    
+
     def build_eta_date_cell(self, entity):
         """``eta_date`` column cell renderer"""
         if entity.finished():
@@ -133,7 +131,7 @@
             else:
                 formated_date = u'%s %s' % (_('expected:'), eta_date)
         return formated_date
-    
+
     def build_todo_by_cell(self, entity):
         """``todo_by`` column cell renderer"""
         return u', '.join(p.view('outofcontext') for p in entity.contractors())
@@ -154,7 +152,7 @@
         if costdescr:
             return u'%s (%s)' % (totalcost, ', '.join(costdescr))
         return unicode(totalcost)
-    
+
     def build_progress_cell(self, entity):
         """``progress`` column cell renderer"""
         progress =  u'<div class="progress_data">%s (%.2f%%)</div>' % (
@@ -167,7 +165,7 @@
     the ``project`` column
     """
     id = 'ic_progress_table_view'
-    
+
     def call(self):
         view = self.vreg.select_view('progress_table_view', self.req, self.rset)
         columns = list(view.columns)
@@ -175,16 +173,14 @@
             columns.remove('project')
         except ValueError:
             self.info('[ic_progress_table_view] could not remove project from columns')
-        view.dispatch(w=self.w, columns=columns)
+        view.render(w=self.w, columns=columns)
 
 
 class ProgressBarView(EntityView):
     """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')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/isioc.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,93 @@
+"""Specific views for SIOC interfaces
+
+:organization: Logilab
+:copyright: 2003-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.view import EntityView
+from cubicweb.selectors import implements
+from cubicweb.interfaces import ISiocItem, ISiocContainer
+
+class SIOCView(EntityView):
+    id = 'sioc'
+    __select__ = EntityView.__select__ & implements(ISiocItem, ISiocContainer)
+    title = _('sioc')
+    templatable = False
+    content_type = 'text/xml'
+
+    def call(self):
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        self.w(u'''<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:owl="http://www.w3.org/2002/07/owl#"
+             xmlns:foaf="http://xmlns.com/foaf/0.1/"
+             xmlns:sioc="http://rdfs.org/sioc/ns#"
+             xmlns:sioctype="http://rdfs.org/sioc/types#"
+             xmlns:dcterms="http://purl.org/dc/terms/">\n''')
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</rdf:RDF>\n')
+
+    def cell_call(self, row, col):
+        self.wview('sioc_element', self.rset, row=row, col=col)
+
+class SIOCContainerView(EntityView):
+    id = 'sioc_element'
+    __select__ = EntityView.__select__ & implements(ISiocContainer)
+    templatable = False
+    content_type = 'text/xml'
+
+    def cell_call(self, row, col):
+        entity = self.complete_entity(row, col)
+        sioct = html_escape(entity.isioc_type())
+        self.w(u'<sioc:%s rdf:about="%s">\n'
+               % (sioct, html_escape(entity.absolute_url())))
+        self.w(u'<dcterms:title>%s</dcterms:title>'
+               % html_escape(entity.dc_title()))
+        self.w(u'<dcterms:created>%s</dcterms:created>'
+               % entity.creation_date)
+        self.w(u'<dcterms:modified>%s</dcterms:modified>'
+               % entity.modification_date)
+        self.w(u'<!-- FIXME : here be items -->')#entity.isioc_items()
+        self.w(u'</sioc:%s>\n' % sioct)
+
+
+class SIOCItemView(EntityView):
+    id = 'sioc_element'
+    __select__ = EntityView.__select__ & implements(ISiocItem)
+    templatable = False
+    content_type = 'text/xml'
+
+    def cell_call(self, row, col):
+        entity = self.complete_entity(row, col)
+        sioct = html_escape(entity.isioc_type())
+        self.w(u'<sioc:%s rdf:about="%s">\n'
+               %  (sioct, html_escape(entity.absolute_url())))
+        self.w(u'<dcterms:title>%s</dcterms:title>'
+               % html_escape(entity.dc_title()))
+        self.w(u'<dcterms:created>%s</dcterms:created>'
+               % entity.creation_date)
+        self.w(u'<dcterms:modified>%s</dcterms:modified>'
+               % entity.modification_date)
+        if entity.content:
+            self.w(u'<sioc:content>%s</sioc:content>'''
+                   % html_escape(entity.isioc_content()))
+        if entity.related('entry_of'):
+            self.w(u'<sioc:has_container rdf:resource="%s"/>\n'
+                   % html_escape(entity.isioc_container().absolute_url()))
+        if entity.creator:
+            self.w(u'<sioc:has_creator>\n')
+            self.w(u'<sioc:User rdf:about="%s">\n'
+                   % html_escape(entity.creator.absolute_url()))
+            self.w(entity.creator.view('foaf'))
+            self.w(u'</sioc:User>\n')
+            self.w(u'</sioc:has_creator>\n')
+        self.w(u'<!-- FIXME : here be topics -->')#entity.isioc_topics()
+        self.w(u'<!-- FIXME : here be replies -->')#entity.isioc_replies()
+        self.w(u' </sioc:%s>\n' % sioct)
+
--- a/web/views/magicsearch.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/magicsearch.py	Thu May 14 12:50:34 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')
 
@@ -41,7 +41,7 @@
     :param translations: the reverted l10n dict
 
     :type schema: `cubicweb.schema.Schema`
-    :param schema: the application's schema    
+    :param schema: the application's schema
     """
     # var_types is used as a map : var_name / var_type
     vartypes = {}
@@ -107,7 +107,7 @@
         if rtype is None:
             continue
         relation.r_type = rtype
-    
+
 
 
 QUOTED_SRE = re.compile(r'(.*?)(["\'])(.+?)\2')
@@ -145,7 +145,7 @@
             return req.execute(*args)
         finally:
             # rollback necessary to avoid leaving the connection in a bad state
-            req.cnx.rollback() 
+            req.cnx.rollback()
 
     def preprocess_query(self, uquery, req):
         raise NotImplementedError()
@@ -161,10 +161,10 @@
     priority = 0
     def preprocess_query(self, uquery, req):
         return uquery,
-    
+
 
 class QueryTranslator(BaseQueryProcessor):
-    """ parses through rql and translates into schema language entity names 
+    """ parses through rql and translates into schema language entity names
     and attributes
     """
     priority = 2
@@ -185,15 +185,15 @@
     preprocessing query in shortcut form to their RQL form
     """
     priority = 4
-    
+
     def preprocess_query(self, uquery, req):
-        """"""
+        """try to get rql from an unicode query string"""
         args = None
         self.req = req
         try:
             # Process as if there was a quoted part
             args = self._quoted_words_query(uquery)
-        ## No quoted part  
+        ## No quoted part
         except BadRQLQuery:
             words = uquery.split()
             if len(words) == 1:
@@ -205,7 +205,7 @@
             else:
                 args = self._multiple_words_query(words)
         return args
-    
+
     def _get_entity_type(self, word):
         """check if the given word is matching an entity type, return it if
         it's the case or raise BadRQLQuery if not
@@ -214,7 +214,7 @@
         try:
             return trmap(self.config, self.vreg.schema, self.req.lang)[etype]
         except KeyError:
-            raise BadRQLQuery('%s is not a valid entity name' % etype)        
+            raise BadRQLQuery('%s is not a valid entity name' % etype)
 
     def _get_attribute_name(self, word, eschema):
         """check if the given word is matching an attribute of the given entity type,
@@ -261,7 +261,7 @@
         if var is None:
             var = etype[0]
         return '%s %s %s%%(text)s' % (var, searchattr, searchop)
-        
+
     def _two_words_query(self, word1, word2):
         """Specific process for two words query (case (2) of preprocess_rql)
         """
@@ -272,7 +272,7 @@
         # else, suppose it's a shortcut like : Person Smith
         rql = '%s %s WHERE %s' % (etype, etype[0], self._complete_rql(word2, etype))
         return rql, {'text': word2}
-           
+
     def _three_words_query(self, word1, word2, word3):
         """Specific process for three words query (case (3) of preprocess_rql)
         """
@@ -336,20 +336,20 @@
             return self._three_words_query(word1, word2, quoted_part)
             # return ori_rql
         raise BadRQLQuery("unable to handle request %r" % ori_rql)
-    
+
 
- 
+
 class FullTextTranslator(BaseQueryProcessor):
     priority = 10
     name = 'text'
-    
+
     def preprocess_query(self, uquery, req):
         """suppose it's a plain text query"""
         return 'Any X WHERE X has_text %(text)s', {'text': uquery}
 
 
 
-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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/management.py	Thu May 14 12:50:34 2009 +0200
@@ -1,46 +1,74 @@
-"""management and error screens
+"""security management and error screens
 
 
 :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"
+_ = unicode
 
 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
+from cubicweb.web import formwidgets
+from cubicweb.web.form import FieldsForm, EntityFieldsForm
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formrenderers import HTableFormRenderer
+
+SUBMIT_MSGID = _('Submit bug report')
+MAIL_SUBMIT_MSGID = _('Submit bug report by mail')
 
-_ = unicode
+class SecurityViewMixIn(object):
+    """display security information for a given schema """
+    def schema_definition(self, eschema, link=True,  access_types=None):
+        w = self.w
+        _ = self.req._
+        if not access_types:
+            access_types = eschema.ACTIONS
+        w(u'<table class="schemaInfo">')
+        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
+            _("permission"), _('granted to groups'), _('rql expressions')))
+        for access_type in access_types:
+            w(u'<tr>')
+            w(u'<td>%s</td>' % self.req.__('%s_perm' % access_type))
+            groups = eschema.get_groups(access_type)
+            l = []
+            groups = [(_(group), group) for group in groups]
+            for trad, group in sorted(groups):
+                if link:
+                    l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
+                    self.build_url('egroup/%s' % group), group, trad))
+                else:
+                    l.append(u'<div class="%s">%s</div>' % (group, trad))
+            w(u'<td>%s</td>' % u''.join(l))
+            rqlexprs = eschema.get_rqlexprs(access_type)
+            w(u'<td>%s</td>' % u'<br/><br/>'.join(expr.expression for expr in rqlexprs))
+            w(u'</tr>\n')
+        w(u'</table>')
 
-def begin_form(w, entity, redirectvid, redirectpath=None, msg=None):
-    w(u'<form method="post" action="%s">\n' % entity.req.build_url('edit'))
-    w(u'<fieldset>\n')
-    w(u'<input type="hidden" name="__redirectvid" value="%s"/>\n' % redirectvid)
-    w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % (
-        html_escape(redirectpath or entity.rest_path())))
-    w(u'<input type="hidden" name="eid" value="%s"/>\n' % entity.eid)
-    w(u'<input type="hidden" name="%s" value="%s"/>\n' % (
-        eid_param('__type', entity.eid), entity.e_schema))
-    if msg:
-        w(u'<input type="hidden" name="__message" value="%s"/>\n'
-          % html_escape(msg))
+    def has_schema_modified_permissions(self, eschema, access_types):
+        """ return True if eschema's actual permissions are diffrents
+        from the default ones
+        """
+        for access_type in access_types:
+            if eschema.get_rqlexprs(access_type):
+                return True
+            if eschema.get_groups(access_type) != \
+                    frozenset(eschema.get_default_groups()[access_type]):
+                return True
+        return False
 
 
-class SecurityManagementView(EntityView):
+class SecurityManagementView(EntityView, SecurityViewMixIn):
     """display security information for a given entity"""
     id = 'security'
     title = _('security')
+    def call(self):
+        self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+        super(SecurityManagementView, self).call()
 
     def cell_call(self, row, col):
         self.req.add_js('cubicweb.edition.js')
@@ -54,7 +82,7 @@
              html_escape(entity.dc_title())))
         # first show permissions defined by the schema
         self.w('<h2>%s</h2>' % _('schema\'s permissions definitions'))
-        self.schema_definition(entity)
+        self.schema_definition(entity.e_schema)
         self.w('<h2>%s</h2>' % _('manage security'))
         # ownership information
         if self.schema.rschema('owned_by').has_perm(self.req, 'add',
@@ -62,7 +90,7 @@
             self.owned_by_edit_form(entity)
         else:
             self.owned_by_information(entity)
-        # epermissions
+        # cwpermissions
         if 'require_permission' in entity.e_schema.subject_relations():
             w('<h3>%s</h3>' % _('permissions for this entity'))
             reqpermschema = self.schema.rschema('require_permission')
@@ -70,37 +98,17 @@
             if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid):
                 self.require_permission_edit_form(entity)
 
-    def schema_definition(self, entity):
-        w = self.w
-        _ = self.req._
-        w(u'<table class="schemaInfo">')
-        w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
-            _("access type"), _('granted to groups'), _('rql expressions')))
-        for access_type in ('read', 'add', 'update', 'delete'):
-            w(u'<tr>')
-            w(u'<th>%s</th>' % self.req.__('%s_permission' % access_type))
-            groups = entity.e_schema.get_groups(access_type)
-            l = []
-            for group in groups:
-                l.append(u'<a href="%s">%s</a>' % (
-                    self.build_url('egroup/%s' % group), _(group)))
-            w(u'<td>%s</td>' % u', '.join(l))
-            rqlexprs = entity.e_schema.get_rqlexprs(access_type)
-            w(u'<td>%s</td>' % u'<br/>'.join(expr.expression for expr in rqlexprs))
-            w(u'</tr>\n')
-        w(u'</table>')
-
     def owned_by_edit_form(self, entity):
         self.w('<h3>%s</h3>' % self.req._('ownership'))
-        begin_form(self.w, entity, 'security', msg= _('ownerships have been changed'))
-        self.w(u'<table border="0">\n')
-        self.w(u'<tr><td>\n')
-        wdg = entity.get_widget('owned_by')
-        self.w(wdg.edit_render(entity))
-        self.w(u'</td><td>\n')
-        self.w(self.button_ok())
-        self.w(u'</td></tr>\n</table>\n')
-        self.w(u'</fieldset></form>\n')
+        msg = self.req._('ownerships have been changed')
+        form = EntityFieldsForm(self.req, None, entity=entity, submitmsg=msg,
+                                form_buttons=[formwidgets.SubmitButton()],
+                                domid='ownership%s' % entity.eid,
+                                __redirectvid='security',
+                                __redirectpath=entity.rest_path())
+        field = guess_field(entity.e_schema, self.schema.rschema('owned_by'))
+        form.append_field(field)
+        self.w(form.form_render(display_progress_div=False))
 
     def owned_by_information(self, entity):
         ownersrset = entity.related('owned_by')
@@ -131,56 +139,52 @@
             w(u'<table class="schemaInfo">')
             w(u'<tr><th>%s</th><th>%s</th></tr>' % (_("permission"),
                                                     _('granted to groups')))
-            for eperm in entity.require_permission:
+            for cwperm in entity.require_permission:
                 w(u'<tr>')
                 if dellinktempl:
-                    w(u'<td>%s%s</td>' % (dellinktempl % eperm.eid,
-                                          eperm.view('oneline')))
+                    w(u'<td>%s%s</td>' % (dellinktempl % cwperm.eid,
+                                          cwperm.view('oneline')))
                 else:
-                    w(u'<td>%s</td>' % eperm.view('oneline'))
-                w(u'<td>%s</td>' % self.view('csv', eperm.related('require_group'), 'null'))
+                    w(u'<td>%s</td>' % cwperm.view('oneline'))
+                w(u'<td>%s</td>' % self.view('csv', cwperm.related('require_group'), 'null'))
                 w(u'</tr>\n')
             w(u'</table>')
         else:
-            self.w(self.req._('no associated epermissions'))
+            self.w(self.req._('no associated permissions'))
 
     def require_permission_edit_form(self, entity):
         w = self.w
         _ = self.req._
-        newperm = self.vreg.etype_class('EPermission')(self.req, None)
+        newperm = self.vreg.etype_class('CWPermission')(self.req, None)
         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('require_permission', newperm.eid), entity.eid))
-        w(u'<table border="0">\n')
-        w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>&nbsp;</th></tr>\n'
-               % (_("name"), _("label"), _('granted to groups')))
-        if getattr(entity, '__permissions__', None):
-            wdg = StaticComboBoxWidget(self.vreg, self.schema['EPermission'],
-                                       self.schema['name'], self.schema['String'],
-                                       vocabfunc=lambda x: entity.__permissions__)
+        form = EntityFieldsForm(self.req, None, entity=newperm,
+                                form_buttons=[formwidgets.SubmitButton()],
+                                domid='reqperm%s' % entity.eid,
+                                __redirectvid='security',
+                                __redirectpath=entity.rest_path())
+        form.form_add_hidden('require_permission', entity.eid, role='object',
+                             eidparam=True)
+        permnames = getattr(entity, '__permissions__', None)
+        cwpermschema = newperm.e_schema
+        if permnames is not None:
+            field = guess_field(cwpermschema, self.schema.rschema('name'),
+                                widget=formwidgets.Select({'size': 1}),
+                                choices=permnames)
         else:
-            wdg = newperm.get_widget('name')
-        w(u'<tr><td>%s</td>\n' % wdg.edit_render(newperm))
-        wdg = newperm.get_widget('label')
-        w(u'<td>%s</td>\n' % wdg.edit_render(newperm))
-        wdg = newperm.get_widget('require_group')
-        w(u'<td>%s</td>\n' % wdg.edit_render(newperm))
-        w(u'<td>%s</td></tr>\n' % self.button_ok())
-        w(u'</table>')
-        w(u'</fieldset></form>\n')
+            field = guess_field(cwpermschema, self.schema.rschema('name'))
+        form.append_field(field)
+        field = guess_field(cwpermschema, self.schema.rschema('label'))
+        form.append_field(field)
+        field = guess_field(cwpermschema, self.schema.rschema('require_group'))
+        form.append_field(field)
+        self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False)))
 
-    def button_ok(self):
-        return (u'<input class="validateButton" type="submit" name="submit" value="%s"/>'
-                % self.req._(stdmsgs.BUTTON_OK))
 
 
 class ErrorView(AnyRsetView):
     """default view when no result has been found"""
-    __selectors__ = (yes,)
+    __select__ = yes()
     id = 'error'
 
     def page_title(self):
@@ -191,10 +195,10 @@
 
     def call(self):
         req = self.req.reset_headers()
-        _ = req._; w = self.w
+        w = self.w
         ex = req.data.get('ex')#_("unable to find exception information"))
         excinfo = req.data.get('excinfo')
-        title = _('an error occured')
+        title = self.req._('an error occured')
         w(u'<h2>%s</h2>' % title)
         if 'errmsg' in req.data:
             ex = req.data['errmsg']
@@ -218,39 +222,32 @@
             return
         vcconf = self.config.vc_config()
         w(u"<div>")
-        eversion = vcconf.get('cubicweb', _('no version information'))
+        eversion = vcconf.get('cubicweb', self.req._('no version information'))
         # NOTE: tuple wrapping needed since eversion is itself a tuple
         w(u"<b>CubicWeb version:</b> %s<br/>\n" % (eversion,))
-        for pkg in self.config.cubes():
-            pkgversion = vcconf.get(pkg, _('no version information'))
-            w(u"<b>Package %s version:</b> %s<br/>\n" % (pkg, pkgversion))
+        cversions = []
+        for cube in self.config.cubes():
+            cubeversion = vcconf.get(cube, self.req._('no version information'))
+            w(u"<b>Package %s version:</b> %s<br/>\n" % (cube, cubeversion))
+            cversions.append((cube, cubeversion))
         w(u"</div>")
         # creates a bug submission link if SUBMIT_URL is set
         submiturl = self.config['submit-url']
-        if submiturl:
-            binfo = text_error_description(ex, excinfo, req, eversion,
-                                           [(pkg, vcconf.get(pkg, _('no version information')))
-                                            for pkg in self.config.cubes()])
-            w(u'<form action="%s" method="post">\n' % html_escape(submiturl))
-            w(u'<fieldset>\n')
-            w(u'<textarea class="hidden" name="description">%s</textarea>' % html_escape(binfo))
-            w(u'<input type="hidden" name="description_format" value="text/rest"/>')
-            w(u'<input type="hidden" name="__bugreporting" value="1"/>')
-            w(u'<input type="submit" value="%s"/>' % _('Submit bug report'))
-            w(u'</fieldset>\n')
-            w(u'</form>\n')
         submitmail = self.config['submit-mail']
-        if submitmail:
-            binfo = text_error_description(ex, excinfo, req, eversion,
-                                           [(pkg, vcconf.get(pkg, _('no version information')))
-                                            for pkg in self.config.cubes()])
-            w(u'<form action="%s" method="post">\n' % req.build_url('reportbug'))
-            w(u'<fieldset>\n')
-            w(u'<input type="hidden" name="description" value="%s"/>' % html_escape(binfo))
-            w(u'<input type="hidden" name="__bugreporting" value="1"/>')
-            w(u'<input type="submit" value="%s"/>' % _('Submit bug report by mail'))
-            w(u'</fieldset>\n')
-            w(u'</form>\n')
+        if submiturl or submitmail:
+            form = FieldsForm(self.req, set_error_url=False)
+            binfo = text_error_description(ex, excinfo, req, eversion, cversions)
+            form.form_add_hidden('description', binfo)
+            form.form_add_hidden('__bugreporting', '1')
+            if submitmail:
+                form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)]
+                form.action = req.build_url('reportbug')
+                w(form.form_render())
+            if submiturl:
+                form.form_add_hidden('description_format', 'text/rest')
+                form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)]
+                form.action = submiturl
+                w(form.form_render())
 
 
 def exc_message(ex, encoding):
@@ -274,242 +271,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/massmailing.py	Thu May 14 12:50:34 2009 +0200
@@ -1,29 +1,32 @@
 """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, FormViewMixIn
+from cubicweb.web.formrenderers import FormRenderer
+from cubicweb.web.formfields import StringField
+from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton
 
-class SendEmailAction(EntityAction):
+_ = unicode
+
+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}
@@ -33,97 +36,92 @@
                               **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='mailbody'))
+    form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
+                              _('send email'), 'SEND_EMAIL_ICON'),
+                    ImgButton('cancelbutton', "javascript: history.back()",
+                              stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+
+    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'] = ','.join(self.get_allowed_substitutions())
+        return super(MassMailingForm, self).form_field_value(field, values)
+
+    def get_allowed_substitutions(self):
+        attrs = []
+        for coltype in self.rset.column_types(0):
+            eclass = self.vreg.etype_class(coltype)
+            attrs.append(eclass.allowed_massmail_keys())
+        return sorted(reduce(operator.and_, attrs))
 
-    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 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):
+        w(u'<table class="headersform">')
+        for field in fields:
+            if field.name == 'mailbody':
+                w(u'</table>')
+                self._render_toolbar(w, form)
+                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>')
+
+    def _render_toolbar(self, w, form):
+        w(u'<div id="toolbar">')
+        w(u'<ul>')
+        for button in form.form_buttons:
+            w(u'<li>%s</li>' % button.render(form))
+        w(u'</ul>')
+        w(u'</div>')
+
+    def render_buttons(self, w, form):
+        pass
+
+class MassMailingFormView(FormViewMixIn, 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)
-
-
-    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))
-
-    
+        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()))
--- a/web/views/navigation.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/navigation.py	Thu May 14 12:50:34 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
 
@@ -43,7 +44,7 @@
         w(u'[&nbsp;%s&nbsp;]' % u'&nbsp;| '.join(blocklist))
         w(u'&nbsp;%s' % self.next_link(params))
         w(u'</div>')
-        
+
     def index_display(self, start, stop):
         return u'%s - %s' % (start+1, stop+1)
 
@@ -51,11 +52,11 @@
     """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
-    
+
     def display_func(self, rset, col, attrname):
         req = self.req
         if attrname is not None:
@@ -69,7 +70,7 @@
             def index_display(row):
                 return rset.get_entity(row, col).view('text')
         return index_display
-    
+
     def call(self):
         """displays links to navigate accross pages of a result set
 
@@ -145,14 +146,14 @@
 
 
 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)
     if nav:
         # get boundaries before component rendering
         start, stop = nav.page_boundaries()
-        nav.dispatch(w=w)
+        nav.render(w=w)
         params = dict(req.form)
         nav.clean_params(params)
         # make a link to see them all
@@ -165,13 +166,13 @@
 
 # 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
-# XXX deprecated, use paginate
-View.pagination = limit_rset_using_paged_nav
+from cubicweb.view import View
+View.pagination = obsolete('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
 
-def paginate(view, show_all_option=True, w=None):
+def paginate(view, show_all_option=True, w=None, page_size=None):
     limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w,
-                               not view.need_navigation, show_all_option)
+                               not view.need_navigation, show_all_option,
+                               page_size=page_size)
 View.paginate = paginate
 
 class NextPrevNavigationComponent(EntityVComponent):
@@ -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):
@@ -212,7 +212,7 @@
             html_escape(previous.absolute_url()),
             self.req._('i18nprevnext_previous'),
             html_escape(cut(previous.dc_title(), textsize)))
-    
+
     def next_link(self, next, textsize):
         return u'<a href="%s" title="%s">%s &gt;&gt;</a>' % (
             html_escape(next.absolute_url()),
--- a/web/views/old_calendar.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/old_calendar.py	Thu May 14 12:50:34 2009 +0200
@@ -1,30 +1,22 @@
 """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, Date, Time, today, Sunday
+from datetime import date, time, timedelta
 
 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
+from cubicweb.utils import ONEDAY, ONEWEEK, date_range, first_day, last_day, previous_month, next_month, days_in_month
+from cubicweb.selectors import implements
+from cubicweb.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
+# used by i18n tools
 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')
@@ -32,9 +24,7 @@
 
 class _CalendarView(EntityView):
     """base calendar view containing helpful methods to build calendar views"""
-    __registerer__ = priority_registerer
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarViews,)
+    __select__ = implements(ICalendarViews,)
     need_navigation = False
 
     # Navigation building methods / views ####################################
@@ -45,40 +35,45 @@
 <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
+        prev1 = next1 = prev2 = nex2 = date
+        prev1 = previous_month(date, smallshift)
+        next1 = next_month(date, smallshift)
+        prev2 = previous_month(date, bigshift)
+        next2 = next_month(date, bigshift)
+        rql = self.rset.printable_rql()
         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)))
-        
-    
+            html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
+                                       month=prev2.month)),
+            html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
+                                       month=prev1.month)),
+            html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
+                                       month=next1.month)),
+            html_escape(self.build_url(rql=rql, vid=self.id, 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)]
-    
+                for date in date_range(begin, end, incmonth=1)]
+
     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):
+        current_row = [NO_CELL] * first_day.weekday()
+        for daynum in xrange(0, days_in_month(first_day)):
             # build cell day
-            day = first_day + daynum
+            day = first_day + timedelta(daynum)
             events = schedule.get(day)
             if events:
                 events = [u'\n'.join(event) for event in events.values()]
@@ -86,13 +81,13 @@
             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)))
+            if day.weekday() == 6:
+                rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
                 current_row = []
-        current_row.extend([NO_CELL] * (Sunday-day.day_of_week))
+        current_row.extend([NO_CELL] * (6-day.weekday()))
         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)))
+        if day.weekday() != 6:
+            rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
         url = self.build_url(rql=rql, vid='calendarmonth',
                              year=first_day.year, month=first_day.month)
         monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
@@ -109,35 +104,37 @@
         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') 
+        # 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) 
+            for date_ in entity.matching_dates(begin, end):
+                day = date(date_.year, date_.month, date_.day)
+                try:
+                    dt = time(date_.hour, date_.minute, date_.second)
+                except AttributeError:
+                    # date instance
+                    dt = time(0, 0, 0)
                 schedule.setdefault(day, {})
-                schedule[day].setdefault(time, []).append(infos)
+                schedule[day].setdefault(dt, []).append(infos)
         return schedule
-        
+
 
     @staticmethod
-    def get_date_range(day=TODAY, shift=4):
+    def get_date_range(day, 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
+        begin = first_day(previous_month(day, shift))
+        end = last_day(next_month(day, shift))
         return begin, end
 
-
     def _build_ampm_cells(self, daynum, events):
         """create a view without any hourly details.
 
@@ -167,11 +164,11 @@
     id = 'calendaryear'
     title = _('calendar (year)')
 
-    def call(self, year=THIS_YEAR, month=THIS_MONTH):
+    def call(self, year=None, month=None):
         """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)
+        year = year or int(self.req.form.get('year', date.today().year))
+        month = month or int(self.req.form.get('month', date.today().month))
+        center_date = date(year, month, 1)
         begin, end = self.get_date_range(day=center_date)
         schedule = self._mk_schedule(begin, end)
         self.w(self.nav_header(center_date))
@@ -186,22 +183,22 @@
     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)
+    def call(self, year=None, month=None):
+        year = year or int(self.req.form.get('year', date.today().year))
+        month = month or int(self.req.form.get('month', date.today().month))
+        begin = previous_month(date(year, month, 1), 2)
+        end = next_month(date(year, month, 1), 3)
         schedule = self._mk_schedule(begin, end)
-        self.w(self.nav_header(DateTime(year, month), 1, 6))
+        self.w(self.nav_header(date(year, month, 1), 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))
+        self.w(self.nav_header(date(year, month, 1), 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):
+        for cur_month in date_range(begin, end, incmonth=1):
             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)
@@ -211,33 +208,33 @@
         _ = 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:
+            for cur_month in date_range(begin, end, incmonth=1):
+                if day_num >= days_in_month(cur_month):
                     self.w(u'%s%s' % (NO_CELL, NO_CELL))
                 else:
-                    day = DateTime(cur_month.year, cur_month.month, day_num+1)
+                    day = date(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.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.weekday()])[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)
+
+    def call(self, year=None, month=None):
+        year = year or int(self.req.form.get('year', date.today().year))
+        month = month or int(self.req.form.get('month', date.today().month))
+        center_date = date(year, month, 1)
         begin, end = self.get_date_range(day=center_date, shift=1)
         schedule = self._mk_schedule(begin, end)
         calendars = self.build_calendars(schedule, begin, end)
@@ -245,41 +242,41 @@
         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
+
+    def call(self, year=None, week=None):
+        year = year or int(self.req.form.get('year', date.today().year))
+        week = week or int(self.req.form.get('week', date.today().isocalendar()[1]))
+        day0 = date(year, 1, 1)
+        first_day_of_week = day0 - day0.weekday()*ONEDAY + ONEWEEK
+        begin, end = first_day_of_week- ONEWEEK, first_day_of_week + 2*ONEWEEK
         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)]
+        _weeks = [(first_day_of_week-ONEWEEK, first_day_of_week-ONEDAY),
+                  (first_day_of_week, first_day_of_week+6*ONEDAY),
+                  (first_day_of_week+ONEWEEK, first_day_of_week+13*ONEDAY)]
         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:            
+        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))
+                  % (_('week'), monday.isocalendar()[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>' % _(WEEKDAYS[day.weekday()]))
                 self.w(u'<td>%s</td>' % (day.strftime('%Y-%m-%d')))
                 events = schedule.get(day)
                 if events:
@@ -288,35 +285,35 @@
                 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
+        prev1 = date - ONEWEEK * smallshift
+        prev2 = date - ONEWEEK * bigshift
+        next1 = date + ONEWEEK * smallshift
+        next2 = date + ONEWEEK * bigshift
+        rql = self.rset.printable_rql()
         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])))
+            html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[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
+        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
         rql = self.rset.printable_rql()
-        for daynum in xrange(0, first_day.days_in_month):
+        for daynum in xrange(0, days_in_month(first_day)):
             # build cells day
-            day = first_day + daynum
+            day = first_day + timedelta(daynum)
             events = schedule.get(day)
             if events:
                 current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
@@ -325,18 +322,18 @@
                                     AMPM_EMPTY % ("amCell", "am"),
                                     AMPM_EMPTY % ("pmCell", "pm")))
             # store & reset current row on Sundays
-            if day.day_of_week == Sunday:
+            if day.weekday() == 6:
                 url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                                     year=day.year, week=day.iso_week[1])
+                                     year=day.year, week=day.isocalendar()[1])
                 weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                                    day.iso_week[1])
+                                                    day.isocalendar()[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))
+        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
         url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                             year=day.year, week=day.iso_week[1])
-        weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.iso_week[1])
+                             year=day.year, week=day.isocalendar()[1])
+        weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
         rows.append(current_row)
         # build two rows for each week: am & pm
@@ -354,7 +351,7 @@
                              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):
@@ -365,7 +362,7 @@
     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):
+        for cur_month in date_range(begin, end, incmonth=1):
             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)
@@ -375,22 +372,22 @@
         _ = 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:
+            for cur_month in date_range(begin, end, incmonth=1):
+                if day_num >= days_in_month(cur_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)
+                    day = date(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(),
+                    self.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.weekday()])[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"), 
+            self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"),
                               AMPM_EMPTY % ("pmCell", "pm")))
 
 
@@ -403,11 +400,11 @@
         """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
+        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
         rql = self.rset.printable_rql()
-        for daynum in xrange(0, first_day.days_in_month):
+        for daynum in xrange(0, days_in_month(first_day)):
             # build cells day
-            day = first_day + daynum
+            day = first_day + timedelta(daynum)
             events = schedule.get(day)
             if events:
                 current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
@@ -416,19 +413,19 @@
                                     AMPM_EMPTY % ("amCell", "am"),
                                     AMPM_EMPTY % ("pmCell", "pm")))
             # store & reset current row on Sundays
-            if day.day_of_week == Sunday:
+            if day.weekday() == 6:
                 url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                                     year=day.year, week=day.iso_week[1])
+                                     year=day.year, week=day.isocalendar()[1])
                 weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                                    day.iso_week[1])
+                                                    day.isocalendar()[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))
+        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
         url = self.build_url(rql=rql, vid='ampmcalendarweek',
-                             year=day.year, week=day.iso_week[1])
+                             year=day.year, week=day.isocalendar()[1])
         weeklink = '<a href="%s">%s</a>' % (html_escape(url),
-                                            day.iso_week[1])
+                                            day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
         rows.append(current_row)
         # build two rows for each week: am & pm
@@ -446,10 +443,10 @@
                              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))      
-    
+        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
 
-    
+
+
 class AMPMWeekCalendarView(WeekCalendarView):
     """this view renders a 3x1 calendars' table"""
     id = 'ampmcalendarweek'
@@ -465,17 +462,17 @@
                                  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)))
+                WEEK_TITLE % (_('week'), monday.isocalendar()[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"
+                style = day.weekday() % 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]),
+                        len(hours), _(WEEKDAYS[day.weekday()]),
                         self.format_date(day)))
                     w(AMPM_WEEK_CELL % (
                         hours[0].hour, hours[0].minute,
@@ -487,7 +484,7 @@
                                                      '\n'.join(events[hour]))))
                 else:
                     w(AMPM_DAYWEEK_EMPTY % (
-                        _(WEEKDAYS[day.day_of_week]),
+                        _(WEEKDAYS[day.weekday()]),
                         self.format_date(day)))
                     w(WEEK_EMPTY_CELL)
                     w(u'</tr>')
--- a/web/views/owl.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/owl.py	Thu May 14 12:50:34 2009 +0200
@@ -1,11 +1,20 @@
+"""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
 
-OWL_CARD_MAP = {'1': '<rdf:type rdf:resource="&owl;FunctionalProperty"/>',                      
+OWL_CARD_MAP = {'1': '<rdf:type rdf:resource="&owl;FunctionalProperty"/>',
                 '?': '<owl:maxCardinality rdf:datatype="&xsd;int">1</owl:maxCardinality>',
                 '+': '<owl:minCardinality rdf:datatype="&xsd;int">1</owl:minCardinality>',
                 '*': ''
@@ -28,7 +37,7 @@
 <!DOCTYPE rdf:RDF [
         <!ENTITY owl "http://www.w3.org/2002/07/owl#" >
         <!ENTITY xsd "http://www.w3.org/2001/XMLSchema#" >
-]>        
+]>
 <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#"
@@ -39,7 +48,7 @@
 
   <owl:Ontology rdf:about="">
     <rdfs:comment>
-    %(appid)s Cubicweb OWL Ontology                           
+    %(appid)s Cubicweb OWL Ontology
     </rdfs:comment>
   </owl:Ontology>'''
 
@@ -62,7 +71,7 @@
         self.visit_schema(skipmeta=skipmeta)
         if writeprefix:
             self.w(OWL_CLOSING_ROOT)
-        
+
     def visit_schema(self, skiprels=DEFAULT_SKIP_RELS, skipmeta=True):
         """get a layout for a whole schema"""
         entities = sorted([eschema for eschema in self.schema.entities()
@@ -80,8 +89,8 @@
 
     def visit_entityschema(self, eschema, skiprels=()):
         """get a layout for an entity OWL schema"""
-        self.w(u'<owl:Class rdf:ID="%s">'% eschema)         
-        self.w(u'<!-- relations -->')    
+        self.w(u'<owl:Class rdf:ID="%s">'% eschema)
+        self.w(u'<!-- relations -->')
         for rschema, targetschemas, role in eschema.relation_definitions():
             if rschema.type in skiprels:
                 continue
@@ -104,7 +113,7 @@
 ''' % (label, cardtag))
 
         self.w(u'<!-- attributes -->')
-              
+
         for rschema, aschema in eschema.attribute_definitions():
             if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
                 continue
@@ -116,10 +125,10 @@
    <owl:onProperty rdf:resource="#%s"/>
    <rdf:type rdf:resource="&owl;FunctionalProperty"/>
   </owl:Restriction>
-</rdfs:subClassOf>'''                         
+</rdfs:subClassOf>'''
                    % aname)
         self.w(u'</owl:Class>')
-    
+
     def visit_property_schema(self, eschema, skiprels=()):
         """get a layout for property entity OWL schema"""
         for rschema, targetschemas, role in eschema.relation_definitions():
@@ -132,7 +141,7 @@
                 self.w(u'''<owl:ObjectProperty rdf:ID="%s">
  <rdfs:domain rdf:resource="#%s"/>
  <rdfs:range rdf:resource="#%s"/>
-</owl:ObjectProperty>                                                
+</owl:ObjectProperty>
 ''' % (label, eschema, oeschema.type))
 
     def visit_property_object_schema(self, eschema):
@@ -148,16 +157,15 @@
 </owl:DatatypeProperty>'''
                    % (aname, eschema, OWL_TYPE_MAP[aschema.type]))
 
-            
+
 class OWLABOXView(EntityView):
     '''This view represents a part of the ABOX for a given entity.'''
-    
+
     id = 'owlabox'
     title = _('owlabox')
     templatable = False
-    accepts = ('Any',)
     content_type = 'application/xml' # 'text/xml'
-    
+
     def call(self):
         self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
         for i in xrange(self.rset.rowcount):
@@ -167,13 +175,11 @@
     def cell_call(self, row, col, skiprels=DEFAULT_SKIP_RELS):
         self.wview('owlaboxitem', self.rset, row=row, col=col, skiprels=skiprels)
 
-        
+
 class OWLABOXItemView(EntityView):
     '''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):
@@ -202,10 +208,20 @@
             if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
                 continue
             if role == 'object':
-                attr = 'reverse_%s' % rschema.type 
+                attr = 'reverse_%s' % rschema.type
             else:
-                attr = rschema.type        
+                attr = rschema.type
             for x in getattr(entity, attr):
                 self.w(u'<%s>%s %s</%s>' % (attr, x.id, x.eid, attr))
         self.w(u'</%s>'% 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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/plots.py	Thu May 14 12:50:34 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/primary.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,240 @@
+"""The default primary view
+
+: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"
+_ = unicode
+
+from warnings import warn
+
+from logilab.mtconverter import html_escape
+
+from cubicweb import Unauthorized
+from cubicweb.view import EntityView
+from cubicweb.web import uicfg
+
+
+
+class PrimaryView(EntityView):
+    """the full view of an non final entity"""
+    id = 'primary'
+    title = _('primary')
+    show_attr_label = True
+    show_rel_label = True
+    skip_none = True
+    rsection = uicfg.primaryview_section
+    display_ctrl = uicfg.primaryview_display_ctrl
+    main_related_section = True
+
+    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 primary views are indexed
+        """
+        return []
+
+    def cell_call(self, row, col):
+        self.row = row
+        # XXX move render_entity implementation here
+        self.render_entity(self.complete_entity(row, col))
+        self.maxrelated = self.req.property_value('navigation.related-limit')
+
+    def render_entity(self, entity):
+        """return html to display the given entity"""
+        self.render_entity_title(entity)
+        self.render_entity_metadata(entity)
+        # entity's attributes and relations, excluding meta data
+        # if the entity isn't meta itself
+        boxes = self._prepare_side_boxes(entity)
+        if boxes or hasattr(self, 'render_side_related'):
+            self.w(u'<table width="100%"><tr><td style="width: 75%">')
+        self.w(u'<div>')
+        self.w(u'<div class="mainInfo">')
+        try:
+            self.render_entity_attributes(entity)
+        except TypeError: # XXX bw compat
+            warn('siderelations argument of render_entity_attributes is '
+                 'deprecated (%s)' % self.__class__)
+            self.render_entity_attributes(entity, [])
+        self.w(u'</div>')
+        self.content_navigation_components('navcontenttop')
+        if self.main_related_section:
+            try:
+                self.render_entity_relations(entity)
+            except TypeError: # XXX bw compat
+                warn('siderelations argument of render_entity_relations is '
+                     'deprecated')
+                self.render_entity_relations(entity, [])
+        self.w(u'</div>')
+        if boxes or hasattr(self, 'render_side_related'):
+            self.w(u'</td><td>')
+            # side boxes
+            self.w(u'<div class="primaryRight">')
+            if hasattr(self, 'render_side_related'):
+                warn('render_side_related is deprecated')
+                self.render_side_related(entity, [])
+            self.render_side_boxes(boxes)
+            self.w(u'</div>')
+            self.w(u'</td></tr></table>')
+        self.content_navigation_components('navcontentbottom')
+
+
+    def content_navigation_components(self, context):
+        self.w(u'<div class="%s">' % context)
+        for comp in self.vreg.possible_vobjects('contentnavigation',
+                                                self.req, self.rset, row=self.row,
+                                                view=self, context=context):
+            try:
+                comp.render(w=self.w, row=self.row, view=self)
+            except NotImplementedError:
+                warn('component %s doesnt implement cell_call, please update'
+                     % comp.__class__, DeprecationWarning)
+                comp.render(w=self.w, view=self)
+        self.w(u'</div>')
+
+    def render_entity_title(self, entity):
+        """default implementation return dc_title"""
+        title = html_escape(entity.dc_title())
+        if title:
+            self.w(u'<h1><span class="etype">%s</span> %s</h1>'
+                   % (entity.dc_type().capitalize(), title))
+
+    def render_entity_metadata(self, entity):
+        entity.view('metadata', w=self.w)
+        summary = self.summary(entity) # deprecate summary?
+        if summary:
+            self.w(u'<div class="summary">%s</div>' % summary)
+
+    def summary(self, entity):
+        """default implementation return an empty string"""
+        return u''
+
+    def render_entity_attributes(self, entity, siderelations=None):
+        for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
+            vid =  dispctrl.get('vid', 'reledit')
+            if rschema.is_final() or vid == 'reledit':
+                value = entity.view(vid, rtype=rschema.type, role=role)
+            else:
+                rset = self._relation_rset(entity, rschema, role, dispctrl)
+                if rset:
+                    value = self.view(vid, rset)
+                else:
+                    value = None
+            if self.skip_none and (value is None or value == ''):
+                continue
+            self._render_attribute(rschema, value)
+
+    def render_entity_relations(self, entity, siderelations=None):
+        for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
+            rset = self._relation_rset(entity, rschema, role, dispctrl)
+            if rset:
+                self._render_relation(rset, dispctrl, 'autolimited',
+                                      self.show_rel_label)
+
+    def render_side_boxes(self, boxes):
+        """display side related relations:
+        non-meta in a first step, meta in a second step
+        """
+        for box in boxes:
+            if isinstance(box, tuple):
+                label, rset, vid  = box
+                self.w(u'<div class="sideRelated">')
+                self.wview(vid, rset, title=label)
+                self.w(u'</div>')
+            else:
+                try:
+                    box.render(w=self.w, row=self.row)
+                except NotImplementedError:
+                    # much probably a context insensitive box, which only implements
+                    # .call() and not cell_call()
+                    box.render(w=self.w)
+
+    def _prepare_side_boxes(self, entity):
+        sideboxes = []
+        for rschema, tschemas, role, dispctrl in self._section_def(entity, 'sideboxes'):
+            rset = self._relation_rset(entity, rschema, role, dispctrl)
+            if not rset:
+                continue
+            label = display_name(self.req, rschema.type, role)
+            vid = dispctrl.get('vid', 'sidebox')
+            sideboxes.append( (label, rset, vid) )
+        sideboxes += self.vreg.possible_vobjects('boxes', self.req, self.rset,
+                                                 row=self.row, view=self,
+                                                 context='incontext')
+        return sideboxes
+
+    def _section_def(self, entity, where):
+        rdefs = []
+        eschema = entity.e_schema
+        for rschema, tschemas, role in eschema.relation_definitions(True):
+            matchtschemas = []
+            for tschema in tschemas:
+                section = self.rsection.etype_get(eschema, rschema, role,
+                                                  tschema)
+                if section == where:
+                    matchtschemas.append(tschema)
+            if matchtschemas:
+                # XXX pick the latest dispctrl
+                dispctrl = self.display_ctrl.etype_get(eschema, rschema, role,
+                                                       matchtschemas[-1])
+
+                rdefs.append( (rschema, matchtschemas, role, dispctrl) )
+        return sorted(rdefs, key=lambda x: x[-1]['order'])
+
+    def _relation_rset(self, entity, rschema, role, dispctrl):
+        try:
+            if dispctrl.get('limit'):
+                rset = entity.related(rschema.type, role,
+                                      limit=self.maxrelated+1)
+            else:
+                rset = entity.related(rschema.type, role)
+        except Unauthorized:
+            return
+        if 'filter' in dispctrl:
+            rset = dispctrl['filter'](rset)
+        return rset
+
+    def _render_relation(self, rset, dispctrl, defaultvid, showlabel):
+        self.w(u'<div class="section">')
+        if showlabel:
+            self.w(u'<h4>%s</h4>' % self.req._(dispctrl['label']))
+        self.wview(dispctrl.get('vid', defaultvid), rset)
+        self.w(u'</div>')
+
+    def _render_attribute(self, rschema, value, role='subject'):
+        if rschema.is_final():
+            show_label = self.show_attr_label
+        else:
+            show_label = self.show_rel_label
+        label = display_name(self.req, rschema.type, role)
+        self.field(label, value, show_label=show_label, tr=False)
+
+
+class RelatedView(EntityView):
+    id = 'autolimited'
+    def call(self, title=None, **kwargs):
+        # if not too many entities, show them all in a list
+        maxrelated = self.req.property_value('navigation.related-limit')
+        if title:
+            self.w(u'<div class="title"><span>%s</span></div>' % title)
+        if self.rset.rowcount <= maxrelated:
+            if self.rset.rowcount == 1:
+                self.wview('incontext', self.rset, row=0)
+            elif 1 < self.rset.rowcount <= 5:
+                self.wview('csv', self.rset)
+            else:
+                self.w(u'<div>')
+                self.wview('simplelist', self.rset)
+                self.w(u'</div>')
+        # else show links to display related entities
+        else:
+            rql = self.rset.printable_rql()
+            self.rset.limit(maxrelated)
+            self.w(u'<div>')
+            self.wview('simplelist', self.rset)
+            self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
+                                               self.req._('see them all')))
+            self.w(u'</div>')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/schema.py	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,213 @@
+"""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 import tags, uilib
+from cubicweb.web import action
+from cubicweb.web.views import TmpFileViewMixin, primary, baseviews
+
+
+class ViewSchemaAction(action.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 CWRDEFPrimaryView(primary.PrimaryView):
+    __select__ = implements('CWAttribute', 'CWRelation')
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+    def render_entity_title(self, entity):
+        self.w(u'<h1><span class="etype">%s</span> %s</h1>'
+               % (entity.dc_type().capitalize(),
+                  html_escape(entity.dc_long_title())))
+
+
+class CWETypeOneLineView(baseviews.OneLineView):
+    __select__ = implements('CWEType')
+
+    def cell_call(self, row, col, **kwargs):
+        entity = self.entity(row, col)
+        final = entity.final
+        if final:
+            self.w(u'<em class="finalentity">')
+        super(CWETypeOneLineView, self).cell_call(row, col, **kwargs)
+        if final:
+            self.w(u'</em>')
+
+
+# in memory schema views (yams class instances) ###############################
+SKIPPED_RELS = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
+                'has_text',)
+
+class CWETypeSchemaView(primary.PrimaryView):
+    id = 'eschema'
+    __select__ = implements('CWEType')
+    title = _('in memory entity schema')
+    main_related_section = False
+    skip_rels = SKIPPED_RELS
+
+    def render_entity_attributes(self, entity):
+        super(CWETypeSchemaView, self).render_entity_attributes(entity)
+        eschema = self.vreg.schema.eschema(entity.name)
+        viewer = SchemaViewer(self.req)
+        layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels)
+        self.w(uilib.ureport_as_html(layout))
+        if not eschema.is_final():
+            msg = self.req._('graphical schema for %s') % entity.name
+            self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'),
+                            alt=msg))
+
+
+class CWRTypeSchemaView(primary.PrimaryView):
+    id = 'eschema'
+    __select__ = implements('CWRType')
+    title = _('in memory relation schema')
+    main_related_section = False
+
+    def render_entity_attributes(self, entity):
+        super(CWRTypeSchemaView, self).render_entity_attributes(entity)
+        rschema = self.vreg.schema.rschema(entity.name)
+        viewer = SchemaViewer(self.req)
+        layout = viewer.visit_relationschema(rschema)
+        self.w(uilib.ureport_as_html(layout))
+        if not rschema.is_final():
+            msg = self.req._('graphical schema for %s') % entity.name
+            self.w(tags.img(src=entity.absolute_url(vid='eschemagraph'),
+                            alt=msg))
+
+
+# schema images ###############################################################
+
+class ImageView(EntityView):
+    __select__ = implements('CWEType')
+    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 = SKIPPED_RELS
+    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 CWETypeSchemaImageView(TmpFileViewMixin, EntityView):
+    id = 'eschemagraph'
+    __select__ = implements('CWEType')
+
+    content_type = 'image/png'
+    skip_rels = SKIPPED_RELS
+
+    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 CWRTypeSchemaImageView(CWETypeSchemaImageView):
+    __select__ = implements('CWRType')
+
+    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	Thu May 14 12:50:14 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/searchrestriction.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/searchrestriction.py	Thu May 14 12:50:34 2009 +0200
@@ -2,7 +2,7 @@
 a search
 
 :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/web/views/sessions.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/sessions.py	Thu May 14 12:50:34 2009 +0200
@@ -2,18 +2,18 @@
 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
 
 
 class InMemoryRepositorySessionManager(AbstractSessionManager):
     """manage session data associated to a session identifier"""
-    
+
     def __init__(self):
         AbstractSessionManager.__init__(self)
         # XXX require a RepositoryAuthenticationManager which violates
@@ -23,7 +23,7 @@
 
     def current_sessions(self):
         return self._sessions.values()
-    
+
     def get_session(self, req, sessionid):
         """return existing session for the given session identifier"""
         if not sessionid in self._sessions:
@@ -47,13 +47,13 @@
 
     def open_session(self, req):
         """open and return a new session for the given request
-        
+
         :raise ExplicitLogin: if authentication is required
         """
         session = self.authmanager.authenticate(req)
         self._sessions[session.sessionid] = session
         return session
-    
+
     def close_session(self, session):
         """close session on logout or on invalid session detected (expired out,
         corrupted...)
@@ -66,4 +66,3 @@
             # already closed, may occurs if the repository session expired but
             # not the web session
             pass
-    
--- a/web/views/startup.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/startup.py	Thu May 14 12:50:34 2009 +0200
@@ -6,24 +6,38 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
+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.web.httpcache import EtagHTTPCacheManager
-
-_ = unicode
+from cubicweb.view import StartupView
+from cubicweb.selectors import match_user_groups
+from cubicweb.common.uilib import ureport_as_html
+from cubicweb.web import ajax_replace_url, uicfg, httpcache
+from cubicweb.web.views.management import SecurityViewMixIn
 
 
 class ManageView(StartupView):
     id = 'manage'
-    title = _('manage')    
-    http_cache_manager = EtagHTTPCacheManager
+    title = _('manage')
+    http_cache_manager = httpcache.EtagHTTPCacheManager
+
+    @classmethod
+    def vreg_initialization_completed(cls):
+        for eschema in cls.schema.entities():
+            if eschema.schema_entity():
+                uicfg.indexview_etype_section.setdefault(eschema, 'schema')
+            elif eschema.is_subobject(strict=True):
+                uicfg.indexview_etype_section.setdefault(eschema, 'subobject')
+            elif eschema.meta:
+                uicfg.indexview_etype_section.setdefault(eschema, 'system')
+            else:
+                uicfg.indexview_etype_section.setdefault(eschema, 'application')
 
     def display_folders(self):
         return False
-    
+
     def call(self, **kwargs):
         """The default view representing the application's management"""
         self.req.add_css('cubicweb.manageview.css')
@@ -34,7 +48,7 @@
             self.w(u'<table><tr>\n')
             self.w(u'<td style="width:40%">')
             self._main_index()
-            self.w(u'</td><td style="width:60%">')            
+            self.w(u'</td><td style="width:60%">')
             self.folders()
             self.w(u'</td>')
             self.w(u'</tr></table>\n')
@@ -62,22 +76,22 @@
                 href = req.build_url('view', vid='creation', etype='Card', wikiid='index')
                 label = self.req._('create an index page')
             self.w(u'<br/><a href="%s">%s</a>\n' % (html_escape(href), label))
-        
+
     def folders(self):
         self.w(u'<h4>%s</h4>\n' % self.req._('Browse by category'))
-        self.vreg.select_view('tree', self.req, None).dispatch(w=self.w)
-        
+        self.vreg.select_view('tree', self.req, None).render(w=self.w)
+
     def startup_views(self):
         self.w(u'<h4>%s</h4>\n' % self.req._('Startup views'))
         self.startupviews_table()
-        
+
     def startupviews_table(self):
         for v in self.vreg.possible_views(self.req, None):
             if v.category != 'startupview' or v.id in ('index', 'tree', 'manage'):
                 continue
             self.w('<p><a href="%s">%s</a></p>' % (
                 html_escape(v.url()), html_escape(self.req._(v.title).capitalize())))
-        
+
     def entities(self):
         schema = self.schema
         self.w(u'<h4>%s</h4>\n' % self.req._('The repository holds the following entities'))
@@ -86,17 +100,17 @@
         if manager:
             self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('application entities'))
         self.entity_types_table(eschema for eschema in schema.entities()
-                                if not eschema.meta and not eschema.is_subobject(strict=True))
-        if manager: 
+                                if uicfg.indexview_etype_section.get(eschema) == 'application')
+        if manager:
             self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('system entities'))
             self.entity_types_table(eschema for eschema in schema.entities()
-                                    if eschema.meta and not eschema.schema_entity())
-            if 'EFRDef' in schema: # check schema support
+                                if uicfg.indexview_etype_section.get(eschema) == 'system')
+            if 'CWAttribute' in schema: # check schema support
                 self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('schema entities'))
-                self.entity_types_table(schema.eschema(etype)
-                                        for etype in schema.schema_entity_types())
+                self.entity_types_table(eschema for eschema in schema.entities()
+                                        if uicfg.indexview_etype_section.get(eschema) == 'schema')
         self.w(u'</table>')
-        
+
     def entity_types_table(self, eschemas):
         newline = 0
         infos = sorted(self.entity_types(eschemas),
@@ -110,8 +124,8 @@
             self.w(u'<td class="addcol">%s</td><td>%s</td>\n' % (addlink,  etypelink))
             self.w(u'<td class="addcol">%s</td><td>%s</td>\n' % (addlink2, etypelink2))
             self.w(u'</tr>\n')
-        
-        
+
+
     def entity_types(self, eschemas):
         """return a list of formatted links to get a list of entities of
         a each entity's types
@@ -132,7 +146,7 @@
             etypelink = u'&nbsp;<a href="%s">%s</a> (%d)' % (
                 html_escape(url), label, nb)
             yield (label, etypelink, self.add_entity_link(eschema, req))
-    
+
     def add_entity_link(self, eschema, req):
         """creates a [+] link for adding an entity if user has permission to do so"""
         if not eschema.has_perm(req, 'add'):
@@ -141,14 +155,14 @@
             html_escape(self.create_url(eschema.type)),
             self.req.__('add a %s' % eschema))
 
-    
+
 class IndexView(ManageView):
     id = 'index'
     title = _('index')
-    
+
     def display_folders(self):
         return 'Folder' in self.schema and self.req.execute('Any COUNT(X) WHERE X is Folder')[0][0]
-    
+
 
 
 class SchemaView(StartupView):
@@ -158,23 +172,132 @@
     def call(self):
         """display schema information"""
         self.req.add_js('cubicweb.ajax.js')
-        self.req.add_css('cubicweb.schema.css')
+        self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
         withmeta = int(self.req.form.get('withmeta', 0))
+        section = self.req.form.get('sec', '')
         self.w(u'<img src="%s" alt="%s"/>\n' % (
             html_escape(self.req.build_url('view', vid='schemagraph', withmeta=withmeta)),
             self.req._("graphical representation of the application'schema")))
         if withmeta:
             self.w(u'<div><a href="%s">%s</a></div>' % (
-                self.build_url('schema', withmeta=0),
+                html_escape(self.build_url('schema', withmeta=0, sec=section)),
                 self.req._('hide meta-data')))
         else:
             self.w(u'<div><a href="%s">%s</a></div>' % (
-                self.build_url('schema', withmeta=1),
+                html_escape(self.build_url('schema', withmeta=1, sec=section)),
                 self.req._('show meta-data')))
-        self.w(u'<div id="detailed_schema"><a href="%s">%s</a></div>' %
+        self.w(u'<a href="%s">%s</a><br/>' %
                (html_escape(ajax_replace_url('detailed_schema', '', 'schematext',
                                              skipmeta=int(not withmeta))),
                 self.req._('detailed schema view')))
+        if self.req.user.matching_groups('managers'):
+            self.w(u'<a href="%s">%s</a>' %
+                   (html_escape(ajax_replace_url('detailed_schema', '', 'schema_security',
+                                                 skipmeta=int(not withmeta))),
+                self.req._('security')))
+        self.w(u'<div id="detailed_schema">')
+        if section:
+            self.wview(section, None)
+        self.w(u'</div>')
+
+
+class ManagerSchemaPermissionsView(StartupView, SecurityViewMixIn):
+    id = 'schema_security'
+    __select__ = StartupView.__select__ & match_user_groups('managers')
+
+    def call(self, display_relations=True,
+             skiprels=('is', 'is_instance_of', 'identity', 'owned_by', 'created_by')):
+        _ = self.req._
+        formparams = {}
+        formparams['sec'] = self.id
+        formparams['withmeta'] = int(self.req.form.get('withmeta', True))
+        schema = self.schema
+        # compute entities
+        entities = [eschema for eschema in schema.entities()
+                   if not eschema.is_final()]
+        if not formparams['withmeta']:
+            entities = [eschema for eschema in entities
+                        if not eschema.meta]
+        # compute relations
+        relations = []
+        if display_relations:
+            relations = [rschema for rschema in schema.relations()
+                         if not (rschema.is_final() or rschema.type in skiprels)]
+            if not formparams['withmeta']:
+                relations = [rschema for rschema in relations
+                             if not rschema.meta]
+        # index
+        self.w(u'<div id="schema_security"><a id="index" href="index"/>')
+        self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
+        self.w(u'<h4>%s</h4>' %   _('Entities').capitalize())
+        ents = []
+        for eschema in sorted(entities):
+            url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type)
+            ents.append(u'<a class="grey" href="%s">%s</a> (%s)' % (url,  eschema.type, _(eschema.type)))
+        self.w('%s' %  ', '.join(ents))
+        self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
+        rels = []
+        for eschema in sorted(relations):
+            url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type)
+            rels.append(u'<a class="grey" href="%s">%s</a> (%s), ' %  (url , eschema.type, _(eschema.type)))
+        self.w('%s' %  ', '.join(ents))
+        # entities
+        self.display_entities(entities, formparams)
+        # relations
+        if relations:
+            self.display_relations(relations, formparams)
+        self.w(u'</div>')
+
+    def display_entities(self, entities, formparams):
+        _ = self.req._
+        self.w(u'<a id="entities" href="entities"/>')
+        self.w(u'<h2 class="schema">%s</h2>' % _('permissions for entities').capitalize())
+        for eschema in sorted(entities):
+            self.w(u'<a id="%s" href="%s"/>' %  (eschema.type, eschema.type))
+            self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
+            url = html_escape(self.build_url('schema', **formparams) + '#index')
+            self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (url,  self.req.external_resource('UP_ICON'), _('up')))
+            self.w(u'</h3>')
+            self.w(u'<div style="margin: 0px 1.5em">')
+            self.schema_definition(eschema, link=False)
+
+            # display entity attributes only if they have some permissions modified
+            modified_attrs = []
+            for attr, etype in  eschema.attribute_definitions():
+                if self.has_schema_modified_permissions(attr, attr.ACTIONS):
+                    modified_attrs.append(attr)
+            if  modified_attrs:
+                self.w(u'<h4>%s</h4>' % _('attributes with modified permissions:').capitalize())
+                self.w(u'</div>')
+                self.w(u'<div style="margin: 0px 6em">')
+                for attr in  modified_attrs:
+                    self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attr.type, _(attr.type)))
+                    self.schema_definition(attr, link=False)
+                self.w(u'</div>')
+            else:
+                self.w(u'</div>')
+
+
+    def display_relations(self, relations, formparams):
+        _ = self.req._
+        self.w(u'<a id="relations" href="relations"/>')
+        self.w(u'<h2 class="schema">%s </h2>' % _('permissions for relations').capitalize())
+        for rschema in sorted(relations):
+            self.w(u'<a id="%s" href="%s"/>' %  (rschema.type, rschema.type))
+            self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
+            url = html_escape(self.build_url('schema', **formparams) + '#index')
+            self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (url,  self.req.external_resource('UP_ICON'), _('up')))
+            self.w(u'</h3>')
+            self.w(u'<div style="margin: 0px 1.5em">')
+            subjects = [str(subj) for subj in rschema.subjects()]
+            self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (_('subject_plural:'),
+                                                ', '.join( [str(subj) for subj in rschema.subjects()]),
+                                                ', '.join( [_(str(subj)) for subj in rschema.subjects()])))
+            self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (_('object_plural:'),
+                                                ', '.join( [str(obj) for obj in rschema.objects()]),
+                                                ', '.join( [_(str(obj)) for obj in rschema.objects()])))
+            self.schema_definition(rschema, link=False)
+            self.w(u'</div>')
 
 
 class SchemaUreportsView(StartupView):
--- a/web/views/tableview.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/tableview.py	Thu May 14 12:50:34 2009 +0200
@@ -11,13 +11,13 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.common.utils import make_uid
-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.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, htmlescape
+from cubicweb.web import jsonize
 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):
@@ -45,7 +45,7 @@
                                         'displayfilter': displayfilter})
             return self.show_hide_actions(divid, not hidden)
         return ()
-    
+
     def _generate_form(self, divid, baserql, fwidgets, hidden=True, vidargs={}):
         """display a form to filter table's content. This should only
         occurs when a context eid is given
@@ -89,7 +89,7 @@
             else:
                 displaycols = range(len(self.rset.syntax_tree().children[0].selection))
         return displaycols
-    
+
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
              displaycols=None, displayactions=None, actions=(), divid=None,
              cellvids=None, cellattrs=None):
@@ -170,7 +170,7 @@
         if currentlydisplayed:
             return [(showhide, showlabel, 'hidden', '%sShow' % divid),
                     (showhide, hidelabel, None, '%sHide' % divid)]
-        return [(showhide, showlabel, None, '%sShow' % divid), 
+        return [(showhide, showlabel, None, '%sShow' % divid),
                 (showhide, hidelabel, 'hidden', '%sHide' % divid)]
 
     def render_actions(self, divid, actions):
@@ -185,7 +185,7 @@
             menu.append(BoxLink(url, label, klass, ident=ident, escape=True))
         box.render(w=self.w)
         self.w(u'<div class="clear"/>')
-        
+
     def get_columns(self, rqlstdescr, displaycols, headers, subvid, cellvids,
                     cellattrs, mainindex):
         columns = []
@@ -215,15 +215,15 @@
 
             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
-        
+
 
-    def render(self, cellvid, row, col, w):
+    def render_cell(self, cellvid, row, col, w):
         self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w)
-        
+
     def get_rows(self):
         return self.rset
 
@@ -246,18 +246,18 @@
         entity = self.rset.get_entity(row, col)
         return entity.sortvalue()
 
+
 class EditableTableView(TableView):
     id = 'editable-table'
     finalview = 'editable-final'
     title = _('editable-table')
 
-    
+
 class CellView(EntityView):
-    __selectors__ = (nonempty_rset, accept_rset)
-    
+    __select__ = nonempty_rset()
+
     id = 'cell'
-    accepts = ('Any',)
-    
+
     def cell_call(self, row, col, cellvid=None):
         """
         :param row, col: indexes locating the cell value in view's result set
@@ -278,23 +278,22 @@
 
 class InitialTableView(TableView):
     """same display as  table view but consider two rql queries :
-    
+
     * the default query (ie `rql` form parameter), which is only used to select
       this view and to build the filter form. This query should have the same
       structure as the actual without actual restriction (but link to
       restriction variables) and usually with a limit for efficiency (limit set
       to 2 is advised)
-      
+
     * the actual query (`actualrql` form parameter) whose results will be
       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
-    
+
     def call(self, title=None, subvid=None, headers=None, divid=None,
              displaycols=None, displayactions=None):
         """Dumps a table displaying a composite query"""
@@ -328,4 +327,3 @@
 class EditableInitialTableTableView(InitialTableView):
     id = 'editable-initialtable'
     finalview = 'editable-final'
-    
--- a/web/views/tabs.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/tabs.py	Thu May 14 12:50:34 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):
@@ -42,11 +38,11 @@
             'lazyview wants at least : rql, or an eid, or an rset -- or call it with static=True'
         w = w or self.w
         self.req.add_js('cubicweb.lazy.js')
-        urlparams = {'vid' : vid, 'mode' : 'html'}
+        urlparams = {'vid' : vid, 'fname' : 'view'}
         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">' % (
@@ -96,19 +92,25 @@
         return selected_tabs
 
     def render_tabs(self, tabs, default, entity):
+        # tabbed views do no support concatenation
+        # hence we delegate to the default tab if there is more than on entity
+        # in the result set
+        if len(self.rset) > 1:
+            entity.view(default, w=self.w)
+            return
+        # XXX (syt) fix below add been introduced at some point to fix something
+        # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean
+        # way. We must not consider form['rql'] here since it introduces some
+        # other failures on non rql queries (plain text, shortcuts,... handled by
+        # magicsearch) which has a single result whose primary view is using tabs
+        # (https://www.logilab.net/cwo/ticket/342789)
+        #rql = self.req.form.get('rql')
+        #if rql:
+        #    self.req.execute(rql).get_entity(0,0).view(default, w=self.w)
+        #    return
         self.req.add_css('ui.tabs.css')
         self.req.add_js(('ui.core.js', 'ui.tabs.js',
                          'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
-        # tabbed views do no support concatenation
-        # hence we delegate to the default tab
-        form = self.req.form
-        if form.get('vid') == 'primary':
-            entity.view(default, w=self.w)
-            return
-        rql = form.get('rql')
-        if rql:
-            self.req.execute(rql).get_entity(0,0).view(default, w=self.w)
-            return
         # prune tabs : not all are to be shown
         tabs = self.prune_tabs(tabs)
         # select a tab
@@ -142,9 +144,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 +153,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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/timeline.py	Thu May 14 12:50:34 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,10 +27,9 @@
     templatable = False
     content_type = 'application/json'
 
-    __selectors__ = (implement_interface,)
-    accepts_interfaces = (ICalendarable,)
+    __select__ = implements(ICalendarable)
     date_fmt = '%Y/%m/%d'
-    
+
     def call(self):
         events = []
         for entity in self.rset.entities():
@@ -45,13 +43,13 @@
     # FIXME: those properties should be defined by the entity class
     def onclick_url(self, entity):
         return entity.absolute_url()
-    
+
     def onclick(self, entity):
         url = self.onclick_url(entity)
         if url:
             return u"javascript: document.location.href='%s'" % url
         return None
-    
+
     def build_event(self, entity):
         """converts `entity` into a JSON object
         {'start': '1891',
@@ -79,13 +77,13 @@
             event_data['end'] = stop.strftime(self.date_fmt)
         return event_data
 
-    
+
 class TimelineViewMixIn(object):
     widget_class = 'TimelineWidget'
     jsfiles = ('cubicweb.timeline-bundle.js', 'cubicweb.widgets.js',
                'cubicweb.timeline-ext.js', 'cubicweb.ajax.js')
-    
-    def render(self, loadurl, tlunit=None):
+
+    def render_url(self, loadurl, tlunit=None):
         tlunit = tlunit or self.req.form.get('tlunit')
         self.req.add_js(self.jsfiles)
         self.req.add_css('timeline-bundle.css')
@@ -103,22 +101,21 @@
 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)
         rql = self.rset.printable_rql()
         loadurl = self.build_url(rql=rql, vid='timeline-json')
-        self.render(loadurl, tlunit)
-        
-    
+        self.render_url(loadurl, tlunit)
+
+
 class StaticTimelineView(TimelineViewMixIn, StartupView):
     """similar to `TimelineView` but loads data from a static
     JSON file instead of one after a RQL query.
     """
     id = 'static-timeline'
-    
+
     def call(self, loadurl, tlunit=None, wdgclass=None):
-        self.widget_class = wdgclass or self.widget_clas
-        self.render(loadurl, tlunit)
+        self.widget_class = wdgclass or self.widget_class
+        self.render_url(loadurl, tlunit)
--- a/web/views/timetable.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/timetable.py	Thu May 14 12:50:34 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,14 +38,14 @@
 
         # 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 = []
             if task.start and task.stop:
-                if task.start.absdate == task.stop.absdate:
+                if task.start.toordinal() == task.stop.toordinal():
                     the_dates.append(task.start)
                 else:
                     the_dates += date_range( task.start, task.stop )
@@ -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.weekday() in (5, 6) and not empty_line:
                 klass = "odd"
             self.w(u'<tr class="%s">' % klass)
             odd = not odd
--- a/web/views/treeview.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/treeview.py	Thu May 14 12:50:34 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,14 +39,14 @@
             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,35 @@
 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
+    __select__ = implements(ITree)
 
-    def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview'):
-        w = self.w
+    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
-
-@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)
+        self.w(u'</li>')
--- a/web/views/urlpublishing.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/urlpublishing.py	Thu May 14 12:50:34 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,15 +28,15 @@
 
 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):
     """exception used by url evaluators to notify they can't evaluate
     a path
     """
-    
-class URLPublisherComponent(SingletonComponent):
+
+class URLPublisherComponent(Component):
     """associate url's path to view identifier / rql queries,
     by applying a chain of urlpathevaluator components.
 
@@ -50,33 +50,33 @@
     something else than `PathDontMatch` will stop the handlers chain.
     """
     id = 'urlpublisher'
-    
+
     def __init__(self, default_method='view'):
         super(URLPublisherComponent, self).__init__()
         self.default_method = default_method
-        evaluators = []        
+        evaluators = []
         for evaluatorcls in self.vreg.registry_objects('components',
                                                        'urlpathevaluator'):
             # instantiation needed
             evaluator = evaluatorcls(self)
             evaluators.append(evaluator)
         self.evaluators = sorted(evaluators, key=lambda x: x.priority)
-        
+
     def process(self, req, path):
         """given an url (essentialy caracterized by a path on the server,
         but additional information may be found in the request object), return
         a publishing method identifier (eg controller) and an optional result
         set
-        
+
         :type req: `cubicweb.web.Request`
         :param req: the request object
-        
+
         :type path: str
         :param path: the path of the resource to publish
 
         :rtype: tuple(str, `cubicweb.common.utils.ResultSet` or None)
         :return: the publishing method identifier and an optional result set
-        
+
         :raise NotFound: if no handler is able to decode the given path
         """
         parts = [part for part in path.split('/')
@@ -97,7 +97,7 @@
             pmid = self.default_method
         return pmid, rset
 
-        
+
 class URLPathEvaluator(Component):
     __abstract__ = True
     id = 'urlpathevaluator'
@@ -136,7 +136,7 @@
             raise NotFound()
         return None, rset
 
-        
+
 class RestPathEvaluator(URLPathEvaluator):
     """handle path with the form::
 
@@ -149,7 +149,7 @@
         for etype in self.schema.entities():
             etype = str(etype)
             self.etype_map[etype.lower()] = etype
-            
+
     def evaluate_path(self, req, parts):
         if not (0 < len(parts) < 4):
             raise PathDontMatch()
@@ -177,7 +177,7 @@
 
     def cls_rset(self, req, cls):
         return req.execute(cls.fetch_rql(req.user))
-        
+
     def attr_rset(self, req, etype, attrname, value):
         rql = u'Any X WHERE X is %s, X %s %%(x)s' % (etype, attrname)
         if attrname == 'eid':
@@ -211,7 +211,7 @@
             except KeyError:
                 continue
         raise PathDontMatch()
-        
+
 
 class ActionPathEvaluator(URLPathEvaluator):
     """handle path with the form::
--- a/web/views/urlrewrite.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/urlrewrite.py	Thu May 14 12:50:34 2009 +0200
@@ -1,22 +1,19 @@
 """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.common.registerers import accepts_registerer
-from cubicweb.common.appobject import AppObject
+from cubicweb.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
@@ -55,11 +52,9 @@
     """
     __metaclass__ = metarewriter
     __registry__ = 'urlrewriting'
-    __registerer__ = accepts_registerer
     __abstract__ = True
 
     id = 'urlrewriting'
-    accepts = ('Any',)
     priority = 1
 
     def rewrite(self, req, uri):
@@ -83,19 +78,18 @@
         ('/manage', dict(vid='manage')),
         ('/notfound', dict(vid='404')),
         ('/error', dict(vid='error')),
-        (rgx('/schema/([^/]+?)/?'),  dict(vid='eschema', rql=r'Any X WHERE X is EEType, X name "\1"')),
+        (rgx('/schema/([^/]+?)/?'),  dict(vid='eschema', rql=r'Any X WHERE X is CWEType, X name "\1"')),
         (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')),
         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
         (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
         (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
         (rgx('/changelog/?'), dict(vid='changelog')),
         ]
-    
+
     def rewrite(self, req, uri):
         """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 +106,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
@@ -185,7 +179,7 @@
     rules = [
         # rgxp : callback
         (rgx('/search/(.+)'), build_rset(rql=r'Any X WHERE X has_text %(text)s',
-                                         rgxgroups=[('text', 1)])), 
+                                         rgxgroups=[('text', 1)])),
         ]
 
     def rewrite(self, req, uri):
--- a/web/views/vcard.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/vcard.py	Thu May 14 12:50:34 2009 +0200
@@ -1,30 +1,30 @@
 """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 
+_ = unicode
 
 VCARD_PHONE_TYPES = {'home': 'HOME', 'office': 'WORK', 'mobile': 'CELL', 'fax': 'FAX'}
 
-class VCardEUserView(EntityView):
+class VCardCWUserView(EntityView):
     """export a person information as a vcard"""
     id = 'vcard'
     title = _('vcard')
     templatable = False
     content_type = 'text/x-vcard'
-    accepts = ('EUser',)
-        
+    __select__ = implements('CWUser')
 
     def set_request_content_type(self):
         """overriden to set a .vcf filename"""
         self.req.set_content_type(self.content_type, filename='vcard.vcf')
-        
+
     def cell_call(self, row, col):
         self.vcard_header()
         self.vcard_content(self.complete_entity(row, col))
@@ -33,11 +33,11 @@
     def vcard_header(self):
         self.w(u'BEGIN:vcard\n')
         self.w(u'VERSION:3.0\n')
-        
+
     def vcard_footer(self):
         self.w(u'NOTE:this card has been generated by CubicWeb\n')
         self.w(u'END:vcard\n')
-        
+
     def vcard_content(self, entity):
         who = u'%s %s' % (entity.surname or '',
                           entity.firstname or '')
@@ -49,4 +49,4 @@
             w(u'EMAIL;TYPE=INTERNET:%s\n' % email.address)
 
 from logilab.common.deprecation import class_renamed
-VCardEuserView = class_renamed('VCardEuserView', VCardEUserView)
+VCardEuserView = VCardEUserView = class_renamed('VCardEuserView', VCardCWUserView)
--- a/web/views/wdoc.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/wdoc.py	Thu May 14 12:50:34 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, todate
 from cubicweb.common.uilib import rest_publish
 from cubicweb.web import NotFound
 
@@ -53,7 +53,7 @@
         node = index[section.attrib['appendto']]
         idx = None
     return node, idx
-                     
+
 def build_toc(config):
     alltocfiles = reversed(tuple(config.locate_all_files('toc.xml')))
     maintoc = parse(alltocfiles.next()).getroot()
@@ -61,7 +61,7 @@
     index = {}
     build_toc_index(maintoc, index)
     # insert component documentation into the tree according to their toc.xml
-    # file 
+    # file
     for fpath in alltocfiles:
         toc = parse(fpath).getroot()
         for section in toc:
@@ -73,8 +73,8 @@
             section.parent = node
             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,11 +85,10 @@
 # help views ##################################################################
 
 class InlineHelpView(StartupView):
-    __selectors__ = (match_form_params,)
-    form_params = ('fid',)
+    __select__ = match_form_params('fid')
     id = 'wdoc'
     title = _('site documentation')
-    
+
     def call(self):
         fid = self.req.form['fid']
         for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
@@ -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:
@@ -125,26 +124,26 @@
         self.w(u'<div class="docnav">\n')
         previousidx = brothers.index(node) - 1
         if previousidx >= 0:
-            self.navsection(brothers[previousidx], 'prev')            
-        self.navsection(parent, 'up')            
+            self.navsection(brothers[previousidx], 'prev')
+        self.navsection(parent, 'up')
         nextidx = brothers.index(node) + 1
         if nextidx < len(brothers):
-            self.navsection(brothers[nextidx], 'next')            
+            self.navsection(brothers[nextidx], 'next')
         self.w(u'</div>\n')
 
     navinfo = {'prev': ('', 'data/previous.png', _('i18nprevnext_previous')),
                'next': ('', 'data/next.png', _('i18nprevnext_next')),
                'up': ('', 'data/up.png', _('i18nprevnext_up'))}
-               
+
     def navsection(self, node, navtype):
         htmlclass, imgpath, msgid = self.navinfo[navtype]
         self.w(u'<span class="%s">' % htmlclass)
         self.w(u'%s : ' % self.req._(msgid))
         self.w(u'<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):
         sub = subsections(node)
         if not sub:
@@ -155,21 +154,20 @@
         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')
-        
+
 
 
 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'
-    
+
     def call(self):
         fid = self.req.form['fid']
         for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
@@ -187,13 +185,14 @@
     id = 'changelog'
     title = _('What\'s new?')
     maxentries = 25
-    
+
     def call(self):
         rid = 'ChangeLog_%s' % (self.req.lang)
         allentries = []
         title = self.req._(self.title)
         restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
         w = restdata.append
+        today = date.today()
         for fpath in self.config.locate_all_files(rid):
             cl = ChangeLog(fpath)
             encoding = 'utf-8'
@@ -207,9 +206,9 @@
                     w(unicode(line, encoding))
             for entry in cl.entries:
                 if entry.date:
-                    date = strptime(entry.date, '%Y-%m-%d')
+                    edate = todate(strptime(entry.date, '%Y-%m-%d'))
                 else:
-                    date = today()
+                    edate = today
                 messages = []
                 for msglines, submsgs in entry.messages:
                     msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
@@ -218,16 +217,16 @@
                         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))
-                latestdate = date
+                w('~' * len(fdate))
+                latestdate = edate
             for msg in messages:
                 w(u'* %s' % msg)
                 i += 1
@@ -235,4 +234,3 @@
                     break
         w('') # blank line
         self.w(rest_publish(self, '\n'.join(restdata)))
-        
--- a/web/views/wfentities.py	Thu May 14 12:50:14 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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,216 @@
+"""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"
+_ = unicode
+
+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.form import FormViewMixIn
+from cubicweb.web.formfields import StringField,  RichTextField
+from cubicweb.web.formwidgets import HiddenInput, SubmitButton, Button
+from cubicweb.web.views import TmpFileViewMixin
+from cubicweb.web.views.boxes import EditBox
+
+
+# IWorkflowable views #########################################################
+
+class ChangeStateForm(form.EntityFieldsForm):
+    id = 'changestate'
+
+    __method = StringField(name='__method', initial='set_state',
+                           widget=HiddenInput)
+    state = StringField(eidparam=True, widget=HiddenInput)
+    trcomment = RichTextField(label=_('comment:'), eidparam=True)
+    form_buttons = [SubmitButton(stdmsgs.YES),
+                     Button(stdmsgs.NO, cwaction='cancel')]
+
+
+class ChangeStateFormView(FormViewMixIn, 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('CWUser').has_perm(self.req, 'read'):
+            sel += ',U,C'
+            rql += ', WF owned_by U?'
+            displaycols = range(5)
+            headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
+                       _('CWUser'))
+        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('CWEType') & 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 CWETypeWorkflowView(view.EntityView):
+    id = 'workflow'
+    __select__ = implements('CWEType')
+    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 CWETypeWorkflowImageView(TmpFileViewMixin, view.EntityView):
+    id = 'ewfgraph'
+    content_type = 'image/png'
+    __select__ = implements('CWEType')
+
+    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	Thu May 14 12:50:14 2009 +0200
+++ b/web/views/xbel.py	Thu May 14 12:50:34 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,18 +9,20 @@
 
 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):
+class XbelView(XMLView):
     id = 'xbel'
     title = _('xbel')
     templatable = False
-    content_type = 'text/xml' #application/xbel+xml 
-    
+    content_type = 'text/xml' #application/xbel+xml
+
     def cell_call(self, row, col):
         self.wview('xbelitem', self.rset, row=row, col=col)
-        
+
     def call(self):
         """display a list of entities by calling their <item_vid> view"""
         title = self.page_title()
@@ -32,7 +34,7 @@
         for i in xrange(self.rset.rowcount):
             self.cell_call(i, 0)
         self.w(u"</xbel>")
-    
+
 
 class XbelItemView(EntityView):
     id = 'xbelitem'
@@ -45,10 +47,11 @@
 
     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	Thu May 14 12:50:34 2009 +0200
@@ -0,0 +1,208 @@
+"""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 _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:dc="http://purl.org/dc/elements/1.1/">\n')
+        self.w(u'  <channel>\n')
+        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)))
+
+    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>\n')
+        self.w(u'<guid isPermaLink="true">%s</guid>\n'
+               % xml_escape(entity.absolute_url()))
+        self.render_title_link(entity)
+        self._marker('description', entity.dc_description(format='text/html'))
+        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.dc_creator())
+
+
+    def _marker(self, marker, value):
+        if value:
+            self.w(u'  <%s>%s</%s>\n' % (marker, xml_escape(value), marker))
--- a/web/wdoc/custom_view_rss_fr.rst	Thu May 14 12:50:14 2009 +0200
+++ b/web/wdoc/custom_view_rss_fr.rst	Thu May 14 12:50:34 2009 +0200
@@ -14,5 +14,5 @@
 
 :raw-html:`<p><a class="reference"
 href="view?vid=rss&amp;rql=Any+X%2CM+WHERE+X+modification_date+M+ORDERBY+M+DESC+LIMIT+30"><img
-alt="rss" src="data/rss.png"> latest changes</a></p>`
+alt="rss" src="data/rss.png"> latest changes</img></a></p>`
 
--- a/web/wdoc/tut_rql_en.rst	Thu May 14 12:50:14 2009 +0200
+++ b/web/wdoc/tut_rql_en.rst	Thu May 14 12:50:34 2009 +0200
@@ -127,16 +127,16 @@
 * `creation_date (Datetime)`, date on which the entity has been created
 * `modification_date (Datetime)`, lastest date on which the entity has been modified
 
-* `created_by (EUser)`, relation to the user which has created this entity
+* `created_by (CWUser)`, relation to the user which has created this entity
 
-* `owned_by (EUser)`, relation to the user()s considered as owner of this
+* `owned_by (CWUser)`, relation to the user()s considered as owner of this
   entity, the entity's creator by default
 
 * `is (Eetype)`, special relation to specify a variable type.
 
 A user's entity has the following schema:
 
-:EUser:
+:CWUser:
   ::
 
 	login  	  (String) not null
--- a/web/wdoc/tut_rql_fr.rst	Thu May 14 12:50:14 2009 +0200
+++ b/web/wdoc/tut_rql_fr.rst	Thu May 14 12:50:34 2009 +0200
@@ -134,9 +134,9 @@
 * `creation_date (Datetime)`, date de création de l'entité
 * `modification_date (Datetime)`, date de dernière modification de l'entité
 
-* `created_by (EUser)`, relation vers l'utilisateur ayant créé l'entité
+* `created_by (CWUser)`, relation vers l'utilisateur ayant créé l'entité
 
-* `owned_by (EUser)`, relation vers le où les utilisateurs considérés comme 
+* `owned_by (CWUser)`, relation vers le où les utilisateurs considérés comme 
   propriétaire de l'entité, par défaut le créateur de l'entité
 
 * `is (Eetype)`, relation spéciale permettant de spécifier le
@@ -144,7 +144,7 @@
 
 Enfin, le schéma standard d'un utilisateur est le suivant :
 
-:EUser:
+:CWUser:
   ::
 
 	login  	  (String, obligatoire)
--- a/web/webconfig.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/webconfig.py	Thu May 14 12:50:34 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
@@ -23,10 +22,10 @@
     ('site-title',
      {'type' : 'string', 'default': 'unset title',
       'help': _('site title'),
-      'sitewide': True, 'group': 'ui', 
+      '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',
       }),
@@ -55,7 +54,7 @@
       'help': _('maximum number of entities to display in related combo box'),
       'group': 'navigation',
       }),
-    
+
     ))
 
 
@@ -65,7 +64,7 @@
     """
     cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['web/views'])
     cube_vobject_path = CubicWebConfiguration.cube_vobject_path | set(['views'])
-    
+
     options = merge_options(CubicWebConfiguration.options + (
         ('anonymous-user',
          {'type' : 'string',
@@ -80,6 +79,12 @@
           'if anonymous-user is set',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('allow-email-login',
+         {'type' : 'yn',
+          'default': False,
+          'help': 'allow users to login with their primary email if set',
+          'group': 'main', 'inputlevel': 2,
+          }),
         ('query-log-file',
          {'type' : 'string',
           'default': None,
@@ -180,7 +185,7 @@
           'interface\'s language according to browser defined preferences',
           'group': 'web', 'inputlevel': 2,
           }),
-        
+
         ('print-traceback',
          {'type' : 'yn',
           'default': not CubicWebConfiguration.mode == 'installed',
@@ -193,16 +198,25 @@
         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'
-    
+
     # don't use @cached: we want to be able to disable it while this must still
     # be cached
     def repository(self, vreg=None):
@@ -221,7 +235,7 @@
 
     def vc_config(self):
         return self.repository().get_versions()
-    
+
     # mapping to external resources (id -> path) (`external_resources` file) ##
     ext_resources = {
         'FAVICON':  'DATADIR/favicon.ico',
@@ -230,13 +244,13 @@
         'HELP':     'DATADIR/help.png',
         'CALENDAR_ICON': 'DATADIR/calendar.gif',
         'SEARCH_GO':'DATADIR/go.png',
-        
+
         'FCKEDITOR_PATH':  '/usr/share/fckeditor/',
-        
+
         'IE_STYLESHEETS':    ['DATADIR/cubicweb.ie.css'],
         'STYLESHEETS':       ['DATADIR/cubicweb.css'],
         'STYLESHEETS_PRINT': ['DATADIR/cubicweb.print.css'],
-        
+
         'JAVASCRIPTS':       ['DATADIR/jquery.js',
                               'DATADIR/jquery.corner.js',
                               'DATADIR/jquery.json.js',
@@ -244,8 +258,8 @@
                               'DATADIR/cubicweb.python.js',
                               'DATADIR/cubicweb.htmlhelpers.js'],
         }
-        
-        
+
+
     def anonymous_user(self):
         """return a login and password to use for anonymous users. None
         may be returned for both if anonymous connections are not allowed
@@ -258,7 +272,7 @@
         if user is not None:
             user = unicode(user)
         return user, passwd
-    
+
     def has_resource(self, rid):
         """return true if an external resource is defined"""
         return bool(self.ext_resources.get(rid))
@@ -267,19 +281,19 @@
     def locate_resource(self, rid):
         """return the directory where the given resource may be found"""
         return self._fs_locate(rid, 'data')
-            
+
     @cached
     def locate_doc_file(self, fname):
         """return the directory where the given resource may be found"""
         return self._fs_locate(fname, 'wdoc')
-            
+
     def _fs_locate(self, rid, rdirectory):
         """return the directory where the given resource may be found"""
         path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
         for directory in path:
             if exists(join(directory, rdirectory, rid)):
                 return join(directory, rdirectory)
-            
+
     def locate_all_files(self, rid, rdirectory='wdoc'):
         """return all files corresponding to the given resource"""
         path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())]
@@ -294,7 +308,7 @@
         # load external resources definition
         self._build_ext_resources()
         self._init_base_url()
-        
+
     def _init_base_url(self):
         # normalize base url(s)
         baseurl = self['base-url']
@@ -327,11 +341,11 @@
 
 
     # static files handling ###################################################
-    
+
     @property
     def static_directory(self):
         return join(self.appdatahome, 'static')
-    
+
     def static_file_exists(self, rpath):
         return exists(join(self.static_directory, rpath))
 
@@ -351,4 +365,3 @@
     def static_file_del(self, rpath):
         if self.static_file_exists(rpath):
             os.remove(join(self.static_directory, rpath))
-        
--- a/web/webctl.py	Thu May 14 12:50:14 2009 +0200
+++ b/web/webctl.py	Thu May 14 12:50:34 2009 +0200
@@ -12,7 +12,7 @@
 
 class WebCreateHandler(CommandHandler):
     cmdname = 'create'
-    
+
     def bootstrap(self, cubes, inputlevel=0):
         """bootstrap this configuration"""
         print '** generic web configuration'
@@ -23,9 +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	Thu May 14 12:50:14 2009 +0200
+++ b/web/widgets.py	Thu May 14 12:50:34 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
 
@@ -64,7 +63,7 @@
     autoid = True
     html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress'))
     cubicwebns_attributes = set()
-    
+
     def __init__(self, vreg, subjschema, rschema, objschema,
                  role='subject', description=None,
                  **kwattrs):
@@ -84,12 +83,12 @@
         because widget instances are cached)
         """
         # brute force copy (subclasses don't have the
-        # same __init__ prototype) 
+        # same __init__ prototype)
         widget = self.__new__(self.__class__)
         widget.__dict__ = dict(self.__dict__)
         widget.attrs = dict(widget.attrs)
         return widget
-    
+
     @staticmethod
     def size_constraint_attrs(attrs, maxsize):
         """set html attributes in the attrs dict to consider maxsize"""
@@ -106,7 +105,7 @@
             elif name in self.html_attributes:
                 attrs.append(u'%s="%s"' % (name, value))
         return u' '.join(sorted(attrs))
-        
+
     def required(self, entity):
         """indicates if the widget needs a value to be filled in"""
         card = self.rschema.cardinality(self.subjtype, self.objtype, self.role)
@@ -117,7 +116,7 @@
             return self.rname
         except AttributeError:
             return eid_param(self.name, entity.eid)
-    
+
     def render_label(self, entity, label=None):
         """render widget's label"""
         label = label or self.rschema.display_name(entity.req, self.role)
@@ -131,7 +130,7 @@
         else:
             label = u'<label%s>%s</label>' % (forattr, label)
         return label
-    
+
     def render_error(self, entity):
         """return validation error for widget's field of the given entity, if
         any
@@ -145,25 +144,26 @@
     def render_help(self, entity):
         """render a help message about the (edited) field"""
         req = entity.req
-        help = [u'<br/>']
+        help = [u'<div class="helper">']
         descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description')
         if descr:
-            help.append(u'<span class="helper">%s</span>' % req._(descr))
+            help.append(u'<span>%s</span>' % req._(descr))
         example = self.render_example(req)
         if example:
-            help.append(u'<span class="helper">(%s: %s)</span>'
+            help.append(u'<span>(%s: %s)</span>'
                         % (req._('sample format'), example))
+	help.append(u'</div>')
         return u'&nbsp;'.join(help)
-    
+
     def render_example(self, req):
         return u''
-        
+
     def render(self, entity):
         """render the widget for a simple view"""
         if not entity.has_eid():
             return u''
         return entity.printable_value(self.name)
-    
+
     def edit_render(self, entity, tabindex=None,
                     includehelp=False, useid=None, **kwargs):
         """render the widget for edition"""
@@ -181,7 +181,7 @@
         if includehelp:
             output += self.render_help(entity)
         return output
-    
+
     def _edit_render(self, entity):
         """do the actual job to render the widget for edition"""
         raise NotImplementedError
@@ -197,7 +197,7 @@
         elif entity.has_eid():
             return [row[0] for row in entity.related(self.name, self.role)]
         return ()
-            
+
     def current_value(self, entity):
         return _value_from_values(self.current_values(entity))
 
@@ -214,13 +214,13 @@
         if not isinstance(cdvalues, (list, tuple)):
             cdvalues = (cdvalues,)
         return cdvalues
-    
+
     def current_display_value(self, entity):
         """same as .current_value but consider values stored in session in case
         of validation error
         """
         return _value_from_values(self.current_display_values(entity))
-    
+
     def hidden_input(self, entity, qvalue):
         """return an hidden field which
         1. indicates that a field is edited
@@ -259,7 +259,7 @@
     def __init__(self, vreg, subjschema, rschema, objschema,
                  role='subject', **kwattrs):
         InputWidget.__init__(self, vreg, subjschema, rschema, objschema,
-                             role='subject', 
+                             role='subject',
                              **kwattrs)
         # disable access key
         del self.attrs['accesskey']
@@ -271,18 +271,18 @@
     def current_display_value(self, entity):
         value = InputWidget.current_display_value(self, entity)
         return value or INTERNAL_FIELD_VALUE
-    
+
     def render_label(self, entity, label=None):
         """render widget's label"""
         return u''
-    
+
     def render_help(self, entity):
         return u''
-    
+
     def hidden_input(self, entity, value):
         """no hidden input for hidden input"""
         return ''
-    
+
 
 class EidWidget(HiddenWidget):
 
@@ -298,15 +298,15 @@
         """set html attributes in the attrs dict to consider maxsize"""
         attrs['size'] = min(maxsize, 40)
         attrs['maxlength'] = maxsize
-        
-        
+
+
 class AutoCompletionWidget(StringWidget):
     cubicwebns_attributes = (StringWidget.cubicwebns_attributes |
                           set(('accesskey', 'size', 'maxlength')))
     attrs = ()
-    
+
     wdgtype = 'SuggestField'
-    
+
     def current_value(self, entity):
         value = StringWidget.current_value(self, entity)
         return value or INTERNAL_FIELD_VALUE
@@ -345,22 +345,22 @@
 
 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
     wdgtype = 'StaticFileSuggestField'
-    
+
     def _get_url(self, entity):
         return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
 
 class RestrictedAutoCompletionWidget(AutoCompletionWidget):
-    wdgtype = 'RestrictedSuggestField'    
+    wdgtype = 'RestrictedSuggestField'
 
-    
+
 class PasswordWidget(InputWidget):
     input_type = 'password'
-    
+
     def required(self, entity):
         if InputWidget.required(self, entity) and not entity.has_eid():
             return True
         return False
-    
+
     def current_values(self, entity):
         # on existant entity, show password field has non empty (we don't have
         # the actual value
@@ -375,10 +375,10 @@
             html, self.input_type, name, name, entity.req.next_tabindex(),
             entity.req._('confirm password'))
 
-    
+
 class TextWidget(Widget):
     html_attributes = Widget.html_attributes | set(('rows', 'cols'))
-    
+
     @staticmethod
     def size_constraint_attrs(attrs, maxsize):
         """set html attributes in the attrs dict to consider maxsize"""
@@ -386,16 +386,12 @@
             attrs['cols'], attrs['rows'] = 60, 5
         else:
             attrs['cols'], attrs['rows'] = 80, 10
-    
+
     def render(self, entity):
         if not entity.has_eid():
             return u''
         return entity.printable_value(self.name)
-    
-    def 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)
@@ -403,7 +399,7 @@
         if isinstance(value, basestring):
             value = html_escape(value)
         return u'%s%s' % (self.hidden_input(entity, value), editor)
-    
+
     def _edit_render_textarea(self, entity, with_format):
         self.attrs.setdefault('cols', 80)
         self.attrs.setdefault('rows', 20)
@@ -411,10 +407,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.attr_metadata(self.name, 'format')
                 else:
                     format = ''
                 frname = eid_param(self.name + '_format', entity.eid)
@@ -423,7 +419,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()
@@ -431,8 +427,8 @@
             fmtwdgstr = ''
         return u'%s<br/><textarea onkeypress="autogrow(this)" name="%s" %s>%s</textarea>' % (
             fmtwdgstr, self.rname, self.format_attrs(), dvalue)
-            
-    
+
+
 class CheckBoxWidget(Widget):
     html_attributes = Widget.html_attributes | set(('checked', ))
     def _edit_render(self, entity):
@@ -449,7 +445,7 @@
 
 
 class YesNoRadioWidget(CheckBoxWidget):
-    
+    html_attributes = Widget.html_attributes | set(('disabled',))
     def _edit_render(self, entity):
         value = self.current_value(entity)
         dvalue = self.current_display_value(entity)
@@ -465,13 +461,14 @@
                 u'<input type="radio" name="%s" value="" %s/>%s<br/>' % (self.rname, attrs2, entity.req._('no'))]
         return '\n'.join(wdgs)
 
-    
+
 class FileWidget(Widget):
     need_multipart = True
     def _file_wdg(self, entity):
         wdgs = [u'<input type="file" name="%s" %s/>' % (self.rname, self.format_attrs())]
         req = entity.req
-        if entity.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)),
@@ -486,13 +483,17 @@
                     wdgs.append(ewdg.edit_render(entity, includehelp=True))
                     wdgs.append(u'<br/>')
             wdgs.append(u'</div>')
-        if entity.has_eid() and not self.required(entity):
-            # trick to be able to delete an uploaded file
-            wdgs.append(u'<br/>')
-            wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False))
-            wdgs.append(req._('detach attached file'))
+        if entity.has_eid():
+            if not self.required(entity):
+                # trick to be able to delete an uploaded file
+                wdgs.append(u'<br/>')
+                wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False))
+                wdgs.append(req._('detach attached file %s' % entity.dc_title()))
+            else:
+                wdgs.append(u'<br/>')
+                wdgs.append(req._('currently attached file: %s' % entity.dc_title()))
         return '\n'.join(wdgs)
-    
+
     def _edit_render(self, entity):
         return self.hidden_input(entity, None) + self._file_wdg(entity)
 
@@ -510,17 +511,17 @@
                 'You can either submit a new file using the browse button above'
                 ', or edit file content online with the widget below.')
         return msg
-    
+
     def _edit_render(self, entity):
         wdgs = [self._file_wdg(entity)]
-        if entity.format(self.name) in ('text/plain', 'text/html', 'text/rest'):
+        if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'):
             msg = self._edit_msg(entity)
             wdgs.append(u'<p><b>%s</b></p>' % msg)
             twdg = TextWidget(self.vreg, self.subjtype, self.rschema, self.objtype)
             twdg.rname = self.rname
             data = getattr(entity, self.name)
             if data:
-                encoding = entity.text_encoding(self.name)
+                encoding = entity.attr_metadata(self.name, 'encoding')
                 try:
                     entity[self.name] = unicode(data.getvalue(), encoding)
                 except UnicodeError:
@@ -534,7 +535,7 @@
 
 class ComboBoxWidget(Widget):
     html_attributes = Widget.html_attributes | set(('multiple', 'size'))
-    
+
     def __init__(self, vreg, subjschema, rschema, objschema,
                  multiple=False, **kwattrs):
         super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
@@ -545,10 +546,10 @@
                 self.attrs['size'] = '5'
         # disable access key (dunno why but this is not allowed by xhtml 1.0)
         del self.attrs['accesskey']
-        
+
     def vocabulary(self, entity):
         raise NotImplementedError()
-    
+
     def form_value(self, entity, value, values):
         if value in values:
             flag = 'selected="selected"'
@@ -574,9 +575,9 @@
         res.append(u'</select>')
         return '\n'.join(res)
 
- 
+
 class StaticComboBoxWidget(ComboBoxWidget):
-    
+
     def __init__(self, vreg, subjschema, rschema, objschema,
                  vocabfunc, multiple=False, sort=False, **kwattrs):
         super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
@@ -585,17 +586,17 @@
         self.vocabfunc = vocabfunc
 
     def vocabulary(self, entity):
-        choices = self.vocabfunc(entity)
+        choices = self.vocabfunc(entity=entity)
         if self.sort:
             choices = sorted(choices)
         if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'):
             return zip((entity.req._(v) for v in choices), choices)
         return zip(choices, choices)
-    
+
 
 class EntityLinkComboBoxWidget(ComboBoxWidget):
     """to be used be specific forms"""
-    
+
     def current_values(self, entity):
         if entity.has_eid():
             return [r[0] for r in entity.related(self.name, self.role)]
@@ -603,13 +604,13 @@
         if hasattr(entity, defaultmeth):
             return getattr(entity, defaultmeth)()
         return ()
-    
+
     def vocabulary(self, entity):
         return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role)
 
 
 class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget):
-    
+
     def vocabulary(self, entity, limit=None):
         req = entity.req
         # first see if its specified by __linkto form parameters
@@ -632,7 +633,7 @@
 
 
 class DynamicComboBoxWidget(RawDynamicComboBoxWidget):
-    
+
     def vocabulary(self, entity, limit=None):
         return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit))
 
@@ -663,17 +664,17 @@
         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
         kwattrs['maxlength'] = 15
         StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-        
+
     def render_example(self, req):
         return '23'
-    
 
-        
+
 class FloatWidget(StringWidget):
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
         kwattrs['size'] = 5
@@ -683,7 +684,7 @@
     def render_example(self, req):
         formatstr = req.property_value('ui.float-format')
         return formatstr % 1.23
-    
+
     def current_values(self, entity):
         values = entity.attribute_values(self.name)
         if values:
@@ -696,41 +697,24 @@
             return [formatstr % value]
         return ()
 
+
 class DecimalWidget(StringWidget):
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
         kwattrs['size'] = 5
         kwattrs['maxlength'] = 15
         StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-        
+
     def render_example(self, req):
         return '345.0300'
-    
 
 
 class DateWidget(StringWidget):
     format_key = 'ui.date-format'
-    monthnames = ("january", "february", "march", "april",
-                  "may", "june", "july", "august",
-                  "september", "october", "november", "december")
-    
-    daynames = ("monday", "tuesday", "wednesday", "thursday",
-                "friday", "saturday", "sunday")
-    
-    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
-        kwattrs.setdefault('size', 10)
-        kwattrs.setdefault('maxlength', 10)
-        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-    def current_values(self, entity):
-        values = entity.attribute_values(self.name)
-        if values and hasattr(values[0], 'strftime'):
-            formatstr = entity.req.property_value(self.format_key)
-            return [values[0].strftime(formatstr)]
-        return values
-
-    def render_example(self, req):
-        formatstr = req.property_value(self.format_key)
-        return now().strftime(formatstr)
+    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):
@@ -742,6 +726,22 @@
         req.html_headers.define_var('MONTHNAMES', monthnames)
         req.html_headers.define_var('DAYNAMES', daynames)
 
+    def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
+        kwattrs.setdefault('size', 10)
+        kwattrs.setdefault('maxlength', 10)
+        StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
+
+    def current_values(self, entity):
+        values = entity.attribute_values(self.name)
+        if values and hasattr(values[0], 'strftime'):
+            formatstr = entity.req.property_value(self.format_key)
+            return [values[0].strftime(str(formatstr))]
+        return values
+
+    def render_example(self, req):
+        formatstr = req.property_value(self.format_key)
+        return datetime.now().strftime(str(formatstr))
+
 
     def _edit_render(self, entity):
         wdg = super(DateWidget, self)._edit_render(entity)
@@ -751,14 +751,15 @@
     def render_help(self, entity):
         """calendar popup widget"""
         req = entity.req
-        help = [ u'<br/>' ]
+        help = [ u'<div class="helper">' ]
         descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description')
         if descr:
-            help.append('<span class="helper">%s</span>' % req._(descr))
+            help.append('<span>%s</span>' % req._(descr))
         example = self.render_example(req)
         if example:
-            help.append('<span class="helper">(%s: %s)</span>'
+            help.append('<span>(%s: %s)</span>'
                         % (req._('sample format'), example))
+        help.append(u'</div>')
         return u'&nbsp;'.join(help)
 
     def render_calendar_popup(self, entity):
@@ -769,7 +770,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))
 
@@ -780,23 +781,20 @@
 
 class DateTimeWidget(DateWidget):
     format_key = 'ui.datetime-format'
-    
-    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),
-            }
-
-
-
 
     def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
         kwattrs['size'] = 16
         kwattrs['maxlength'] = 16
         DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
 
+    def render_example(self, req):
+        formatstr1 = req.property_value('ui.datetime-format')
+        formatstr2 = req.property_value('ui.date-format')
+        return req._('%(fmt1)s, or without time: %(fmt2)s') % {
+            'fmt1': datetime.now().strftime(str(formatstr1)),
+            'fmt2': datetime.now().strftime(str(formatstr2)),
+            }
+
 
 class TimeWidget(StringWidget):
     format_key = 'ui.time-format'
@@ -805,26 +803,26 @@
         kwattrs['maxlength'] = 5
         StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
 
-        
+
 class EmailWidget(StringWidget):
-    
+
     def render(self, entity):
         email = getattr(entity, self.name)
         if not email:
             return u''
         return u'<a href="mailto:%s">%s</a>' % (email, email)
-        
+
 class URLWidget(StringWidget):
-    
+
     def render(self, entity):
         url = getattr(entity, self.name)
         if not url:
             return u''
         url = html_escape(url)
         return u'<a href="%s">%s</a>' % (url, url)
-    
+
 class EmbededURLWidget(StringWidget):
-    
+
     def render(self, entity):
         url = getattr(entity, self.name)
         if not url:
@@ -834,59 +832,6 @@
 
 
 
-class PropertyKeyWidget(ComboBoxWidget):
-    """specific widget for EProperty.pkey field to set the value widget according to
-    the selected key
-    """
-    
-    def _edit_render(self, entity):
-        entity.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        vtabindex = self.attrs.get('tabindex', 0) + 1
-        self.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
-            entity.eid, vtabindex)
-        # limit size
-        if not entity.has_eid():
-            self.attrs['size'] = 10
-        else:
-            self.attrs['size'] = 1
-        return super(PropertyKeyWidget, self)._edit_render(entity)
-    
-    def vocabulary(self, entity):
-        _ = entity.req._
-        if entity.has_eid():
-            return [(_(entity.pkey), entity.pkey)]
-        # key beginning with 'system.' should usually not be edited by hand
-        choices = entity.vreg.user_property_keys()
-        return sorted(zip((_(v) for v in choices), choices))
-
-
-class PropertyValueWidget(Widget):
-    """specific widget for EProperty.value field which will be different according to
-    the selected key type and vocabulary information
-    """
-    
-    def render_help(self, entity):
-        return u''
-        
-    def render(self, entity):
-        assert entity.has_eid()
-        w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs)
-        return w.render(entity)
-        
-    def _edit_render(self, entity):
-        if not entity.has_eid():
-            # no key set yet, just include an empty div which will be filled
-            # on key selection
-            # empty span as well else html validation fail (label is refering to this id)
-            return u'<div id="div:%s"><span id="%s"/></div>' % (self.rname, self.attrs.get('id'))
-        w = self.vreg.property_value_widget(entity.pkey, req=entity.req, **self.attrs)
-        if entity.pkey.startswith('system.'):
-            value = '<span class="value" id="%s">%s</span>' % (self.attrs.get('id'), w.render(entity))
-            msg = entity.req._('value associated to this key is not editable manually')
-            return value + '<div>%s</div>' % msg
-        return w.edit_render(entity, self.attrs.get('tabindex'), includehelp=True)
-    
-
 def widget_factory(vreg, subjschema, rschema, objschema, role='subject',
                    **kwargs):
     """return the most adapated widget to edit the relation
@@ -896,7 +841,7 @@
         eclass, subjschema = _eclass_eschema(subjschema)
     else:
         eclass, objschema = _eclass_eschema(objschema)
-    if eclass is not None and rschema in eclass.widgets:
+    if eclass is not None and rschema in getattr(eclass, 'widgets', ()):
         wcls = WIDGETS[eclass.widgets[rschema]]
     elif not rschema.is_final():
         card = rschema.rproperty(subjschema, objschema, 'cardinality')
@@ -914,14 +859,14 @@
 
 
 # factories to find the most adapated widget according to a type and other constraints
-                
+
 def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
     w = None
     for c in rschema.rproperty(subjschema, objschema, 'constraints'):
         if isinstance(c, StaticVocabularyConstraint):
             # may have been set by a previous SizeConstraint but doesn't make sense
             # here (even doesn't have the same meaning on a combobox actually)
-            kwargs.pop('size', None) 
+            kwargs.pop('size', None)
             return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema,
                                                   vocabfunc=c.vocabulary, **kwargs)
         if isinstance(c, SizeConstraint) and c.max is not None:
@@ -971,7 +916,7 @@
     'String' :  StringWidget,
     'Time':     TimeWidget,
     }
-    
+
 # widgets registry
 WIDGETS = {}
 def register(widget_list):
--- a/wsgi/__init__.py	Thu May 14 12:50:14 2009 +0200
+++ b/wsgi/__init__.py	Thu May 14 12:50:34 2009 +0200
@@ -7,7 +7,7 @@
 WSGI corresponding PEP: http://www.python.org/dev/peps/pep-0333/
 
 :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"
@@ -25,7 +25,7 @@
         return _pformat(obj)
     except:
         return u'<could not parse>'
-    
+
 def qs2dict(qs):
     """transforms a query string into a regular python dict"""
     result = {}
@@ -35,7 +35,7 @@
 
 def normalize_header(header):
     """returns a normalized header name
-    
+
     >>> normalize_header('User_Agent')
     'User-agent'
     """
@@ -84,4 +84,3 @@
         else:
             post.setdefault(key, []).append(submessage.get_payload())
     return post, files
-
--- a/wsgi/handler.py	Thu May 14 12:50:14 2009 +0200
+++ b/wsgi/handler.py	Thu May 14 12:50:34 2009 +0200
@@ -1,7 +1,7 @@
 """WSGI request handler for cubicweb
 
 :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
 """
 
@@ -74,7 +74,7 @@
 
     def __iter__(self):
         return iter(self.body)
-    
+
 
 
 class CubicWebWSGIApplication(object):
@@ -100,7 +100,7 @@
             self.url_rewriter = self.appli.vreg.select_component('urlrewriter')
         except ObjectNotFound:
             self.url_rewriter = None
-        
+
     def _render(self, req):
         """this function performs the actual rendering
         XXX missing: https handling, url rewriting, cache management,
@@ -156,8 +156,8 @@
             # 500 Internal server error
             return self.redirect(req, req.build_url('error'))
         return WSGIResponse(200, req, result)
-        
-    
+
+
     def __call__(self, environ, start_response):
         """WSGI protocol entry point"""
         req = CubicWebWsgiRequest(environ, self.appli.vreg, self.base_url)
@@ -170,7 +170,7 @@
         self.debug('redirecting to %s', location)
         req.set_header('location', str(location))
         return WSGIResponse(303, req)
-        
+
     def request_auth(self, req, loggedout=False):
         """returns the appropriate WSGIResponse to require the user to log in
         """
--- a/wsgi/request.py	Thu May 14 12:50:14 2009 +0200
+++ b/wsgi/request.py	Thu May 14 12:50:34 2009 +0200
@@ -5,7 +5,7 @@
   http://www.djangoproject.com/
 
 :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
 """
 
@@ -25,7 +25,7 @@
 class CubicWebWsgiRequest(CubicWebRequestBase):
     """most of this code COMES FROM DJANO
     """
-    
+
     def __init__(self, environ, vreg, base_url=None):
         self.environ = environ
         self.path = environ['PATH_INFO']
@@ -42,7 +42,7 @@
             self.form.update(files)
         # prepare output headers
         self.headers_out = {}
-        
+
     def __repr__(self):
         # Since this is called as part of error handling, we need to be very
         # robust against potentially malformed input.
@@ -52,14 +52,14 @@
             (form, meta)
 
     ## cubicweb request interface ################################################
-    
+
     def base_url(self):
         return self._base_url
 
     def http_method(self):
         """returns 'POST', 'GET', 'HEAD', etc."""
         return self.method
-    
+
     def relative_path(self, includeparams=True):
         """return the normalized path of the request (ie at least relative
         to the application's root, but some other normalization may be needed
@@ -74,7 +74,7 @@
             qs = self.environ.get('QUERY_STRING')
             if qs:
                 return '%s?%s' % (path, qs)
-        
+
         return path
 
     def get_header(self, header, default=None):
@@ -82,7 +82,7 @@
         raise KeyError if the header is not set
         """
         return self._headers.get(normalize_header(header), default)
-    
+
     def set_header(self, header, value, raw=True):
         """set an output HTTP header"""
         assert raw, "don't know anything about non-raw headers for wsgi requests"
@@ -91,7 +91,7 @@
     def add_header(self, header, value):
         """add an output HTTP header"""
         self.headers_out[header] = value
-    
+
     def remove_header(self, header):
         """remove an output HTTP header"""
         self.headers_out.pop(header, None)
@@ -101,9 +101,9 @@
         mx date time value (GMT), else return None
         """
         return None
-        
+
     ## wsgi request helpers ###################################################
-    
+
     def application_uri(self):
         """Return the application's base URI (no PATH_INFO or QUERY_STRING)
 
@@ -123,7 +123,7 @@
                     url += ':' + environ['SERVER_PORT']
         url += quote(environ.get('SCRIPT_NAME') or '/')
         return url
-        
+
     def get_full_path(self):
         return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')