backport stable branch
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Thu, 16 Jul 2009 13:30:13 +0200
changeset 2381 caad2367d940
parent 2374 ea1a44e4ad62 (current diff)
parent 2380 5d980ba57632 (diff)
child 2383 96780c1e0c53
child 2540 fba943784b1c
backport stable branch
entity.py
goa/appobjects/components.py
rset.py
selectors.py
server/__init__.py
server/test/data/schema/Affaire.py
server/test/data/schema/Note.sql
server/test/data/schema/Personne.sql
server/test/data/schema/Societe.py
server/test/data/schema/custom.py
server/test/data/schema/note.py
server/test/data/schema/relations.rel
vregistry.py
web/box.py
web/component.py
web/controller.py
web/data/cubicweb.edition.js
web/test/unittest_form.py
web/test/unittest_views_baseviews.py
web/uicfg.py
web/views/basecomponents.py
web/views/basecontrollers.py
web/views/basetemplates.py
web/views/cwproperties.py
web/views/editforms.py
web/views/editviews.py
web/views/facets.py
web/views/formrenderers.py
web/views/iprogress.py
web/views/management.py
web/views/navigation.py
web/views/primary.py
web/views/schema.py
web/views/startup.py
web/views/tableview.py
web/views/tabs.py
web/views/treeview.py
web/views/workflow.py
--- a/common/uilib.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/common/uilib.py	Thu Jul 16 13:30:13 2009 +0200
@@ -15,7 +15,7 @@
 from urllib import quote as urlquote
 from StringIO import StringIO
 
-from logilab.mtconverter import html_escape, html_unescape
+from logilab.mtconverter import xml_escape, html_unescape
 
 from cubicweb.utils import ustrftime
 
@@ -66,7 +66,7 @@
 except ImportError:
     def rest_publish(entity, data):
         """default behaviour if docutils was not found"""
-        return html_escape(data)
+        return xml_escape(data)
 
 TAG_PROG = re.compile(r'</?.*?>', re.U)
 def remove_html_tags(text):
@@ -108,7 +108,7 @@
     if len(text_nohtml) <= length:
         return text
     # else if un-tagged text is too long, cut it
-    return html_escape(text_nohtml[:length] + u'...')
+    return xml_escape(text_nohtml[:length] + u'...')
 
 fallback_safe_cut = safe_cut
 
@@ -220,15 +220,15 @@
             attrs['class'] = attrs.pop('klass')
         except KeyError:
             pass
-        value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value)))
+        value += u' ' + u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
                                   for attr, value in sorted(attrs.items())
                                   if value is not None)
     if content:
         if escapecontent:
-            content = html_escape(unicode(content))
+            content = xml_escape(unicode(content))
         value += u'>%s</%s>' % (content, tag)
     else:
-        value += u'/>'
+        value += u'></%s>' % tag
     return value
 
 def tooltipize(text, tooltip, url=None):
@@ -406,9 +406,9 @@
         strings.append(body)
         strings.append(u'</div>')
     if title:
-        strings.append(u'<h1 class="error">%s</h1>'% html_escape(title))
+        strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
     try:
-        strings.append(u'<p class="error">%s</p>' % html_escape(str(exception)).replace("\n","<br />"))
+        strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
     except UnicodeError:
         pass
     strings.append(u'<div class="error_traceback">')
@@ -416,9 +416,9 @@
         strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
                        u'<b class="line">%s</b>, <b>function</b> '
                        u'<b class="function">%s</b>:<br/>'%(
-            html_escape(stackentry[0]), stackentry[1], html_escape(stackentry[2])))
+            xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
         if stackentry[3]:
-            string = html_escape(stackentry[3]).decode('utf-8', 'replace')
+            string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
             strings.append(u'&nbsp;&nbsp;%s<br/>\n' % (string))
         # add locals info for each entry
         try:
@@ -426,7 +426,7 @@
             html_info = []
             chars = 0
             for name, value in local_context.iteritems():
-                value = html_escape(repr(value))
+                value = xml_escape(repr(value))
                 info = u'<span class="name">%s</span>=%s, ' % (name, value)
                 line_length = len(name) + len(value)
                 chars += line_length
@@ -491,5 +491,5 @@
     def newfunc(*args, **kwargs):
         ret = function(*args, **kwargs)
         assert isinstance(ret, basestring)
-        return html_escape(ret)
+        return xml_escape(ret)
     return newfunc
--- a/cwconfig.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/cwconfig.py	Thu Jul 16 13:30:13 2009 +0200
@@ -571,7 +571,7 @@
           }),
         ('sender-addr',
          {'type' : 'string',
-          'default': 'devel@logilab.fr',
+          'default': 'cubicweb@mydomain.com',
           'help': 'email address used as HELO address for outgoing emails from \
 the repository',
           'group': 'email', 'inputlevel': 1,
--- a/doc/book/en/development/datamodel/define-workflows.rst	Wed Jul 15 09:45:13 2009 +0200
+++ b/doc/book/en/development/datamodel/define-workflows.rst	Thu Jul 16 13:30:13 2009 +0200
@@ -118,7 +118,7 @@
 * `%(seid)s`, the object's current state eid
 
 
-.. image:: images/lax-book.03-transitions-view.en.png
+.. image:: ../../images/lax-book.03-transitions-view.en.png
 
 You can notice that in the action box of a BlogEntry, the state
 is now listed as well as the possible transitions defined by the workflow.
--- a/entity.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/entity.py	Thu Jul 16 13:30:13 2009 +0200
@@ -13,7 +13,7 @@
 from logilab.common.compat import all
 from logilab.common.decorators import cached
 from logilab.common.deprecation import obsolete
-from logilab.mtconverter import TransformData, TransformError, html_escape
+from logilab.mtconverter import TransformData, TransformError, xml_escape
 
 from rql.utils import rqlvar_maker
 
@@ -463,7 +463,7 @@
             return u''
         value = printable_value(self.req, attrtype, value, props, displaytime)
         if format == 'text/html':
-            value = html_escape(value)
+            value = xml_escape(value)
         return value
 
     def mtc_transform(self, data, format, target_format, encoding,
@@ -666,6 +666,7 @@
                     self.critical("can't get value for attribute %s of entity with eid %s",
                                   name, self.eid)
                     if self.e_schema.destination(name) == 'String':
+                        # XXX (syt) imo emtpy string is better
                         self[name] = value = self.req._('unaccessible')
                     else:
                         self[name] = value = None
--- a/ext/html4zope.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/ext/html4zope.py	Thu Jul 16 13:30:13 2009 +0200
@@ -24,7 +24,7 @@
 
 __docformat__ = 'reStructuredText'
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from docutils import nodes
 from docutils.writers.html4css1 import Writer as CSS1Writer
@@ -154,7 +154,7 @@
         error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
             a_start, node['type'], node['level'], a_end,
             self.encode(node['source']), line, backref_text)
-        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % xml_escape(error))
 
     def depart_system_message(self, node):
         pass
--- a/ext/rest.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/ext/rest.py	Thu Jul 16 13:30:13 2009 +0200
@@ -29,7 +29,7 @@
 from docutils.parsers.rst import Parser, states, directives
 from docutils.parsers.rst.roles import register_canonical_role, set_classes
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import ESC_UCAR_TABLE, ESC_CAR_TABLE, xml_escape
 
 from cubicweb.ext.html4zope import Writer
 
@@ -207,8 +207,12 @@
     req = context.req
     if isinstance(data, unicode):
         encoding = 'unicode'
+        # remove unprintable characters unauthorized in xml
+        data = data.translate(ESC_UCAR_TABLE)
     else:
         encoding = req.encoding
+        # remove unprintable characters unauthorized in xml
+        data = data.translate(ESC_CAR_TABLE)
     settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
                 'warning_stream': StringIO(), 'context': context,
                 # dunno what's the max, severe is 4, and we never want a crash
@@ -232,5 +236,5 @@
         LOGGER.exception('error while publishing ReST text')
         if not isinstance(data, unicode):
             data = unicode(data, encoding, 'replace')
-        return html_escape(req._('error while publishing ReST text')
+        return xml_escape(req._('error while publishing ReST text')
                            + '\n\n' + data)
--- a/goa/appobjects/components.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/goa/appobjects/components.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,7 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import typed_eid
 from cubicweb.selectors import one_line_rset, match_search_state, accept
@@ -74,7 +74,7 @@
         label = display_name(req, etype, 'plural')
         view = self.vreg.select('views', 'list', req, req.etype_rset(etype))
         url = view.url()
-        etypelink = u'&nbsp;<a href="%s">%s</a>' % (html_escape(url), label)
+        etypelink = u'&nbsp;<a href="%s">%s</a>' % (xml_escape(url), label)
         yield (label, etypelink, self.add_entity_link(eschema, req))
 
 ManageView.entity_types = entity_types_no_count
--- a/goa/appobjects/dbmgmt.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/goa/appobjects/dbmgmt.py	Thu Jul 16 13:30:13 2009 +0200
@@ -12,7 +12,7 @@
 from pickle import loads, dumps
 
 from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import none_rset, match_user_groups
 from cubicweb.common.view import StartupView
@@ -54,7 +54,7 @@
                     break
         values.append('__session=%s' % cookie['__session'].value)
         self.w(u"<p>pass this flag to the client: --cookie='%s'</p>"
-               % html_escape('; '.join(values)))
+               % xml_escape('; '.join(values)))
 
 
 
@@ -148,7 +148,7 @@
                          % cpath)
                 self.w(u'<div>click <a href="%s?vid=contentclear">here</a> to '
                        '<b>delete all datastore content</b> so process can be '
-                       'reinitialized</div>' % html_escape(self.req.base_url()))
+                       'reinitialized</div>' % xml_escape(self.req.base_url()))
         Put(status)
 
     @property
@@ -159,11 +159,11 @@
                                              repo=self.config.repository())
 
     def msg(self, msg):
-        self.w(u'<div class="message">%s</div>' % html_escape(msg))
+        self.w(u'<div class="message">%s</div>' % xml_escape(msg))
     def redirect(self, msg):
         raise Redirect(self.req.build_url('', msg))
     def continue_link(self):
-        self.w(u'<a href="%s">continue</a><br/>' % html_escape(self.req.url()))
+        self.w(u'<a href="%s">continue</a><br/>' % xml_escape(self.req.url()))
 
 
 class ContentClear(StartupView):
--- a/i18n/en.po	Wed Jul 15 09:45:13 2009 +0200
+++ b/i18n/en.po	Thu Jul 16 13:30:13 2009 +0200
@@ -160,6 +160,9 @@
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
 msgstr ""
 
+msgid "<no value>"
+msgstr ""
+
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -1147,6 +1150,9 @@
 msgid "click on the box to cancel the deletion"
 msgstr ""
 
+msgid "click to edit this field"
+msgstr ""
+
 msgid "comment"
 msgstr ""
 
@@ -1721,12 +1727,6 @@
 msgid "facets_cwfinal-facet_description"
 msgstr ""
 
-msgid "facets_cwmeta-facet"
-msgstr ""
-
-msgid "facets_cwmeta-facet_description"
-msgstr ""
-
 msgid "facets_etype-facet"
 msgstr "\"entity type\" facet"
 
@@ -2259,9 +2259,6 @@
 msgid "not selected"
 msgstr ""
 
-msgid "not specified"
-msgstr ""
-
 msgid "not the initial state for this entity"
 msgstr ""
 
@@ -2946,6 +2943,12 @@
 msgid "view workflow"
 msgstr ""
 
+msgid "view_index"
+msgstr "index"
+
+msgid "view_manage"
+msgstr "site management"
+
 msgid "views"
 msgstr ""
 
--- a/i18n/fr.po	Wed Jul 15 09:45:13 2009 +0200
+++ b/i18n/fr.po	Thu Jul 16 13:30:13 2009 +0200
@@ -164,8 +164,11 @@
 "<div>This schema of the data model <em>excludes</em> the meta-data, but you "
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
 msgstr ""
-"<div>Ce schéma du modèle de données <em>exclue</em> les méta-données, mais vous "
-"pouvez afficher un <a href=\"%s\">schéma complet</a>.</div>"
+"<div>Ce schéma du modèle de données <em>exclue</em> les méta-données, mais "
+"vous pouvez afficher un <a href=\"%s\">schéma complet</a>.</div>"
+
+msgid "<no value>"
+msgstr "<non spécifié>"
 
 msgid "?*"
 msgstr "0..1 0..n"
@@ -633,9 +636,10 @@
 "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
 "get_cache() method."
 msgstr ""
-"une simple entité de cache, caractérisées par un nom et une date de validité. L'application "
-"est responsable de la mise à jour de la date quand il est nécessaire d'invalider le cache (typiquement dans les crochets). "
-"Voir aussi la méthode get_cache() sur la classe AppRsetObject."
+"une simple entité de cache, caractérisées par un nom et une date de "
+"validité. L'application est responsable de la mise à jour de la date quand "
+"il est nécessaire d'invalider le cache (typiquement dans les crochets). Voir "
+"aussi la méthode get_cache() sur la classe AppRsetObject."
 
 msgid "about this site"
 msgstr "à propos de ce site"
@@ -1039,7 +1043,8 @@
 msgstr "signets"
 
 msgid "bookmarks are used to have user's specific internal links"
-msgstr "les signets sont utilisés pour gérer des liens internes par utilisateur"
+msgstr ""
+"les signets sont utilisés pour gérer des liens internes par utilisateur"
 
 msgid "boxes"
 msgstr "boîtes"
@@ -1178,7 +1183,10 @@
 msgstr "cliquez ici pour voir l'entité créée"
 
 msgid "click on the box to cancel the deletion"
-msgstr "cliquer dans la zone d'édition pour annuler la suppression"
+msgstr "cliquez dans la zone d'édition pour annuler la suppression"
+
+msgid "click to edit this field"
+msgstr "cliquez pour éditer ce champ"
 
 msgid "comment"
 msgstr "commentaire"
@@ -1800,12 +1808,6 @@
 msgid "facets_cwfinal-facet_description"
 msgstr ""
 
-msgid "facets_cwmeta-facet"
-msgstr ""
-
-msgid "facets_cwmeta-facet_description"
-msgstr ""
-
 msgid "facets_etype-facet"
 msgstr "facette \"est de type\""
 
@@ -2360,9 +2362,6 @@
 msgid "not selected"
 msgstr "non sélectionné"
 
-msgid "not specified"
-msgstr "non spécifié"
-
 msgid "not the initial state for this entity"
 msgstr "n'est pas l'état initial pour cette entité"
 
@@ -3069,6 +3068,12 @@
 msgid "view workflow"
 msgstr "voir les états possibles"
 
+msgid "view_index"
+msgstr "accueil"
+
+msgid "view_manage"
+msgstr "gestion du site"
+
 msgid "views"
 msgstr "vues"
 
@@ -3252,6 +3257,9 @@
 #~ msgid "no associated epermissions"
 #~ msgstr "aucune permission spécifique n'est définie"
 
+#~ msgid "not specified"
+#~ msgstr "non spécifié"
+
 #~ msgid "owned by"
 #~ msgstr "appartient à"
 
--- a/rset.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/rset.py	Thu Jul 16 13:30:13 2009 +0200
@@ -538,10 +538,12 @@
     for i, term in enumerate(rqlst.selection):
         if i == index:
             continue
-        try:
-            # XXX rewritten const
-            var = term.variable
-        except AttributeError:
+        # XXX rewritten const
+        # use iget_nodes for (hack) case where we have things like MAX(V)
+        for vref in term.iget_nodes(nodes.VariableRef):
+            var = vref.variable
+            break
+        else:
             continue
         #varname = var.name
         for ref in var.references():
--- a/rtags.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/rtags.py	Thu Jul 16 13:30:13 2009 +0200
@@ -101,6 +101,7 @@
                    '%r is not an allowed tag (should be in %s)' % (
                 tag, self._allowed_values)
         self._tagdefs[(rtype, tagged, stype, otype)] = tag
+        return tag
 
     # rtag runtime api ########################################################
 
@@ -123,15 +124,19 @@
 
 
 class RelationTagsSet(RelationTags):
-    """This class associates a set of tags to each key."""
+    """This class associates a set of tags to each key.
+    """
+    tag_container_cls = set
 
     def tag_relation(self, key, tag):
         stype, rtype, otype, tagged = [str(k) for k in key]
-        rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set())
+        rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype),
+                                         self.tag_container_cls())
         rtags.add(tag)
+        return rtags
 
     def get(self, stype, rtype, otype, tagged):
-        rtags = set()
+        rtags = self.tag_container_cls()
         for key in self._get_keys(stype, rtype, otype, tagged):
             try:
                 rtags.update(self._tagdefs[key])
@@ -140,6 +145,31 @@
         return rtags
 
 
+class RelationTagsDict(RelationTagsSet):
+    """This class associates a set of tags to each key."""
+    tag_container_cls = dict
+
+    def tag_relation(self, key, tag):
+        stype, rtype, otype, tagged = [str(k) for k in key]
+        try:
+            rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+            rtags.update(tag)
+            return rtags
+        except KeyError:
+            self._tagdefs[(rtype, tagged, stype, otype)] = tag
+            return tag
+
+    def setdefault(self, key, tagkey, tagvalue):
+        stype, rtype, otype, tagged = [str(k) for k in key]
+        try:
+            rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+            rtags.setdefault(tagkey, tagvalue)
+            return rtags
+        except KeyError:
+            self._tagdefs[(rtype, tagged, stype, otype)] = {tagkey: tagvalue}
+            return self._tagdefs[(rtype, tagged, stype, otype)]
+
+
 class RelationTagsBool(RelationTags):
     _allowed_values = frozenset((True, False))
 
--- a/selectors.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/selectors.py	Thu Jul 16 13:30:13 2009 +0200
@@ -79,7 +79,7 @@
         ret = selector(cls, *args, **kwargs)
         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
-            print 'selector %s returned %s for %s' % (selname, ret, vobj)
+            print '%s -> %s for %s' % (selname, ret, vobj)
         return ret
     traced.__name__ = selector.__name__
     return traced
--- a/server/__init__.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/__init__.py	Thu Jul 16 13:30:13 2009 +0200
@@ -50,8 +50,7 @@
     driver = source['db-driver']
     sqlcnx = repo.system_source.get_connection()
     sqlcursor = sqlcnx.cursor()
-    def execute(sql, args=None):
-        repo.system_source.doexec(sqlcursor, sql, args)
+    execute = sqlcursor.execute
     if drop:
         dropsql = sqldropschema(schema, driver)
         try:
--- a/server/hooksmanager.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/hooksmanager.py	Thu Jul 16 13:30:13 2009 +0200
@@ -216,7 +216,14 @@
             cls.warning('%s hook has been disabled', cls)
             return
         done = set()
+        assert isinstance(cls.events, (tuple, list)), \
+               '%s: events is expected to be a tuple, not %s' % (
+            cls, type(cls.events))
         for event in cls.events:
+            if event == 'server_startup':
+                assert not cls.accepts or cls.accepts == ('Any',), \
+                       '%s doesnt make sense on server_startup' % cls.accepts
+                cls.accepts = ('Any',)
             for ertype in cls.accepts:
                 if (event, ertype) in done:
                     continue
--- a/server/rqlannotation.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/rqlannotation.py	Thu Jul 16 13:30:13 2009 +0200
@@ -22,7 +22,7 @@
     has_text_query = False
     need_distinct = rqlst.distinct
     for rel in rqlst.iget_nodes(Relation):
-        if getrschema(rel.r_type).symetric:
+        if getrschema(rel.r_type).symetric and not rel.neged(strict=True):
             for vref in rel.iget_nodes(VariableRef):
                 stinfo = vref.variable.stinfo
                 if not stinfo['constnode'] and stinfo['selected']:
--- a/server/session.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/session.py	Thu Jul 16 13:30:13 2009 +0200
@@ -88,9 +88,7 @@
         """return a sql cursor on the system database"""
         if not sql.split(None, 1)[0].upper() == 'SELECT':
             self.mode = 'write'
-        cursor = self.pool['system']
-        self.pool.source('system').doexec(cursor, sql, args)
-        return cursor
+        return self.pool.source('system').doexec(self, sql, args)
 
     def set_language(self, language):
         """i18n configuration for translation"""
@@ -137,24 +135,27 @@
             raise Exception('try to set pool on a closed session')
         if self.pool is None:
             # get pool first to avoid race-condition
-            self._threaddata.pool = self.repo._get_pool()
+            self._threaddata.pool = pool = self.repo._get_pool()
             try:
-                self._threaddata.pool.pool_set()
+                pool.pool_set()
             except:
                 self._threaddata.pool = None
-                self.repo._free_pool(self.pool)
+                self.repo._free_pool(pool)
                 raise
             self._threads_in_transaction.add(threading.currentThread())
         return self._threaddata.pool
 
     def reset_pool(self):
-        """the session has no longer using its pool, at least for some time"""
+        """the session is no longer using its pool, at least for some time"""
         # pool may be none if no operation has been done since last commit
         # or rollback
         if self.pool is not None and self.mode == 'read':
             # even in read mode, we must release the current transaction
             pool = self.pool
-            self._threads_in_transaction.remove(threading.currentThread())
+            try:
+                self._threads_in_transaction.remove(threading.currentThread())
+            except KeyError:
+                pass
             pool.pool_reset()
             self._threaddata.pool = None
             # free pool once everything is done to avoid race-condition
--- a/server/sources/extlite.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/sources/extlite.py	Thu Jul 16 13:30:13 2009 +0200
@@ -174,9 +174,7 @@
         if server.DEBUG:
             print self.uri, 'SOURCE RQL', union.as_string()
         args = self.sqladapter.merge_args(args, query_args)
-        cursor = session.pool[self.uri]
-        self.doexec(cursor, sql, args)
-        res = self.sqladapter.process_result(cursor)
+        res = self.sqladapter.process_result(self.doexec(session, sql, args))
         if server.DEBUG:
             print '------>', res
         return res
@@ -190,7 +188,7 @@
         """
         attrs = self.sqladapter.preprocess_entity(entity)
         sql = self.sqladapter.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def add_entity(self, session, entity):
         """add a new entity to the source"""
@@ -207,7 +205,7 @@
             attrs = self.sqladapter.preprocess_entity(entity)
         sql = self.sqladapter.sqlgen.update(SQL_PREFIX + str(entity.e_schema),
                                             attrs, [SQL_PREFIX + 'eid'])
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def update_entity(self, session, entity):
         """update an entity in the source"""
@@ -222,7 +220,7 @@
         """
         attrs = {SQL_PREFIX + 'eid': eid}
         sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + etype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def local_add_relation(self, session, subject, rtype, object):
         """add a relation to the source
@@ -233,7 +231,7 @@
         """
         attrs = {'eid_from': subject, 'eid_to': object}
         sql = self.sqladapter.sqlgen.insert('%s_relation' % rtype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def add_relation(self, session, subject, rtype, object):
         """add a relation to the source"""
@@ -252,21 +250,25 @@
         else:
             attrs = {'eid_from': subject, 'eid_to': object}
             sql = self.sqladapter.sqlgen.delete('%s_relation' % rtype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
-    def doexec(self, cursor, query, args=None):
+    def doexec(self, session, query, args=None):
         """Execute a query.
         it's a function just so that it shows up in profiling
         """
-        #t1 = time()
         if server.DEBUG:
             print 'exec', query, args
-        #import sys
-        #sys.stdout.flush()
-        # str(query) to avoid error if it's an unicode string
+        cursor = session.pool[self.uri]
         try:
+            # str(query) to avoid error if it's an unicode string
             cursor.execute(str(query), args)
         except Exception, ex:
             self.critical("sql: %r\n args: %s\ndbms message: %r",
                           query, args, ex.args[0])
+            try:
+                session.pool.connection(self.uri).rollback()
+                self.critical('transaction has been rollbacked')
+            except:
+                pass
             raise
+        return cursor
--- a/server/sources/native.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/sources/native.py	Thu Jul 16 13:30:13 2009 +0200
@@ -31,6 +31,7 @@
 from cubicweb.server.sources.rql2sql import SQLGenerator
 
 
+ATTR_MAP = {}
 NONSYSTEM_ETYPES = set()
 NONSYSTEM_RELATIONS = set()
 
@@ -90,6 +91,7 @@
 class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
     """adapter for source using the native cubicweb schema (see below)
     """
+    sqlgen_class = SQLGenerator
     # need default value on class since migration doesn't call init method
     has_deleted_entitites_table = True
 
@@ -141,8 +143,8 @@
         AbstractSource.__init__(self, repo, appschema, source_config,
                                 *args, **kwargs)
         # sql generator
-        self._rql_sqlgen = SQLGenerator(appschema, self.dbhelper,
-                                        self.encoding)
+        self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
+                                             self.encoding, ATTR_MAP.copy())
         # full text index helper
         self.indexer = get_indexer(self.dbdriver, self.encoding)
         # advanced functionality helper
@@ -185,9 +187,7 @@
 
     def sqlexec(self, session, sql, args=None):
         """execute the query and return its result"""
-        cursor = session.pool[self.uri]
-        self.doexec(cursor, sql, args)
-        return self.process_result(cursor)
+        return self.process_result(self.doexec(session, sql, args))
 
     def init_creating(self):
         pool = self.repo._get_pool()
@@ -211,6 +211,9 @@
         pool.pool_reset()
         self.repo._free_pool(pool)
 
+    def map_attribute(self, etype, attr, cb):
+        self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
+        
     # ISource interface #######################################################
 
     def compile_rql(self, rql):
@@ -305,17 +308,15 @@
                 sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
                 self._cache[cachekey] = sql, query_args
         args = self.merge_args(args, query_args)
-        cursor = session.pool[self.uri]
         assert isinstance(sql, basestring), repr(sql)
         try:
-            self.doexec(cursor, sql, args)
+            cursor = self.doexec(session, sql, args)
         except (self.dbapi_module.OperationalError,
                 self.dbapi_module.InterfaceError):
             # FIXME: better detection of deconnection pb
             self.info("request failed '%s' ... retry with a new cursor", sql)
             session.pool.reconnect(self)
-            cursor = session.pool[self.uri]
-            self.doexec(cursor, sql, args)
+            cursor = self.doexec(session, sql, args)
         res = self.process_result(cursor)
         if server.DEBUG:
             print '------>', res
@@ -337,8 +338,7 @@
             # generate sql queries if we are able to do so
             sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
             query = 'INSERT INTO %s %s' % (table, sql.encode(self.encoding))
-            self.doexec(session.pool[self.uri], query,
-                        self.merge_args(args, query_args))
+            self.doexec(session, query, self.merge_args(args, query_args))
         else:
             super(NativeSQLSource, self).flying_insert(table, session, union,
                                                        args, varmap)
@@ -358,15 +358,14 @@
                     cell = self.binary(cell.getvalue())
                 kwargs[str(index)] = cell
             kwargs_list.append(kwargs)
-        self.doexecmany(session.pool[self.uri], query, kwargs_list)
+        self.doexecmany(session, query, kwargs_list)
 
     def clean_temp_data(self, session, temptables):
         """remove temporary data, usually associated to temporary tables"""
         if temptables:
-            cursor = session.pool[self.uri]
             for table in temptables:
                 try:
-                    self.doexec(cursor,'DROP TABLE %s' % table)
+                    self.doexec(session,'DROP TABLE %s' % table)
                 except:
                     pass
                 try:
@@ -378,25 +377,25 @@
         """add a new entity to the source"""
         attrs = self.preprocess_entity(entity)
         sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def update_entity(self, session, entity):
         """replace an entity in the source"""
         attrs = self.preprocess_entity(entity)
         sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, [SQL_PREFIX + 'eid'])
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def delete_entity(self, session, etype, eid):
         """delete an entity from the source"""
         attrs = {SQL_PREFIX + 'eid': eid}
         sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def add_relation(self, session, subject, rtype, object):
         """add a relation to the source"""
         attrs = {'eid_from': subject, 'eid_to': object}
         sql = self.sqlgen.insert('%s_relation' % rtype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
@@ -410,39 +409,47 @@
         else:
             attrs = {'eid_from': subject, 'eid_to': object}
             sql = self.sqlgen.delete('%s_relation' % rtype, attrs)
-        self.doexec(session.pool[self.uri], sql, attrs)
+        self.doexec(session, sql, attrs)
 
-    def doexec(self, cursor, query, args=None):
+    def doexec(self, session, query, args=None):
         """Execute a query.
         it's a function just so that it shows up in profiling
         """
-        #t1 = time()
         if server.DEBUG:
             print 'exec', query, args
-        #import sys
-        #sys.stdout.flush()
-        # str(query) to avoid error if it's an unicode string
+        cursor = session.pool[self.uri]
         try:
+            # str(query) to avoid error if it's an unicode string
             cursor.execute(str(query), args)
         except Exception, ex:
             self.critical("sql: %r\n args: %s\ndbms message: %r",
                           query, args, ex.args[0])
+            try:
+                session.pool.connection(self.uri).rollback()
+                self.critical('transaction has been rollbacked')
+            except:
+                pass
             raise
+        return cursor
 
-    def doexecmany(self, cursor, query, args):
+    def doexecmany(self, session, query, args):
         """Execute a query.
         it's a function just so that it shows up in profiling
         """
-        #t1 = time()
         if server.DEBUG:
             print 'execmany', query, 'with', len(args), 'arguments'
-        #import sys
-        #sys.stdout.flush()
-        # str(query) to avoid error if it's an unicode string
+        cursor = session.pool[self.uri]
         try:
+            # str(query) to avoid error if it's an unicode string
             cursor.executemany(str(query), args)
-        except:
-            self.critical("sql many: %r\n args: %s", query, args)
+        except Exception, ex:
+            self.critical("sql many: %r\n args: %s\ndbms message: %r",
+                          query, args, ex.args[0])
+            try:
+                session.pool.connection(self.uri).rollback()
+                self.critical('transaction has been rollbacked')
+            except:
+                pass
             raise
 
     # short cut to method requiring advanced db helper usage ##################
@@ -498,14 +505,13 @@
         # running with an ldap source, and table will be deleted manually any way
         # on commit
         sql = self.dbhelper.sql_temporary_table(table, schema, False)
-        self.doexec(session.pool[self.uri], sql)
+        self.doexec(session, sql)
 
     def create_eid(self, session):
         self._eid_creation_lock.acquire()
         try:
-            cursor = session.pool[self.uri]
             for sql in self.dbhelper.sqls_increment_sequence('entities_id_seq'):
-                self.doexec(cursor, sql)
+                cursor = self.doexec(session, sql)
             return cursor.fetchone()[0]
         finally:
             self._eid_creation_lock.release()
--- a/server/sources/rql2sql.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/sources/rql2sql.py	Thu Jul 16 13:30:13 2009 +0200
@@ -303,7 +303,7 @@
     protected by a lock
     """
 
-    def __init__(self, schema, dbms_helper, dbencoding='UTF-8'):
+    def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None):
         self.schema = schema
         self.dbms_helper = dbms_helper
         self.dbencoding = dbencoding
@@ -313,6 +313,9 @@
         if not self.dbms_helper.union_parentheses_support:
             self.union_sql = self.noparen_union_sql
         self._lock = threading.Lock()
+        if attrmap is None:
+            attrmap = {}
+        self.attr_map = attrmap
 
     def generate(self, union, args=None, varmap=None):
         """return SQL queries and a variable dictionnary from a RQL syntax tree
@@ -853,27 +856,30 @@
                                        relation.r_type)
         return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels))
 
-    def _visit_attribute_relation(self, relation):
+    def _visit_attribute_relation(self, rel):
         """generate SQL for an attribute relation"""
-        lhs, rhs = relation.get_parts()
+        lhs, rhs = rel.get_parts()
         rhssql = rhs.accept(self)
         table = self._var_table(lhs.variable)
         if table is None:
-            assert relation.r_type == 'eid'
+            assert rel.r_type == 'eid'
             lhssql = lhs.accept(self)
         else:
             try:
-                lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)]
+                lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)]
             except KeyError:
-                if relation.r_type == 'eid':
+                mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type)
+                if mapkey in self.attr_map:
+                    lhssql = self.attr_map[mapkey](self, lhs.variable, rel)
+                elif rel.r_type == 'eid':
                     lhssql = lhs.variable._q_sql
                 else:
-                    lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type)
+                    lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type)
         try:
-            if relation._q_needcast == 'TODAY':
+            if rel._q_needcast == 'TODAY':
                 sql = 'DATE(%s)%s' % (lhssql, rhssql)
             # XXX which cast function should be used
-            #elif relation._q_needcast == 'NOW':
+            #elif rel._q_needcast == 'NOW':
             #    sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql)
             else:
                 sql = '%s%s' % (lhssql, rhssql)
@@ -884,15 +890,15 @@
         else:
             return sql
 
-    def _visit_has_text_relation(self, relation):
+    def _visit_has_text_relation(self, rel):
         """generate SQL for a has_text relation"""
-        lhs, rhs = relation.get_parts()
+        lhs, rhs = rel.get_parts()
         const = rhs.children[0]
-        alias = self._fti_table(relation)
+        alias = self._fti_table(rel)
         jointo = lhs.accept(self)
         restriction = ''
         lhsvar = lhs.variable
-        me_is_principal = lhsvar.stinfo.get('principal') is relation
+        me_is_principal = lhsvar.stinfo.get('principal') is rel
         if me_is_principal:
             if not lhsvar.stinfo['typerels']:
                 # the variable is using the fti table, no join needed
@@ -908,8 +914,8 @@
                 else:
                     etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes'])
                     restriction = " AND %s.type IN (%s)" % (ealias, etypes)
-        if isinstance(relation.parent, Not):
-            self._state.done.add(relation.parent)
+        if isinstance(rel.parent, Not):
+            self._state.done.add(rel.parent)
             not_ = True
         else:
             not_ = False
@@ -1117,6 +1123,9 @@
         if isinstance(linkedvar, ColumnAlias):
             raise BadRQLQuery('variable %s should be selected by the subquery'
                               % variable.name)
+        mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type)
+        if mapkey in self.attr_map:
+            return self.attr_map[mapkey](self, linkedvar, rel)
         try:
             sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)]
         except KeyError:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/schema.py	Thu Jul 16 13:30:13 2009 +0200
@@ -0,0 +1,207 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+from cubicweb.schema import format_constraint
+
+class Affaire(WorkflowableEntityType):
+    permissions = {
+        'read':   ('managers',
+                   ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
+        'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
+        'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')),
+        'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
+        }
+
+    ref = String(fulltextindexed=True, indexed=True,
+                 constraints=[SizeConstraint(16)])
+    sujet = String(fulltextindexed=True,
+                   constraints=[SizeConstraint(256)])
+    descr_format = String(meta=True, internationalizable=True,
+                                default='text/rest', constraints=[format_constraint])
+    descr = String(fulltextindexed=True,
+                   description=_('more detailed description'))
+
+    duration = Int()
+    invoiced = Int()
+
+    depends_on = SubjectRelation('Affaire')
+    require_permission = SubjectRelation('CWPermission')
+    concerne = SubjectRelation(('Societe', 'Note'))
+    todo_by = SubjectRelation('Personne')
+    documented_by = SubjectRelation('Card')
+
+
+class Societe(EntityType):
+    permissions = {
+        'read': ('managers', 'users', 'guests'),
+        'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
+        'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
+        'add': ('managers', 'users',)
+        }
+
+    nom  = String(maxsize=64, fulltextindexed=True)
+    web  = String(maxsize=128)
+    type  = String(maxsize=128) # attribute in common with Note
+    tel  = Int()
+    fax  = Int()
+    rncs = String(maxsize=128)
+    ad1  = String(maxsize=128)
+    ad2  = String(maxsize=128)
+    ad3  = String(maxsize=128)
+    cp   = String(maxsize=12)
+    ville= String(maxsize=32)
+
+
+class Division(Societe):
+    __specializes_schema__ = True
+
+class SubDivision(Division):
+    __specializes_schema__ = True
+    travaille_subdivision = ObjectRelation('Personne')
+
+_euser = import_schema('base').CWUser
+_euser.__relations__[0].fulltextindexed = True
+
+class Note(EntityType):
+    date = String(maxsize=10)
+    type = String(maxsize=6)
+    para = String(maxsize=512)
+
+    migrated_from = SubjectRelation('Note')
+    attachment = SubjectRelation(('File', 'Image'))
+    inline1 = SubjectRelation('Affaire', inlined=True)
+    todo_by = SubjectRelation('CWUser')
+
+class Personne(EntityType):
+    nom    = String(fulltextindexed=True, required=True, maxsize=64)
+    prenom = String(fulltextindexed=True, maxsize=64)
+    sexe   = String(maxsize=1, default='M')
+    promo  = String(vocabulary=('bon','pasbon'))
+    titre  = String(fulltextindexed=True, maxsize=128)
+    adel   = String(maxsize=128)
+    ass    = String(maxsize=128)
+    web    = String(maxsize=128)
+    tel    = Int()
+    fax    = Int()
+    datenaiss = Datetime()
+    test   = Boolean()
+    description = String()
+    firstname = String(fulltextindexed=True, maxsize=64)
+
+    travaille = SubjectRelation('Societe')
+    concerne = SubjectRelation('Affaire')
+    connait = SubjectRelation('Personne')
+    inline2 = SubjectRelation('Affaire', inlined=True)
+    comments = ObjectRelation('Comment')
+
+
+class fiche(RelationType):
+    inlined = True
+    subject = 'Personne'
+    object = 'Card'
+    cardinality = '??'
+
+class multisource_inlined_rel(RelationType):
+    inlined = True
+    cardinality = '?*'
+    subject = ('Card', 'Note')
+    object = ('Affaire', 'Note')
+
+class ecrit_par(RelationType):
+    inlined = True
+
+class connait(RelationType):
+    symetric = True
+
+class concerne(RelationType):
+    permissions = {
+        'read':   ('managers', 'users', 'guests'),
+        'add':    ('managers', RRQLExpression('U has_update_permission S')),
+        'delete': ('managers', RRQLExpression('O owned_by U')),
+        }
+
+class travaille(RelationType):
+    permissions = {
+        'read':   ('managers', 'users', 'guests'),
+        'add':    ('managers', RRQLExpression('U has_update_permission S')),
+        'delete': ('managers', RRQLExpression('O owned_by U')),
+        }
+
+class para(AttributeRelationType):
+    permissions = {
+        'read':   ('managers', 'users', 'guests'),
+        'add':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
+        'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
+        }
+
+class test(AttributeRelationType):
+    permissions = {'read': ('managers', 'users', 'guests'),
+                   'delete': ('managers',),
+                   'add': ('managers',)}
+
+
+class in_state(RelationDefinition):
+    subject = 'Note'
+    object = 'State'
+    cardinality = '1*'
+    constraints=[RQLConstraint('S is ET, O state_of ET')]
+
+class wf_info_for(RelationDefinition):
+    subject = 'TrInfo'
+    object = 'Note'
+    cardinality = '1*'
+
+class multisource_rel(RelationDefinition):
+    subject = ('Card', 'Note')
+    object = 'Note'
+
+class multisource_crossed_rel(RelationDefinition):
+    subject = ('Card', 'Note')
+    object = 'Note'
+
+
+class see_also(RelationDefinition):
+    subject = ('Bookmark', 'Note')
+    object = ('Bookmark', 'Note')
+
+class evaluee(RelationDefinition):
+    subject = ('Personne', 'CWUser', 'Societe')
+    object = ('Note')
+
+class ecrit_par_1(RelationDefinition):
+    name = 'ecrit_par'
+    subject = 'Note'
+    object ='Personne'
+    constraints = [RQLConstraint('E concerns P, X version_of P')]
+
+class ecrit_par_2(RelationDefinition):
+    name = 'ecrit_par'
+    subject = 'Note'
+    object ='CWUser'
+
+class see_also(RelationDefinition):
+    subject = object = 'Folder'
+
+
+class copain(RelationDefinition):
+    subject = object = 'CWUser'
+
+class tags(RelationDefinition):
+    subject = 'Tag'
+    object = ('CWUser', 'CWGroup', 'State', 'Note', 'Card', 'Affaire')
+
+class filed_under(RelationDefinition):
+    subject = ('Note', 'Affaire')
+    object = 'Folder'
+
+class require_permission(RelationDefinition):
+    subject = ('Card', 'Note')
+    object = 'CWPermission'
+
+class require_state(RelationDefinition):
+    subject = 'CWPermission'
+    object = 'State'
--- a/server/test/data/schema/Affaire.py	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-from cubicweb.schema import format_constraint
-
-class Affaire(WorkflowableEntityType):
-    permissions = {
-        'read':   ('managers',
-                   ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
-        'add':    ('managers', ERQLExpression('X concerne S, S owned_by U')),
-        'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')),
-        'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
-        }
-
-    ref = String(fulltextindexed=True, indexed=True,
-                 constraints=[SizeConstraint(16)])
-    sujet = String(fulltextindexed=True,
-                   constraints=[SizeConstraint(256)])
-    descr_format = String(meta=True, internationalizable=True,
-                                default='text/rest', constraints=[format_constraint])
-    descr = String(fulltextindexed=True,
-                   description=_('more detailed description'))
-
-    duration = Int()
-    invoiced = Int()
-
-    depends_on = SubjectRelation('Affaire')
-    require_permission = SubjectRelation('CWPermission')
-
-class concerne(RelationType):
-    permissions = {
-        'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', RRQLExpression('U has_update_permission S')),
-        'delete': ('managers', RRQLExpression('O owned_by U')),
-        }
-
-
--- a/server/test/data/schema/Note.sql	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-date varchar(10)
-type char(6)
-para varchar(512)
--- a/server/test/data/schema/Personne.sql	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-nom    ivarchar(64) NOT NULL
-prenom ivarchar(64)
-sexe   char(1) DEFAULT 'M' 
-promo  choice('bon','pasbon')
-titre  ivarchar(128)
-adel   varchar(128)
-ass    varchar(128)
-web    varchar(128)
-tel    integer
-fax    integer
-datenaiss datetime
-test   boolean 
-description text
-firstname ivarchar(64)
--- a/server/test/data/schema/Societe.py	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-class Societe(EntityType):
-    permissions = {
-        'read': ('managers', 'users', 'guests'),
-        'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
-        'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
-        'add': ('managers', 'users',)
-        }
-
-    nom  = String(maxsize=64, fulltextindexed=True)
-    web  = String(maxsize=128)
-    type  = String(maxsize=128) # attribute in common with Note
-    tel  = Int()
-    fax  = Int()
-    rncs = String(maxsize=128)
-    ad1  = String(maxsize=128)
-    ad2  = String(maxsize=128)
-    ad3  = String(maxsize=128)
-    cp   = String(maxsize=12)
-    ville= String(maxsize=32)
-
-
-class travaille(RelationType):
-    permissions = {
-        'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', RRQLExpression('U has_update_permission S')),
-        'delete': ('managers', RRQLExpression('O owned_by U')),
-        }
-
-
-class Division(Societe):
-    __specializes_schema__ = True
-
-class SubDivision(Division):
-    __specializes_schema__ = True
-    travaille_subdivision = ObjectRelation('Personne')
--- a/server/test/data/schema/custom.py	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-
-class test(AttributeRelationType):
-    permissions = {'read': ('managers', 'users', 'guests'),
-                   'delete': ('managers',),
-                   'add': ('managers',)}
-
-class fiche(RelationType):
-    inlined = True
-    subject = 'Personne'
-    object = 'Card'
-    cardinality = '??'
-
-class multisource_rel(RelationDefinition):
-    subject = ('Card', 'Note')
-    object = 'Note'
-
-class multisource_crossed_rel(RelationDefinition):
-    subject = ('Card', 'Note')
-    object = 'Note'
-
-class multisource_inlined_rel(RelationType):
-    inlined = True
-    cardinality = '?*'
-    subject = ('Card', 'Note')
-    object = ('Affaire', 'Note')
-
-
-class see_also(RelationDefinition):
-    subject = ('Bookmark', 'Note')
-    object = ('Bookmark', 'Note')
-
-_euser = import_schema('base').CWUser
-_euser.__relations__[0].fulltextindexed = True
--- a/server/test/data/schema/note.py	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-class para(AttributeRelationType):
-    permissions = {
-        'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
-        'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
-        }
-
-class in_state(RelationDefinition):
-    subject = 'Note'
-    object = 'State'
-    cardinality = '1*'
-    constraints=[RQLConstraint('S is ET, O state_of ET')]
-
-class wf_info_for(RelationDefinition):
-    subject = 'TrInfo'
-    object = 'Note'
-    cardinality = '1*'
--- a/server/test/data/schema/relations.rel	Wed Jul 15 09:45:13 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-Personne travaille Societe
-Personne evaluee Note
-CWUser evaluee Note
-Societe evaluee Note
-Personne concerne Affaire
-Affaire concerne Societe
-Affaire concerne Note
-
-Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P
-Note ecrit_par CWUser inline CONSTRAINT
-Personne connait Personne symetric
-
-# not inlined intentionaly
-Comment comments Personne
-
-Note inline1 Affaire inline
-Personne inline2 Affaire inline
-
-Note todo_by CWUser
-Affaire todo_by Personne
-
-Folder see_also Folder
-
-
-Affaire documented_by Card
-
-CWUser copain CWUser
-
-Tag tags CWUser
-Tag tags CWGroup
-Tag tags State
-Tag tags Note
-Tag tags Card
-Tag tags Affaire
-
-Note filed_under Folder
-Affaire filed_under Folder
-
-Card require_permission CWPermission
-Note require_permission CWPermission
-Personne require_permission CWPermission
-
-CWPermission require_state State
-
-Note migrated_from Note
-
-Note attachment File
-Note attachment Image
--- a/server/test/unittest_rql2sql.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Thu Jul 16 13:30:13 2009 +0200
@@ -1436,6 +1436,22 @@
                     '''SELECT COUNT(1)
 WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''')
 
+    def test_attr_map(self):
+        def generate_ref(gen, linkedvar, rel):
+            linkedvar.accept(gen)
+            return 'VERSION_DATA(%s)' % linkedvar._q_sql
+        self.o.attr_map['Affaire.ref'] = generate_ref
+        try:
+            self._check('Any R WHERE X ref R',
+                        '''SELECT VERSION_DATA(X.cw_eid)
+FROM cw_Affaire AS X''')
+            self._check('Any X WHERE X ref 1',
+                        '''SELECT X.cw_eid
+FROM cw_Affaire AS X
+WHERE VERSION_DATA(X.cw_eid)=1''')
+        finally:
+            self.o.attr_map.clear()
+
 
 class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):
 
--- a/skeleton/__pkginfo__.py.tmpl	Wed Jul 15 09:45:13 2009 +0200
+++ b/skeleton/__pkginfo__.py.tmpl	Thu Jul 16 13:30:13 2009 +0200
@@ -34,21 +34,16 @@
             and not fname.endswith('~')
             and not isdir(join(dirpath, fname))]
 
-try:
-    data_files = [
-        # common files
-        [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
-        ]
-    # check for possible extended cube layout
-    for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'):
-        if isdir(dirname):
-            data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
-    # Note: here, you'll need to add subdirectories if you want
-    # them to be included in the debian package
-except OSError:
-    if exists(dirname(__file__)):
-        raise
-    # we are in an installed directory
+data_files = [
+    # common files
+    [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
+    ]
+# check for possible extended cube layout
+for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'):
+    if isdir(dirname):
+        data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
+# Note: here, you'll need to add subdirectories if you want
+# them to be included in the debian package
 
 
 cube_eid = None # <=== FIXME if you need direct bug-subscription
--- a/test/unittest_rtags.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/test/unittest_rtags.py	Thu Jul 16 13:30:13 2009 +0200
@@ -6,7 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.rtags import RelationTags, RelationTagsSet
+from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict
 
 class RelationTagsTC(TestCase):
 
@@ -53,12 +53,30 @@
         rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary')
         self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
                           set(('primary', 'secondary')))
-        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
-                          set(('primary', 'secondary')))
         self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
                           set(('secondary',)))
         self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'),
                           set())
 
+    def test_rtagdict_expansion(self):
+        rtags = RelationTagsDict()
+        rtags.tag_subject_of(('Societe', 'travaille', '*'),
+                             {'key1': 'val1', 'key2': 'val1'})
+        rtags.tag_subject_of(('*', 'travaille', '*'),
+                             {'key1': 'val0', 'key3': 'val0'})
+        rtags.tag_subject_of(('Societe', 'travaille', '*'),
+                             {'key2': 'val2'})
+        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+                          {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'})
+        self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
+                          {'key1': 'val0', 'key3': 'val0'})
+        self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'),
+                          {})
+
+        rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4')
+        rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4')
+        self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+                          {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'})
+
 if __name__ == '__main__':
     unittest_main()
--- a/utils.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/utils.py	Thu Jul 16 13:30:13 2009 +0200
@@ -326,4 +326,18 @@
     """
     # XXX deprecated, no more necessary
 
+def get_schema_property(eschema, rschema, role, property):
+    # XXX use entity.e_schema.role_rproperty(role, rschema, property, tschemas[0]) once yams > 0.23.0 is out
+    if role == 'subject':
+        targetschema = rschema.objects(eschema)[0]
+        return rschema.rproperty(eschema, targetschema, property)
+    targetschema = rschema.subjects(eschema)[0]
+    return rschema.rproperty(targetschema, eschema, property)
 
+def compute_cardinality(eschema, rschema, role):
+    if role == 'subject':
+        targetschema = rschema.objects(eschema)[0]
+        return rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+    targetschema = rschema.subjects(eschema)[0]
+    return rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+
--- a/view.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/view.py	Thu Jul 16 13:30:13 2009 +0200
@@ -12,7 +12,7 @@
 from cStringIO import StringIO
 
 from logilab.common.deprecation import obsolete
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import NotAnEntity
 from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
@@ -219,7 +219,7 @@
 
     def wdata(self, data):
         """simple helper that escapes `data` and writes into `self.w`"""
-        self.w(html_escape(data))
+        self.w(xml_escape(data))
 
     def html_headers(self):
         """return a list of html headers (eg something to be inserted between
@@ -440,10 +440,10 @@
             def cb(*args):
                 _cb(*args)
         cbname = self.req.register_onetime_callback(cb, *args)
-        return self.build_js(cbname, html_escape(msg or ''))
+        return self.build_js(cbname, xml_escape(msg or ''))
 
     def build_update_js_call(self, cbname, msg):
-        rql = html_escape(self.rset.printable_rql())
+        rql = xml_escape(self.rset.printable_rql())
         return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
             cbname, self.id, rql, msg, self.__registry__, self.div_id())
 
--- a/vregistry.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/vregistry.py	Thu Jul 16 13:30:13 2009 +0200
@@ -1,10 +1,10 @@
 """
-* the vregistry handle various type of objects interacting
-  together. The vregistry handle registration of dynamically loaded
-  objects and provide a convenient api access to those objects
+* the vregistry handles various types of objects interacting
+  together. The vregistry handles registration of dynamically loaded
+  objects and provides a convenient api to access those objects
   according to a context
 
-* to interact with the vregistry, object should inherit from the
+* to interact with the vregistry, objects should inherit from the
   VObject abstract class
 
 * the selection procedure has been generalized by delegating to a
--- a/web/box.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/box.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized, role as get_role, target as get_target
 from cubicweb.selectors import (one_line_rset,  primary_view,
@@ -74,7 +74,7 @@
         .format_actions method
         """
         if escape:
-            title = html_escape(title)
+            title = xml_escape(title)
         return self.box_action(self._action(title, path, **kwargs))
 
     def _action(self, title, path, **kwargs):
--- a/web/component.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/component.py	Thu Jul 16 13:30:13 2009 +0200
@@ -9,7 +9,7 @@
 _ = unicode
 
 from logilab.common.deprecation import class_renamed
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import role
 from cubicweb.utils import merge_dicts
@@ -117,7 +117,7 @@
     def page_link(self, path, params, start, stop, content):
         url = self.build_url(path, **merge_dicts(params, {self.start_param : start,
                                                           self.stop_param : stop,}))
-        url = html_escape(url)
+        url = xml_escape(url)
         if start == self.starting_from:
             return self.selected_page_link_templ % (url, content, content)
         return self.page_link_templ % (url, content, content)
@@ -130,7 +130,7 @@
         stop = start + self.page_size - 1
         url = self.build_url(**merge_dicts(params, {self.start_param : start,
                                                     self.stop_param : stop,}))
-        url = html_escape(url)
+        url = xml_escape(url)
         return self.previous_page_link_templ % (url, title, content)
 
     def next_link(self, params, content='&gt;&gt;', title=_('next_results')):
@@ -140,7 +140,7 @@
         stop = start + self.page_size - 1
         url = self.build_url(**merge_dicts(params, {self.start_param : start,
                                                     self.stop_param : stop,}))
-        url = html_escape(url)
+        url = xml_escape(url)
         return self.next_page_link_templ % (url, title, content)
 
 
--- a/web/controller.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/controller.py	Thu Jul 16 13:30:13 2009 +0200
@@ -184,11 +184,9 @@
         elif '__redirectpath' in self.req.form:
             # if redirect path was explicitly specified in the form, use it
             path = self.req.form['__redirectpath']
-            if self._edited_entity:
-                msg = newparams.get('__message', '')
-                msg += ' (<a href="%s">%s</a>)' % (
-                    self._edited_entity.absolute_url(),
-                    self.req._('click here to see created entity'))
+            if self._edited_entity and path != self._edited_entity.rest_path():
+                # XXX may be here on modification? if yes the message should be
+                # modified where __createdpath is detected (cw.web.request)
                 newparams['__createdpath'] = self._edited_entity.rest_path()
         elif self._after_deletion_path:
             # else it should have been set during form processing
--- a/web/data/cubicweb.ajax.js	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Thu Jul 16 13:30:13 2009 +0200
@@ -59,8 +59,8 @@
     if (typeof buildWidgets != 'undefined') {
 	buildWidgets(node);
     }
-    if (typeof roundedCornersOnLoad != 'undefined') {
-	roundedCornersOnLoad();
+    if (typeof roundedCorners != 'undefined') {
+	roundedCorners(node);
     }
     loadDynamicFragments(node);
     jQuery(CubicWeb).trigger('ajax-loaded');
@@ -231,6 +231,7 @@
 	    // make sure the component is visible
 	    removeElementClass(node, "hidden");
 	    swapDOM(node, domnode);
+	    postAjaxLoad(domnode);
 	}
     });
     d.addCallback(resetCursor);
--- a/web/data/cubicweb.edition.js	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/data/cubicweb.edition.js	Thu Jul 16 13:30:13 2009 +0200
@@ -348,11 +348,11 @@
     // Success
     if (result[0]) {
 	if (onsuccess) {
-	    return onsuccess(result[1], formid);
+             onsuccess(result[1], formid);
 	} else {
 	    document.location.href = result[1];
-	    return ;
 	}
+      return;
     }
     unfreezeFormButtons(formid);
     // Failures
@@ -362,7 +362,7 @@
     if ( !isArrayLike(descr) || descr.length != 2 ) {
 	log('got strange error :', descr);
 	updateMessage(descr);
-	return ;
+	return;
     }
     _displayValidationerrors(formid, descr[0], descr[1]);
     updateMessage(_("please correct errors below"));
@@ -370,7 +370,7 @@
     if (onfailure){
 	onfailure(formid);
     }
-    return false;
+    return;
 }
 
 
@@ -422,7 +422,7 @@
     });
 }
 
-$(document).ready(setFormsTarget);
+jQuery(document).ready(setFormsTarget);
 
 
 /*
@@ -454,9 +454,9 @@
  * @param eid : the eid of the entity being edited
  * @param reload: boolean to reload page if true (when changing URL dependant data)
  */
-function inlineValidateAttributeForm(formid, rtype, eid, divid, reload, default_value) {
+function inlineValidateAttributeForm(rtype, eid, divid, reload, default_value) {
     try {
-	var form = getNode(formid);
+	var form = getNode(divid+'-form');
 	if (typeof FCKeditorAPI != "undefined") {
 	    for ( var name in FCKeditorAPI.__Instances ) {
 		var oEditor = FCKeditorAPI.__Instances[name] ;
@@ -473,7 +473,7 @@
 	return false;
     }
     d.addCallback(function (result, req) {
-    handleFormValidationResponse(formid, noop, noop, result);
+        handleFormValidationResponse(divid+'-form', noop, noop, result);
 	if (reload) {
 	    document.location.href = result[1];
 	} else {
@@ -486,7 +486,7 @@
 		// hide global error messages
 		jQuery('div.errorMessage').remove();
 		jQuery('#appMsg').hide();
-		cancelInlineEdit(eid, rtype, divid);
+		hideInlineEdit(eid, rtype, divid);
 	    }
 	}
 	return false;
@@ -494,30 +494,29 @@
     return false;
 }
 
-function inlineValidateRelationForm(formid, rtype, role, eid, divid, vid, default_value) {
+function inlineValidateRelationForm(rtype, role, eid, divid, reload, vid,
+                                    default_value, lzone) {
     try {
-	var form = getNode(formid);
+	var form = getNode(divid+'-form');
         var relname = rtype + ':' + eid;
         var newtarget = jQuery('[name=' + relname + ']').val();
 	var zipped = formContents(form);
-	var d = asyncRemoteExec('edit_relation', 'apply', zipped[0], zipped[1], rtype, role,
-                                eid, vid, default_value);
+	var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]);
     } catch (ex) {
 	log('got exception', ex);
 	return false;
     }
     d.addCallback(function (result, req) {
-        handleFormValidationResponse(formid, noop, noop, result);
-	var fieldview = getNode(divid);
-        fieldview.innerHTML = result[2];
-	// switch inline form off only if no error
-	if (result[0]) {
-          // hide global error messages
-	  jQuery('div.errorMessage').remove();
-	  jQuery('#appMsg').hide();
-          var inputname = 'edit' + role[0] + '-' + relname;
-          jQuery('input[name=' + inputname + ']').val(newtarget);
-	  cancelInlineEdit(eid, rtype, divid);
+        handleFormValidationResponse(divid+'-form', noop, noop, result);
+        if (reload) {
+          document.location.href = result[1];
+        } else {
+	  if (result[0]) {
+            var d = asyncRemoteExec('reledit_form', eid, rtype, role, lzone);
+            d.addCallback(function (result) {
+              jQuery('#'+divid+'-reledit').replaceWith(result);
+            });
+          }
 	}
         return false;
     });
@@ -528,12 +527,12 @@
 /**** inline edition ****/
 function showInlineEditionForm(eid, rtype, divid) {
     jQuery('#' + divid).hide();
-    jQuery('#' + divid + '-form').show();
+    jQuery('#' + divid+'-form').show();
 }
 
-function cancelInlineEdit(eid, rtype, divid) {
+function hideInlineEdit(eid, rtype, divid) {
     jQuery('#' + divid).show();
-    jQuery('#' + divid + '-form').hide();
+    jQuery('#' + divid+'-form').hide();
 }
 
 CubicWeb.provide('edition.js');
--- a/web/data/cubicweb.formfilter.js	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/data/cubicweb.formfilter.js	Thu Jul 16 13:30:13 2009 +0200
@@ -126,27 +126,35 @@
 		});
 		facet.find('div.facetCheckBox').click(function () {
 		    var $this = jQuery(this);
+		    if ($this.hasClass('facetValueDisabled')){
+		     	    return
+		    }
 		    if ($this.hasClass('facetValueSelected')) {
 			$this.removeClass('facetValueSelected');
 			$this.find('img').each(function (i){
 			if (this.getAttribute('cubicweb:unselimg')){
 			       this.setAttribute('src', UNSELECTED_BORDER_IMG);
+			       this.setAttribute('alt', (_('not selected')));
 			    }
 			    else{
 			       this.setAttribute('src', UNSELECTED_IMG);
+			       this.setAttribute('alt', (_('not selected')));
 			    }
 			});
 			var index = parseInt($this.attr('cubicweb:idx'));
-			var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
-			    var nindex = parseInt(n.getAttribute('cubicweb:idx'));
-			    return nindex > index;
-			}).length;
-			index += shift;
-			var parent = this.parentNode;
-			var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
-			if ( ! ($insertAfter.length == 1 && index == 0) ) {
-			    // only rearrange element if necessary
-			    $insertAfter.after(this);
+			// we dont need to move the element when cubicweb:idx == 0
+			if (index > 0){
+			    var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
+				    var nindex = parseInt(n.getAttribute('cubicweb:idx'));
+				    return nindex > index;
+				}).length;
+			    index += shift;
+			    var parent = this.parentNode;
+			    var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
+			    if ( ! ($insertAfter.length == 1 && shift == 0) ) {
+				// only rearrange element if necessary
+				$insertAfter.after(this);
+			    }
 			}
 		    } else {
 			var lastSelected = facet.find('.facetValueSelected:last');
@@ -157,7 +165,8 @@
 			    jQuery(parent).prepend(this);
 			}
 			jQuery(this).addClass('facetValueSelected');
-			jQuery(this).find('img').attr('src', SELECTED_IMG);
+			var $img = jQuery(this).find('img');
+			$img.attr('src', SELECTED_IMG).attr('alt', (_('selected')));
 		    }
 		    buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
 		    facet.find('.facetBody').animate({scrollTop: 0}, '');
--- a/web/data/cubicweb.htmlhelpers.js	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js	Thu Jul 16 13:30:13 2009 +0200
@@ -1,4 +1,5 @@
 CubicWeb.require('python.js');
+CubicWeb.require('jquery.corner.js');
 
 /* returns the document's baseURI. (baseuri() uses document.baseURI if
  * available and inspects the <base> tag manually otherwise.)
@@ -15,6 +16,7 @@
     return '';
 }
 
+// XXX this is used exactly ONCE in web/views/massmailing.py
 function insertText(text, areaId) {
     var textarea = jQuery('#' + areaId);
     if (document.selection) { // IE
@@ -39,6 +41,7 @@
 }
 
 /* taken from dojo toolkit */
+// XXX this looks unused
 function setCaretPos(element, start, end){
     if(!end){ end = element.value.length; }  // NOTE: Strange - should be able to put caret at start of text?
     // Mozilla
@@ -71,30 +74,30 @@
     }
 }
 
+
+// XXX this looks unused
 function setProgressMessage(label) {
     var body = document.getElementsByTagName('body')[0];
     body.appendChild(DIV({id: 'progress'}, label));
     jQuery('#progress').show();
 }
 
+// XXX this looks unused
 function resetProgressMessage() {
     var body = document.getElementsByTagName('body')[0];
     jQuery('#progress').hide();
 }
 
 
-/* set body's cursor to 'progress'
- */
+/* set body's cursor to 'progress' */
 function setProgressCursor() {
     var body = document.getElementsByTagName('body')[0];
     body.style.cursor = 'progress';
 }
 
-/*
- * reset body's cursor to default (mouse cursor). The main
+/* reset body's cursor to default (mouse cursor). The main
  * purpose of this function is to be used as a callback in the
- * deferreds' callbacks chain.
- */
+ * deferreds' callbacks chain. */
 function resetCursor(result) {
     var body = document.getElementsByTagName('body')[0];
     body.style.cursor = 'default';
@@ -137,7 +140,7 @@
  */
 function firstSelected(selectNode) {
     var selection = filter(attrgetter('selected'), selectNode.options);
-    return (selection.length>0) ? getNodeAttribute(selection[0], 'value'):null;
+    return (selection.length > 0) ? getNodeAttribute(selection[0], 'value'):null;
 }
 
 /* toggle visibility of an element by its id
@@ -148,22 +151,12 @@
 
 
 /* toggles visibility of login popup div */
+// XXX used exactly ONCE
 function popupLoginBox() {
     toggleVisibility('popupLoginBox');
     jQuery('#__login:visible').focus();
 }
 
-/*
- * return true (resp. false) if <element> (resp. doesn't) matches <properties>
- */
-function elementMatches(properties, element) {
-    for (prop in properties) {
-	if (getNodeAttribute(element, prop) != properties[prop]) {
-	    return false;
-	}
-    }
-    return true;
-}
 
 /* returns the list of elements in the document matching the tag name
  * and the properties provided
@@ -174,9 +167,13 @@
  *                      list() function)
  */
 function getElementsMatching(tagName, properties, /* optional */ parent) {
-    var filterfunc = partial(elementMatches, properties);
     parent = parent || document;
-    return filter(filterfunc, parent.getElementsByTagName(tagName));
+    return filter(function elementMatches(element) {
+                     for (prop in properties) {
+                       if (getNodeAttribute(element, prop) != properties[prop]) {
+	                 return false;}}
+                    return true;},
+                  parent.getElementsByTagName(tagName));
 }
 
 /*
@@ -196,9 +193,8 @@
     forEach(filter(filterfunc, elements), function(cb) {cb.checked=checked;});
 }
 
-/*
- * centers an HTML element on the screen
- */
+/* centers an HTML element on the screen */
+// XXX looks unused
 function centerElement(obj){
     var vpDim = getViewportDimensions();
     var elemDim = getElementDimensions(obj);
@@ -227,15 +223,9 @@
 function isTextNode(domNode) { return domNode.nodeType == 3; }
 function isElementNode(domNode) { return domNode.nodeType == 1; }
 
+// XXX this looks unused
 function changeLinkText(link, newText) {
     jQuery(link).text(newText);
-//    for (var i=0; i<link.childNodes.length; i++) {
-//	var node = link.childNodes[i];
-//	if (isTextNode(node)) {
-//	    swapDOM(node, document.createTextNode(newText));
-//	    break;
-//	}
-//    }
 }
 
 
@@ -247,19 +237,29 @@
     }
 }
 
+// XXX this looks unused
 function limitTextAreaSize(textarea, size) {
     var $area = jQuery(textarea);
     $area.val($area.val().slice(0, size));
 }
 
 //============= page loading events ==========================================//
-function roundedCornersOnLoad() {
-    jQuery('div.sideBoxBody').corner('bottom 6px');
-    jQuery('div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month').corner('top 6px');
+
+CubicWeb.rounded = [
+		    ['div.sideBoxBody', 'bottom 6px'],
+		    ['div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month', 'top 6px']
+		    ];
+
+
+
+function roundedCorners(node) {
+    node = jQuery(node);
+    for(var r=0; r < CubicWeb.rounded.length; r++) {
+       node.find(CubicWeb.rounded[r][0]).corner(CubicWeb.rounded[r][1]);
+    }
 }
 
-jQuery(document).ready(roundedCornersOnLoad);
-
+jQuery(document).ready(function () {roundedCorners(this.body)});
 
 CubicWeb.provide('htmlhelpers.js');
 
--- a/web/data/cubicweb.python.js	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/data/cubicweb.python.js	Thu Jul 16 13:30:13 2009 +0200
@@ -15,12 +15,12 @@
 	return true;
     }
     return false;
-}
+};
 
 Date.prototype.add = function(days) {
-    var res = new Date()
-    res.setTime(this.getTime() + (days * ONE_DAY))
-    return res
+    var res = new Date();
+    res.setTime(this.getTime() + (days * ONE_DAY));
+    return res;
 };
 
 Date.prototype.sub = function(days) {
@@ -29,14 +29,14 @@
 
 Date.prototype.iadd = function(days) {
     // in-place add
-    this.setTime(this.getTime() + (days * ONE_DAY))
+    this.setTime(this.getTime() + (days * ONE_DAY));
     // avoid strange rounding problems !!
     this.setHours(12);
 };
 
 Date.prototype.isub = function(days) {
     // in-place sub
-    this.setTime(this.getTime() - (days * ONE_DAY))
+    this.setTime(this.getTime() - (days * ONE_DAY));
 };
 
 /*
@@ -170,14 +170,14 @@
  */
 String.prototype.startsWith = function(prefix) {
     return this.indexOf(prefix) == 0;
-}
+};
 
 /* python-like endsWith method for js strings */
 String.prototype.endsWith = function(suffix) {
     var startPos = this.length - suffix.length;
     if (startPos < 0) { return false; }
     return this.lastIndexOf(suffix, startPos) == startPos;
-}
+};
 
 /* python-like strip method for js strings */
 String.prototype.strip = function() {
@@ -187,12 +187,12 @@
 /* py-equiv: string in list */
 String.prototype.in_ = function(values) {
     return findValue(values, this) != -1;
-}
+};
 
 /* py-equiv: str.join(list) */
 String.prototype.join = function(args) {
     return args.join(this);
-}
+};
 
 /* python-like list builtin
  * transforms an iterable in a js sequence
--- a/web/facet.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/facet.py	Thu Jul 16 13:30:13 2009 +0200
@@ -12,7 +12,7 @@
 from copy import deepcopy
 from datetime import date, datetime, timedelta
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from logilab.common.graph import has_path
 from logilab.common.decorators import cached
@@ -21,6 +21,7 @@
 from rql import parse, nodes
 
 from cubicweb import Unauthorized, typed_eid
+from cubicweb.schema import display_name
 from cubicweb.utils import datetime2ticks, make_uid, ustrftime
 from cubicweb.selectors import match_context_prop, partial_relation_possible
 from cubicweb.appobject import AppRsetObject
@@ -70,7 +71,7 @@
 def filter_hiddens(w, **kwargs):
     for key, val in kwargs.items():
         w(u'<input type="hidden" name="%s" value="%s" />' % (
-            key, html_escape(val)))
+            key, xml_escape(val)))
 
 
 def _may_be_removed(rel, schema, mainvar):
@@ -586,11 +587,11 @@
         self.items.append(item)
 
     def _render(self):
-        title = html_escape(self.facet.title)
-        facetid = html_escape(self.facet.id)
+        title = xml_escape(self.facet.title)
+        facetid = xml_escape(self.facet.id)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
-               (html_escape(facetid), title))
+               (xml_escape(facetid), title))
         if self.facet.support_and():
             _ = self.facet.req._
             self.w(u'''<select name="%s" class="radio facetOperator" title="%s">
@@ -616,8 +617,8 @@
         self.value = None
 
     def _render(self):
-        title = html_escape(self.facet.title)
-        facetid = html_escape(self.facet.id)
+        title = xml_escape(self.facet.title)
+        facetid = xml_escape(self.facet.id)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
                (facetid, title))
@@ -660,7 +661,7 @@
         facet.req.add_js('ui.slider.js')
         facet.req.add_css('ui.all.css')
         sliderid = make_uid('the slider')
-        facetid = html_escape(self.facet.id)
+        facetid = xml_escape(self.facet.id)
         facet.req.html_headers.add_onload(self.onload % {
             'sliderid': sliderid,
             'facetid': facetid,
@@ -668,7 +669,7 @@
             'maxvalue': self.maxvalue,
             'formatter': self.formatter,
             })
-        title = html_escape(self.facet.title)
+        title = xml_escape(self.facet.title)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
                (facetid, title))
@@ -720,9 +721,9 @@
             imgsrc = self.req.datadir_url + self.unselected_img
             imgalt = self.req._('not selected')
         self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
-               % (cssclass, html_escape(unicode(self.value))))
+               % (cssclass, xml_escape(unicode(self.value))))
         self.w(u'<img src="%s" alt="%s"/>&nbsp;' % (imgsrc, imgalt))
-        self.w(u'<a href="javascript: {}">%s</a>' % html_escape(self.label))
+        self.w(u'<a href="javascript: {}">%s</a>' % xml_escape(self.label))
         self.w(u'</div>')
 
 class CheckBoxFacetWidget(HTMLWidget):
@@ -736,8 +737,8 @@
         self.selected = selected
 
     def _render(self):
-        title = html_escape(self.facet.title)
-        facetid = html_escape(self.facet.id)
+        title = xml_escape(self.facet.title)
+        facetid = xml_escape(self.facet.id)
         self.w(u'<div id="%s" class="facet">\n' % facetid)
         if self.selected:
             cssclass = ' facetValueSelected'
@@ -748,7 +749,7 @@
             imgsrc = self.req.datadir_url + self.unselected_img
             imgalt = self.req._('not selected')
         self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
-               % (cssclass, html_escape(unicode(self.value))))
+               % (cssclass, xml_escape(unicode(self.value))))
         self.w(u'<div class="facetCheckBoxWidget">')
         self.w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" />&nbsp;' % (imgsrc, imgalt))
         self.w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>' % (facetid, title))
--- a/web/formfields.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/formfields.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,11 +10,11 @@
 from warnings import warn
 from datetime import datetime
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 from yams.constraints import SizeConstraint, StaticVocabularyConstraint
 
 from cubicweb.schema import FormatConstraint
-from cubicweb.utils import ustrftime
+from cubicweb.utils import ustrftime, compute_cardinality
 from cubicweb.common import tags, uilib
 from cubicweb.web import INTERNAL_FIELD_VALUE
 from cubicweb.web.formwidgets import (
@@ -203,9 +203,17 @@
                 widget = Select()
             elif self.max_length and self.max_length < 257:
                 widget = TextInput()
+
         super(StringField, self).init_widget(widget)
         if isinstance(self.widget, TextArea):
             self.init_text_area(self.widget)
+        elif isinstance(self.widget, TextInput):
+            self.init_text_input(self.widget)
+
+    def init_text_input(self, widget):
+        if self.max_length:
+            widget.attrs.setdefault('size', min(45, self.max_length))
+            widget.attrs.setdefault('maxlength', self.max_length)
 
     def init_text_area(self, widget):
         if self.max_length < 513:
@@ -219,6 +227,9 @@
         super(RichTextField, self).__init__(**kwargs)
         self.format_field = format_field
 
+    def init_text_area(self, widget):
+        pass
+
     def get_widget(self, form):
         if self.widget is None:
             if self.use_fckeditor(form):
@@ -300,9 +311,9 @@
         if self.format_field or self.encoding_field:
             divid = '%s-advanced' % form.context[self]['name']
             wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
-                        (html_escape(uilib.toggle_action(divid)),
+                        (xml_escape(uilib.toggle_action(divid)),
                          form.req._('show advanced fields'),
-                         html_escape(form.req.build_url('data/puce_down.png')),
+                         xml_escape(form.req.build_url('data/puce_down.png')),
                          form.req._('show advanced fields')))
             wdgs.append(u'<div id="%s" class="hidden">' % divid)
             if self.format_field:
@@ -460,7 +471,7 @@
     fieldclass = None
     if role == 'subject':
         targetschema = rschema.objects(eschema)[0]
-        card = rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+        card = compute_cardinality(eschema, rschema, role)
         help = rschema.rproperty(eschema, targetschema, 'description')
         if rschema.is_final():
             if rschema.rproperty(eschema, targetschema, 'internationalizable'):
@@ -470,7 +481,7 @@
             kwargs.setdefault('initial', get_default)
     else:
         targetschema = rschema.subjects(eschema)[0]
-        card = rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+        card = compute_cardinality(eschema, rschema, role)
         help = rschema.rproperty(targetschema, eschema, 'description')
     kwargs['required'] = card in '1+'
     kwargs['name'] = rschema.type
--- a/web/formwidgets.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/formwidgets.py	Thu Jul 16 13:30:13 2009 +0200
@@ -146,17 +146,22 @@
 
 class TextArea(FieldWidget):
     """<textarea>"""
+
     def render(self, form, field):
         name, values, attrs = self._render_attrs(form, field)
         attrs.setdefault('onkeyup', 'autogrow(this)')
-        attrs.setdefault('cols', 80)
-        attrs.setdefault('rows', 20)
         if not values:
             value = u''
         elif len(values) == 1:
             value = values[0]
         else:
             raise ValueError('a textarea is not supposed to be multivalued')
+        lines = value.splitlines()
+        linecount = len(lines)
+        for line in lines:
+            linecount += len(line) / 80
+        attrs.setdefault('cols', 80)
+        attrs.setdefault('rows', min(15, linecount + 2))
         return tags.textarea(value, name=name, **attrs)
 
 
--- a/web/htmlwidgets.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/htmlwidgets.py	Thu Jul 16 13:30:13 2009 +0200
@@ -9,7 +9,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import UStringIO
 from cubicweb.common.uilib import toggle_action
@@ -81,7 +81,7 @@
             self.w(u'<div class="%s">' % self._class)
         if self.title:
             if self.escape:
-                title = '<span>%s</span>' % html_escape(self.title)
+                title = '<span>%s</span>' % xml_escape(self.title)
             else:
                 title = '<span>%s</span>' % self.title
             self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
@@ -204,7 +204,7 @@
     def __init__(self, href, label, _class='', title='', ident='', escape=False):
         self.href = href
         if escape:
-            self.label = html_escape(label)
+            self.label = xml_escape(label)
         else:
             self.label = label
         self._class = _class or ''
@@ -213,7 +213,7 @@
 
     def _render(self):
         link = u'<a href="%s" title="%s">%s</a>' % (
-            html_escape(self.href), html_escape(self.title), self.label)
+            xml_escape(self.href), xml_escape(self.title), self.label)
         if self.ident:
             self.w(u'<li id="%s" class="%s">%s</li>\n' % (self.ident, self._class, link))
         else:
--- a/web/request.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/request.py	Thu Jul 16 13:30:13 2009 +0200
@@ -20,7 +20,7 @@
 from logilab.common.decorators import cached
 from logilab.common.deprecation import obsolete
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.dbapi import DBAPIRequest
 from cubicweb.common.mail import header
@@ -153,7 +153,8 @@
             else:
                 self.form[k] = v
         # special key for created entity, added in controller's reset method
-        if '__createdpath' in params:
+        # if no message set, we don't want this neither
+        if '__createdpath' in params and self.message:
             self.message += ' (<a href="%s">%s</a>)' % (
                 self.build_url(params.pop('__createdpath')),
                 self._('click here to see created entity'))
@@ -505,7 +506,7 @@
         url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
                              **extraparams)
         return "javascript: loadxhtml('%s', '%s', '%s')" % (
-            nodeid, html_escape(url), replacemode)
+            nodeid, xml_escape(url), replacemode)
 
     # urls/path management ####################################################
 
--- a/web/test/unittest_form.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/test/unittest_form.py	Thu Jul 16 13:30:13 2009 +0200
@@ -156,12 +156,12 @@
 <option value="text/html">text/html</option>
 <option value="text/plain">text/plain</option>
 <option selected="selected" value="text/rest">text/rest</option>
-</select><textarea cols="60" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="5" tabindex="1"/>''')
+</select><textarea cols="80" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="1"></textarea>''')
 
 
     def test_richtextfield_2(self):
         self.req.use_fckeditor = lambda: True
-        self._test_richtextfield('<input name="description_format:%(eid)s" style="display: block" type="hidden" value="text/rest"/><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="20" tabindex="0"/>')
+        self._test_richtextfield('<input name="description_format:%(eid)s" style="display: block" type="hidden" value="text/rest"></input><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="0"></textarea>')
 
 
     def test_filefield(self):
@@ -172,14 +172,14 @@
                                data=Binary('new widgets system'))
         form = FFForm(self.req, redirect_path='perdu.com', entity=file)
         self.assertTextEquals(self._render_entity_field('data', form),
-                              '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+                              '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""></input>
 <a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="1" type="text" value="text/plain"></input><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="2" type="text" value="UTF-8"></input><br/>
 </div>
 <br/>
-<input name="data:%(eid)s__detach" type="checkbox"/>
+<input name="data:%(eid)s__detach" type="checkbox"></input>
 detach attached file
 ''' % {'eid': file.eid})
 
@@ -196,17 +196,17 @@
                                data=Binary('new widgets system'))
         form = EFFForm(self.req, redirect_path='perdu.com', entity=file)
         self.assertTextEquals(self._render_entity_field('data', form),
-                              '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+                              '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""></input>
 <a href="javascript: toggleVisibility(&#39;data:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="1" type="text" value="text/plain"></input><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="2" type="text" value="UTF-8"></input><br/>
 </div>
 <br/>
-<input name="data:%(eid)s__detach" type="checkbox"/>
+<input name="data:%(eid)s__detach" type="checkbox"></input>
 detach attached file
 <p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
-<textarea cols="80" name="data:%(eid)s" onkeyup="autogrow(this)" rows="20" tabindex="3">new widgets system</textarea>''' % {'eid': file.eid})
+<textarea cols="80" name="data:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="3">new widgets system</textarea>''' % {'eid': file.eid})
 
 
     def test_passwordfield(self):
@@ -214,9 +214,9 @@
             upassword = StringField(widget=PasswordInput)
         form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity)
         self.assertTextEquals(self._render_entity_field('upassword', form),
-                              '''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+                              '''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"></input>
 <br/>
-<input name="upassword-confirm:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+<input name="upassword-confirm:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"></input>
 &nbsp;
 <span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
 
--- a/web/test/unittest_views_baseviews.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/test/unittest_views_baseviews.py	Thu Jul 16 13:30:13 2009 +0200
@@ -102,6 +102,7 @@
         # self.assertAlmostEquals(value, rset.rows[0][3].seconds)
 
     def test_sortvalue_with_display_col(self):
+        self.skip('XXX there is no column_labels on rset')
         e, rset, view = self._prepare_entity()
         labels = rset.column_labels()
         table = TableWidget(view)
--- a/web/uicfg.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/uicfg.py	Thu Jul 16 13:30:13 2009 +0200
@@ -68,7 +68,8 @@
 __docformat__ = "restructuredtext en"
 
 from cubicweb import neg_role
-from cubicweb.rtags import RelationTags, RelationTagsBool, RelationTagsSet
+from cubicweb.rtags import (RelationTags, RelationTagsBool,
+                            RelationTagsSet, RelationTagsDict)
 from cubicweb.web import formwidgets
 
 
@@ -118,14 +119,13 @@
     primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
 
 
-class DisplayCtrlRelationTags(RelationTags):
+class DisplayCtrlRelationTags(RelationTagsDict):
     def __init__(self, *args, **kwargs):
         super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
         self._counter = 0
 
     def tag_relation(self, key, tag):
-        assert isinstance(tag, dict)
-        super(DisplayCtrlRelationTags, self).tag_relation(key, tag)
+        tag = super(DisplayCtrlRelationTags, self).tag_relation(key, tag)
         self._counter += 1
         tag.setdefault('order', self._counter)
 
@@ -152,11 +152,8 @@
     else:
         sschema = '*'
         label = '%s_%s' % (rschema, role)
-    displayinfo = rtag.get(sschema, rschema, oschema, role)
-    if displayinfo is None:
-        displayinfo = {}
-        rtag.tag_relation((sschema, rschema, oschema, role), displayinfo)
-    displayinfo.setdefault('label', label)
+    rtag.setdefault((sschema, rschema, oschema, role), 'label', label)
+    rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag._counter)
 
 primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
                                                    init_primaryview_display_ctrl)
@@ -243,7 +240,7 @@
 autoform_field = RelationTags('autoform_field')
 
 # relations'field explicit kwargs (given to field's __init__)
-autoform_field_kwargs = RelationTags()
+autoform_field_kwargs = RelationTagsDict()
 autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
                                     {'widget': formwidgets.TextInput})
 autoform_field_kwargs.tag_attribute(('Bookmark', 'path'),
--- a/web/views/basecomponents.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/basecomponents.py	Thu Jul 16 13:30:13 2009 +0200
@@ -11,11 +11,12 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+from logilab.mtconverter import xml_escape
 from rql import parse
 
 from cubicweb.selectors import yes, two_etypes_rset, match_form_params
 from cubicweb.schema import display_name
-from cubicweb.common.uilib import html_escape, toggle_action
+from cubicweb.common.uilib import toggle_action
 from cubicweb.web import component
 from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
                                       BoxLink)
@@ -47,7 +48,7 @@
 <input type="submit" value="" class="rqlsubmit" tabindex="%s" />
 </fieldset>
 ''' % (not self.propval('visible') and 'hidden' or '',
-       self.build_url('view'), html_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
+       self.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
         req.next_tabindex()))
         if self.req.search_state[0] != 'normal':
             self.w(u'<input type="hidden" name="__mode" value="%s"/>'
@@ -202,7 +203,7 @@
                 url = self.build_url(rql=newrql, __restrrql=restrrql,
                                      __restrtype=etype, __restrtypes=','.join(restrtypes))
                 html.append(u'<span><a href="%s">%s</a></span>' % (
-                        html_escape(url), elabel))
+                        xml_escape(url), elabel))
                 rqlst.recover()
         if on_etype:
             url = self.build_url(rql=restrrql)
--- a/web/views/basecontrollers.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/basecontrollers.py	Thu Jul 16 13:30:13 2009 +0200
@@ -15,7 +15,7 @@
 import simplejson
 
 from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
 from cubicweb.utils import strptime
@@ -394,27 +394,16 @@
             rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
                                     {'x': eid}, 'x')
             entity = rset.get_entity(0, 0)
-            value = entity.printable_value(rtype)
-            return (success, args, value or default)
+            value = entity.printable_value(rtype) or default
+            return (success, args, value)
         else:
             return (success, args, None)
 
     @jsonize
-    def js_edit_relation(self, action, names, values,
-                         rtype, role, eid, vid, default):
-        success, args = self.validate_form(action, names, values)
-        if success:
-            entity = self.req.eid_rset(eid).get_entity(0, 0)
-            rset = entity.related(rtype, role)
-            if rset:
-                output = self.view(vid, rset)
-                if vid == 'textoutofcontext':
-                    output = html_escape(output)
-            else:
-                output = default
-            return (success, args, output)
-        else:
-            return (success, args, None)
+    def js_reledit_form(self, eid, rtype, role, lzone):
+        entity = self.req.eid_rset(eid).get_entity(0, 0)
+        return entity.view('reledit', rtype=rtype, role=role,
+                           landing_zone=lzone)
 
     @jsonize
     def js_i18n(self, msgids):
--- a/web/views/baseforms.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/baseforms.py	Thu Jul 16 13:30:13 2009 +0200
@@ -12,7 +12,7 @@
 
 from simplejson import dumps
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 from logilab.common.decorators import cached
 
 from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat,
@@ -148,7 +148,7 @@
         output = []
         for name, value, iid in self._hiddens:
             if isinstance(value, basestring):
-                value = html_escape(value)
+                value = xml_escape(value)
             if iid:
                 output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
                               % (iid, name, value))
@@ -249,14 +249,14 @@
                 w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
                   (_('cancel this insert'), row[2]))
                 w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
-                  % (row[1], row[4], html_escape(row[5])))
+                  % (row[1], row[4], xml_escape(row[5])))
                 w(u'</td>')
                 w(u'</tr>')
         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
         w(u'<th class="labelCol">')
         w(u'<span>%s</span>' % _('add relation'))
         w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
         w(u'<option value="">%s</option>' % _('select a relation'))
         for i18nrtype, rschema, target in srels_by_cat:
             # more entities to link to
@@ -551,10 +551,10 @@
         ctx = {'action' : self.build_url('edit'),
                'error': self.error_message(),
                'progress': _('validating...'),
-               'url': html_escape(req.url()),
+               'url': xml_escape(req.url()),
                'formid': self.id,
-               'redirectvid': html_escape(form.get('__redirectvid', 'list')),
-               'redirectrql': html_escape(form.get('__redirectrql', self.rset.printable_rql())),
+               'redirectvid': xml_escape(form.get('__redirectvid', 'list')),
+               'redirectrql': xml_escape(form.get('__redirectrql', self.rset.printable_rql())),
                'attrheaders': u'\n'.join(attrheaders),
                'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
                'okvalue': _('button_ok').capitalize(),
@@ -583,7 +583,7 @@
         wdg = entity.get_widget
         wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
                         if rschema.type != 'eid'] # XXX both (add, delete)
-        seid = html_escape(dumps(eid))
+        seid = xml_escape(dumps(eid))
         for wobj in wdgfactories:
             if isinstance(wobj, ComboBoxWidget):
                 wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
--- a/web/views/basetemplates.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/basetemplates.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.vregistry import objectify_selector
 from cubicweb.selectors import match_kwargs
@@ -31,14 +31,14 @@
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         w = self.whead
         # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
-        w(u'<base href="%s"></base>' % html_escape(self.req.base_url()))
+        w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self.req.encoding))
         w(NOINDEX)
         w(NOFOLLOW)
         w(u'\n'.join(additional_headers) + u'\n')
         self.wview('htmlheader', rset=self.rset)
-        w(u'<title>%s</title>\n' % html_escape(page_title))
+        w(u'<title>%s</title>\n' % xml_escape(page_title))
 
 
 class LogInTemplate(LogInOutTemplate):
@@ -60,7 +60,7 @@
         if self.config['anonymous-user']:
             indexurl = self.build_url('view', vid='index', __message=msg)
             w(u'<p><a href="%s">%s</a><p>' % (
-                html_escape(indexurl),
+                xml_escape(indexurl),
                 self.req._('go back to the index page')))
 
 @objectify_selector
@@ -110,7 +110,7 @@
         w(u'<div id="pageContent">\n')
         vtitle = self.req.form.get('vtitle')
         if vtitle:
-            w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
+            w(u'<h1 class="vtitle">%s</h1>\n' % xml_escape(vtitle))
         # display entity type restriction component
         etypefilter = self.vreg.select_vobject('components', 'etypenavigation',
                                               self.req, rset=self.rset)
@@ -137,13 +137,13 @@
         w = self.whead
         lang = self.req.lang
         self.write_doctype()
-        w(u'<base href="%s" />' % html_escape(self.req.base_url()))
+        w(u'<base href="%s" />' % xml_escape(self.req.base_url()))
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self.req.encoding))
         w(u'\n'.join(additional_headers) + u'\n')
         self.wview('htmlheader', rset=self.rset)
         if page_title:
-            w(u'<title>%s</title>\n' % html_escape(page_title))
+            w(u'<title>%s</title>\n' % xml_escape(page_title))
 
     def template_body_header(self, view):
         w = self.w
@@ -212,7 +212,7 @@
           % (content_type, self.req.encoding))
         w(u'\n'.join(additional_headers))
         self.wview('htmlheader', rset=self.rset)
-        w(u'<title>%s</title>\n' % html_escape(page_title))
+        w(u'<title>%s</title>\n' % xml_escape(page_title))
         self.w(u'<body>\n')
 
     def template_footer(self, view=None):
@@ -234,7 +234,7 @@
         whead(u'\n'.join(additional_headers) + u'\n')
         self.wview('htmlheader', rset=self.rset)
         w = self.w
-        w(u'<title>%s</title>\n' % html_escape(page_title))
+        w(u'<title>%s</title>\n' % xml_escape(page_title))
         w(u'<body>\n')
         w(u'<div id="page">')
         w(u'<table width="100%" height="100%" border="0"><tr>\n')
@@ -252,7 +252,7 @@
         w(u'<div id="pageContent">\n')
         vtitle = self.req.form.get('vtitle')
         if vtitle:
-            w(u'<h1 class="vtitle">%s</h1>' % html_escape(vtitle))
+            w(u'<h1 class="vtitle">%s</h1>' % xml_escape(vtitle))
 
     def topleft_header(self):
         logo = self.vreg.select_vobject('components', 'logo', self.req,
@@ -301,7 +301,7 @@
                                             self.req, rset=self.rset)
         if urlgetter is not None:
             self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
-                       %  html_escape(urlgetter.feed_url()))
+                       %  xml_escape(urlgetter.feed_url()))
 
     def pageid(self):
         req = self.req
@@ -462,7 +462,7 @@
     def login_form(self, id):
         _ = self.req._
         self.w(u'<form method="post" action="%s" id="login_form">\n'
-               % html_escape(login_form_url(self.config, self.req)))
+               % xml_escape(login_form_url(self.config, self.req)))
         self.w(u'<table>\n')
         self.w(u'<tr>\n')
         msg = (self.config['allow-email-login'] and _('login or email')) or _('login')
--- a/web/views/baseviews.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/baseviews.py	Thu Jul 16 13:30:13 2009 +0200
@@ -17,7 +17,7 @@
 
 from rql import nodes
 
-from logilab.mtconverter import TransformError, html_escape, xml_escape
+from logilab.mtconverter import TransformError, xml_escape, xml_escape
 
 from cubicweb import NoSelectableObject
 from cubicweb.selectors import yes, empty_rset
@@ -121,8 +121,8 @@
         """the one line view for an entity: linked text view
         """
         entity = self.entity(row, col)
-        self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
-        self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
+        self.w(u'<a href="%s">' % xml_escape(entity.absolute_url()))
+        self.w(xml_escape(self.view('text', self.rset, row=row, col=col)))
         self.w(u'</a>')
 
 
@@ -205,8 +205,8 @@
         entity = self.entity(row, col)
         desc = cut(entity.dc_description(), 50)
         self.w(u'<a href="%s" title="%s">' % (
-            html_escape(entity.absolute_url()), html_escape(desc)))
-        self.w(html_escape(self.view('textincontext', self.rset,
+            xml_escape(entity.absolute_url()), xml_escape(desc)))
+        self.w(xml_escape(self.view('textincontext', self.rset,
                                      row=row, col=col)))
         self.w(u'</a>')
 
@@ -218,8 +218,8 @@
         entity = self.entity(row, col)
         desc = cut(entity.dc_description(), 50)
         self.w(u'<a href="%s" title="%s">' % (
-            html_escape(entity.absolute_url()), html_escape(desc)))
-        self.w(html_escape(self.view('textoutofcontext', self.rset,
+            xml_escape(entity.absolute_url()), xml_escape(desc)))
+        self.w(xml_escape(self.view('textoutofcontext', self.rset,
                                      row=row, col=col)))
         self.w(u'</a>')
 
--- a/web/views/bookmark.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/bookmark.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,7 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized
 from cubicweb.selectors import implements
@@ -35,12 +35,12 @@
         entity = self.complete_entity(row, col)
         self.w(u'&nbsp;')
         self.w(u"<span class='title'><b>")
-        self.w(u"%s : %s" % (self.req._('Bookmark'), html_escape(entity.title)))
+        self.w(u"%s : %s" % (self.req._('Bookmark'), xml_escape(entity.title)))
         self.w(u"</b></span>")
         self.w(u'<br/><br/><div class="content"><a href="%s">' % (
-            html_escape(entity.actual_url())))
+            xml_escape(entity.actual_url())))
         self.w(u'</a>')
-        self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(html_escape(u.name())
+        self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(xml_escape(u.name())
                                                                    for u in entity.bookmarked_by)))
         self.w(u'</div>')
 
@@ -75,8 +75,8 @@
         else:
             dlink = None
         for bookmark in rset.entities():
-            label = '<a href="%s">%s</a>' % (html_escape(bookmark.action_url()),
-                                             html_escape(bookmark.title))
+            label = '<a href="%s">%s</a>' % (xml_escape(bookmark.action_url()),
+                                             xml_escape(bookmark.title))
             if candelete:
                 dlink = u'[<a href="javascript:removeBookmark(%s)" title="%s">-</a>]' % (
                     bookmark.eid, _('delete this bookmark'))
--- a/web/views/boxes.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/boxes.py	Thu Jul 16 13:30:13 2009 +0200
@@ -16,7 +16,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import match_user_groups, non_final_entity
 from cubicweb.view import EntityView
@@ -191,7 +191,7 @@
         else:
             rql = ''
         form = self.formdef % (req.build_url('view'), req.next_tabindex(),
-                               html_escape(rql), req.next_tabindex())
+                               xml_escape(rql), req.next_tabindex())
         title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
         box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
         box.append(BoxHtml(form))
--- a/web/views/calendar.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/calendar.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,7 +10,7 @@
 
 from datetime import datetime, date, timedelta
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.interfaces import ICalendarable
 from cubicweb.selectors import implements
@@ -82,7 +82,7 @@
         for i in range(len(self.rset.rows)):
             task = self.complete_entity(i)
             self.w(u'<div class="vevent">')
-            self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
+            self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
             self.w(u'<div class="description">%s</div>'
                    % task.dc_description(format='text/html'))
             if task.start:
@@ -244,8 +244,8 @@
         prevlink, nextlink = self._prevnext_links(curdate)  # XXX
         self.w(u'<tr><th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s</th>'
                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
-               (html_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
-                curdate.year, html_escape(nextlink)))
+               (xml_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
+                curdate.year, xml_escape(nextlink)))
 
         # output header
         self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
@@ -292,7 +292,7 @@
                                  __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
                                  __redirectvid=self.id
                                  )
-            self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (html_escape(url), self.req._(u'add')))
+            self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self.req._(u'add')))
             self.w(u'&nbsp;')
         self.w(u'</div>')
         self.w(u'<div class="cellContent">')
@@ -307,7 +307,7 @@
                                         __redirectvid=self.id
                                         )
 
-                self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
+                self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
                 task.view('tooltip', w=self.w )
                 self.w(u'</div>')
             else:
@@ -388,9 +388,9 @@
         self.w(u'<tr><th class="transparent"></th>')
         self.w(u'<th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s %s</th>'
                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
-               (html_escape(prevlink), first_day_of_week.year,
+               (xml_escape(prevlink), first_day_of_week.year,
                 self.req._(u'week'), first_day_of_week.isocalendar()[1],
-                html_escape(nextlink)))
+                xml_escape(nextlink)))
 
         # output header
         self.w(u'<tr>')
@@ -429,7 +429,7 @@
                                      __redirectvid=self.id
                                      )
                 extra = ' ondblclick="addCalendarItem(event, hmin=8, hmax=20, year=%s, month=%s, day=%s, duration=2, baseurl=\'%s\')"' % (
-                    wdate.year, wdate.month, wdate.day, html_escape(url))
+                    wdate.year, wdate.month, wdate.day, xml_escape(url))
             else:
                 extra = ""
             self.w(u'<div class="columndiv"%s>'% extra)
@@ -501,7 +501,7 @@
                                     __redirectvid=self.id
                                  )
 
-            self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
+            self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
             task.view('tooltip', w=self.w)
             self.w(u'</div>')
             if task.start is None:
--- a/web/views/cwproperties.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/cwproperties.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from logilab.common.decorators import cached
 
@@ -154,7 +154,7 @@
                 docmsgid = '%s_%s_description' % (group, oid)
                 doc = _(docmsgid)
                 if doc != docmsgid:
-                    w(u'<div class="helper">%s</div>' % html_escape(doc).capitalize())
+                    w(u'<div class="helper">%s</div>' % xml_escape(doc).capitalize())
                 w(u'</div>')
                 w(u'<fieldset id="field_%(oid)s_%(group)s" class="%(group)s preferences hidden">'
                   % {'oid':oid, 'group':group})
--- a/web/views/cwuser.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/cwuser.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,7 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import one_line_rset, implements, match_user_groups
 from cubicweb.view import EntityView
@@ -52,14 +52,14 @@
                       <foaf:primaryTopic rdf:resource="%s"/>
                    </foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
         self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
-        self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
+        self.w(u'<foaf:name>%s</foaf:name>\n' % xml_escape(entity.dc_long_title()))
         if entity.surname:
             self.w(u'<foaf:family_name>%s</foaf:family_name>\n'
-                   % html_escape(entity.surname))
+                   % xml_escape(entity.surname))
         if entity.firstname:
             self.w(u'<foaf:givenname>%s</foaf:givenname>\n'
-                   % html_escape(entity.firstname))
+                   % xml_escape(entity.firstname))
         emailaddr = entity.get_email()
         if emailaddr:
-            self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
+            self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % xml_escape(emailaddr))
         self.w(u'</foaf:Person>\n')
--- a/web/views/debug.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/debug.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,7 +10,7 @@
 
 from time import strftime, localtime
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import none_rset, match_user_groups
 from cubicweb.view import StartupView
@@ -21,7 +21,7 @@
         w(u'<ul>')
         for key in sorted(dict):
             w(u'<li><span class="label">%s</span>: <span>%s</span></li>' % (
-                html_escape(str(key)), html_escape(repr(dict[key]))))
+                xml_escape(str(key)), xml_escape(repr(dict[key]))))
         w(u'</ul>')
 
 
@@ -38,7 +38,7 @@
         if sessions:
             w(u'<ul>')
             for sid, session in sessions:
-                w(u'<li>%s  (last usage: %s)<br/>' % (html_escape(str(session)),
+                w(u'<li>%s  (last usage: %s)<br/>' % (xml_escape(str(session)),
                                                       strftime('%Y-%m-%d %H:%M:%S',
                                                                localtime(session.timestamp))))
                 dict_to_html(w, session.data)
--- a/web/views/editforms.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/editforms.py	Thu Jul 16 13:30:13 2009 +0200
@@ -13,17 +13,17 @@
 
 from simplejson import dumps
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
                                 specified_etype_implements, yes)
-from cubicweb.utils import make_uid
+from cubicweb.utils import make_uid, compute_cardinality, get_schema_property
 from cubicweb.view import EntityView
 from cubicweb.common import tags
-from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param, uicfg
 from cubicweb.web.form import FormViewMixIn
-from cubicweb.web.formfields import RelationField
-from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton, Select
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
 from cubicweb.web.views import forms
 
 
@@ -38,7 +38,7 @@
     entities
     """
     js = u"javascript: togglePendingDelete('%s', %s);" % (
-        nodeid, html_escape(dumps(eid)))
+        nodeid, xml_escape(dumps(eid)))
     return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
         js, nodeid, label)
 
@@ -90,91 +90,127 @@
 
     # FIXME editableField class could be toggleable from userprefs
 
-    onsubmit = ("return inlineValidateAttributeForm('%(divid)s-form', '%(rtype)s', "
-                "'%(eid)s', '%(divid)s', %(reload)s, '%(default)s');")
-    ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+    _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+    _defaultlandingzone = u'<img title="%(msg)s" src="data/file.gif" alt="%(msg)s"/>'
+    _landingzonemsg = _('click to edit this field')
+    # default relation vids according to cardinality
+    _one_rvid = 'incontext'
+    _many_rvid = 'csv'
+
+    def _compute_best_vid(self, entity, rtype, role):
+        if compute_cardinality(entity.e_schema,
+                               entity.schema.rschema(rtype),
+                               role) in '+*':
+            return self._many_rvid
+        return self._one_rvid
+
+    def _build_landing_zone(self, lzone):
+        return lzone or self._defaultlandingzone % {'msg' : self.req._(self._landingzonemsg)}
 
-    def cell_call(self, row, col, rtype=None, role='subject', reload=False,
-                  vid='textoutofcontext', default=None):
-        """display field to edit entity's `rtype` relation on double-click"""
-        rschema = self.schema.rschema(rtype)
+    def _build_renderer(self, entity, rtype, role):
+        return self.vreg.select_object('formrenderers', 'base', self.req,
+                                       entity=entity,
+                                       display_label=False, display_help=False,
+                                       display_fields=[(rtype, role)],
+                                       table_class='', button_bar_class='buttonbar',
+                                       display_progress_div=False)
+
+    def cell_call(self, row, col, rtype=None, role='subject',
+                  reload=False,      # controls reloading the whole page after change
+                  rvid=None,         # vid to be applied to other side of rtype
+                  default=None,      # default value
+                  landing_zone=None  # prepend value with a separate html element to click onto
+                                     # (esp. needed when values are links)
+                  ):
+        """display field to edit entity's `rtype` relation on click"""
+        assert rtype
+        assert role in ('subject', 'object')
+        if default is None:
+            default = xml_escape(self.req._('<no value>'))
         entity = self.entity(row, col)
-        if not default:
-            default = self.req._('not specified')
+        rschema = entity.schema.rschema(rtype)
+        # compute value, checking perms, build form
         if rschema.is_final():
-            value = entity.printable_value(rtype)
+            value = entity.printable_value(rtype) or default
             if not entity.has_perm('update'):
                 self.w(value)
                 return
-        else:
-            rset = entity.related(rtype, role)
-            # XXX html_escape but that depends of the actual vid
-            value = html_escape(self.view(vid, rset, 'null') or default)
-        # XXX consider local roles ?
-        if role == 'subject'and not rschema.has_perm(self.req, 'add',
-                                                    fromeid=entity.eid):
-            self.w(value)
-            return
-        elif role == 'object'and not rschema.has_perm(self.req, 'add',
-                                                      toeid=entity.eid):
-            self.w(value)
-            return
-        if not value.strip():
-            value = default
-        if rschema.is_final():
-            form = self._build_attribute_form(entity, value, rtype, role,
-                                              reload, row, col, default)
+            self._attribute_form(entity, value, rtype, role, reload,
+                                 row, col, default, landing_zone)
         else:
-            form = self._build_relation_form(entity, value, rtype, role,
-                                             row, col, vid, default)
-        renderer = self.vreg.select('formrenderers', 'base', self.req,
-                                    entity=entity,
-                                    display_label=False, display_help=False,
-                                    display_fields=[(rtype, role)],
-                                    table_class='', button_bar_class='buttonbar',
-                                    display_progress_div=False)
-        self.w(form.form_render(renderer=renderer))
+            dispctrl = uicfg.primaryview_display_ctrl.etype_get(entity.e_schema,
+                                                                rtype, role)
+            vid = dispctrl.get('vid', 'reledit')
+            if vid != 'reledit': # reledit explicitly disabled
+                self.wview(vid, entity.related(rtype, role))
+                return
+            if rvid is None:
+                rvid = self._compute_best_vid(entity, rtype, role)
+            rset = entity.related(rtype, role)
+            candidate = self.view(rvid, rset, 'null')
+            value = candidate or default
+            if role == 'subject' and not rschema.has_perm(self.req, 'add',
+                                                          fromeid=entity.eid):
+                return self.w(value)
+            elif role == 'object' and not rschema.has_perm(self.req, 'add',
+                                                           toeid=entity.eid):
+                return self.w(value)
+            elif get_schema_property(entity.e_schema, rschema,
+                                     role, 'composite') == role:
+                self.warning('reledit cannot be applied : (... %s %s [composite])'
+                             % (rtype, entity.e_schema))
+                return self.w(value)
+            self._relation_form(entity, value, rtype, role, reload, row, col,
+                                rvid, default, landing_zone)
 
-    def _build_relation_form(self, entity, value, rtype, role, row, col, vid, default):
-        entity = self.entity(row, col)
+
+    def _relation_form(self, entity, value, rtype, role, row, col, reload, rvid, default, lzone):
+        lzone = self._build_landing_zone(lzone)
+        value = lzone + value
         divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
-        event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : vid,
-                      'default' : default, 'role' : role}
-        onsubmit = ("return inlineValidateRelationForm('%(divid)s-form', '%(rtype)s', "
-                    "'%(role)s', '%(eid)s', '%(divid)s', '%(vid)s', '%(default)s');"
+        event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : rvid,
+                      'reload' : reload, 'default' : default, 'role' : role,
+                      'lzone' : lzone}
+        onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
+                    "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');"
                     % event_data)
-        cancelclick = "cancelInlineEdit(%s,\'%s\',\'%s\')" % (
+        cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')" % (
             entity.eid, rtype, divid)
-        form = self.vreg.select('forms', 'base', self.req, entity=entity,
-                                domid='%s-form' % divid, cssstyle='display: none',
-                                onsubmit=onsubmit, action='#',
-                                form_buttons=[SubmitButton(),
-                                              Button(stdmsgs.BUTTON_CANCEL,
-                                                     onclick=cancelclick)])
-        form.append_field(RelationField(name=rtype, role=role, sort=True,
-                                        widget=Select(),
-                                        label=u' '))
+        form = self.vreg.select_object('forms', 'base', self.req, entity=entity,
+                                       domid='%s-form' % divid, cssstyle='display: none',
+                                       onsubmit=onsubmit, action='#',
+                                       form_buttons=[SubmitButton(),
+                                                     Button(stdmsgs.BUTTON_CANCEL,
+                                                            onclick=cancelclick)])
+        field = guess_field(entity.e_schema, entity.schema.rschema(rtype), role)
+        form.append_field(field)
+        self.w(u'<div id="%s-reledit" class="field">' % divid)
         self.w(tags.div(value, klass='editableField', id=divid,
-                        ondblclick=self.ondblclick % event_data))
-        return form
+                        onclick=self._onclick % event_data))
+        renderer = self._build_renderer(entity, rtype, role)
+        self.w(form.form_render(renderer=renderer))
+        self.w(u'</div>')
 
-    def _build_attribute_form(self, entity, value, rtype, role, reload, row, col, default):
+    def _attribute_form(self, entity, value, rtype, role, reload, row, col, default, lzone):
         eid = entity.eid
         divid = 'd%s' % make_uid('%s-%s' % (rtype, eid))
         event_data = {'divid' : divid, 'eid' : eid, 'rtype' : rtype,
-                      'reload' : dumps(reload), 'default' : default}
+                      'reload' : dumps(reload), 'default' : default, 'lzone' : lzone}
+        onsubmit = ("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', "
+                    "%(reload)s, '%(default)s', '%(lzone)s');")
         buttons = [SubmitButton(stdmsgs.BUTTON_OK),
                    Button(stdmsgs.BUTTON_CANCEL,
-                          onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (
+                          onclick="hideInlineEdit(%s,\'%s\',\'%s\')" % (
                               eid, rtype, divid))]
-        form = self.vreg.select('forms', 'edition', self.req, rset=self.rset,
-                                row=row, col=col, form_buttons=buttons,
-                                domid='%s-form' % divid, action='#',
-                                cssstyle='display: none',
-                                onsubmit=self.onsubmit % event_data)
+        form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
+                                       row=row, col=col, form_buttons=buttons,
+                                       domid='%s-form' % divid, action='#',
+                                       cssstyle='display: none',
+                                       onsubmit=onsubmit % event_data)
         self.w(tags.div(value, klass='editableField', id=divid,
-                        ondblclick=self.ondblclick % event_data))
-        return form
+                        onclick=self._onclick % event_data))
+        renderer = self._build_renderer(entity, rtype, role)
+        self.w(form.form_render(renderer=renderer))
 
 
 class EditionFormView(FormViewMixIn, EntityView):
--- a/web/views/editviews.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/editviews.py	Thu Jul 16 13:30:13 2009 +0200
@@ -11,7 +11,7 @@
 from simplejson import dumps
 
 from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import typed_eid
 from cubicweb.view import EntityView
@@ -64,10 +64,10 @@
         erset = entity.as_rset()
         if self.req.match_search_state(erset):
             self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
-                html_escape(linksearch_select_url(self.req, erset)),
+                xml_escape(linksearch_select_url(self.req, erset)),
                 self.req._('select this entity'),
-                html_escape(entity.view('textoutofcontext')),
-                html_escape(entity.absolute_url(vid='primary')),
+                xml_escape(entity.view('textoutofcontext')),
+                xml_escape(entity.absolute_url(vid='primary')),
                 self.req._('view detail for this entity')))
         else:
             entity.view('outofcontext', w=self.w)
@@ -111,7 +111,7 @@
   </select>
 </div>
 """ % (hidden and 'hidden' or '', divid, selectid,
-       html_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+       xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
        '\n'.join(options))
 
     def _get_select_options(self, entity, rschema, target):
@@ -126,13 +126,13 @@
         for eview, reid in form.form_field_vocabulary(field, limit):
             if reid is None:
                 options.append('<option class="separator">-- %s --</option>'
-                               % html_escape(eview))
+                               % xml_escape(eview))
             else:
                 optionid = relation_id(eid, rtype, target, reid)
                 if optionid not in pending_inserts:
                     # prefix option's id with letters to make valid XHTML wise
                     options.append('<option id="id%s" value="%s">%s</option>' %
-                                   (optionid, reid, html_escape(eview)))
+                                   (optionid, reid, xml_escape(eview)))
         return options
 
     def _get_search_options(self, entity, rschema, target, targettypes):
@@ -145,7 +145,7 @@
                                  __mode=mode)
             options.append((eschema.display_name(self.req),
                             '<option value="%s">%s %s</option>' % (
-                html_escape(url), _('Search for'), eschema.display_name(self.req))))
+                xml_escape(url), _('Search for'), eschema.display_name(self.req))))
         return [o for l, o in sorted(options)]
 
     def _get_basket_options(self, entity, rschema, target, targettypes):
@@ -156,7 +156,7 @@
                                                             target, targettypes):
             optionid = relation_id(entity.eid, rtype, target, basketeid)
             options.append('<option id="%s" value="%s">%s %s</option>' % (
-                optionid, basketeid, _('link to each item in'), html_escape(basketname)))
+                optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
         return options
 
     def _get_basket_links(self, ueid, target, targettypes):
--- a/web/views/emailaddress.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/emailaddress.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,8 +7,9 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
+from cubicweb.schema import display_name
 from cubicweb.selectors import implements
 from cubicweb.common import Unauthorized
 from cubicweb.web.views import baseviews, primary
@@ -79,9 +80,9 @@
         if entity.reverse_primary_email:
             self.w(u'<b>')
         if entity.alias:
-            self.w(u'%s &lt;' % html_escape(entity.alias))
-        self.w('<a href="%s">%s</a>' % (html_escape(entity.absolute_url()),
-                                        html_escape(entity.display_address())))
+            self.w(u'%s &lt;' % xml_escape(entity.alias))
+        self.w('<a href="%s">%s</a>' % (xml_escape(entity.absolute_url()),
+                                        xml_escape(entity.display_address())))
         if entity.alias:
             self.w(u'&gt;\n')
         if entity.reverse_primary_email:
@@ -108,8 +109,8 @@
             mailto = "mailto:%s <%s>" % (alias, entity.display_address())
         else:
             mailto = "mailto:%s" % entity.display_address()
-        self.w(u'<a href="%s">%s</a>' % (html_escape(mailto),
-                                         html_escape(entity.display_address())))
+        self.w(u'<a href="%s">%s</a>' % (xml_escape(mailto),
+                                         xml_escape(entity.display_address())))
         if entity.reverse_primary_email:
             self.w(u'</b>')
 
--- a/web/views/facets.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/facets.py	Thu Jul 16 13:30:13 2009 +0200
@@ -9,14 +9,15 @@
 
 from simplejson import dumps
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.vregistry import objectify_selector
 from cubicweb.selectors import (non_final_entity, two_lines_rset,
                                 match_context_prop, yes, relation_possible)
 from cubicweb.web.box import BoxTemplate
 from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
-                                prepare_facets_rqlst, filter_hiddens)
+                                prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
+                                _prepare_vocabulary_rqlst)
 
 @objectify_selector
 def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
@@ -41,7 +42,7 @@
     needs_css = 'cubicweb.facets.css'
     needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
 
-    bkLinkBox_template = u'<div class="facetTitle">%s</div>'
+    bk_linkbox_template = u'<div class="facetTitle">%s</div>'
 
     def facetargs(self):
         """this method returns the list of extra arguments that should
@@ -82,10 +83,11 @@
                         widgets.append(wdg)
             if not widgets:
                 return
-            self.displayBookmarkLink(rset)
+            if self.bk_linkbox_template:
+                self.display_bookmark_link(rset)
             w = self.w
             w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">'  % (
-                divid, html_escape(dumps([divid, vid, paginate, self.facetargs()]))))
+                divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()]))))
             w(u'<fieldset>')
             hiddens = {'facets': ','.join(wdg.facet.id for wdg in widgets),
                        'baserql': baserql}
@@ -101,7 +103,7 @@
             import cubicweb
             cubicweb.info('after facets with rql: %s' % repr(rqlst))
 
-    def displayBookmarkLink(self, rset):
+    def display_bookmark_link(self, rset):
         eschema = self.schema.eschema('Bookmark')
         if eschema.has_perm(self.req, 'add'):
             bk_path = 'view?rql=%s' % rset.printable_rql()
@@ -110,10 +112,10 @@
             bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
             bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
             bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
-                    html_escape(bk_base_url),
-                    html_escape(bk_add_url),
+                    xml_escape(bk_base_url),
+                    xml_escape(bk_add_url),
                     self.req._('bookmark this search'))
-            self.w(self.bkLinkBox_template % bk_link)
+            self.w(self.bk_linkbox_template % bk_link)
 
     def get_facets(self, rset, mainvar):
         return self.vreg.possible_vobjects('facets', self.req, rset=rset,
@@ -162,6 +164,21 @@
             return
         self.rqlst.add_type_restriction(self.filtered_variable, value)
 
+    def possible_values(self):
+        """return a list of possible values (as string since it's used to
+        compare to a form value in javascript) for this facet
+        """
+        rqlst = self.rqlst
+        rqlst.save_state()
+        try:
+            _cleanup_rqlst(rqlst, self.filtered_variable)
+            etype_var = _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, self.role)
+            attrvar = rqlst.make_variable()
+            rqlst.add_selected(attrvar)
+            rqlst.add_relation(etype_var, 'name', attrvar)
+            return [etype for _, etype in self.rqlexec(rqlst.as_string())]
+        finally:
+            rqlst.recover()
 
 class HasTextFacet(AbstractFacet):
     __select__ = relation_possible('has_text', 'subject') & match_context_prop()
--- a/web/views/formrenderers.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/formrenderers.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 
 from logilab.common import dictattr
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from simplejson import dumps
 
@@ -144,17 +144,17 @@
         else:
             action = form.action
         tag = ('<form action="%s" method="post" enctype="%s"' % (
-            html_escape(action or '#'), enctype))
+            xml_escape(action or '#'), enctype))
         if form.domid:
             tag += ' id="%s"' % form.domid
         if form.onsubmit:
-            tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
+            tag += ' onsubmit="%s"' % xml_escape(form.onsubmit % dictattr(form))
         if form.cssstyle:
-            tag += ' style="%s"' % html_escape(form.cssstyle)
+            tag += ' style="%s"' % xml_escape(form.cssstyle)
         if form.cssclass:
-            tag += ' class="%s"' % html_escape(form.cssclass)
+            tag += ' class="%s"' % xml_escape(form.cssclass)
         if form.cwtarget:
-            tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+            tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
         return tag + '>'
 
     def display_field(self, form, field):
@@ -298,7 +298,7 @@
             entity = form.edited_entity
             values = form.form_previous_values
             qeid = eid_param('eid', entity.eid)
-            cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid))
+            cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % xml_escape(dumps(entity.eid))
             w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
             # XXX turn this into a widget used on the eid field
             w(u'<td>%s</td>' % checkbox('eid', entity.eid, checked=qeid in values))
@@ -411,7 +411,7 @@
                 w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
                   (_('cancel this insert'), row[2]))
                 w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
-                  % (row[1], row[4], html_escape(row[5])))
+                  % (row[1], row[4], xml_escape(row[5])))
                 w(u'</td>')
                 w(u'</tr>')
         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
@@ -419,7 +419,7 @@
         w(u'<span>%s</span>' % _('add relation'))
         w(u'<select id="relationSelector_%s" tabindex="%s" '
           'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
         w(u'<option value="">%s</option>' % _('select a relation'))
         for i18nrtype, rschema, target in srels_by_cat:
             # more entities to link to
--- a/web/views/ibreadcrumbs.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/ibreadcrumbs.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 # don't use AnyEntity since this may cause bug with isinstance() due to reloading
 from cubicweb.interfaces import IBreadCrumbs
@@ -21,7 +21,7 @@
 
 def bc_title(entity):
     textsize = entity.req.property_value('navigation.short-line-size')
-    return html_escape(cut(entity.dc_title(), textsize))
+    return xml_escape(cut(entity.dc_title(), textsize))
 
 
 class BreadCrumbEntityVComponent(EntityVComponent):
@@ -64,7 +64,7 @@
             url, title = part
             textsize = self.req.property_value('navigation.short-line-size')
             self.w(u'<a href="%s">%s</a>' % (
-                html_escape(url), html_escape(cut(title, textsize))))
+                xml_escape(url), xml_escape(cut(title, textsize))))
         else:
             textsize = self.req.property_value('navigation.short-line-size')
             self.w(cut(unicode(part), textsize))
@@ -81,6 +81,6 @@
 
     def cell_call(self, row, col):
         entity = self.entity(row, col)
-        desc = html_escape(cut(entity.dc_description(), 50))
+        desc = xml_escape(cut(entity.dc_description(), 50))
         self.w(u'<a href="%s" title="%s">%s</a>' % (
-            html_escape(entity.absolute_url()), desc, bc_title(entity)))
+            xml_escape(entity.absolute_url()), desc, bc_title(entity)))
--- a/web/views/idownloadable.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/idownloadable.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape
+from logilab.mtconverter import BINARY_ENCODINGS, TransformError, xml_escape
 
 from cubicweb.view import EntityView
 from cubicweb.selectors import (one_line_rset, score_entity,
@@ -31,12 +31,12 @@
     if title is None:
         title = req._('download')
     w(u'<div class="sideBoxTitle downloadBoxTitle"><span>%s</span></div>'
-      % html_escape(title))
+      % xml_escape(title))
     w(u'<div class="sideBox downloadBox"><div class="sideBoxBody">')
     w(u'<a href="%s"><img src="%s" alt="%s"/> %s</a>'
-      % (html_escape(entity.download_url()),
+      % (xml_escape(entity.download_url()),
          req.external_resource('DOWNLOAD_ICON'),
-         _('download icon'), html_escape(label or entity.dc_title())))
+         _('download icon'), xml_escape(label or entity.dc_title())))
     w(u'</div>')
     w(u'</div>\n</div>\n')
 
@@ -92,8 +92,8 @@
 
     def cell_call(self, row, col, title=None, **kwargs):
         entity = self.entity(row, col)
-        url = html_escape(entity.download_url())
-        self.w(u'<a href="%s">%s</a>' % (url, html_escape(title or entity.dc_title())))
+        url = xml_escape(entity.download_url())
+        self.w(u'<a href="%s">%s</a>' % (url, xml_escape(title or entity.dc_title())))
 
 
 class IDownloadablePrimaryView(primary.PrimaryView):
@@ -124,9 +124,9 @@
     def cell_call(self, row, col, title=None, **kwargs):
         """the secondary view is a link to download the file"""
         entity = self.entity(row, col)
-        url = html_escape(entity.absolute_url())
-        name = html_escape(title or entity.download_file_name())
-        durl = html_escape(entity.download_url())
+        url = xml_escape(entity.absolute_url())
+        name = xml_escape(title or entity.download_file_name())
+        durl = xml_escape(entity.download_url())
         self.w(u'<a href="%s">%s</a> [<a href="%s">%s</a>]' %
                (url, name, durl, self.req._('download')))
 
@@ -147,6 +147,6 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         #if entity.data_format.startswith('image/'):
-        self.w(u'<img src="%s" alt="%s"/>' % (html_escape(entity.download_url()),
-                                              html_escape(entity.download_file_name())))
+        self.w(u'<img src="%s" alt="%s"/>' % (xml_escape(entity.download_url()),
+                                              xml_escape(entity.download_file_name())))
 
--- a/web/views/iprogress.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/iprogress.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import implements
 from cubicweb.interfaces import IProgress, IMileStone
@@ -99,7 +99,7 @@
                 colname = meth(ecls)
             else:
                 colname = _(column)
-            self.w(u'<th>%s</th>' % html_escape(colname))
+            self.w(u'<th>%s</th>' % xml_escape(colname))
         self.w(u'</tr></thead>\n')
 
 
@@ -117,7 +117,7 @@
 
     def build_state_cell(self, entity):
         """``state`` column cell renderer"""
-        return html_escape(self.req._(entity.state))
+        return xml_escape(self.req._(entity.state))
 
     def build_eta_date_cell(self, entity):
         """``eta_date`` column cell renderer"""
--- a/web/views/isioc.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/isioc.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,7 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.view import EntityView
 from cubicweb.selectors import implements
@@ -45,11 +45,11 @@
 
     def cell_call(self, row, col):
         entity = self.complete_entity(row, col)
-        sioct = html_escape(entity.isioc_type())
+        sioct = xml_escape(entity.isioc_type())
         self.w(u'<sioc:%s rdf:about="%s">\n'
-               % (sioct, html_escape(entity.absolute_url())))
+               % (sioct, xml_escape(entity.absolute_url())))
         self.w(u'<dcterms:title>%s</dcterms:title>'
-               % html_escape(entity.dc_title()))
+               % xml_escape(entity.dc_title()))
         self.w(u'<dcterms:created>%s</dcterms:created>'
                % entity.creation_date)
         self.w(u'<dcterms:modified>%s</dcterms:modified>'
@@ -66,25 +66,25 @@
 
     def cell_call(self, row, col):
         entity = self.complete_entity(row, col)
-        sioct = html_escape(entity.isioc_type())
+        sioct = xml_escape(entity.isioc_type())
         self.w(u'<sioc:%s rdf:about="%s">\n'
-               %  (sioct, html_escape(entity.absolute_url())))
+               %  (sioct, xml_escape(entity.absolute_url())))
         self.w(u'<dcterms:title>%s</dcterms:title>'
-               % html_escape(entity.dc_title()))
+               % xml_escape(entity.dc_title()))
         self.w(u'<dcterms:created>%s</dcterms:created>'
                % entity.creation_date)
         self.w(u'<dcterms:modified>%s</dcterms:modified>'
                % entity.modification_date)
         if entity.content:
             self.w(u'<sioc:content>%s</sioc:content>'''
-                   % html_escape(entity.isioc_content()))
+                   % xml_escape(entity.isioc_content()))
         if entity.related('entry_of'):
             self.w(u'<sioc:has_container rdf:resource="%s"/>\n'
-                   % html_escape(entity.isioc_container().absolute_url()))
+                   % xml_escape(entity.isioc_container().absolute_url()))
         if entity.creator:
             self.w(u'<sioc:has_creator>\n')
             self.w(u'<sioc:User rdf:about="%s">\n'
-                   % html_escape(entity.creator.absolute_url()))
+                   % xml_escape(entity.creator.absolute_url()))
             self.w(entity.creator.view('foaf'))
             self.w(u'</sioc:User>\n')
             self.w(u'</sioc:has_creator>\n')
--- a/web/views/management.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/management.py	Thu Jul 16 13:30:13 2009 +0200
@@ -9,7 +9,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
 from cubicweb.view import AnyRsetView, StartupView, EntityView
@@ -84,8 +84,8 @@
         _ = self.req._
         w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
           % (entity.dc_type().capitalize(),
-             html_escape(entity.absolute_url()),
-             html_escape(entity.dc_title())))
+             xml_escape(entity.absolute_url()),
+             xml_escape(entity.dc_title())))
         # first show permissions defined by the schema
         self.w('<h2>%s</h2>' % _('schema\'s permissions definitions'))
         self.schema_definition(entity.e_schema)
@@ -140,7 +140,7 @@
                 # and this will replace %s by %25s
                 delurl += '&__delete=%s:require_permission:%%s' % entity.eid
                 dellinktempl = u'[<a href="%s" title="%s">-</a>]&nbsp;' % (
-                    html_escape(delurl), _('delete this permission'))
+                    xml_escape(delurl), _('delete this permission'))
             else:
                 dellinktempl = None
             w(u'<table class="schemaInfo">')
@@ -215,14 +215,14 @@
         if excinfo is not None and self.config['print-traceback']:
             if exclass is None:
                 w(u'<div class="tb">%s</div>'
-                       % html_escape(ex).replace("\n","<br />"))
+                       % xml_escape(ex).replace("\n","<br />"))
             else:
                 w(u'<div class="tb">%s: %s</div>'
-                       % (exclass, html_escape(ex).replace("\n","<br />")))
+                       % (exclass, xml_escape(ex).replace("\n","<br />")))
             w(u'<hr />')
             w(u'<div class="tb">%s</div>' % html_traceback(excinfo, ex, ''))
         else:
-            w(u'<div class="tb">%s</div>' % (html_escape(ex).replace("\n","<br />")))
+            w(u'<div class="tb">%s</div>' % (xml_escape(ex).replace("\n","<br />")))
         # if excinfo is not None, it's probably not a bug
         if excinfo is None:
             return
@@ -269,7 +269,7 @@
             return unicode(repr(ex), encoding, 'replace')
 
 def text_error_description(ex, excinfo, req, eversion, cubes):
-    binfo = rest_traceback(excinfo, html_escape(ex))
+    binfo = rest_traceback(excinfo, xml_escape(ex))
     binfo += u'\n\n:URL: %s\n' % req.url()
     if not '__bugreporting' in req.form:
         binfo += u'\n:form params:\n'
@@ -319,7 +319,7 @@
         self.w(u'<table border="1">')
         for attr in env.keys():
             self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, html_escape(env[attr])))
+                   % (attr, xml_escape(env[attr])))
         self.w(u'</table>')
         self.w(u'<h3>%s</h3>' % _('Request'))
         self.w(u'<table border="1">')
@@ -328,7 +328,7 @@
                      'search_state', 'the_request', 'unparsed_uri', 'uri'):
             val = getattr(req, attr)
             self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, html_escape(val)))
+                   % (attr, xml_escape(val)))
         self.w(u'</table>')
         server = req.server
         self.w(u'<h3>%s</h3>' % _('Server'))
@@ -338,6 +338,6 @@
             if attr.startswith('_') or callable(val):
                 continue
             self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, html_escape(val)))
+                   % (attr, xml_escape(val)))
         self.w(u'</table>')
 
--- a/web/views/navigation.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/navigation.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,7 +10,7 @@
 
 from rql.nodes import VariableRef, Constant
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import obsolete
 
 from cubicweb.interfaces import IPrevNext
@@ -136,7 +136,7 @@
     def format_link_content(self, startstr, stopstr):
         text = u'%s - %s' % (startstr.lower()[:self.nb_chars],
                              stopstr.lower()[:self.nb_chars])
-        return html_escape(text)
+        return xml_escape(text)
 
     def write_links(self, params, blocklist):
         self.w(u'<div class="pagination">')
@@ -159,7 +159,7 @@
             nav.clean_params(params)
             # make a link to see them all
             if show_all_option:
-                url = html_escape(self.build_url(__force_display=1, **params))
+                url = xml_escape(self.build_url(__force_display=1, **params))
                 w(u'<p><a href="%s">%s</a></p>\n'
                   % (url, req._('show %s results') % len(rset)))
             rset.limit(offset=start, limit=stop-start, inplace=True)
@@ -198,24 +198,24 @@
                 self.w(self.previous_link(previous, textsize))
                 self.w(u'</div>')
                 self.req.html_headers.add_raw('<link rel="prev" href="%s" />'
-                                              % html_escape(previous.absolute_url()))
+                                              % xml_escape(previous.absolute_url()))
             if next:
                 self.w(u'<div class="nextEntity right">')
                 self.w(self.next_link(next, textsize))
                 self.w(u'</div>')
                 self.req.html_headers.add_raw('<link rel="next" href="%s" />'
-                                              % html_escape(next.absolute_url()))
+                                              % xml_escape(next.absolute_url()))
             self.w(u'</div>')
             self.w(u'<div class="clear"></div>')
 
     def previous_link(self, previous, textsize):
         return u'<a href="%s" title="%s">&lt;&lt; %s</a>' % (
-            html_escape(previous.absolute_url()),
+            xml_escape(previous.absolute_url()),
             self.req._('i18nprevnext_previous'),
-            html_escape(cut(previous.dc_title(), textsize)))
+            xml_escape(cut(previous.dc_title(), textsize)))
 
     def next_link(self, next, textsize):
         return u'<a href="%s" title="%s">%s &gt;&gt;</a>' % (
-            html_escape(next.absolute_url()),
+            xml_escape(next.absolute_url()),
             self.req._('i18nprevnext_next'),
-            html_escape(cut(next.dc_title(), textsize)))
+            xml_escape(cut(next.dc_title(), textsize)))
--- a/web/views/old_calendar.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/old_calendar.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 
 from datetime import date, time, timedelta
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.interfaces import ICalendarViews
 from cubicweb.utils import ONEDAY, ONEWEEK, date_range, first_day, last_day, previous_month, next_month, days_in_month
@@ -46,13 +46,13 @@
         next2 = next_month(date, bigshift)
         rql = self.rset.printable_rql()
         return self.NAV_HEADER % (
-            html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
                                        month=prev2.month)),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
                                        month=prev1.month)),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
                                        month=next1.month)),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
                                        month=next2.month)))
 
 
@@ -91,7 +91,7 @@
             rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
         url = self.build_url(rql=rql, vid='calendarmonth',
                              year=first_day.year, month=first_day.month)
-        monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
+        monthlink = u'<a href="%s">%s</a>' % (xml_escape(url), umonth)
         return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
 
     def _mk_schedule(self, begin, end, itemvid='calendaritem'):
@@ -203,7 +203,7 @@
             umonth = u'%s&nbsp;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
             url = self.build_url(rql=rql, vid=self.id,
                                  year=cur_month.year, month=cur_month.month)
-            self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (html_escape(url),
+            self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (xml_escape(url),
                                                                   umonth))
         self.w(u'</tr>')
         _ = self.req._
@@ -272,7 +272,7 @@
             umonth = self.format_date(monday, '%B %Y')
             url = self.build_url(rql=rql, vid='calendarmonth',
                                  year=monday.year, month=monday.month)
-            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+            monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
             self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
                   % (_('week'), monday.isocalendar()[1], monthlink))
             for day in date_range(monday, sunday):
@@ -295,10 +295,10 @@
         next2 = date + ONEWEEK * bigshift
         rql = self.rset.printable_rql()
         return self.NAV_HEADER % (
-            html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
-            html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
+            xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
 
 
 
@@ -326,7 +326,7 @@
             if day.weekday() == 6:
                 url = self.build_url(rql=rql, vid='ampmcalendarweek',
                                      year=day.year, week=day.isocalendar()[1])
-                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+                weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                                     day.isocalendar()[1])
                 current_row.append(WEEKNUM_CELL % weeklink)
                 rows.append(current_row)
@@ -334,7 +334,7 @@
         current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
         url = self.build_url(rql=rql, vid='ampmcalendarweek',
                              year=day.year, week=day.isocalendar()[1])
-        weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.isocalendar()[1])
+        weeklink = '<a href="%s">%s</a>' % (xml_escape(url), day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
         rows.append(current_row)
         # build two rows for each week: am & pm
@@ -350,7 +350,7 @@
         # tigh everything together
         url = self.build_url(rql=rql, vid='ampmcalendarmonth',
                              year=first_day.year, month=first_day.month)
-        monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+        monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
         return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
 
 
@@ -367,7 +367,7 @@
             umonth = u'%s&nbsp;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
             url = self.build_url(rql=rql, vid=self.id,
                                  year=cur_month.year, month=cur_month.month)
-            self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (html_escape(url),
+            self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (xml_escape(url),
                                                                   umonth))
         self.w(u'</tr>')
         _ = self.req._
@@ -417,7 +417,7 @@
             if day.weekday() == 6:
                 url = self.build_url(rql=rql, vid='ampmcalendarweek',
                                      year=day.year, week=day.isocalendar()[1])
-                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+                weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                                     day.isocalendar()[1])
                 current_row.append(WEEKNUM_CELL % weeklink)
                 rows.append(current_row)
@@ -425,7 +425,7 @@
         current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
         url = self.build_url(rql=rql, vid='ampmcalendarweek',
                              year=day.year, week=day.isocalendar()[1])
-        weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+        weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
                                             day.isocalendar()[1])
         current_row.append(WEEKNUM_CELL % weeklink)
         rows.append(current_row)
@@ -442,7 +442,7 @@
         # tigh everything together
         url = self.build_url(rql=rql, vid='ampmcalendarmonth',
                              year=first_day.year, month=first_day.month)
-        monthlink = '<a href="%s">%s</a>' % (html_escape(url),
+        monthlink = '<a href="%s">%s</a>' % (xml_escape(url),
                                              umonth)
         return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
 
@@ -461,7 +461,7 @@
             umonth = self.format_date(monday, '%B %Y')
             url = self.build_url(rql=rql, vid='ampmcalendarmonth',
                                  year=monday.year, month=monday.month)
-            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+            monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
             w(u'<tr>%s</tr>' % (
                 WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
             w(u'<tr><th>%s</th><th>&nbsp;</th></tr>'% _(u'Date'))
--- a/web/views/plots.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/plots.py	Thu Jul 16 13:30:13 2009 +0200
@@ -13,7 +13,7 @@
 from simplejson import dumps
 
 from logilab.common import flatten
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import make_uid, UStringIO, datetime2ticks
 from cubicweb.vregistry import objectify_selector
@@ -167,7 +167,7 @@
                 piechart.size(width, height)
             if self.title:
                 piechart.title(self.title)
-            self.w(u'<img src="%s" />' % html_escape(piechart.url))
+            self.w(u'<img src="%s" />' % xml_escape(piechart.url))
 
     class PieChartView(baseviews.AnyRsetView):
         id = 'piechart'
--- a/web/views/primary.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/primary.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,7 +10,7 @@
 
 from warnings import warn
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized
 from cubicweb.view import EntityView
@@ -100,7 +100,7 @@
 
     def render_entity_title(self, entity):
         """default implementation return dc_title"""
-        title = html_escape(entity.dc_title())
+        title = xml_escape(entity.dc_title())
         if title:
             self.w(u'<h1><span class="etype">%s</span> %s</h1>'
                    % (entity.dc_type().capitalize(), title))
@@ -117,19 +117,7 @@
 
     def render_entity_attributes(self, entity, siderelations=None):
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
-            # don't use reledit as default vid for composite relation
-            if rschema.is_final():
-                defaultvid = 'reledit'
-            # XXX use entity.e_schema.role_rproperty(role, rschema, 'composite', tschemas[0]) once yams > 0.23.0 is out
-            elif role == 'subject' and \
-                 rschema.rproperty(entity.e_schema, tschemas[0], 'composite'):
-                defaultvid = 'csv'
-            elif role == 'object' and \
-                 rschema.rproperty(tschemas[0], entity.e_schema, 'composite'):
-                defaultvid = 'csv'
-            else:
-                defaultvid = 'reledit'
-            vid =  dispctrl.get('vid', defaultvid)
+            vid = dispctrl.get('vid', 'reledit')
             if rschema.is_final() or vid == 'reledit':
                 value = entity.view(vid, rtype=rschema.type, role=role)
             else:
--- a/web/views/schema.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/schema.py	Thu Jul 16 13:30:13 2009 +0200
@@ -7,7 +7,9 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from itertools import cycle
+
+from logilab.mtconverter import xml_escape
 from yams import schema2dot as s2d
 
 from cubicweb.selectors import implements, yes
@@ -47,7 +49,7 @@
     def render_entity_title(self, entity):
         self.w(u'<h1><span class="etype">%s</span> %s</h1>'
                % (entity.dc_type().capitalize(),
-                  html_escape(entity.dc_long_title())))
+                  xml_escape(entity.dc_long_title())))
 
 
 # CWEType ######################################################################
@@ -123,8 +125,8 @@
         entity = self.entity(row, col)
         url = entity.absolute_url(vid='schemagraph')
         self.w(u'<img src="%s" alt="%s"/>' % (
-            html_escape(url),
-            html_escape(self.req._('graphical schema for %s') % entity.name)))
+            xml_escape(url),
+            xml_escape(self.req._('graphical schema for %s') % entity.name)))
 
 class CWETypeSPermView(EntityView):
     id = 'cwetype-schema-permissions'
@@ -161,8 +163,8 @@
         entity = self.entity(row, col)
         if entity.reverse_state_of:
             self.w(u'<img src="%s" alt="%s"/>' % (
-                    html_escape(entity.absolute_url(vid='ewfgraph')),
-                    html_escape(self.req._('graphical workflow for %s') % entity.name)))
+                    xml_escape(entity.absolute_url(vid='ewfgraph')),
+                    xml_escape(self.req._('graphical workflow for %s') % entity.name)))
         else:
             self.w(u'<p>%s</p>' % _('There is no workflow defined for this entity.'))
 
--- a/web/views/startup.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/startup.py	Thu Jul 16 13:30:13 2009 +0200
@@ -74,7 +74,7 @@
             else:
                 href = req.build_url('view', vid='creation', etype='Card', wikiid='index')
                 label = self.req._('create an index page')
-            self.w(u'<br/><a href="%s">%s</a>\n' % (html_escape(href), label))
+            self.w(u'<br/><a href="%s">%s</a>\n' % (xml_escape(href), label))
 
     def folders(self):
         self.w(u'<h4>%s</h4>\n' % self.req._('Browse by category'))
@@ -89,7 +89,7 @@
             if v.category != 'startupview' or v.id in ('index', 'tree', 'manage'):
                 continue
             self.w('<p><a href="%s">%s</a></p>' % (
-                html_escape(v.url()), html_escape(self.req._(v.title).capitalize())))
+                xml_escape(v.url()), xml_escape(self.req._(v.title).capitalize())))
 
     def entities(self):
         schema = self.schema
@@ -144,7 +144,7 @@
             else:
                 url = self.build_url('view', rql='%s X' % etype)
             etypelink = u'&nbsp;<a href="%s">%s</a> (%d)' % (
-                html_escape(url), label, nb)
+                xml_escape(url), label, nb)
             yield (label, etypelink, self.add_entity_link(eschema, req))
 
     def add_entity_link(self, eschema, req):
@@ -152,13 +152,13 @@
         if not eschema.has_perm(req, 'add'):
             return u''
         return u'[<a href="%s" title="%s">+</a>]' % (
-            html_escape(self.create_url(eschema.type)),
+            xml_escape(self.create_url(eschema.type)),
             self.req.__('add a %s' % eschema))
 
 
 class IndexView(ManageView):
     id = 'index'
-    title = _('index')
+    title = _('view_index')
 
     def display_folders(self):
         return 'Folder' in self.schema and self.req.execute('Any COUNT(X) WHERE X is Folder')[0][0]
@@ -184,9 +184,9 @@
         self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
                  u'meta-data, but you can also display a <a href="%s">complete '
                  u'schema with meta-data</a>.</div>')
-               % html_escape(self.build_url('view', vid='schemagraph', skipmeta=0)))
+               % xml_escape(self.build_url('view', vid='schemagraph', withmeta=1)))
         self.w(u'<img src="%s" alt="%s"/>\n' % (
-            html_escape(self.req.build_url('view', vid='schemagraph', skipmeta=1)),
+            xml_escape(self.req.build_url('view', vid='schemagraph', skipmeta=1)),
             self.req._("graphical representation of the application'schema")))
 
 
@@ -228,15 +228,15 @@
         self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
         self.w(u'<h4>%s</h4>' %   _('Entities').capitalize())
         ents = []
-        for eschema in entities:
-            url = html_escape(self.build_url('schema', **formparams))
+        for eschema in sorted(entities):
+            url = xml_escape(self.build_url('schema', **formparams))
             ents.append(u'<a class="grey" href="%s#%s">%s</a> (%s)' % (
                 url,  eschema.type, eschema.type, _(eschema.type)))
         self.w(u', '.join(ents))
         self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
         rels = []
-        for rschema in relations:
-            url = html_escape(self.build_url('schema', **formparams))
+        for rschema in sorted(relations):
+            url = xml_escape(self.build_url('schema', **formparams))
             rels.append(u'<a class="grey" href="%s#%s">%s</a> (%s), ' %  (
                 url , rschema.type, rschema.type, _(rschema.type)))
         self.w(u', '.join(ents))
@@ -254,7 +254,7 @@
         for eschema in entities:
             self.w(u'<a id="%s" href="%s"/>' %  (eschema.type, eschema.type))
             self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
-            url = html_escape(self.build_url('schema', **formparams) + '#index')
+            url = xml_escape(self.build_url('schema', **formparams) + '#index')
             self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
                 url,  self.req.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
@@ -281,7 +281,7 @@
         for rschema in relations:
             self.w(u'<a id="%s" href="%s"/>' %  (rschema.type, rschema.type))
             self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
-            url = html_escape(self.build_url('schema', **formparams) + '#index')
+            url = xml_escape(self.build_url('schema', **formparams) + '#index')
             self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
                 url,  self.req.external_resource('UP_ICON'), _('up')))
             self.w(u'</h3>')
--- a/web/views/tableview.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/tableview.py	Thu Jul 16 13:30:13 2009 +0200
@@ -10,7 +10,7 @@
 
 from simplejson import dumps
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import nonempty_rset, match_form_params
 from cubicweb.utils import make_uid
@@ -55,7 +55,7 @@
         # drop False / None values from vidargs
         vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
         self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
-               html_escape(dumps([divid, 'table', False, vidargs])))
+               xml_escape(dumps([divid, 'table', False, vidargs])))
         self.w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
         self.w(u'<input type="hidden" name="divid" value="%s" />' % divid)
         filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets), baserql=baserql)
@@ -178,7 +178,7 @@
         box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
         label = '<img src="%s" alt="%s"/>' % (
             self.req.datadir_url + 'liveclipboard-icon.png',
-            html_escape(self.req._('action(s) on this selection')))
+            xml_escape(self.req._('action(s) on this selection')))
         menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
                             ident='%sActions' % divid)
         box.append(menu)
--- a/web/views/tabs.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/tabs.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb import NoSelectableObject, role
 from cubicweb.selectors import partial_has_related_entities
@@ -47,7 +47,7 @@
         elif rset:
             urlparams['rql'] = rset.printable_rql()
         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
-            vid, html_escape(self.build_url('json', **urlparams))))
+            vid, xml_escape(self.build_url('json', **urlparams))))
         if show_spinbox:
             w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
               % (vid, self.req._('loading')))
--- a/web/views/timeline.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/timeline.py	Thu Jul 16 13:30:13 2009 +0200
@@ -11,7 +11,7 @@
 
 import simplejson
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.interfaces import ICalendarable
 from cubicweb.selectors import implements
@@ -68,7 +68,7 @@
         if start is None and stop is None:
             return None
         event_data = {'start': start.strftime(self.date_fmt),
-                      'title': html_escape(entity.dc_title()),
+                      'title': xml_escape(entity.dc_title()),
                       'description': entity.dc_description(format='text/html'),
                       'link': entity.absolute_url(),
                       }
@@ -95,7 +95,7 @@
             additional = u''
         self.w(u'<div class="widget" cubicweb:wdgtype="%s" '
                u'cubicweb:loadtype="auto" cubicweb:loadurl="%s" %s >' %
-               (self.widget_class, html_escape(loadurl),
+               (self.widget_class, xml_escape(loadurl),
                 additional))
         self.w(u'</div>')
 
--- a/web/views/timetable.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/timetable.py	Thu Jul 16 13:30:13 2009 +0200
@@ -6,7 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.interfaces import ITimetableViews
 from cubicweb.selectors import implements
@@ -190,7 +190,7 @@
                     if value:
                         task_descr, first_row = value
                         if first_row:
-                            url = html_escape(task_descr.task.absolute_url(vid="edition"))
+                            url = xml_escape(task_descr.task.absolute_url(vid="edition"))
                             self.w(u'<td rowspan="%d" class="%s %s" onclick="document.location=\'%s\'">&nbsp;<div>' % (
                                 task_descr.lines, task_descr.color, filled_klasses[kj], url))
                             task_descr.task.view('tooltip', w=self.w)
--- a/web/views/treeview.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/treeview.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 
 from logilab.common.decorators import monkeypatch
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import make_uid
 from cubicweb.interfaces import ITree
@@ -39,7 +39,7 @@
         self.w(u'</ul>')
         if initial_load and not self.req.form.get('fname'):
             self.req.add_css('jquery.treeview.css')
-            self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
+            self.req.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
             self.req.html_headers.add_onload(u"""
 jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
 
@@ -113,7 +113,7 @@
             w(u'<li class="%s">' % u' '.join(liclasses))
         else:
             rql = entity.children_rql() % {'x': entity.eid}
-            url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
+            url = xml_escape(self.build_url('json', rql=rql, vid=parentvid,
                                              pageid=self.req.pageid,
                                              treeid=treeid,
                                              fname='view',
--- a/web/views/workflow.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/workflow.py	Thu Jul 16 13:30:13 2009 +0200
@@ -11,7 +11,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 from logilab.common.graph import escape, GraphGenerator, DotBackend
 
 from cubicweb import Unauthorized, view
@@ -118,7 +118,7 @@
     __select__ = implements('State')
 
     def cell_call(self, row, col):
-        self.w(html_escape(self.view('textincontext', self.rset,
+        self.w(xml_escape(self.view('textincontext', self.rset,
                                      row=row, col=col)))
 
 
@@ -145,8 +145,8 @@
         self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
                                  % display_name(self.req, entity.name)))
         self.w(u'<img src="%s" alt="%s"/>' % (
-            html_escape(entity.absolute_url(vid='ewfgraph')),
-            html_escape(self.req._('graphical workflow for %s') % entity.name)))
+            xml_escape(entity.absolute_url(vid='ewfgraph')),
+            xml_escape(self.req._('graphical workflow for %s') % entity.name)))
 
 
 class WorkflowDotPropsHandler(object):
--- a/web/views/xbel.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/views/xbel.py	Thu Jul 16 13:30:13 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import implements
 from cubicweb.view import EntityView
@@ -42,8 +42,8 @@
 
     def cell_call(self, row, col):
         entity = self.complete_entity(row, col)
-        self.w(u'<bookmark href="%s">' % html_escape(self.url(entity)))
-        self.w(u'  <title>%s</title>' % html_escape(entity.dc_title()))
+        self.w(u'<bookmark href="%s">' % xml_escape(self.url(entity)))
+        self.w(u'  <title>%s</title>' % xml_escape(entity.dc_title()))
         self.w(u'</bookmark>')
 
     def url(self, entity):
--- a/web/widgets.py	Wed Jul 15 09:45:13 2009 +0200
+++ b/web/widgets.py	Thu Jul 16 13:30:13 2009 +0200
@@ -12,7 +12,7 @@
 
 from datetime import datetime
 
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
 
 from yams.constraints import SizeConstraint, StaticVocabularyConstraint
 
@@ -247,9 +247,9 @@
         value = self.current_value(entity)
         dvalue = self.current_display_value(entity)
         if isinstance(value, basestring):
-            value = html_escape(value)
+            value = xml_escape(value)
         if isinstance(dvalue, basestring):
-            dvalue = html_escape(dvalue)
+            dvalue = xml_escape(dvalue)
         return u'%s<input type="%s" name="%s" value="%s" %s/>' % (
             self.hidden_input(entity, value), self.input_type,
             self.rname, dvalue, self.format_attrs())
@@ -323,9 +323,9 @@
         value = self.current_value(entity)
         dvalue = self.current_display_value(entity)
         if isinstance(value, basestring):
-            value = html_escape(value)
+            value = xml_escape(value)
         if isinstance(dvalue, basestring):
-            dvalue = html_escape(dvalue)
+            dvalue = xml_escape(dvalue)
         iid = self.attrs.pop('id')
         if self.required(entity):
             cssclass = u' required'
@@ -337,7 +337,7 @@
                     'iid': iid,
                     'hidden': self.hidden_input(entity, value),
                     'wdgtype': self.wdgtype,
-                    'url': html_escape(dataurl),
+                    'url': xml_escape(dataurl),
                     'tabindex': self.attrs.pop('tabindex'),
                     'value': dvalue,
                     'attrs': self.format_attrs(),
@@ -398,7 +398,7 @@
         editor = self._edit_render_textarea(entity, with_format)
         value = self.current_value(entity)
         if isinstance(value, basestring):
-            value = html_escape(value)
+            value = xml_escape(value)
         return u'%s%s' % (self.hidden_input(entity, value), editor)
 
     def _edit_render_textarea(self, entity, with_format):
@@ -406,7 +406,7 @@
         self.attrs.setdefault('rows', 20)
         dvalue = self.current_display_value(entity)
         if isinstance(dvalue, basestring):
-            dvalue = html_escape(dvalue)
+            dvalue = xml_escape(dvalue)
         if entity.use_fckeditor(self.name):
             entity.req.fckeditor_config()
             if with_format:
@@ -472,9 +472,9 @@
             or entity.e_schema.has_metadata(self.name, 'encoding')):
             divid = '%s-%s-advanced' % (self.name, entity.eid)
             wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
-                        (html_escape(toggle_action(divid)),
+                        (xml_escape(toggle_action(divid)),
                          req._('show advanced fields'),
-                         html_escape(req.build_url('data/puce_down.png')),
+                         xml_escape(req.build_url('data/puce_down.png')),
                          req._('show advanced fields')))
             wdgs.append(u'<div id="%s" class="hidden">' % divid)
             for extraattr in ('_format', '_encoding'):
@@ -572,7 +572,7 @@
                 res.append(u'<optgroup label="%s"/>' % (label or ''))
             else:
                 value, flag = self.form_value(entity, value, dvalues)
-                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, html_escape(label)))
+                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
         res.append(u'</select>')
         return '\n'.join(res)
 
@@ -658,7 +658,7 @@
                 res.append(u'<optgroup label="%s"/>' % (label or ''))
             else:
                 value, flag = self.form_value(entity, value, dvalues)
-                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, html_escape(label)))
+                res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
         res.append(u'</select>')
         res.append(u'<div id="newvalue">')
         res.append(u'<input type="text" id="newopt" />')
@@ -819,7 +819,7 @@
         url = getattr(entity, self.name)
         if not url:
             return u''
-        url = html_escape(url)
+        url = xml_escape(url)
         return u'<a href="%s">%s</a>' % (url, url)
 
 class EmbededURLWidget(StringWidget):
@@ -828,7 +828,7 @@
         url = getattr(entity, self.name)
         if not url:
             return u''
-        aurl = html_escape(entity.build_url('embed', url=url))
+        aurl = xml_escape(entity.build_url('embed', url=url))
         return u'<a href="%s">%s</a>' % (aurl, url)