backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 13 Apr 2011 11:46:00 +0200
changeset 7218 78622865b6c1
parent 7216 6053bf221fa9 (diff)
parent 7217 4b860903d8c7 (current diff)
child 7221 dc79f1a3c6ed
backport stable
--- a/.hgtags	Wed Apr 13 11:34:53 2011 +0200
+++ b/.hgtags	Wed Apr 13 11:46:00 2011 +0200
@@ -188,3 +188,9 @@
 77318f1ec4aae3523d455e884daf3708c3c79af7 cubicweb-debian-version-3.11.1-1
 56ae3cd5f8553678a2b1d4121b61241598d0ca68 cubicweb-version-3.11.2
 954b5b51cd9278eb45d66be1967064d01ab08453 cubicweb-debian-version-3.11.2-1
+fd502219eb76f4bfd239d838a498a1d1e8204baf cubicweb-version-3.12.0
+92b56939b7c77bbf443b893c495a20f19bc30702 cubicweb-debian-version-3.12.0-1
+59701627adba73ee97529f6ea0e250a0f3748e32 cubicweb-version-3.12.1
+07e2c9c7df2617c5ecfa84cb819b3ee8ef91d1f2 cubicweb-debian-version-3.12.1-1
+5a9b6bc5653807500c30a7eb0e95b90fd714fec3 cubicweb-version-3.12.2
+6d418fb3ffed273562aae411efe323d5138b592a cubicweb-debian-version-3.12.2-1
--- a/__pkginfo__.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/__pkginfo__.py	Wed Apr 13 11:46:00 2011 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 11, 2)
+numversion = (3, 12, 2)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -43,7 +43,7 @@
     'logilab-common': '>= 0.55.2',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.28.0',
-    'yams': '>= 0.30.4',
+    'yams': '>= 0.32.0',
     'docutils': '>= 0.6',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
@@ -52,7 +52,7 @@
     'Twisted': '',
     # XXX graphviz
     # server dependencies
-    'logilab-database': '>= 1.4.0',
+    'logilab-database': '>= 1.5.0',
     'pysqlite': '>= 2.5.5', # XXX install pysqlite2
     }
 
--- a/cwvreg.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/cwvreg.py	Wed Apr 13 11:46:00 2011 +0200
@@ -312,6 +312,10 @@
         kwargs['clear'] = True
         super(ETypeRegistry, self).register(obj, **kwargs)
 
+    def iter_classes(self):
+        for etype in self.vreg.schema.entities():
+            yield self.etype_class(etype)
+
     @cached
     def parent_classes(self, etype):
         if etype == 'Any':
@@ -835,18 +839,24 @@
         return self['views'].select(__vid, req, rset=rset, **kwargs)
 
 
+import decimal
 from datetime import datetime, date, time, timedelta
 
-YAMS_TO_PY = {
-    'Boolean':  bool,
+YAMS_TO_PY = { # XXX unify with yams.constraints.BASE_CONVERTERS?
     'String' :  unicode,
+    'Bytes':    Binary,
     'Password': str,
-    'Bytes':    Binary,
+
+    'Boolean':  bool,
     'Int':      int,
     'Float':    float,
-    'Date':     date,
-    'Datetime': datetime,
-    'Time':     time,
-    'Interval': timedelta,
+    'Decimal':  decimal.Decimal,
+
+    'Date':       date,
+    'Datetime':   datetime,
+    'TZDatetime': datetime,
+    'Time':       time,
+    'TZTime':     time,
+    'Interval':   timedelta,
     }
 
--- a/debian/changelog	Wed Apr 13 11:34:53 2011 +0200
+++ b/debian/changelog	Wed Apr 13 11:46:00 2011 +0200
@@ -1,6 +1,24 @@
+cubicweb (3.12.2-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Mon, 11 Apr 2011 22:15:21 +0200
+
+cubicweb (3.12.1-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 06 Apr 2011 23:25:12 +0200
+
+cubicweb (3.12.0-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Alexandre Fayolle <alexandre.fayolle@logilab.fr>  Fri, 01 Apr 2011 15:59:37 +0200
+
 cubicweb (3.11.2-1) unstable; urgency=low
 
-  * new upstream release 
+  * new upstream release
 
  -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Mon, 28 Mar 2011 19:18:54 +0200
 
--- a/debian/control	Wed Apr 13 11:34:53 2011 +0200
+++ b/debian/control	Wed Apr 13 11:46:00 2011 +0200
@@ -33,7 +33,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.4.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.5.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
 Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
 Description: server part of the CubicWeb framework
  CubicWeb is a semantic web application framework.
@@ -97,7 +97,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.32.0), python-rql (>= 0.28.0), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/fill.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/devtools/fill.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,5 +1,5 @@
 # -*- coding: iso-8859-1 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -152,6 +152,8 @@
         base = datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
         return self._constrained_generate(entity, attrname, base, timedelta(hours=1), index)
 
+    generate_tzdatetime = generate_datetime # XXX implementation should add a timezone
+
     def generate_date(self, entity, attrname, index):
         """generates a random date (format is 'yyyy-mm-dd')"""
         base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365))
@@ -166,6 +168,8 @@
         """generates a random time (format is ' HH:MM')"""
         return time(11, index%60) #'11:%02d' % (index % 60)
 
+    generate_tztime = generate_time # XXX implementation should add a timezone
+
     def generate_bytes(self, entity, attrname, index, format=None):
         fakefile = Binary("%s%s" % (attrname, index))
         fakefile.filename = u"file_%s" % attrname
@@ -441,7 +445,7 @@
         constraints = [c for c in rdef.constraints
                        if isinstance(c, RQLConstraint)]
         if constraints:
-            restrictions = ', '.join(c.restriction for c in constraints)
+            restrictions = ', '.join(c.expression for c in constraints)
             q += ', %s' % restrictions
             # restrict object eids if possible
             # XXX the attempt to restrict below in completely wrong
--- a/entity.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/entity.py	Wed Apr 13 11:46:00 2011 +0200
@@ -28,7 +28,7 @@
 
 from rql.utils import rqlvar_maker
 
-from cubicweb import Unauthorized, typed_eid
+from cubicweb import Unauthorized, typed_eid, neg_role
 from cubicweb.rset import ResultSet
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
@@ -157,6 +157,7 @@
     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"""
+        # XXX update api and implementation to AST manipulation (see unrelated rql)
         restrictions = restriction or []
         if settype:
             restrictions.append('%s is %s' % (mainvar, cls.__regid__))
@@ -165,6 +166,7 @@
         selection = [mainvar]
         orderby = []
         # start from 26 to avoid possible conflicts with X
+        # XXX not enough to be sure it'll be no conflicts
         varmaker = rqlvar_maker(index=26)
         cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
                                 orderby, restrictions, user, ordermethod)
@@ -762,7 +764,7 @@
     # generic vocabulary methods ##############################################
 
     def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None,
-                      vocabconstraints=True):
+                         vocabconstraints=True):
         """build a rql to fetch `targettype` entities unrelated to this entity
         using (rtype, role) relation.
 
@@ -772,58 +774,83 @@
         ordermethod = ordermethod or 'fetch_unrelated_order'
         if isinstance(rtype, basestring):
             rtype = self._cw.vreg.schema.rschema(rtype)
+        rdef = rtype.role_rdef(self.e_schema, targettype, role)
+        rewriter = RQLRewriter(self._cw)
+        # initialize some variables according to the `role` of `self` in the
+        # relation:
+        # * variable for myself (`evar`) and searched entities (`searchvedvar`)
+        # * entity type of the subject (`subjtype`) and of the object
+        #   (`objtype`) of the relation
         if role == 'subject':
             evar, searchedvar = 'S', 'O'
             subjtype, objtype = self.e_schema, targettype
         else:
             searchedvar, evar = 'S', 'O'
             objtype, subjtype = self.e_schema, targettype
+        # initialize some variables according to `self` existance
+        if rdef.role_cardinality(neg_role(role)) in '?1':
+            # if cardinality in '1?', we want a target entity which isn't
+            # already linked using this relation
+            if searchedvar == 'S':
+                restriction = ['NOT S %s ZZ' % rtype]
+            else:
+                restriction = ['NOT ZZ %s O' % rtype]
+        elif self.has_eid():
+            # elif we have an eid, we don't want a target entity which is
+            # already linked to ourself through this relation
+            restriction = ['NOT S %s O' % rtype]
+        else:
+            restriction = []
         if self.has_eid():
-            restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
+            restriction += ['%s eid %%(x)s' % evar]
             args = {'x': self.eid}
             if role == 'subject':
-                securitycheck_args = {'fromeid': self.eid}
+                sec_check_args = {'fromeid': self.eid}
             else:
-                securitycheck_args = {'toeid': self.eid}
+                sec_check_args = {'toeid': self.eid}
+            existant = None # instead of 'SO', improve perfs
         else:
-            restriction = []
             args = {}
-            securitycheck_args = {}
-        rdef = rtype.role_rdef(self.e_schema, targettype, role)
-        insertsecurity = (rdef.has_local_role('add') and not
-                          rdef.has_perm(self._cw, 'add', **securitycheck_args))
-        # XXX consider constraint.mainvars to check if constraint apply
+            sec_check_args = {}
+            existant = searchedvar
+        # retreive entity class for targettype to compute base rql
+        etypecls = self._cw.vreg['etypes'].etype_class(targettype)
+        rql = etypecls.fetch_rql(self._cw.user, restriction,
+                                 mainvar=searchedvar, ordermethod=ordermethod)
+        select = self._cw.vreg.parse(self._cw, rql, args).children[0]
+        # insert RQL expressions for schema constraints into the rql syntax tree
         if vocabconstraints:
             # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
             # will be included as well
-            restriction += [cstr.restriction for cstr in rdef.constraints
-                            if isinstance(cstr, RQLVocabularyConstraint)]
+            cstrcls = RQLVocabularyConstraint
         else:
-            restriction += [cstr.restriction for cstr in rdef.constraints
-                            if isinstance(cstr, RQLConstraint)]
-        etypecls = self._cw.vreg['etypes'].etype_class(targettype)
-        rql = etypecls.fetch_rql(self._cw.user, restriction,
-                                 mainvar=searchedvar, ordermethod=ordermethod)
+            cstrcls = RQLConstraint
+        for cstr in rdef.constraints:
+            # consider constraint.mainvars to check if constraint apply
+            if isinstance(cstr, cstrcls) and searchedvar in cstr.mainvars:
+                if not self.has_eid() and evar in cstr.mainvars:
+                    continue
+                # compute a varmap suitable to RQLRewriter.rewrite argument
+                varmap = dict((v, v) for v in 'SO' if v in select.defined_vars
+                              and v in cstr.mainvars)
+                # rewrite constraint by constraint since we want a AND between
+                # expressions.
+                rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions,
+                                 args, existant)
+        # insert security RQL expressions granting the permission to 'add' the
+        # relation into the rql syntax tree, if necessary
+        rqlexprs = rdef.get_rqlexprs('add')
+        if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args):
+            # compute a varmap suitable to RQLRewriter.rewrite argument
+            varmap = dict((v, v) for v in 'SO' if v in select.defined_vars)
+            # rewrite all expressions at once since we want a OR between them.
+            rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions,
+                             args, existant)
         # 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)
-        if insertsecurity:
-            rqlexprs = rdef.get_rqlexprs('add')
-            rewriter = RQLRewriter(self._cw)
-            rqlst = self._cw.vreg.parse(self._cw, rql, args)
-            if not self.has_eid():
-                existant = searchedvar
-            else:
-                existant = None # instead of 'SO', improve perfs
-            for select in rqlst.children:
-                varmap = {}
-                for var in 'SO':
-                    if var in select.defined_vars:
-                        varmap[var] = var
-                rewriter.rewrite(select, [(varmap, rqlexprs)],
-                                 select.solutions, args, existant)
-            rql = rqlst.as_string()
+        if not select.orderby:
+            select.add_sort_var(select.defined_vars[searchedvar])
+        # we're done, turn the rql syntax tree as a string
+        rql = select.as_string()
         return rql, args
 
     def unrelated(self, rtype, targettype, role='subject', limit=None,
@@ -835,6 +862,7 @@
             rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod)
         except Unauthorized:
             return self._cw.empty_rset()
+        # XXX should be set in unrelated rql when manipulating the AST
         if limit is not None:
             before, after = rql.split(' WHERE ', 1)
             rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
--- a/i18n/de.po	Wed Apr 13 11:34:53 2011 +0200
+++ b/i18n/de.po	Wed Apr 13 11:46:00 2011 +0200
@@ -737,6 +737,18 @@
 msgid "Submit bug report by mail"
 msgstr "Diesen Bericht als E-Mail senden"
 
+msgid "TZDatetime"
+msgstr ""
+
+msgid "TZDatetime_plural"
+msgstr ""
+
+msgid "TZTime"
+msgstr ""
+
+msgid "TZTime_plural"
+msgstr ""
+
 #, python-format
 msgid "The view %s can not be applied to this query"
 msgstr "Die Ansicht %s ist auf diese Anfrage nicht anwendbar."
--- a/i18n/en.po	Wed Apr 13 11:34:53 2011 +0200
+++ b/i18n/en.po	Wed Apr 13 11:46:00 2011 +0200
@@ -711,6 +711,18 @@
 msgid "Submit bug report by mail"
 msgstr ""
 
+msgid "TZDatetime"
+msgstr "International date and time"
+
+msgid "TZDatetime_plural"
+msgstr "International dates and times"
+
+msgid "TZTime"
+msgstr "International time"
+
+msgid "TZTime_plural"
+msgstr "International times"
+
 #, python-format
 msgid "The view %s can not be applied to this query"
 msgstr ""
--- a/i18n/es.po	Wed Apr 13 11:34:53 2011 +0200
+++ b/i18n/es.po	Wed Apr 13 11:46:00 2011 +0200
@@ -732,6 +732,18 @@
 msgid "Submit bug report by mail"
 msgstr "Enviar este reporte por email"
 
+msgid "TZDatetime"
+msgstr ""
+
+msgid "TZDatetime_plural"
+msgstr ""
+
+msgid "TZTime"
+msgstr ""
+
+msgid "TZTime_plural"
+msgstr ""
+
 #, python-format
 msgid "The view %s can not be applied to this query"
 msgstr "La vista %s no puede ser aplicada a esta búsqueda"
--- a/i18n/fr.po	Wed Apr 13 11:34:53 2011 +0200
+++ b/i18n/fr.po	Wed Apr 13 11:46:00 2011 +0200
@@ -449,7 +449,7 @@
 msgstr "Date et heure"
 
 msgid "Datetime_plural"
-msgstr "Date et heure"
+msgstr "Dates et heures"
 
 msgid "Decimal"
 msgstr "Nombre décimal"
@@ -736,6 +736,18 @@
 msgid "Submit bug report by mail"
 msgstr "Soumettre ce rapport par email"
 
+msgid "TZDatetime"
+msgstr "Date et heure internationale"
+
+msgid "TZDatetime_plural"
+msgstr "Dates et heures internationales"
+
+msgid "TZTime"
+msgstr "Heure internationale"
+
+msgid "TZTime_plural"
+msgstr "Heures internationales"
+
 #, python-format
 msgid "The view %s can not be applied to this query"
 msgstr "La vue %s ne peut être appliquée à cette requête"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.12.0_Any.py	Wed Apr 13 11:46:00 2011 +0200
@@ -0,0 +1,2 @@
+add_entity_type('TZDatetime')
+add_entity_type('TZTime')
--- a/rqlrewrite.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/rqlrewrite.py	Wed Apr 13 11:46:00 2011 +0200
@@ -257,6 +257,11 @@
                 insert_scope = None
                 for vi in self.varinfos:
                     scope = vi.get('stinfo', {}).get('scope', self.select)
+                    while True:
+                        negstmt = scope.neged()
+                        if negstmt is None:
+                            break
+                        scope = negstmt.scope
                     if insert_scope is None:
                         insert_scope = scope
                     else:
--- a/schema.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/schema.py	Wed Apr 13 11:46:00 2011 +0200
@@ -28,6 +28,7 @@
 from logilab.common.decorators import cached, clear_cache, monkeypatch
 from logilab.common.logging_ext import set_log_methods
 from logilab.common.deprecation import deprecated, class_moved
+from logilab.common.textutils import splitstrip
 from logilab.common.graph import get_cycles
 from logilab.common.compat import any
 
@@ -179,35 +180,6 @@
 __builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name)
 
 
-# rql expression utilities function ############################################
-
-def guess_rrqlexpr_mainvars(expression):
-    defined = set(split_expression(expression))
-    mainvars = []
-    if 'S' in defined:
-        mainvars.append('S')
-    if 'O' in defined:
-        mainvars.append('O')
-    if 'U' in defined:
-        mainvars.append('U')
-    if not mainvars:
-        raise Exception('unable to guess selection variables')
-    return ','.join(sorted(mainvars))
-
-def split_expression(rqlstring):
-    for expr in rqlstring.split(','):
-        for noparen in expr.split('('):
-            for word in noparen.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
-    for instance)
-    """
-    return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
-
-
 # Schema objects definition ###################################################
 
 def ERSchema_display_name(self, req, form='', context=None):
@@ -640,175 +612,57 @@
     def schema_by_eid(self, eid):
         return self._eid_index[eid]
 
-
-# Possible constraints ########################################################
-
-class BaseRQLConstraint(BaseConstraint):
-    """base class for rql constraints
-    """
-    distinct_query = None
-
-    def __init__(self, restriction, mainvars=None):
-        self.restriction = normalize_expression(restriction)
-        if mainvars is None:
-            mainvars = guess_rrqlexpr_mainvars(restriction)
-        else:
-            normmainvars = []
-            for mainvar in mainvars.split(','):
-                mainvar = mainvar.strip()
-                if not mainvar.isalpha():
-                    raise Exception('bad mainvars %s' % mainvars)
-                normmainvars.append(mainvar)
-            assert mainvars, 'bad mainvars %s' % mainvars
-            mainvars = ','.join(sorted(normmainvars))
-        self.mainvars = mainvars
-
-    def serialize(self):
-        # start with a comma for bw compat, see below
-        return ';' + self.mainvars + ';' + self.restriction
-
-    @classmethod
-    def deserialize(cls, value):
-        # XXX < 3.5.10 bw compat
-        if not value.startswith(';'):
-            return cls(value)
-        _, mainvars, restriction = value.split(';', 2)
-        return cls(restriction, mainvars)
-
-    def check(self, entity, rtype, value):
-        """return true if the value satisfy the constraint, else false"""
-        # implemented as a hook in the repository
-        return 1
-
-    def repo_check(self, session, eidfrom, rtype, eidto):
-        """raise ValidationError if the relation doesn't satisfy the constraint
-        """
-        pass # this is a vocabulary constraint, not enforce XXX why?
-
-    def __str__(self):
-        if self.distinct_query:
-            selop = 'Any'
-        else:
-            selop = 'DISTINCT Any'
-        return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop,
-                                       self.mainvars, self.restriction)
-
-    def __repr__(self):
-        return '<%s @%#x>' % (self.__str__(), id(self))
-
-
-class RQLVocabularyConstraint(BaseRQLConstraint):
-    """the rql vocabulary constraint :
-
-    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
-
-     mainvars is a string that should be used as selection variable (eg
-     `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
-     done to guess it according to variable used in the expression.
-    """
-
-
-class RepoEnforcedRQLConstraintMixIn(object):
+# Bases for manipulating RQL in schema #########################################
 
-    def __init__(self, restriction, mainvars=None, msg=None):
-        super(RepoEnforcedRQLConstraintMixIn, self).__init__(restriction, mainvars)
-        self.msg = msg
-
-    def serialize(self):
-        # start with a semicolon for bw compat, see below
-        return ';%s;%s\n%s' % (self.mainvars, self.restriction,
-                               self.msg or '')
-
-    def deserialize(cls, value):
-        # XXX < 3.5.10 bw compat
-        if not value.startswith(';'):
-            return cls(value)
-        value, msg = value.split('\n', 1)
-        _, mainvars, restriction = value.split(';', 2)
-        return cls(restriction, mainvars, msg)
-    deserialize = classmethod(deserialize)
+def guess_rrqlexpr_mainvars(expression):
+    defined = set(split_expression(expression))
+    mainvars = set()
+    if 'S' in defined:
+        mainvars.add('S')
+    if 'O' in defined:
+        mainvars.add('O')
+    if 'U' in defined:
+        mainvars.add('U')
+    if not mainvars:
+        raise Exception('unable to guess selection variables')
+    return mainvars
 
-    def repo_check(self, session, eidfrom, rtype, eidto=None):
-        """raise ValidationError if the relation doesn't satisfy the constraint
-        """
-        if not self.match_condition(session, eidfrom, eidto):
-            # XXX at this point if both or neither of S and O are in mainvar we
-            # dunno if the validation error `occurred` on eidfrom or eidto (from
-            # user interface point of view)
-            #
-            # possible enhancement: check entity being created, it's probably
-            # the main eid unless this is a composite relation
-            if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
-                maineid = eidfrom
-                qname = role_name(rtype, 'subject')
-            else:
-                maineid = eidto
-                qname = role_name(rtype, 'object')
-            if self.msg:
-                msg = session._(self.msg)
-            else:
-                msg = '%(constraint)s %(restriction)s failed' % {
-                    'constraint':  session._(self.type()),
-                    'restriction': self.restriction}
-            raise ValidationError(maineid, {qname: msg})
+def split_expression(rqlstring):
+    for expr in rqlstring.split(','):
+        for noparen in expr.split('('):
+            for word in noparen.split():
+                yield word
 
-    def exec_query(self, session, eidfrom, eidto):
-        if eidto is None:
-            # checking constraint for an attribute relation
-            restriction = 'S eid %(s)s, ' + self.restriction
-            args = {'s': eidfrom}
-        else:
-            restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
-            args = {'s': eidfrom, 'o': eidto}
-        rql = 'Any %s WHERE %s' % (self.mainvars,  restriction)
-        if self.distinct_query:
-            rql = 'DISTINCT ' + rql
-        return session.execute(rql, args, build_descr=False)
-
-
-class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
-    """the rql constraint is similar to the RQLVocabularyConstraint but
-    are also enforced at the repository level
+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 for instance)
     """
-    distinct_query = False
-
-    def match_condition(self, session, eidfrom, eidto):
-        return self.exec_query(session, eidfrom, eidto)
-
-
-class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
-    """the unique rql constraint check that the result of the query isn't
-    greater than one.
-
-    You *must* specify mainvars when instantiating the constraint since there is
-    no way to guess it correctly (e.g. if using S,O or U the constraint will
-    always be satisfied because we've to use a DISTINCT query).
-    """
-    # XXX turns mainvars into a required argument in __init__
-    distinct_query = True
-
-    def match_condition(self, session, eidfrom, eidto):
-        return len(self.exec_query(session, eidfrom, eidto)) <= 1
+    return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
 
 
 class RQLExpression(object):
+    """Base class for RQL expression used in schema (constraints and
+    permissions)
+    """
+    # these are overridden by set_log_methods below
+    # only defining here to prevent pylint from complaining
+    info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
 
     def __init__(self, expression, mainvars, eid):
         self.eid = eid # eid of the entity representing this rql expression
-        if not isinstance(mainvars, unicode):
-            mainvars = unicode(mainvars)
+        assert mainvars, 'bad mainvars %s' % mainvars
+        if isinstance(mainvars, basestring):
+            mainvars = set(splitstrip(mainvars))
+        elif not isinstance(mainvars, set):
+            mainvars = set(mainvars)
         self.mainvars = mainvars
         self.expression = normalize_expression(expression)
         try:
             self.rqlst = parse(self.full_rql, print_errors=False).children[0]
         except RQLSyntaxError:
             raise RQLSyntaxError(expression)
-        for mainvar in mainvars.split(','):
+        for mainvar in mainvars:
             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)
@@ -832,6 +686,8 @@
     def __setstate__(self, state):
         self.__init__(*state)
 
+    # permission rql expression specific stuff #################################
+
     @cached
     def transform_has_permission(self):
         found = None
@@ -942,12 +798,10 @@
 
     @property
     def minimal_rql(self):
-        return 'Any %s WHERE %s' % (self.mainvars, self.expression)
+        return 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)),
+                                    self.expression)
 
-    # these are overridden by set_log_methods below
-    # only defining here to prevent pylint from complaining
-    info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
-
+# rql expressions for use in permission definition #############################
 
 class ERQLExpression(RQLExpression):
     def __init__(self, expression, mainvars=None, eid=None):
@@ -1024,12 +878,153 @@
             kwargs['o'] = toeid
         return self._check(session, **kwargs)
 
+
 # in yams, default 'update' perm for attributes granted to managers and owners.
 # Within cw, we want to default to users who may edit the entity holding the
 # attribute.
 ybo.DEFAULT_ATTRPERMS['update'] = (
     'managers', ERQLExpression('U has_update_permission X'))
 
+# additional cw specific constraints ###########################################
+
+class BaseRQLConstraint(RRQLExpression, BaseConstraint):
+    """base class for rql constraints"""
+    distinct_query = None
+
+    def serialize(self):
+        # start with a comma for bw compat,see below
+        return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression
+
+    @classmethod
+    def deserialize(cls, value):
+        # XXX < 3.5.10 bw compat
+        if not value.startswith(';'):
+            return cls(value)
+        _, mainvars, expression = value.split(';', 2)
+        return cls(expression, mainvars)
+
+    def check(self, entity, rtype, value):
+        """return true if the value satisfy the constraint, else false"""
+        # implemented as a hook in the repository
+        return 1
+
+    def __str__(self):
+        if self.distinct_query:
+            selop = 'Any'
+        else:
+            selop = 'DISTINCT Any'
+        return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop,
+                                       ','.join(sorted(self.mainvars)),
+                                       self.expression)
+
+    def __repr__(self):
+        return '<%s @%#x>' % (self.__str__(), id(self))
+
+
+class RQLVocabularyConstraint(BaseRQLConstraint):
+    """the rql vocabulary constraint:
+
+    limit the proposed values to a set of entities returned by a rql query,
+    but this is not enforced at the repository level
+
+     `expression` 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
+
+     `mainvars` is a set of variables that should be used as selection variable
+     (eg `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
+     done to guess it according to variable used in the expression.
+    """
+
+    def repo_check(self, session, eidfrom, rtype, eidto):
+        """raise ValidationError if the relation doesn't satisfy the constraint
+        """
+        pass # this is a vocabulary constraint, not enforce 
+
+
+class RepoEnforcedRQLConstraintMixIn(object):
+
+    def __init__(self, expression, mainvars=None, msg=None):
+        super(RepoEnforcedRQLConstraintMixIn, self).__init__(expression, mainvars)
+        self.msg = msg
+
+    def serialize(self):
+        # start with a semicolon for bw compat, see below
+        return ';%s;%s\n%s' % (','.join(sorted(self.mainvars)), self.expression,
+                               self.msg or '')
+
+    def deserialize(cls, value):
+        # XXX < 3.5.10 bw compat
+        if not value.startswith(';'):
+            return cls(value)
+        value, msg = value.split('\n', 1)
+        _, mainvars, expression = value.split(';', 2)
+        return cls(expression, mainvars, msg)
+    deserialize = classmethod(deserialize)
+
+    def repo_check(self, session, eidfrom, rtype, eidto=None):
+        """raise ValidationError if the relation doesn't satisfy the constraint
+        """
+        if not self.match_condition(session, eidfrom, eidto):
+            # XXX at this point if both or neither of S and O are in mainvar we
+            # dunno if the validation error `occurred` on eidfrom or eidto (from
+            # user interface point of view)
+            #
+            # possible enhancement: check entity being created, it's probably
+            # the main eid unless this is a composite relation
+            if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
+                maineid = eidfrom
+                qname = role_name(rtype, 'subject')
+            else:
+                maineid = eidto
+                qname = role_name(rtype, 'object')
+            if self.msg:
+                msg = session._(self.msg)
+            else:
+                msg = '%(constraint)s %(expression)s failed' % {
+                    'constraint':  session._(self.type()),
+                    'expression': self.expression}
+            raise ValidationError(maineid, {qname: msg})
+
+    def exec_query(self, session, eidfrom, eidto):
+        if eidto is None:
+            # checking constraint for an attribute relation
+            expression = 'S eid %(s)s, ' + self.expression
+            args = {'s': eidfrom}
+        else:
+            expression = 'S eid %(s)s, O eid %(o)s, ' + self.expression
+            args = {'s': eidfrom, 'o': eidto}
+        rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression)
+        if self.distinct_query:
+            rql = 'DISTINCT ' + rql
+        return session.execute(rql, args, build_descr=False)
+
+
+class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
+    """the rql constraint is similar to the RQLVocabularyConstraint but
+    are also enforced at the repository level
+    """
+    distinct_query = False
+
+    def match_condition(self, session, eidfrom, eidto):
+        return self.exec_query(session, eidfrom, eidto)
+
+
+class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
+    """the unique rql constraint check that the result of the query isn't
+    greater than one.
+
+    You *must* specify `mainvars` when instantiating the constraint since there
+    is no way to guess it correctly (e.g. if using S,O or U the constraint will
+    always be satisfied because we've to use a DISTINCT query).
+    """
+    # XXX turns mainvars into a required argument in __init__
+    distinct_query = True
+
+    def match_condition(self, session, eidfrom, eidto):
+        return len(self.exec_query(session, eidfrom, eidto)) <= 1
+
+
 # workflow extensions #########################################################
 
 from yams.buildobjs import _add_relation as yams_add_relation
--- a/server/migractions.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/migractions.py	Wed Apr 13 11:46:00 2011 +0200
@@ -438,7 +438,8 @@
                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
                                  'WHERE T eid %%(x)s' % perm,
                                  {'expr': expr, 'exprtype': exprtype,
-                                  'vars': expression.mainvars, 'x': teid},
+                                  'vars': u','.join(sorted(expression.mainvars)),
+                                  'x': teid},
                                  ask_confirm=False)
 
     def _synchronize_rschema(self, rtype, syncrdefs=True,
@@ -757,9 +758,9 @@
         targeted type is known
         """
         instschema = self.repo.schema
-        assert not etype in instschema, \
+        eschema = self.fs_schema.eschema(etype)
+        assert eschema.final or not etype in instschema, \
                '%s already defined in the instance schema' % etype
-        eschema = self.fs_schema.eschema(etype)
         confirm = self.verbosity >= 2
         groupmap = self.group_mapping()
         cstrtypemap = self.cstrtype_mapping()
--- a/server/schemaserial.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/schemaserial.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -564,7 +564,7 @@
                 yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
                        'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
                        {'e': unicode(rqlexpr.expression),
-                        'v': unicode(rqlexpr.mainvars),
+                        'v': unicode(','.join(sorted(rqlexpr.mainvars))),
                         't': unicode(rqlexpr.__class__.__name__)})
 
 # update functions
--- a/server/sources/rql2sql.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/sources/rql2sql.py	Wed Apr 13 11:46:00 2011 +0200
@@ -50,7 +50,9 @@
 __docformat__ = "restructuredtext en"
 
 import threading
+from datetime import datetime, time
 
+from logilab.common.date import utcdatetime, utctime
 from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY
 
 from rql import BadRQLQuery, CoercionError
@@ -1424,6 +1426,14 @@
                 _id = value
                 if isinstance(_id, unicode):
                     _id = _id.encode()
+                # convert timestamp to utc.
+                # expect SET TiME ZONE to UTC at connection opening time.
+                # This shouldn't change anything for datetime without TZ.
+                value = self._args[_id]
+                if isinstance(value, datetime) and value.tzinfo is not None:
+                    self._query_attrs[_id] = utcdatetime(value)
+                elif isinstance(value, time) and value.tzinfo is not None:
+                    self._query_attrs[_id] = utctime(value)
         else:
             _id = str(id(constant)).replace('-', '', 1)
             self._query_attrs[_id] = value
--- a/server/sqlutils.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/sqlutils.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -25,7 +25,7 @@
 
 from logilab import database as db, common as lgc
 from logilab.common.shellutils import ProgressBar
-from logilab.common.date import todate, todatetime
+from logilab.common.date import todate, todatetime, utcdatetime, utctime
 from logilab.database.sqlgen import SQLGenerator
 
 from cubicweb import Binary, ConfigurationError
@@ -274,10 +274,15 @@
                         value = crypt_password(value)
                     value = self._binary(value)
                 # XXX needed for sqlite but I don't think it is for other backends
-                elif atype == 'Datetime' and isinstance(value, date):
+                # Note: use is __class__ since issubclass(datetime, date)
+                elif atype in ('Datetime', 'TZDatetime') and value.__class__ is date:
                     value = todatetime(value)
                 elif atype == 'Date' and isinstance(value, datetime):
                     value = todate(value)
+                elif atype == 'TZDatetime' and getattr(value, 'tzinfo', None):
+                    value = utcdatetime(value)
+                elif atype == 'TZTime' and getattr(value, 'tzinfo', None):
+                    value = utctime(value)
                 elif isinstance(value, Binary):
                     value = self._binary(value.getvalue())
             attrs[SQL_PREFIX+str(attr)] = value
@@ -326,3 +331,10 @@
 
 sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
 sqlite_hooks.append(init_sqlite_connexion)
+
+
+def init_postgres_connexion(cnx):
+    cnx.cursor().execute('SET TIME ZONE UTC')
+
+postgres_hooks = SQL_CONNECT_HOOKS.setdefault('postgres', [])
+postgres_hooks.append(init_postgres_connexion)
--- a/server/test/data/schema.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/test/data/schema.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -18,7 +18,7 @@
 
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, RichString, String, Int, Float,
-                            Boolean, Datetime)
+                            Boolean, Datetime, TZDatetime)
 from yams.constraints import SizeConstraint
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLUniqueConstraint,
@@ -114,6 +114,7 @@
     tel    = Int()
     fax    = Int()
     datenaiss = Datetime()
+    tzdatenaiss = TZDatetime()
     test   = Boolean(__permissions__={
         'read': ('managers', 'users', 'guests'),
         'update': ('managers',),
--- a/server/test/data/sources_fti	Wed Apr 13 11:34:53 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-[system]
-
-db-driver   = postgres
-db-host     = localhost
-db-port     = 
-adapter     = native
-db-name     = cw_fti_test
-db-encoding = UTF-8
-db-user     = syt
-db-password = syt
-
-[admin]
-login = admin
-password = gingkow
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/sources_postgres	Wed Apr 13 11:46:00 2011 +0200
@@ -0,0 +1,14 @@
+[system]
+
+db-driver   = postgres
+db-host     = localhost
+db-port     = 5433
+adapter     = native
+db-name     = cw_fti_test
+db-encoding = UTF-8
+db-user     = syt
+db-password = syt
+
+[admin]
+login = admin
+password = gingkow
--- a/server/test/unittest_fti.py	Wed Apr 13 11:34:53 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-from __future__ import with_statement
-
-import socket
-
-from logilab.common.testlib import SkipTest
-
-from cubicweb.devtools import ApptestConfiguration
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.selectors import is_instance
-from cubicweb.entities.adapters import IFTIndexableAdapter
-
-AT_LOGILAB = socket.gethostname().endswith('.logilab.fr')
-
-
-class PostgresFTITC(CubicWebTC):
-    config = ApptestConfiguration('data', sourcefile='sources_fti')
-
-    @classmethod
-    def setUpClass(cls):
-        if not AT_LOGILAB:
-            raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
-
-    def test_occurence_count(self):
-        req = self.request()
-        c1 = req.create_entity('Card', title=u'c1',
-                               content=u'cubicweb cubicweb cubicweb')
-        c2 = req.create_entity('Card', title=u'c3',
-                               content=u'cubicweb')
-        c3 = req.create_entity('Card', title=u'c2',
-                               content=u'cubicweb cubicweb')
-        self.commit()
-        self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                          [[c1.eid], [c3.eid], [c2.eid]])
-
-
-    def test_attr_weight(self):
-        class CardIFTIndexableAdapter(IFTIndexableAdapter):
-            __select__ = is_instance('Card')
-            attr_weight = {'title': 'A'}
-        with self.temporary_appobjects(CardIFTIndexableAdapter):
-            req = self.request()
-            c1 = req.create_entity('Card', title=u'c1',
-                                   content=u'cubicweb cubicweb cubicweb')
-            c2 = req.create_entity('Card', title=u'c2',
-                                   content=u'cubicweb cubicweb')
-            c3 = req.create_entity('Card', title=u'cubicweb',
-                                   content=u'autre chose')
-            self.commit()
-            self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                              [[c3.eid], [c1.eid], [c2.eid]])
-
-    def test_entity_weight(self):
-        class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
-            __select__ = is_instance('Personne')
-            entity_weight = 2.0
-        with self.temporary_appobjects(PersonneIFTIndexableAdapter):
-            req = self.request()
-            c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
-            c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
-            c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
-            self.commit()
-            self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
-                              [[c1.eid], [c3.eid], [c2.eid]])
-
-
-if __name__ == '__main__':
-    from logilab.common.testlib import unittest_main
-    unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_postgres.py	Wed Apr 13 11:46:00 2011 +0200
@@ -0,0 +1,78 @@
+from __future__ import with_statement
+
+import socket
+from datetime import datetime
+
+from logilab.common.testlib import SkipTest
+
+from cubicweb.devtools import ApptestConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.selectors import is_instance
+from cubicweb.entities.adapters import IFTIndexableAdapter
+
+AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') # XXX
+
+from unittest_querier import FixedOffset
+
+class PostgresFTITC(CubicWebTC):
+    config = ApptestConfiguration('data', sourcefile='sources_postgres')
+
+    @classmethod
+    def setUpClass(cls):
+        if not AT_LOGILAB: # XXX here until we can raise SkipTest in setUp to detect we can't connect to the db
+            raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
+
+    def test_occurence_count(self):
+        req = self.request()
+        c1 = req.create_entity('Card', title=u'c1',
+                               content=u'cubicweb cubicweb cubicweb')
+        c2 = req.create_entity('Card', title=u'c3',
+                               content=u'cubicweb')
+        c3 = req.create_entity('Card', title=u'c2',
+                               content=u'cubicweb cubicweb')
+        self.commit()
+        self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                          [[c1.eid], [c3.eid], [c2.eid]])
+
+
+    def test_attr_weight(self):
+        class CardIFTIndexableAdapter(IFTIndexableAdapter):
+            __select__ = is_instance('Card')
+            attr_weight = {'title': 'A'}
+        with self.temporary_appobjects(CardIFTIndexableAdapter):
+            req = self.request()
+            c1 = req.create_entity('Card', title=u'c1',
+                                   content=u'cubicweb cubicweb cubicweb')
+            c2 = req.create_entity('Card', title=u'c2',
+                                   content=u'cubicweb cubicweb')
+            c3 = req.create_entity('Card', title=u'cubicweb',
+                                   content=u'autre chose')
+            self.commit()
+            self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                              [[c3.eid], [c1.eid], [c2.eid]])
+
+    def test_entity_weight(self):
+        class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
+            __select__ = is_instance('Personne')
+            entity_weight = 2.0
+        with self.temporary_appobjects(PersonneIFTIndexableAdapter):
+            req = self.request()
+            c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
+            c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
+            c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
+            self.commit()
+            self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+                              [[c1.eid], [c3.eid], [c2.eid]])
+
+
+    def test_tz_datetime(self):
+        self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+                     {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+        datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+        self.assertEqual(datenaiss.tzinfo, None)
+        self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()
--- a/server/test/unittest_querier.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/test/unittest_querier.py	Wed Apr 13 11:46:00 2011 +0200
@@ -18,7 +18,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
 """
-from datetime import date, datetime
+from datetime import date, datetime, timedelta, tzinfo
 
 from logilab.common.testlib import TestCase, unittest_main
 from rql import BadRQLQuery, RQLSyntaxError
@@ -32,6 +32,14 @@
 from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
 from unittest_session import Variable
 
+class FixedOffset(tzinfo):
+    def __init__(self, hours=0):
+        self.hours = hours
+    def utcoffset(self, dt):
+        return timedelta(hours=self.hours)
+    def dst(self, dt):
+        return timedelta(0)
+
 
 # register priority/severity sorting registered procedure
 from rql.utils import register_function, FunctionDescr
@@ -761,18 +769,20 @@
         rset = self.execute('Any N WHERE X is CWEType, X name N, X final %(val)s',
                             {'val': True})
         self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
-                                                            'Date', 'Datetime',
-                                                            'Decimal', 'Float',
-                                                            'Int', 'Interval',
-                                                            'Password', 'String',
-                                                            'Time'])
+                                                           'Date', 'Datetime',
+                                                           'Decimal', 'Float',
+                                                           'Int', 'Interval',
+                                                           'Password', 'String',
+                                                           'TZDatetime', 'TZTime',
+                                                           'Time'])
         rset = self.execute('Any N WHERE X is CWEType, X name N, X final TRUE')
         self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
-                                                            'Date', 'Datetime',
-                                                            'Decimal', 'Float',
-                                                            'Int', 'Interval',
-                                                            'Password', 'String',
-                                                            'Time'])
+                                                           'Date', 'Datetime',
+                                                           'Decimal', 'Float',
+                                                           'Int', 'Interval',
+                                                           'Password', 'String',
+                                                           'TZDatetime', 'TZTime',
+                                                           'Time'])
 
     def test_select_constant(self):
         rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup')
@@ -1213,11 +1223,11 @@
         self.assertEqual(rset.description, [('CWUser',)])
 
     def test_update_upassword(self):
-        cursor = self.pool['system']
         rset = self.execute("INSERT CWUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'})
         self.assertEqual(rset.description[0][0], 'CWUser')
         rset = self.execute("SET X upassword %(pwd)s WHERE X is CWUser, X login 'bob'",
                             {'pwd': 'tutu'})
+        cursor = self.pool['system']
         cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
                        % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
         passwd = str(cursor.fetchone()[0])
@@ -1227,7 +1237,16 @@
         self.assertEqual(len(rset.rows), 1)
         self.assertEqual(rset.description, [('CWUser',)])
 
-    # non regression tests ####################################################
+    # ZT datetime tests ########################################################
+
+    def test_tz_datetime(self):
+        self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+                     {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+        datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+        self.assertEqual(datenaiss.tzinfo, None)
+        self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+
+    # non regression tests #####################################################
 
     def test_nonregr_1(self):
         teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
--- a/server/test/unittest_repository.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/test/unittest_repository.py	Wed Apr 13 11:46:00 2011 +0200
@@ -69,11 +69,12 @@
             cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
                                          % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
             self.assertEqual(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
-                                              (u'Date',), (u'Datetime',),
-                                              (u'Decimal',),(u'Float',),
-                                              (u'Int',),
-                                              (u'Interval',), (u'Password',),
-                                              (u'String',), (u'Time',)])
+                                             (u'Date',), (u'Datetime',),
+                                             (u'Decimal',),(u'Float',),
+                                             (u'Int',),
+                                             (u'Interval',), (u'Password',),
+                                             (u'String',),
+                                             (u'TZDatetime',), (u'TZTime',), (u'Time',)])
             sql = ("SELECT etype.cw_eid, etype.cw_name, cstr.cw_eid, rel.eid_to "
                    "FROM cw_CWUniqueTogetherConstraint as cstr, "
                    "     relations_relation as rel, "
@@ -327,7 +328,7 @@
         self.assertEqual(len(constraints), 1)
         cstr = constraints[0]
         self.assert_(isinstance(cstr, RQLConstraint))
-        self.assertEqual(cstr.restriction, 'O final TRUE')
+        self.assertEqual(cstr.expression, 'O final TRUE')
 
         ownedby = schema.rschema('owned_by')
         self.assertEqual(ownedby.objects('CWEType'), ('CWUser',))
--- a/server/test/unittest_rql2sql.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/server/test/unittest_rql2sql.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1225,9 +1225,13 @@
             yield self._check, rql, sql
 
     def _checkall(self, rql, sql):
+        if isinstance(rql, tuple):
+            rql, args = rql
+        else:
+            args = None
         try:
             rqlst = self._prepare(rql)
-            r, args, cbs = self.o.generate(rqlst)
+            r, args, cbs = self.o.generate(rqlst, args)
             self.assertEqual((r.strip(), args), sql)
         except Exception, ex:
             print rql
@@ -1239,7 +1243,7 @@
         return
 
     def test1(self):
-        self._checkall('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s',
+        self._checkall(('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s', {'x': None}),
                        ("""SELECT COUNT(T1.C0) FROM (SELECT _RDEF.cw_eid AS C0
 FROM cw_CWAttribute AS _RDEF
 WHERE _RDEF.cw_relation_type=%(x)s
@@ -1250,7 +1254,7 @@
                        )
 
     def test2(self):
-        self._checkall('Any X WHERE C comments X, C eid %(x)s',
+        self._checkall(('Any X WHERE C comments X, C eid %(x)s', {'x': None}),
                        ('''SELECT rel_comments0.eid_to
 FROM comments_relation AS rel_comments0
 WHERE rel_comments0.eid_from=%(x)s''', {})
--- a/test/data/schema.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/test/data/schema.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,13 +15,11 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
 
 from yams.buildobjs import (EntityType, String, SubjectRelation,
                             RelationDefinition)
-from cubicweb.schema import  WorkflowableEntityType
+from cubicweb.schema import (WorkflowableEntityType,
+                             RQLConstraint, RQLVocabularyConstraint)
 
 class Personne(EntityType):
     nom = String(required=True)
@@ -29,7 +27,14 @@
     type = String()
     travaille = SubjectRelation('Societe')
     evaluee = SubjectRelation(('Note', 'Personne'))
-    connait = SubjectRelation('Personne', symmetric=True)
+    connait = SubjectRelation(
+        'Personne', symmetric=True,
+        constraints=[
+            RQLConstraint('NOT S identity O'),
+            # conflicting constraints, see cw_unrelated_rql tests in
+            # unittest_entity.py
+            RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'),
+            RQLVocabularyConstraint('S travaille P, P nom "tutu"')])
 
 class Societe(EntityType):
     nom = String()
--- a/test/unittest_entity.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/test/unittest_entity.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -19,7 +19,7 @@
 """unit tests for cubicweb.web.views.entities module"""
 
 from datetime import datetime
-
+from logilab.common import tempattr
 from cubicweb import Binary, Unauthorized
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.mttransforms import HAS_TAL
@@ -29,6 +29,17 @@
 
 class EntityTC(CubicWebTC):
 
+    def setUp(self):
+        super(EntityTC, self).setUp()
+        self.backup_dict = {}
+        for cls in self.vreg['etypes'].iter_classes():
+            self.backup_dict[cls] = (cls.fetch_attrs, cls.fetch_order)
+
+    def tearDown(self):
+        super(EntityTC, self).tearDown()
+        for cls in self.vreg['etypes'].iter_classes():
+            cls.fetch_attrs, cls.fetch_order = self.backup_dict[cls]
+
     def test_boolean_value(self):
         e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         self.failUnless(e)
@@ -136,17 +147,19 @@
         Note = self.vreg['etypes'].etype_class('Note')
         peschema = Personne.e_schema
         seschema = Societe.e_schema
-        peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '1*'
-        peschema.subjrels['connait'].rdef(peschema, peschema).cardinality = '11'
-        peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema).cardinality = '1*'
-        seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema).cardinality = '1*'
-        # testing basic fetch_attrs attribute
-        self.assertEqual(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
+        torestore = []
+        for rdef, card in [(peschema.subjrels['travaille'].rdef(peschema, seschema), '1*'),
+                           (peschema.subjrels['connait'].rdef(peschema, peschema), '11'),
+                           (peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema), '1*'),
+                           (seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema), '1*')]:
+            cm = tempattr(rdef, 'cardinality', card)
+            cm.__enter__()
+            torestore.append(cm)
         try:
+            # testing basic fetch_attrs attribute
+            self.assertEqual(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')
             # testing unknown attributes
             Personne.fetch_attrs = ('bloug', 'beep')
             self.assertEqual(Personne.fetch_rql(user), 'Any X WHERE X is Personne')
@@ -185,8 +198,9 @@
                               '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
+            # fetch_attrs restored by generic tearDown
+            for cm in torestore:
+                cm.__exit__(None, None, None)
 
     def test_related_rql_base(self):
         Personne = self.vreg['etypes'].etype_class('Personne')
@@ -227,7 +241,7 @@
         user = self.request().user
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
         self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
-                         'WHERE NOT S use_email O, S eid %(x)s, '
+                         'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, '
                          'O is EmailAddress, O address AA, O alias AB, O modification_date AC')
 
     def test_unrelated_rql_security_1_user(self):
@@ -236,43 +250,80 @@
         user = self.request().user
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
         self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
-                          'WHERE NOT S use_email O, S eid %(x)s, '
+                         'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, '
                          'O is EmailAddress, O address AA, O alias AB, O modification_date AC')
         user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0)
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
-        self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
-                         'NOT EXISTS(S use_email O), S eid %(x)s, '
-                         'O is EmailAddress, O address AA, O alias AB, O modification_date AC, '
-                         'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+        self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
+                         'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, '
+                         'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, '
+                         'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
 
     def test_unrelated_rql_security_1_anon(self):
         self.login('anon')
         user = self.request().user
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
-        self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
-                         'NOT EXISTS(S use_email O), S eid %(x)s, '
-                         'O is EmailAddress, O address AA, O alias AB, O modification_date AC, '
-                         'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+        self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
+                         'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, '
+                         'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, '
+                         'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
 
     def test_unrelated_rql_security_2(self):
         email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0)
         rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
-        self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ASC '
-                          'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD')
+        self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
+                         'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, '
+                         'S login AA, S firstname AB, S surname AC, S modification_date AD')
         self.login('anon')
         email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0)
         rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
         self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
-                          'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
-                          'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+                         'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, '
+                         'S login AA, S firstname AB, S surname AC, S modification_date AD, '
+                         'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
 
     def test_unrelated_rql_security_nonexistant(self):
         self.login('anon')
         email = self.vreg['etypes'].etype_class('EmailAddress')(self.request())
         rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
         self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
-                          'WHERE S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
-                          'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+                         'WHERE S is CWUser, '
+                         'S login AA, S firstname AB, S surname AC, S modification_date AD, '
+                         'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+
+    def test_unrelated_rql_constraints_creation_subject(self):
+        person = self.vreg['etypes'].etype_class('Personne')(self.request())
+        rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0]
+        self.assertEqual(
+            rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
+            'O is Personne, O nom AA, O prenom AB, O modification_date AC')
+
+    def test_unrelated_rql_constraints_creation_object(self):
+        person = self.vreg['etypes'].etype_class('Personne')(self.request())
+        rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0]
+        self.assertEqual(
+            rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE '
+            'S is Personne, S nom AA, S prenom AB, S modification_date AC, '
+            'NOT (S connait A, A nom "toto"), A is Personne, EXISTS(S travaille B, B nom "tutu")')
+
+    def test_unrelated_rql_constraints_edition_subject(self):
+        person = self.request().create_entity('Personne', nom=u'sylvain')
+        rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0]
+        self.assertEqual(
+            rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
+            'NOT EXISTS(S connait O), S eid %(x)s, O is Personne, '
+            'O nom AA, O prenom AB, O modification_date AC, '
+            'NOT S identity O')
+
+    def test_unrelated_rql_constraints_edition_object(self):
+        person = self.request().create_entity('Personne', nom=u'sylvain')
+        rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0]
+        self.assertEqual(
+            rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE '
+            'NOT EXISTS(S connait O), O eid %(x)s, S is Personne, '
+            'S nom AA, S prenom AB, S modification_date AC, '
+            'NOT S identity O, NOT (S connait A, A nom "toto"), '
+            'EXISTS(S travaille B, B nom "tutu")')
 
     def test_unrelated_base(self):
         req = self.request()
--- a/test/unittest_schema.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/test/unittest_schema.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -169,7 +169,7 @@
                              'Password', 'Personne',
                              'RQLExpression',
                              'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
-                             'Tag', 'Time', 'Transition', 'TrInfo',
+                             'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo',
                              'Workflow', 'WorkflowTransition']
         self.assertListEqual(sorted(expected_entities), entities)
         relations = sorted([str(r) for r in schema.relations()])
@@ -239,7 +239,7 @@
         self.failUnlessEqual(len(constraints), 1, constraints)
         constraint = constraints[0]
         self.failUnless(isinstance(constraint, RQLConstraint))
-        self.failUnlessEqual(constraint.restriction, 'O final TRUE')
+        self.failUnlessEqual(constraint.expression, 'O final TRUE')
 
     def test_fulltext_container(self):
         schema = loader.load(config)
@@ -315,7 +315,7 @@
 class GuessRrqlExprMainVarsTC(TestCase):
     def test_exists(self):
         mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3)'))
-        self.assertEqual(mainvars, 'O')
+        self.assertEqual(mainvars, set(['O']))
 
 
 if __name__ == '__main__':
--- a/uilib.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/uilib.py	Wed Apr 13 11:46:00 2011 +0200
@@ -62,9 +62,9 @@
         return value
     if attrtype == 'Date':
         return ustrftime(value, req.property_value('ui.date-format'))
-    if attrtype == 'Time':
+    if attrtype in ('Time', 'TZTime'):
         return ustrftime(value, req.property_value('ui.time-format'))
-    if attrtype == 'Datetime':
+    if attrtype in ('Datetime', 'TZDatetime'):
         if displaytime:
             return ustrftime(value, req.property_value('ui.datetime-format'))
         return ustrftime(value, req.property_value('ui.date-format'))
@@ -72,8 +72,9 @@
         if value:
             return req._('yes')
         return req._('no')
-    if attrtype == 'Float':
+    if attrtype in ('Float', 'Decimal'):
         value = req.property_value('ui.float-format') % value
+    # XXX Interval
     return unicode(value)
 
 
--- a/utils.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/utils.py	Wed Apr 13 11:46:00 2011 +0200
@@ -361,17 +361,43 @@
         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)
+        self._namespaces = [('xmlns', 'http://www.w3.org/1999/xhtml'),
+                            ('xmlns:cubicweb','http://www.logilab.org/2008/cubicweb')]
+        self._htmlattrs = [('xml:lang', req.lang),
+                           ('lang', req.lang)]
         # keep main_stream's reference on req for easier text/html demoting
         req.main_stream = self
 
+    def add_namespace(self, prefix, uri):
+        self._namespaces.append( (prefix, uri) )
+
+    def set_namespaces(self, namespaces):
+        self._namespaces = namespaces
+
+    def add_htmlattr(self, attrname, attrvalue):
+        self._htmlattrs.append( (attrname, attrvalue) )
+
+    def set_htmlattrs(self, attrs):
+        self._htmlattrs = attrs
+
+    def set_doctype(self, doctype, reset_xmldecl=True):
+        self.doctype = doctype
+        if reset_xmldecl:
+            self.xmldecl = u''
+
     def write(self, data):
         """StringIO interface: this method will be assigned to self.w
         """
         self.body.write(data)
 
+    @property
+    def htmltag(self):
+        attrs = ' '.join('%s="%s"' % (attr, xml_escape(value))
+                         for attr, value in (self._namespaces + self._htmlattrs))
+        if attrs:
+            return '<html %s>' % attrs
+        return '<html>'
+
     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,
@@ -379,7 +405,6 @@
                                                  self.head.getvalue(),
                                                  self.body.getvalue())
 
-
 try:
     # may not be there if cubicweb-web not installed
     if sys.version_info < (2, 6):
--- a/web/data/cubicweb.reledit.js	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/data/cubicweb.reledit.js	Wed Apr 13 11:46:00 2011 +0200
@@ -18,6 +18,7 @@
     cleanupAfterCancel: function (divid) {
         jQuery('#appMsg').hide();
         jQuery('div.errorMessage').remove();
+        // plus re-set inline style ?
         jQuery('#' + divid).show();
         jQuery('#' + divid + '-value').show();
         jQuery('#' + divid + '-form').hide();
@@ -63,9 +64,9 @@
      * @param reload: boolean to reload page if true (when changing URL dependant data)
      * @param default_value : value if the field is empty
      */
-    loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid) {
+    loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid, action) {
         var args = {fname: 'reledit_form', rtype: rtype, role: role,
-                    pageid: pageid,
+                    pageid: pageid, action: action,
                     eid: eid, divid: divid, formid: formid,
                     reload: reload, vid: vid};
         var d = jQuery('#'+divid+'-reledit').loadxhtml(JSON_BASE_URL, args, 'post');
--- a/web/formfields.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/formfields.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1200,14 +1200,19 @@
 
 
 FIELDS = {
-    'Boolean':  BooleanField,
+    'String' :  StringField,
     'Bytes':    FileField,
-    'Date':     DateField,
-    'Datetime': DateTimeField,
+    'Password': PasswordField,
+
+    'Boolean':  BooleanField,
     'Int':      IntField,
     'Float':    FloatField,
     'Decimal':  StringField,
-    'Password': PasswordField,
-    'String' :  StringField,
-    'Time':     TimeField,
+
+    'Date':       DateField,
+    'Datetime':   DateTimeField,
+    'TZDatetime': DateTimeField,
+    'Time':       TimeField,
+    'TZTime':     TimeField,
+    # XXX implement 'Interval': TimeIntervalField,
     }
--- a/web/request.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/request.py	Wed Apr 13 11:46:00 2011 +0200
@@ -734,26 +734,14 @@
         return None, None
 
     def parse_accept_header(self, header):
-        """returns an ordered list of preferred languages"""
+        """returns an ordered list of accepted values"""
+        try:
+            value_parser, value_sort_key = ACCEPT_HEADER_PARSER[header.lower()]
+        except KeyError:
+            value_parser = value_sort_key = None
         accepteds = self.get_header(header, '')
-        values = []
-        for info in accepteds.split(','):
-            try:
-                value, scores = info.split(';', 1)
-            except ValueError:
-                value = info
-                score = 1.0
-            else:
-                for score in scores.split(';'):
-                    try:
-                        scorekey, scoreval = score.split('=')
-                        if scorekey == 'q': # XXX 'level'
-                            score = float(scoreval)
-                    except ValueError:
-                        continue
-            values.append((score, value))
-        values.sort(reverse=True)
-        return (value for (score, value) in values)
+        values = _parse_accept_header(accepteds, value_parser, value_sort_key)
+        return (raw_value for (raw_value, parsed_value, score) in values)
 
     def header_if_modified_since(self):
         """If the HTTP header If-modified-since is set, return the equivalent
@@ -768,8 +756,16 @@
         will display '<[' at the beginning of the page
         """
         self.set_content_type('text/html')
-        self.main_stream.doctype = TRANSITIONAL_DOCTYPE_NOEXT
-        self.main_stream.xmldecl = u''
+        self.main_stream.set_doctype(TRANSITIONAL_DOCTYPE_NOEXT)
+
+    def set_doctype(self, doctype, reset_xmldecl=True):
+        """helper method to dynamically change page doctype
+
+        :param doctype: the new doctype, e.g. '<!DOCTYPE html>'
+        :param reset_xmldecl: if True, remove the '<?xml version="1.0"?>'
+                              declaration from the page
+        """
+        self.main_stream.set_doctype(doctype, reset_xmldecl)
 
     # page data management ####################################################
 
@@ -858,5 +854,91 @@
                 self.parse_accept_header('Accept-Language')]
 
 
+
+## HTTP-accept parsers / utilies ##############################################
+def _mimetype_sort_key(accept_info):
+    """accepted mimetypes must be sorted by :
+
+    1/ highest score first
+    2/ most specific mimetype first, e.g. :
+       - 'text/html level=1' is more specific 'text/html'
+       - 'text/html' is more specific than 'text/*'
+       - 'text/*' itself more specific than '*/*'
+
+    """
+    raw_value, (media_type, media_subtype, media_type_params), score = accept_info
+    # FIXME: handle '+' in media_subtype ? (should xhtml+xml have a
+    # higher precedence than xml ?)
+    if media_subtype == '*':
+        score -= 0.0001
+    if media_type == '*':
+        score -= 0.0001
+    return 1./score, media_type, media_subtype, 1./(1+len(media_type_params))
+
+def _charset_sort_key(accept_info):
+    """accepted mimetypes must be sorted by :
+
+    1/ highest score first
+    2/ most specific charset first, e.g. :
+       - 'utf-8' is more specific than '*'
+    """
+    raw_value, value, score = accept_info
+    if value == '*':
+        score -= 0.0001
+    return 1./score, value
+
+def _parse_accept_header(raw_header, value_parser=None, value_sort_key=None):
+    """returns an ordered list accepted types
+
+    returned value is a list of 2-tuple (value, score), ordered
+    by score. Exact type of `value` will depend on what `value_parser`
+    will reutrn. if `value_parser` is None, then the raw value, as found
+    in the http header, is used.
+    """
+    if value_sort_key is None:
+        value_sort_key = lambda infos: 1./infos[-1]
+    values = []
+    for info in raw_header.split(','):
+        score = 1.0
+        other_params = {}
+        try:
+            value, infodef = info.split(';', 1)
+        except ValueError:
+            value = info
+        else:
+            for info in infodef.split(';'):
+                try:
+                    infokey, infoval = info.split('=')
+                    if infokey == 'q': # XXX 'level'
+                        score = float(infoval)
+                        continue
+                except ValueError:
+                    continue
+                other_params[infokey] = infoval
+        parsed_value = value_parser(value, other_params) if value_parser else value
+        values.append( (value.strip(), parsed_value, score) )
+    values.sort(key=value_sort_key)
+    return values
+
+
+def _mimetype_parser(value, other_params):
+    """return a 3-tuple
+    (type, subtype, type_params) corresponding to the mimetype definition
+    e.g. : for 'text/*', `mimetypeinfo` will be ('text', '*', {}), for
+    'text/html;level=1', `mimetypeinfo` will be ('text', '*', {'level': '1'})
+    """
+    try:
+        media_type, media_subtype = value.strip().split('/')
+    except ValueError: # safety belt : '/' should always be present
+        media_type = value.strip()
+        media_subtype = '*'
+    return (media_type, media_subtype, other_params)
+
+
+ACCEPT_HEADER_PARSER = {
+    'accept': (_mimetype_parser, _mimetype_sort_key),
+    'accept-charset': (None, _charset_sort_key),
+    }
+
 from cubicweb import set_log_methods
 set_log_methods(CubicWebRequestBase, LOGGER)
--- a/web/schemaviewer.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/schemaviewer.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""an helper class to display CubicWeb schema using ureports
+"""an helper class to display CubicWeb schema using ureports"""
 
-"""
 __docformat__ = "restructuredtext en"
 _ = unicode
 
@@ -217,7 +216,7 @@
                     if val is None:
                         val = ''
                     elif prop == 'constraints':
-                        val = ', '.join([c.restriction for c in val])
+                        val = ', '.join([c.expression for c in val])
                     elif isinstance(val, dict):
                         for key, value in val.iteritems():
                             if isinstance(value, (list, tuple)):
--- a/web/test/unittest_application.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/test/unittest_application.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
--- a/web/test/unittest_form.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/test/unittest_form.py	Wed Apr 13 11:46:00 2011 +0200
@@ -104,9 +104,9 @@
 
     def test_reledit_composite_field(self):
         rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"')
-        form = self.vreg['views'].select('doreledit', self.request(),
+        form = self.vreg['views'].select('reledit', self.request(),
                                          rset=rset, row=0, rtype='content')
-        data = form.render(row=0, rtype='content', formid='base')
+        data = form.render(row=0, rtype='content', formid='base', action='edit_rtype')
         self.failUnless('content_format' in data)
 
     # form view tests #########################################################
--- a/web/test/unittest_reledit.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/test/unittest_reledit.py	Wed Apr 13 11:46:00 2011 +0200
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
-mainly regression-preventing tests for reledit/doreledit views
+mainly regression-preventing tests for reledit views
 """
 
 from cubicweb.devtools.testlib import CubicWebTC
@@ -33,9 +33,9 @@
 class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC):
 
     def test_default_config(self):
-        reledit = {'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, false, &#39;&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-                   'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to add a value"><img title="click to add a value" src="http://testing.fr/cubicweb/data/plus.png" alt="click to add a value"/></div></div></div>""",
-                   'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+        reledit = {'title': '''<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, false, &#39;&#39;, &#39;edit_rtype&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>''',
+                   'long_desc': '''<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, false, &#39;autolimited&#39;, &#39;add&#39;);" title="click to add a value"><img title="click to add a value" src="http://testing.fr/cubicweb/data/plus.png" alt="click to add a value"/></div></div></div>''',
+                   'manager': '''<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;, &#39;edit_rtype&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>''',
                    'composite_card11_2ttypes': """&lt;not specified&gt;""",
                    'concerns': """&lt;not specified&gt;"""}
 
@@ -44,9 +44,11 @@
                 continue
             rtype = rschema.type
             self.assertMultiLineEqual(reledit[rtype] % {'eid': self.proj.eid},
-                                      self.proj.view('reledit', rtype=rtype, role=role), rtype)
+                                      self.proj.view('reledit', rtype=rtype, role=role),
+                                      rtype)
 
     def test_default_forms(self):
+        self.skip('Need to check if this test should still run post reledit/doreledit merge')
         doreledit = {'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="title-subject-%(eid)s-form" onsubmit="return freezeFormButtons(&#39;title-subject-%(eid)s-form&#39;);" class="releditForm" cubicweb:target="eformframe">
 <fieldset>
 <input name="__form_id" type="hidden" value="base" />
@@ -190,11 +192,11 @@
         reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'),
                                    {'edit_target': 'rtype'})
         reledit = {
-            'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, true, &#39;&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-            'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;long_desc is required&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, true, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-            'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/%(toto)s" title="">Toto</a></div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-%(eid)s-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;deleteconf&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to delete this value"><img title="click to delete this value" src="http://testing.fr/cubicweb/data/cancel.png" alt="click to delete this value"/></div></div></div>""",
+            'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, true, &#39;&#39;, &#39;edit_rtype&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;long_desc is required&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, true, &#39;autolimited&#39;, &#39;edit_rtype&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/%(toto)s" title="">Toto</a></div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;, &#39;edit_related&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-%(eid)s-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;deleteconf&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;, &#39;delete&#39;);" title="click to delete this value"><img title="click to delete this value" src="http://testing.fr/cubicweb/data/cancel.png" alt="click to delete this value"/></div></div></div>""",
             'composite_card11_2ttypes': """&lt;not specified&gt;""",
-            'concerns': """<div id="concerns-object-%(eid)s-reledit" onmouseout="jQuery('#concerns-object-%(eid)s').addClass('hidden')" onmouseover="jQuery('#concerns-object-%(eid)s').removeClass('hidden')" class="releditField"><div id="concerns-object-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/%(tick)s" title="">write the code</a></div><div id="concerns-object-%(eid)s" class="editableField hidden"><div id="concerns-object-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;concerns&#39;, &#39;object&#39;, &#39;concerns-object-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
+            'concerns': """<div id="concerns-object-%(eid)s-reledit" onmouseout="jQuery('#concerns-object-%(eid)s').addClass('hidden')" onmouseover="jQuery('#concerns-object-%(eid)s').removeClass('hidden')" class="releditField"><div id="concerns-object-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/%(tick)s" title="">write the code</a></div><div id="concerns-object-%(eid)s" class="editableField hidden"><div id="concerns-object-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;concerns&#39;, &#39;object&#39;, &#39;concerns-object-%(eid)s&#39;, false, &#39;autolimited&#39;, &#39;edit_rtype&#39;);" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
             }
         for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
             if rschema not in reledit:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_request.py	Wed Apr 13 11:46:00 2011 +0200
@@ -0,0 +1,69 @@
+"""misc. unittests for utility functions
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from functools import partial
+
+from cubicweb.web.request import (_parse_accept_header,
+                                  _mimetype_sort_key, _mimetype_parser, _charset_sort_key)
+
+
+
+class AcceptParserTC(TestCase):
+
+    def test_parse_accept(self):
+        parse_accept_header = partial(_parse_accept_header,
+                                      value_parser=_mimetype_parser,
+                                      value_sort_key=_mimetype_sort_key)
+        # compare scores
+        self.assertEqual(parse_accept_header("audio/*;q=0.2, audio/basic"),
+                         [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ),
+                          ( ('audio/*', ('audio', '*', {}), 0.2 ) )])
+        self.assertEqual(parse_accept_header("text/plain;q=0.5, text/html, text/x-dvi;q=0.8, text/x-c"),
+                         [( ('text/html', ('text', 'html', {}), 1.0 ) ),
+                          ( ('text/x-c', ('text', 'x-c', {}), 1.0 ) ),
+                          ( ('text/x-dvi', ('text', 'x-dvi', {}), 0.8 ) ),
+                          ( ('text/plain', ('text', 'plain', {}), 0.5 ) )])
+        # compare mimetype precedence for a same given score
+        self.assertEqual(parse_accept_header("audio/*, audio/basic"),
+                         [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ),
+                          ( ('audio/*', ('audio', '*', {}), 1.0 ) )])
+        self.assertEqual(parse_accept_header("text/*, text/html, text/html;level=1, */*"),
+                         [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ),
+                          ( ('text/html', ('text', 'html', {}), 1.0 ) ),
+                          ( ('text/*', ('text', '*', {}), 1.0 ) ),
+                          ( ('*/*', ('*', '*', {}), 1.0 ) )])
+        # free party
+        self.assertEqual(parse_accept_header("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"),
+                         [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ),
+                          ( ('text/html', ('text', 'html', {}), 0.7 ) ),
+                          ( ('*/*', ('*', '*', {}), 0.5 ) ),
+                          ( ('text/html', ('text', 'html', {'level': '2'}), 0.4 ) ),
+                          ( ('text/*', ('text', '*', {}), 0.3 ) )
+                          ])
+        # chrome sample header
+        self.assertEqual(parse_accept_header("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"),
+                         [( ('application/xhtml+xml', ('application', 'xhtml+xml', {}), 1.0 ) ),
+                          ( ('application/xml', ('application', 'xml', {}), 1.0 ) ),
+                          ( ('image/png', ('image', 'png', {}), 1.0 ) ),
+                          ( ('text/html', ('text', 'html', {}), 0.9 ) ),
+                          ( ('text/plain', ('text', 'plain', {}), 0.8 ) ),
+                          ( ('*/*', ('*', '*', {}), 0.5 ) ),
+                          ])
+
+    def test_parse_accept_language(self):
+        self.assertEqual(_parse_accept_header('fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'),
+                         [('fr', 'fr', 1.0), ('fr-fr', 'fr-fr', 0.8),
+                          ('en-us', 'en-us', 0.5), ('en', 'en', 0.3)])
+
+    def test_parse_accept_charset(self):
+        parse_accept_header = partial(_parse_accept_header,
+                                      value_sort_key=_charset_sort_key)
+        self.assertEqual(parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
+                         [('ISO-8859-1', 'ISO-8859-1', 1.0),
+                          ('utf-8', 'utf-8', 0.7),
+                          ('*', '*', 0.7)])
+
+if __name__ == '__main__':
+    unittest_main()
--- a/web/test/unittest_views_baseviews.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/test/unittest_views_baseviews.py	Wed Apr 13 11:46:00 2011 +0200
@@ -16,11 +16,14 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import with_statement
+
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.utils import json
+from cubicweb.view import StartupView, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web.htmlwidgets import TableWidget
 from cubicweb.web.views import vid_from_rset
 
@@ -125,5 +128,47 @@
         self.assertListEqual(got, expected)
 
 
+class HTMLStreamTests(CubicWebTC):
+
+    def test_set_doctype_reset_xmldecl(self):
+        """
+        tests `cubicweb.web.request.CubicWebRequestBase.set_doctype`
+        with xmldecl reset
+        """
+        class MyView(StartupView):
+            __regid__ = 'my-view'
+            def call(self):
+                self._cw.set_doctype('<!DOCTYPE html>')
+
+        with self.temporary_appobjects(MyView):
+            html_source = self.view('my-view').source
+            source_lines = [line.strip() for line in html_source.splitlines(False)
+                            if line.strip()]
+            self.assertListEqual(source_lines[:2],
+                                 ['<!DOCTYPE html>',
+                                  '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" xml:lang="en" lang="en">'])
+
+    def test_set_doctype_no_reset_xmldecl(self):
+        """
+        tests `cubicweb.web.request.CubicWebRequestBase.set_doctype`
+        with no xmldecl reset
+        """
+        html_doctype = TRANSITIONAL_DOCTYPE_NOEXT.strip()
+        class MyView(StartupView):
+            __regid__ = 'my-view'
+            def call(self):
+                self._cw.set_doctype(html_doctype, reset_xmldecl=False)
+                self._cw.main_stream.set_namespaces([('xmlns', 'http://www.w3.org/1999/xhtml')])
+                self._cw.main_stream.set_htmlattrs([('lang', 'cz')])
+
+        with self.temporary_appobjects(MyView):
+            html_source = self.view('my-view').source
+            source_lines = [line.strip() for line in html_source.splitlines(False)
+                            if line.strip()]
+            self.assertListEqual(source_lines[:3],
+                                 ['<?xml version="1.0" encoding="UTF-8"?>',
+                                  html_doctype,
+                                  '<html xmlns="http://www.w3.org/1999/xhtml" lang="cz">'])
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/views/basecontrollers.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/basecontrollers.py	Wed Apr 13 11:46:00 2011 +0200
@@ -455,13 +455,13 @@
     def js_reledit_form(self):
         req = self._cw
         args = dict((x, req.form[x])
-                    for x in ('formid', 'rtype', 'role', 'reload'))
+                    for x in ('formid', 'rtype', 'role', 'reload', 'action'))
         rset = req.eid_rset(typed_eid(self._cw.form['eid']))
         try:
             args['reload'] = json.loads(args['reload'])
         except ValueError: # not true/false, an absolute url
             assert args['reload'].startswith('http')
-        view = req.vreg['views'].select('doreledit', req, rset=rset, rtype=args['rtype'])
+        view = req.vreg['views'].select('reledit', req, rset=rset, rtype=args['rtype'])
         return self._call_view(view, **args)
 
     @jsonize
--- a/web/views/owl.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/owl.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -35,15 +35,19 @@
                 }
 
 OWL_TYPE_MAP = {'String': 'xsd:string',
-                'Datetime': 'xsd:dateTime',
                 'Bytes': 'xsd:byte',
-                'Float': 'xsd:float',
+                'Password': 'xsd:byte',
+
                 'Boolean': 'xsd:boolean',
                 'Int': 'xsd:int',
+                'Float': 'xsd:float',
+                'Decimal' : 'xsd:decimal',
+
                 'Date':'xsd:date',
+                'Datetime': 'xsd:dateTime',
+                'TZDatetime': 'xsd:dateTime',
                 'Time': 'xsd:time',
-                'Password': 'xsd:byte',
-                'Decimal' : 'xsd:decimal',
+                'TZTime': 'xsd:time',
                 'Interval': 'xsd:duration'
                 }
 
--- a/web/views/plots.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/plots.py	Wed Apr 13 11:46:00 2011 +0200
@@ -47,7 +47,7 @@
 @objectify_selector
 def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
     etypes = rset.description[0]
-    if etypes[0] not in ('Date', 'Datetime'):
+    if etypes[0] not in ('Date', 'Datetime', 'TZDatetime'):
         return 0
     for etype in etypes[1:]:
         if etype not in ('Int', 'Float'):
--- a/web/views/reledit.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/reledit.py	Wed Apr 13 11:46:00 2011 +0200
@@ -15,7 +15,9 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""the 'reledit' feature (eg edit attribute/relation from primary view)"""
+"""edit entity attributes/relations from any view, without going to the entity
+form
+"""
 
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -24,7 +26,8 @@
 from warnings import warn
 
 from logilab.mtconverter import xml_escape
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import deprecated, class_renamed
+from logilab.common.decorators import cached
 
 from cubicweb import neg_role
 from cubicweb.schema import display_name
@@ -43,16 +46,18 @@
         return u''
     def append_field(self, *args):
         pass
+    def add_hidden(self, *args):
+        pass
 
 rctrl = uicfg.reledit_ctrl
 
-class ClickAndEditFormView(EntityView):
-    __regid__ = 'doreledit'
+class AutoClickAndEditFormView(EntityView):
+    __regid__ = 'reledit'
     __select__ = non_final_entity() & match_kwargs('rtype')
 
     # ui side continuations
     _onclick = (u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', "
-                "'%(divid)s', %(reload)s, '%(vid)s');")
+                "'%(divid)s', %(reload)s, '%(vid)s', '%(action)s');")
     _cancelclick = "cw.reledit.cleanupAfterCancel('%s')"
 
     # ui side actions/buttons
@@ -75,93 +80,84 @@
                                 # function taking the subject entity & returning a boolean or an eid
                   rvid=None,    # vid to be applied to other side of rtype (non final relations only)
                   default_value=None,
-                  formid='base'
+                  formid='base',
+                  action=None
                   ):
         """display field to edit entity's `rtype` relation on click"""
         assert rtype
-        assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
         self._cw.add_css('cubicweb.form.css')
         self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
-        entity = self.cw_rset.get_entity(row, col)
+        self.entity = self.cw_rset.get_entity(row, col)
         rschema = self._cw.vreg.schema[rtype]
-        self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
+        self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*')
         if rvid is not None or default_value is not None:
             warn('[3.9] specifying rvid/default_value on select is deprecated, '
                  'reledit_ctrl rtag to control this' % self, DeprecationWarning)
-        reload = self._compute_reload(entity, rschema, role, reload)
-        divid = self._build_divid(rtype, role, entity.eid)
+        reload = self._compute_reload(rschema, role, reload)
+        divid = self._build_divid(rtype, role, self.entity.eid)
         if rschema.final:
-            self._handle_attribute(entity, rschema, role, divid, reload)
+            self._handle_attribute(rschema, role, divid, reload, action)
         else:
             if self._is_composite():
-                self._handle_composite(entity, rschema, role, divid, reload, formid)
+                self._handle_composite(rschema, role, divid, reload, formid, action)
             else:
-                self._handle_relation(entity, rschema, role, divid, reload, formid)
+                self._handle_relation(rschema, role, divid, reload, formid, action)
 
-    def _handle_attribute(self, entity, rschema, role, divid, reload):
-        rtype = rschema.type
-        value = entity.printable_value(rtype)
-        if not self._should_edit_attribute(entity, rschema):
+    def _handle_attribute(self, rschema, role, divid, reload, action):
+        value = self.entity.printable_value(rschema.type)
+        if not self._should_edit_attribute(rschema):
             self.w(value)
             return
-        display_label, related_entity = self._prepare_form(entity, rtype, role)
-        form, renderer = self._build_form(entity, rtype, role, divid, 'base',
-                                          reload, display_label, related_entity)
+        form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action)
         value = value or self._compute_default_value(rschema, role)
         self.view_form(divid, value, form, renderer)
 
-    def _compute_formid_value(self, entity, rschema, role, rvid, formid):
-        related_rset = entity.related(rschema.type, role)
+    def _compute_formid_value(self, rschema, role, rvid, formid):
+        related_rset = self.entity.related(rschema.type, role)
         if related_rset:
             value = self._cw.view(rvid, related_rset)
         else:
             value = self._compute_default_value(rschema, role)
-        if not self._should_edit_relation(entity, rschema, role):
+        if not self._should_edit_relation(rschema, role):
             return None, value
         return formid, value
 
-    def _handle_relation(self, entity, rschema, role, divid, reload, formid):
+    def _handle_relation(self, rschema, role, divid, reload, formid, action):
         rvid = self._rules.get('rvid', 'autolimited')
-        formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
+        formid, value = self._compute_formid_value(rschema, role, rvid, formid)
         if formid is None:
             return self.w(value)
-        rtype = rschema.type
-        display_label, related_entity = self._prepare_form(entity, rtype, role)
-        form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
-                                          display_label, related_entity, dict(vid=rvid))
+        form, renderer = self._build_form(self.entity,  rschema, role, divid, formid,
+                                          reload, action, dict(vid=rvid))
         self.view_form(divid, value, form, renderer)
 
-    def _handle_composite(self, entity, rschema, role, divid, reload, formid):
+    def _handle_composite(self, rschema, role, divid, reload, formid, action):
         # this is for attribute-like composites (1 target type, 1 related entity at most, for now)
-        ttypes = self._compute_ttypes(rschema, role)
+        entity = self.entity
         related_rset = entity.related(rschema.type, role)
-        add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes)
-        edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes)
-        delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role)
+        add_related = self._may_add_related(related_rset, rschema, role)
+        edit_related = self._may_edit_related_entity(related_rset, rschema, role)
+        delete_related = edit_related and self._may_delete_related(related_rset, rschema, role)
         rvid = self._rules.get('rvid', 'autolimited')
-        formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
+        formid, value = self._compute_formid_value(rschema, role, rvid, formid)
         if formid is None or not (edit_related or add_related):
             # till we learn to handle cases where not (edit_related or add_related)
             self.w(value)
             return
-        rtype = rschema.type
-        ttype = ttypes[0]
-        _fdata = self._prepare_composite_form(entity, rtype, role, edit_related,
-                                              add_related and ttype)
-        display_label, related_entity = _fdata
-        form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
-                                          display_label, related_entity, dict(vid=rvid))
+        form, renderer = self._build_form(entity, rschema, role, divid, formid,
+                                          reload, action, dict(vid=rvid))
         self.view_form(divid, value, form, renderer,
                        edit_related, add_related, delete_related)
 
+    @cached
     def _compute_ttypes(self, rschema, role):
         dual_role = neg_role(role)
         return getattr(rschema, '%ss' % dual_role)()
 
-    def _compute_reload(self, entity, rschema, role, reload):
+    def _compute_reload(self, rschema, role, reload):
         ctrl_reload = self._rules.get('reload', reload)
         if callable(ctrl_reload):
-            ctrl_reload = ctrl_reload(entity)
+            ctrl_reload = ctrl_reload(self.entity)
         if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False
             ctrl_reload = self._cw.build_url(ctrl_reload)
         return ctrl_reload
@@ -179,33 +175,36 @@
     def _is_composite(self):
         return self._rules.get('edit_target') == 'related'
 
-    def _may_add_related(self, related_rset, entity, rschema, role, ttypes):
+    def _may_add_related(self, related_rset, rschema, role):
         """ ok for attribute-like composite entities """
+        ttypes = self._compute_ttypes(rschema, role)
         if len(ttypes) > 1: # many etypes: learn how to do it
             return False
-        rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role)
+        rdef = rschema.role_rdef(self.entity.e_schema, ttypes[0], role)
         card = rdef.role_cardinality(role)
         if related_rset or card not in '?1':
             return False
         if role == 'subject':
-            kwargs = {'fromeid': entity.eid}
+            kwargs = {'fromeid': self.entity.eid}
         else:
-            kwargs = {'toeid': entity.eid}
+            kwargs = {'toeid': self.entity.eid}
         return rdef.has_perm(self._cw, 'add', **kwargs)
 
-    def _may_edit_related_entity(self, related_rset, entity, rschema, role, ttypes):
+    def _may_edit_related_entity(self, related_rset, rschema, role):
         """ controls the edition of the related entity """
+        ttypes = self._compute_ttypes(rschema, role)
         if len(ttypes) > 1 or len(related_rset.rows) != 1:
             return False
-        if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
+        if self.entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
             return False
         return related_rset.get_entity(0, 0).cw_has_perm('update')
 
-    def _may_delete_related(self, related_rset, entity, rschema, role):
+    def _may_delete_related(self, related_rset, rschema, role):
         # we assume may_edit_related, only 1 related entity
         if not related_rset:
             return False
         rentity = related_rset.get_entity(0, 0)
+        entity = self.entity
         if role == 'subject':
             kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid}
         else:
@@ -230,33 +229,33 @@
         """ builds an id for the root div of a reledit widget """
         return '%s-%s-%s' % (rtype, role, entity_eid)
 
-    def _build_args(self, entity, rtype, role, formid, reload,
+    def _build_args(self, entity, rtype, role, formid, reload, action,
                     extradata=None):
         divid = self._build_divid(rtype, role, entity.eid)
         event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid,
-                      'reload' : json_dumps(reload),
+                      'reload' : json_dumps(reload), 'action': action,
                       'role' : role, 'vid' : u''}
         if extradata:
             event_args.update(extradata)
         return event_args
 
-    def _prepare_form(self, entity, _rtype, role):
-        display_label = False
-        related_entity = entity
-        return display_label, related_entity
-
-    def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related):
-        display_label = True
-        if edit_related and not add_related:
-            related_entity = entity.related(rtype, role).get_entity(0, 0)
-        elif add_related:
-            _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw)
+    def _prepare_form(self, entity, rschema, role, action):
+        assert action in ('edit_rtype', 'edit_related', 'add', 'delete'), action
+        if action == 'edit_rtype':
+            return False, entity
+        label = True
+        if action in ('edit_related', 'delete'):
+            edit_entity = entity.related(rschema, role).get_entity(0, 0)
+        elif action == 'add':
+            add_etype = self._compute_ttypes(rschema, role)[0]
+            _new_entity = self._cw.vreg['etypes'].etype_class(add_etype)(self._cw)
             _new_entity.eid = self._cw.varmaker.next()
-            related_entity = _new_entity
+            edit_entity = _new_entity
             # XXX see forms.py ~ 276 and entities.linked_to method
             #     is there another way ?
-            self._cw.form['__linkto'] = '%s:%s:%s' % (rtype, entity.eid, neg_role(role))
-        return display_label, related_entity
+            self._cw.form['__linkto'] = '%s:%s:%s' % (rschema, entity.eid, neg_role(role))
+        assert edit_entity
+        return label, edit_entity
 
     def _build_renderer(self, related_entity, display_label):
         return self._cw.vreg['formrenderers'].select(
@@ -266,13 +265,18 @@
             display_help=False, button_bar_class='buttonbar',
             display_progress_div=False)
 
-    def _build_form(self, entity, rtype, role, divid, formid, reload,
-                    display_label, related_entity, extradata=None, **formargs):
-        event_args = self._build_args(entity, rtype, role, formid,
-                                      reload, extradata)
+    def _build_form(self, entity, rschema, role, divid, formid, reload, action,
+                    extradata=None, **formargs):
+        rtype = rschema.type
+        event_args = self._build_args(entity, rtype, role, formid, reload, action, extradata)
+        if not action:
+            form = _DummyForm()
+            form.event_args = event_args
+            return form, None
+        label, edit_entity = self._prepare_form(entity, rschema, role, action)
         cancelclick = self._cancelclick % divid
         form = self._cw.vreg['forms'].select(
-            formid, self._cw, rset=related_entity.as_rset(), entity=related_entity,
+            formid, self._cw, rset=edit_entity.as_rset(), entity=edit_entity,
             domid='%s-form' % divid, formtype='inlined',
             action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'),
             cwtarget='eformframe', cssclass='releditForm',
@@ -298,9 +302,10 @@
         if formid == 'base':
             field = form.field_by_name(rtype, role, entity.e_schema)
             form.append_field(field)
-        return form, self._build_renderer(related_entity, display_label)
+        return form, self._build_renderer(edit_entity, label)
 
-    def _should_edit_attribute(self, entity, rschema):
+    def _should_edit_attribute(self, rschema):
+        entity = self.entity
         rdef = entity.e_schema.rdef(rschema)
         # check permissions
         if not entity.cw_has_perm('update'):
@@ -312,8 +317,8 @@
                                         ' use _should_edit_attribute instead',
                                         _should_edit_attribute)
 
-    def _should_edit_relation(self, entity, rschema, role):
-        eeid = entity.eid
+    def _should_edit_relation(self, rschema, role):
+        eeid = self.entity.eid
         perm_args = {'fromeid': eeid} if role == 'subject' else {'toeid': eeid}
         return rschema.has_perm(self._cw, 'add', **perm_args)
 
@@ -335,9 +340,11 @@
         w(u'<div id="%s" class="editableField hidden">' % divid)
 
     def _edit_action(self, divid, args, edit_related, add_related, _delete_related):
+        # XXX disambiguate wrt edit_related
         if not add_related: # currently, excludes edition
             w = self.w
             args['formid'] = 'edition' if edit_related else 'base'
+            args['action'] = 'edit_related' if edit_related else 'edit_rtype'
             w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' %
               (divid, xml_escape(self._onclick % args), self._cw._(self._editzonemsg)))
             w(self._build_edit_zone())
@@ -346,7 +353,8 @@
     def _add_action(self, divid, args, _edit_related, add_related, _delete_related):
         if add_related:
             w = self.w
-            args['formid'] = 'edition' if add_related else 'base'
+            args['formid'] = 'edition'
+            args['action'] = 'add'
             w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' %
               (divid, xml_escape(self._onclick % args), self._cw._(self._addmsg)))
             w(self._build_add_zone())
@@ -356,6 +364,7 @@
         if delete_related:
             w = self.w
             args['formid'] = 'deleteconf'
+            args['action'] = 'delete'
             w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">' %
               (divid, xml_escape(self._onclick % args), self._cw._(self._deletemsg)))
             w(self._build_delete_zone())
@@ -376,13 +385,4 @@
         self._close_form_wrapper()
 
 
-class AutoClickAndEditFormView(ClickAndEditFormView):
-    __regid__ = 'reledit'
-
-    def _build_form(self, entity, rtype, role, divid, formid, reload,
-                    display_label, related_entity, extradata=None, **formargs):
-        event_args = self._build_args(entity, rtype, role, 'base',
-                                      reload, extradata)
-        form = _DummyForm()
-        form.event_args = event_args
-        return form, None
+ClickAndEditFormView = class_renamed('ClickAndEditFormView', AutoClickAndEditFormView)
--- a/web/views/sparql.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/sparql.py	Wed Apr 13 11:46:00 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -77,17 +77,22 @@
 
 YAMS_XMLSCHEMA_MAPPING = {
     'String': 'string',
+
+    'Boolean': 'boolean',
     'Int': 'integer',
     'Float': 'float',
-    'Boolean': 'boolean',
+
     'Datetime': 'dateTime',
+    'TZDatetime': 'dateTime',
     'Date': 'date',
     'Time': 'time',
+    'TZTime': 'time',
+
     # XXX the following types don't have direct mapping
     'Decimal': 'string',
     'Interval': 'duration',
+    'Bytes': 'base64Binary',
     'Password': 'string',
-    'Bytes': 'base64Binary',
     }
 
 def xmlschema(yamstype):
--- a/web/views/xmlrss.py	Wed Apr 13 11:34:53 2011 +0200
+++ b/web/views/xmlrss.py	Wed Apr 13 11:46:00 2011 +0200
@@ -42,6 +42,8 @@
     'Date': lambda x: x.strftime('%Y-%m-%d'),
     'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'),
     'Time': lambda x: x.strftime('%H:%M:%S'),
+    'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
+    'TZTime': lambda x: x.strftime('%H:%M:%S'),
     'Interval': lambda x: x.days * 60*60*24 + x.seconds,
     }