backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 02 Jul 2009 10:35:03 +0200
changeset 2234 1fbcf202882d
parent 2209 2b91abd9f5a4 (current diff)
parent 2233 bf3603caaf0d (diff)
child 2235 d5987f75c97c
backport stable
cwvreg.py
devtools/apptest.py
devtools/testlib.py
schema.py
schemas/workflow.py
sobjects/supervising.py
web/controller.py
web/test/unittest_form.py
web/test/unittest_views_baseviews.py
web/uicfg.py
web/views/basecomponents.py
web/views/cwproperties.py
web/views/editforms.py
web/views/editviews.py
web/views/facets.py
web/views/formrenderers.py
web/views/primary.py
web/views/schema.py
web/views/startup.py
web/views/tableview.py
web/views/treeview.py
web/views/xmlrss.py
--- a/common/__init__.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/common/__init__.py	Thu Jul 02 10:35:03 2009 +0200
@@ -18,9 +18,9 @@
     rtype = 'String'
 
     @classmethod
-    def st_description(cls, funcnode):
-        return ', '.join(term.get_description()
-                         for term in iter_funcnode_variables(funcnode))
+    def st_description(cls, funcnode, mainindex, tr):
+        return ', '.join(sorted(term.get_description(mainindex, tr)
+                                for term in iter_funcnode_variables(funcnode)))
 
 register_function(COMMA_JOIN)  # XXX do not expose?
 
@@ -41,8 +41,8 @@
     rtype = 'String'
 
     @classmethod
-    def st_description(cls, funcnode):
-        return funcnode.children[0].get_description()
+    def st_description(cls, funcnode, mainindex, tr):
+        return funcnode.children[0].get_description(mainindex, tr)
 
 register_function(LIMIT_SIZE)
 
--- a/common/uilib.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/common/uilib.py	Thu Jul 02 10:35:03 2009 +0200
@@ -92,7 +92,9 @@
 
 # fallback implementation, nicer one defined below if lxml is available
 def soup2xhtml(data, encoding):
-    return data
+    # normalize line break
+    # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+    return u'\n'.join(data.splitlines())
 
 # fallback implementation, nicer one defined below if lxml> 2.0 is available
 def safe_cut(text, length):
@@ -123,6 +125,10 @@
         Note: the function considers a string with no surrounding tag as valid
               if <div>`data`</div> can be parsed by an XML parser
         """
+        # normalize line break
+        # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+        data = u'\n'.join(data.splitlines())
+        # XXX lxml 1.1 support still needed ?
         xmltree = etree.HTML('<div>%s</div>' % data)
         # NOTE: lxml 1.1 (etch platforms) doesn't recognize
         #       the encoding=unicode parameter (lxml 2.0 does), this is
--- a/cwconfig.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/cwconfig.py	Thu Jul 02 10:35:03 2009 +0200
@@ -17,6 +17,8 @@
 import sys
 import os
 import logging
+from smtplib import SMTP
+from threading import Lock
 from os.path import exists, join, expanduser, abspath, normpath, basename, isdir
 
 from logilab.common.decorators import cached
@@ -29,6 +31,8 @@
 
 CONFIGURATIONS = []
 
+SMTP_LOCK = Lock()
+
 
 class metaconfiguration(type):
     """metaclass to automaticaly register configuration"""
@@ -743,7 +747,7 @@
                     self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
 
     def _load_site_cubicweb(self, sitefile):
-        context = {}
+        context = {'__file__': sitefile}
         execfile(sitefile, context, context)
         self.info('%s loaded', sitefile)
         # cube specific options
@@ -827,6 +831,28 @@
         sourcedirs.append(self.i18n_lib_dir())
         return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs)
 
+    def sendmails(self, msgs):
+        """msgs: list of 2-uple (message object, recipients)"""
+        server, port = self['smtp-host'], self['smtp-port']
+        SMTP_LOCK.acquire()
+        try:
+            try:
+                smtp = SMTP(server, port)
+            except Exception, ex:
+                self.exception("can't connect to smtp server %s:%s (%s)",
+                               server, port, ex)
+                return
+            heloaddr = '%s <%s>' % (self['sender-name'], self['sender-addr'])
+            for msg, recipients in msgs:
+                try:
+                    smtp.sendmail(heloaddr, recipients, msg.as_string())
+                except Exception, ex:
+                    self.exception("error sending mail to %s (%s)",
+                                   recipients, ex)
+            smtp.close()
+        finally:
+            SMTP_LOCK.release()
+
 set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
 
 # alias to get a configuration instance from an application id
--- a/cwvreg.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/cwvreg.py	Thu Jul 02 10:35:03 2009 +0200
@@ -303,9 +303,10 @@
 
     def user_property_keys(self, withsitewide=False):
         if withsitewide:
-            return sorted(self['propertydefs'])
+            return sorted(k for k in self['propertydefs']
+                          if not k.startswith('sources.'))
         return sorted(k for k, kd in self['propertydefs'].iteritems()
-                      if not kd['sitewide'])
+                      if not kd['sitewide'] and not k.startswith('sources.'))
 
     def register_property(self, key, type, help, default=None, vocabulary=None,
                           sitewide=False):
--- a/debian/control	Mon Jun 29 14:12:18 2009 +0200
+++ b/debian/control	Thu Jul 02 10:35:03 2009 +0200
@@ -76,7 +76,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.0)
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.1)
 Recommends: python-simpletal (>= 4.0), python-lxml
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/apptest.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/devtools/apptest.py	Thu Jul 02 10:35:03 2009 +0200
@@ -46,8 +46,8 @@
     def sendmail(self, helo_addr, recipients, msg):
         MAILBOX.append(Email(recipients, msg))
 
-from cubicweb.server import hookhelper
-hookhelper.SMTP = MockSMTP
+from cubicweb import cwconfig
+cwconfig.SMTP = MockSMTP
 
 
 def get_versions(self, checkversions=False):
--- a/devtools/repotest.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/devtools/repotest.py	Thu Jul 02 10:35:03 2009 +0200
@@ -208,6 +208,7 @@
 
 
 class BasePlannerTC(BaseQuerierTC):
+    newsources = 0
     def setup(self):
         clear_cache(self.repo, 'rel_type_sources')
         clear_cache(self.repo, 'rel_type_sources')
@@ -220,7 +221,6 @@
         self.schema = self.o.schema
         self.sources = self.o._repo.sources
         self.system = self.sources[-1]
-        self.newsources = 0
         do_monkey_patch()
 
     def add_source(self, sourcecls, uri):
--- a/devtools/testlib.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/devtools/testlib.py	Thu Jul 02 10:35:03 2009 +0200
@@ -249,6 +249,8 @@
     def iter_automatic_rsets(self, limit=10):
         """generates basic resultsets for each entity type"""
         etypes = self.to_test_etypes()
+        if not etypes:
+            return
         for etype in etypes:
             yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype))
         etype1 = etypes.pop()
--- a/schema.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/schema.py	Thu Jul 02 10:35:03 2009 +0200
@@ -14,6 +14,7 @@
 from warnings import warn
 
 from logilab.common.decorators import cached, clear_cache, monkeypatch
+from logilab.common.deprecation import obsolete
 from logilab.common.compat import any
 
 from yams import BadSchemaDefinition, buildobjs as ybo
@@ -97,6 +98,16 @@
         yams_add_relation(relations, format_attrdef, name+'_format', insertidx)
     yams_add_relation(relations, rdef, name, insertidx)
 
+
+yams_EntityType_add_relation = ybo.EntityType.add_relation
+@monkeypatch(ybo.EntityType)
+def add_relation(self, rdef, name=None):
+    yams_EntityType_add_relation(self, rdef, name)
+    if isinstance(rdef, RichString) and not rdef in self._defined:
+        format_attr_name = (name or rdef.name) + '_format'
+        rdef = self.get_relations(format_attr_name).next()
+        self._ensure_relation_type(rdef)
+
 def display_name(req, key, form=''):
     """return a internationalized string for the key (schema entity or relation
     name) in a given form
@@ -109,7 +120,7 @@
     # ensure unicode
     # added .lower() in case no translation are available
     return unicode(req._(key)).lower()
-__builtins__['display_name'] = display_name
+__builtins__['display_name'] = obsolete('display_name should be imported from cubicweb.schema')(display_name)
 
 def ERSchema_display_name(self, req, form=''):
     """return a internationalized string for the entity/relation type name in
--- a/schemas/workflow.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/schemas/workflow.py	Thu Jul 02 10:35:03 2009 +0200
@@ -60,7 +60,7 @@
     transition_of = SubjectRelation('CWEType', cardinality='+*',
                                     description=_('entity types which may use this transition'),
                                     constraints=[RQLConstraint('O final FALSE')])
-    destination_state = SubjectRelation('State', cardinality='?*',
+    destination_state = SubjectRelation('State', cardinality='1*',
                                         constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
                                         description=_('destination state for this transition'))
 
--- a/server/hookhelper.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/hookhelper.py	Thu Jul 02 10:35:03 2009 +0200
@@ -7,9 +7,6 @@
 """
 __docformat__ = "restructuredtext en"
 
-from smtplib import SMTP
-from threading import Lock
-
 from cubicweb import RepositoryError
 from cubicweb.server.pool import SingleLastOperation
 
@@ -47,8 +44,6 @@
 
 # mail related ################################################################
 
-SMTP_LOCK = Lock()
-
 class SendMailOp(SingleLastOperation):
     def __init__(self, session, msg=None, recipients=None, **kwargs):
         # may not specify msg yet, as
@@ -70,26 +65,7 @@
         self.repo.threaded_task(self.sendmails)
 
     def sendmails(self):
-        server, port = self.config['smtp-host'], self.config['smtp-port']
-        SMTP_LOCK.acquire()
-        try:
-            try:
-                smtp = SMTP(server, port)
-            except Exception, ex:
-                self.exception("can't connect to smtp server %s:%s (%s)",
-                               server, port, ex)
-                return
-            heloaddr = '%s <%s>' % (self.config['sender-name'],
-                                    self.config['sender-addr'])
-            for msg, recipients in self.to_send:
-                try:
-                    smtp.sendmail(heloaddr, recipients, msg.as_string())
-                except Exception, ex:
-                    self.exception("error sending mail to %s (%s)",
-                                   recipients, ex)
-            smtp.close()
-        finally:
-            SMTP_LOCK.release()
+        self.config.sendmails(self.to_send)
 
 
 # state related ###############################################################
--- a/server/hooks.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/hooks.py	Thu Jul 02 10:35:03 2009 +0200
@@ -85,16 +85,16 @@
     """
     ftcontainer = session.repo.schema.rschema(rtype).fulltext_container
     if ftcontainer == 'subject':
-        FTIndexEntityOp(session, entity=session.entity(eidto))
+        FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
     elif ftcontainer == 'object':
-        FTIndexEntityOp(session, entity=session.entity(eidfrom))
+        FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
 def fti_update_after_delete_relation(session, eidfrom, rtype, eidto):
     """sync fulltext index when relevant relation is deleted. Reindexing both
     entities is necessary.
     """
     if session.repo.schema.rschema(rtype).fulltext_container:
-        FTIndexEntityOp(session, entity=session.entity(eidto))
-        FTIndexEntityOp(session, entity=session.entity(eidfrom))
+        FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
+        FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
 
 class SyncOwnersOp(PreCommitOperation):
 
@@ -379,7 +379,7 @@
     etype = session.describe(fromeid)[0]
     if not (session.is_super_session or 'managers' in session.user.groups):
         if not state is None:
-            entity = session.entity(fromeid)
+            entity = session.entity_from_eid(fromeid)
             # we should find at least one transition going to this state
             try:
                 iter(state.transitions(entity, toeid)).next()
--- a/server/repository.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/repository.py	Thu Jul 02 10:35:03 2009 +0200
@@ -343,7 +343,6 @@
                             'connections pools size)')
 
     def _free_pool(self, pool):
-        pool.rollback()
         self._available_pools.put_nowait(pool)
 
     def pinfo(self):
--- a/server/rqlannotation.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/rqlannotation.py	Thu Jul 02 10:35:03 2009 +0200
@@ -22,21 +22,7 @@
     has_text_query = False
     need_distinct = rqlst.distinct
     for rel in rqlst.iget_nodes(Relation):
-        if rel.neged(strict=True):
-            if rel.is_types_restriction():
-                need_distinct = True
-            else:
-                rschema = getrschema(rel.r_type)
-                if not rschema.is_final():
-                    if rschema.inlined:
-                        try:
-                            var = rel.children[1].children[0].variable
-                        except AttributeError:
-                            pass # rewritten variable
-                        else:
-                            if not var.stinfo['constnode']:
-                                need_distinct = True
-        elif getrschema(rel.r_type).symetric:
+        if getrschema(rel.r_type).symetric:
             for vref in rel.iget_nodes(VariableRef):
                 stinfo = vref.variable.stinfo
                 if not stinfo['constnode'] and stinfo['selected']:
--- a/server/schemahooks.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/schemahooks.py	Thu Jul 02 10:35:03 2009 +0200
@@ -38,7 +38,7 @@
 def get_constraints(session, entity):
     constraints = []
     for cstreid in session.transaction_data.get(entity.eid, ()):
-        cstrent = session.entity(cstreid)
+        cstrent = session.entity_from_eid(cstreid)
         cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
         cstr.eid = cstreid
         constraints.append(cstr)
--- a/server/session.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/session.py	Thu Jul 02 10:35:03 2009 +0200
@@ -84,10 +84,6 @@
         """return an entity class for the given entity type"""
         return self.vreg.etype_class(etype)
 
-    def entity(self, eid):
-        """return a result set for the given eid"""
-        return self.eid_rset(eid).get_entity(0, 0)
-
     def system_sql(self, sql, args=None):
         """return a sql cursor on the system database"""
         if not sql.split(None, 1)[0].upper() == 'SELECT':
@@ -270,6 +266,7 @@
             assert not self.pending_operations
             self.transaction_data.clear()
             self._touch()
+            self.debug('commit session %s done (no db activity)', self.id)
             return
         if self.commit_state:
             return
@@ -311,6 +308,7 @@
             assert not self.pending_operations
             self.transaction_data.clear()
             self._touch()
+            self.debug('rollback session %s done (no db activity)', self.id)
             return
         try:
             while self.pending_operations:
@@ -321,6 +319,7 @@
                     self.critical('rollback error', exc_info=sys.exc_info())
                     continue
             self.pool.rollback()
+            self.debug('rollback for session %s done', self.id)
         finally:
             self._touch()
             self.pending_operations[:] = []
@@ -359,16 +358,6 @@
             self._threaddata.transaction_data = {}
             return self._threaddata.transaction_data
 
-    @obsolete('use direct access to session.transaction_data')
-    def query_data(self, key, default=None, setdefault=False, pop=False):
-        if setdefault:
-            assert not pop
-            return self.transaction_data.setdefault(key, default)
-        if pop:
-            return self.transaction_data.pop(key, default)
-        else:
-            return self.transaction_data.get(key, default)
-
     @property
     def pending_operations(self):
         try:
@@ -457,6 +446,21 @@
             description.append(tuple(row_descr))
         return description
 
+    @obsolete('use direct access to session.transaction_data')
+    def query_data(self, key, default=None, setdefault=False, pop=False):
+        if setdefault:
+            assert not pop
+            return self.transaction_data.setdefault(key, default)
+        if pop:
+            return self.transaction_data.pop(key, default)
+        else:
+            return self.transaction_data.get(key, default)
+
+    @obsolete('use entity_from_eid(eid, etype=None)')
+    def entity(self, eid):
+        """return a result set for the given eid"""
+        return self.eid_rset(eid).get_entity(0, 0)
+
 
 class ChildSession(Session):
     """child (or internal) session are used to hijack the security system
--- a/server/sources/rql2sql.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/sources/rql2sql.py	Thu Jul 02 10:35:03 2009 +0200
@@ -490,10 +490,10 @@
                 sql.insert(1, 'FROM (SELECT 1) AS _T')
             sqls.append('\n'.join(sql))
         if select.need_intersect:
-            if distinct or not self.dbms_helper.intersect_all_support:
-                return '\nINTERSECT\n'.join(sqls)
-            else:
-                return '\nINTERSECT ALL\n'.join(sqls)
+            #if distinct or not self.dbms_helper.intersect_all_support:
+            return '\nINTERSECT\n'.join(sqls)
+            #else:
+            #    return '\nINTERSECT ALL\n'.join(sqls)
         elif distinct:
             return '\nUNION\n'.join(sqls)
         else:
@@ -661,13 +661,27 @@
         lhsvar, _, rhsvar, rhsconst = relation_info(relation)
         # we are sure here to have a lhsvar
         assert lhsvar is not None
-        lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
         if isinstance(relation.parent, Not):
             self._state.done.add(relation.parent)
-            sql = "%s IS NULL" % lhssql
             if rhsvar is not None and not rhsvar._q_invariant:
-                sql = '(%s OR %s!=%s)' % (sql, lhssql, rhsvar.accept(self))
+                # if the lhs variable is only linked to this relation, this mean we
+                # only want the relation to NOT exists
+                self._state.push_scope()
+                lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
+                rhssql = rhsvar.accept(self)
+                restrictions, tables = self._state.pop_scope()
+                restrictions.append('%s=%s' % (lhssql, rhssql))
+                if not tables:
+                    sql = 'NOT EXISTS(SELECT 1 WHERE %s)' % (
+                        ' AND '.join(restrictions))
+                else:
+                    sql = 'NOT EXISTS(SELECT 1 FROM %s WHERE %s)' % (
+                        ', '.join(tables), ' AND '.join(restrictions))
+            else:
+                lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
+                sql = '%s IS NULL' % self._inlined_var_sql(lhsvar, relation.r_type)
             return sql
+        lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
         if rhsconst is not None:
             return '%s=%s' % (lhssql, rhsconst.accept(self))
         if isinstance(rhsvar, Variable) and not rhsvar.name in self._varmap:
--- a/server/test/unittest_extlite.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/test/unittest_extlite.py	Thu Jul 02 10:35:03 2009 +0200
@@ -7,37 +7,32 @@
     sqlite_file = '_extlite_test.sqlite'
     def setUp(self):
         cnx1 = get_connection('sqlite', database=self.sqlite_file)
-        print 'SET IP'
         cu = cnx1.cursor()
         cu.execute('CREATE TABLE toto(name integer);')
         cnx1.commit()
         cnx1.close()
-        
+
     def tearDown(self):
         try:
             os.remove(self.sqlite_file)
         except:
             pass
+        
     def test(self):
         lock = threading.Lock()
-        
+
         def run_thread():
-            print 'run_thread'
             cnx2 = get_connection('sqlite', database=self.sqlite_file)
             lock.acquire()
-            print 't2 sel1'
             cu = cnx2.cursor()
             cu.execute('SELECT name FROM toto')
             self.failIf(cu.fetchall())
             cnx2.commit()
-            print 'done'
             lock.release()
             time.sleep(0.1)
             lock.acquire()
-            print 't2 sel2'
             cu.execute('SELECT name FROM toto')
             self.failUnless(cu.fetchall())
-            print 'done'
             lock.release()
 
         cnx1 = get_connection('sqlite', database=self.sqlite_file)
@@ -45,17 +40,13 @@
         thread = threading.Thread(target=run_thread)
         thread.start()
         cu = cnx1.cursor()
-        print 't1 sel'
         cu.execute('SELECT name FROM toto')
-        print 'done'
         lock.release()
         time.sleep(0.1)
         cnx1.commit()
         lock.acquire()
-        print 't1 insert'
         cu.execute("INSERT INTO toto(name) VALUES ('toto')")
         cnx1.commit()
-        print 'done'
         lock.release()
 
 if __name__ == '__main__':
--- a/server/test/unittest_repository.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/test/unittest_repository.py	Thu Jul 02 10:35:03 2009 +0200
@@ -56,13 +56,12 @@
         namecol = SQL_PREFIX + 'name'
         finalcol = SQL_PREFIX + 'final'
         try:
-            sqlcursor = pool['system']
-            sqlcursor.execute('SELECT %s FROM %s WHERE %s is NULL' % (
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
                 namecol, table, finalcol))
-            self.assertEquals(sqlcursor.fetchall(), [])
-            sqlcursor.execute('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+            self.assertEquals(cu.fetchall(), [])
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
                               % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
-            self.assertEquals(sqlcursor.fetchall(), [(u'Boolean',), (u'Bytes',),
+            self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
                                                      (u'Date',), (u'Datetime',),
                                                      (u'Decimal',),(u'Float',),
                                                      (u'Int',),
@@ -358,38 +357,36 @@
         entity.eid = -1
         entity.complete = lambda x: None
         self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system'])
-        cursor = self.session.pool['system']
-        cursor.execute('SELECT * FROM entities WHERE eid = -1')
-        data = cursor.fetchall()
+        cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
+        data = cu.fetchall()
         self.assertIsInstance(data[0][3], datetime)
         data[0] = list(data[0])
         data[0][3] = None
         self.assertEquals(tuplify(data), [(-1, 'Personne', 'system', None, None)])
         self.repo.delete_info(self.session, -1)
         #self.repo.commit()
-        cursor.execute('SELECT * FROM entities WHERE eid = -1')
-        data = cursor.fetchall()
+        cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
+        data = cu.fetchall()
         self.assertEquals(data, [])
 
 
 class FTITC(RepositoryBasedTC):
 
     def test_reindex_and_modified_since(self):
-        cursor = self.session.pool['system']
         eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
         self.commit()
         ts = datetime.now()
         self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
-        cursor.execute('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
-        omtime = cursor.fetchone()[0]
+        cu = self.session.system_sql('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
+        omtime = cu.fetchone()[0]
         # our sqlite datetime adapter is ignore seconds fraction, so we have to
         # ensure update is done the next seconds
         time.sleep(1 - (ts.second - int(ts.second)))
         self.execute('SET X nom "tata" WHERE X eid %(x)s', {'x': eidp}, 'x')
         self.commit()
         self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
-        cursor.execute('SELECT mtime FROM entities WHERE eid = %s' % eidp)
-        mtime = cursor.fetchone()[0]
+        cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp)
+        mtime = cu.fetchone()[0]
         self.failUnless(omtime < mtime)
         self.commit()
         date, modified, deleted = self.repo.entities_modified_since(('Personne',), omtime)
--- a/server/test/unittest_rql2sql.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Thu Jul 02 10:35:03 2009 +0200
@@ -251,9 +251,9 @@
 
     # Any O WHERE NOT S corrected_in O, S eid %(x)s, S concerns P, O version_of P, O in_state ST, NOT ST name "published", O modification_date MTIME ORDERBY MTIME DESC LIMIT 9
     ('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P',
-     '''SELECT DISTINCT O.cw_eid
+     '''SELECT O.cw_eid
 FROM cw_Note AS S, cw_Personne AS O
-WHERE (S.cw_ecrit_par IS NULL OR S.cw_ecrit_par!=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''),
+WHERE NOT EXISTS(SELECT 1 WHERE S.cw_ecrit_par=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''),
 
     ('DISTINCT Any S ORDERBY stockproc(SI) WHERE NOT S ecrit_par O, S para SI',
      '''SELECT T1.C0 FROM (SELECT DISTINCT S.cw_eid AS C0, STOCKPROC(S.cw_para) AS C1
@@ -698,7 +698,6 @@
 FROM cw_Tag AS S, cw_Tag AS T, tags_relation AS rel_tags0
 WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid'''),
 
-
     ('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid 6',
      '''SELECT 5, rel_created_by0.eid_to
 FROM created_by_relation AS rel_created_by0
@@ -736,25 +735,25 @@
 WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS Y WHERE rel_evaluee0.eid_from=Y.cw_eid AND rel_evaluee0.eid_to=X.cw_eid)'''),
 
     ('Any X,T WHERE X title T, NOT X is Bookmark',
-     '''SELECT DISTINCT X.cw_eid, X.cw_title
+     '''SELECT X.cw_eid, X.cw_title
 FROM cw_Card AS X
-UNION
-SELECT DISTINCT X.cw_eid, X.cw_title
+UNION ALL
+SELECT X.cw_eid, X.cw_title
 FROM cw_EmailThread AS X'''),
 
     ('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U',
-     '''SELECT DISTINCT P.cw_pkey, P.cw_value
+     '''SELECT P.cw_pkey, P.cw_value
 FROM cw_CWProperty AS P
 WHERE P.cw_for_user IS NULL'''),
 
     ('Any S WHERE NOT X in_state S, X is IN(Affaire, CWUser)',
-     '''SELECT DISTINCT S.cw_eid
-FROM cw_Affaire AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+     '''SELECT S.cw_eid
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.cw_eid
-FROM cw_CWUser AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
+SELECT S.cw_eid
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)'''),
     ]
 
 OUTER_JOIN = [
@@ -1030,9 +1029,9 @@
 WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
 
     ('Any N WHERE NOT N ecrit_par P, P nom "toto"',
-     '''SELECT DISTINCT N.cw_eid
+     '''SELECT N.cw_eid
 FROM cw_Note AS N, cw_Personne AS P
-WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND P.cw_nom=toto'''),
+WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND P.cw_nom=toto'''),
 
     ('Any P WHERE N ecrit_par P, N eid 0',
     '''SELECT N.cw_ecrit_par
@@ -1045,9 +1044,9 @@
 WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
 
     ('Any P WHERE NOT N ecrit_par P, P is Personne, N eid 512',
-     '''SELECT DISTINCT P.cw_eid
+     '''SELECT P.cw_eid
 FROM cw_Note AS N, cw_Personne AS P
-WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND N.cw_eid=512'''),
+WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND N.cw_eid=512'''),
 
     ('Any S,ES,T WHERE S state_of ET, ET name "CWUser", ES allowed_transition T, T destination_state S',
      '''SELECT T.cw_destination_state, rel_allowed_transition1.eid_from, T.cw_eid
@@ -1070,23 +1069,23 @@
 
 INTERSECT = [
     ('Any SN WHERE NOT X in_state S, S name SN',
-     '''SELECT DISTINCT S.cw_name
-FROM cw_Affaire AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+     '''SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.cw_name
-FROM cw_CWUser AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)
 INTERSECT
-SELECT DISTINCT S.cw_name
-FROM cw_Note AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
+SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Note AS X WHERE X.cw_in_state=S.cw_eid)'''),
 
     ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
      '''SELECT X.cw_nom
 FROM cw_Personne AS X
 WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Division AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)
-INTERSECT ALL
+INTERSECT
 SELECT X.cw_nom
 FROM cw_Personne AS X
 WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Societe AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)'''),
--- a/server/test/unittest_schemaserial.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/server/test/unittest_schemaserial.py	Thu Jul 02 10:35:03 2009 +0200
@@ -94,7 +94,7 @@
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
              {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11', u'??', u'1?'"}),
+             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
             ])
 
 
--- a/sobjects/supervising.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/sobjects/supervising.py	Thu Jul 02 10:35:03 2009 +0200
@@ -48,7 +48,7 @@
     events = ('before_delete_entity',)
 
     def _call(self, eid):
-        entity = self.session.entity(eid)
+        entity = self.session.entity_from_eid(eid)
         try:
             title = entity.dc_title()
         except:
--- a/test/unittest_entity.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/test/unittest_entity.py	Thu Jul 02 10:35:03 2009 +0200
@@ -296,6 +296,8 @@
         self.assertEquals(e.printable_value('content'), e['content'])
         e['content'] = u'été'
         self.assertEquals(e.printable_value('content'), e['content'])
+        e['content'] = u'hop\r\nhop\nhip\rmomo'
+        self.assertEquals(e.printable_value('content'), u'hop\nhop\nhip\nmomo')
 
 
     def test_fulltextindex(self):
--- a/view.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/view.py	Thu Jul 02 10:35:03 2009 +0200
@@ -19,7 +19,7 @@
 from cubicweb.selectors import require_group_compat, accepts_compat
 from cubicweb.appobject import AppRsetObject
 from cubicweb.utils import UStringIO, HTMLStream
-
+from cubicweb.schema import display_name
 
 # robots control
 NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
@@ -369,20 +369,20 @@
 
     category = 'anyrsetview'
 
-    def columns_labels(self, tr=True):
+    def columns_labels(self, mainindex=0, tr=True):
         if tr:
-            translate = display_name
+            translate = lambda val, req=self.req: display_name(req, val)
         else:
-            translate = lambda req, val: val
-        rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
+            translate = lambda val: val
+        # XXX [0] because of missing Union support
+        rqlstdescr = self.rset.syntax_tree().get_description(mainindex,
+                                                             translate)[0]
         labels = []
-        for colindex, attr in enumerate(rqlstdescr):
+        for colindex, label in enumerate(rqlstdescr):
             # compute column header
-            if colindex == 0 or attr == 'Any': # find a better label
-                label = ','.join(translate(self.req, et)
+            if label == 'Any': # find a better label
+                label = ','.join(translate(et)
                                  for et in self.rset.column_types(colindex))
-            else:
-                label = translate(self.req, attr)
             labels.append(label)
         return labels
 
--- a/web/controller.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/controller.py	Thu Jul 02 10:35:03 2009 +0200
@@ -184,6 +184,12 @@
         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'))
+                newparams['__createdpath'] = self._edited_entity.rest_path()
         elif self._after_deletion_path:
             # else it should have been set during form processing
             path, params = self._after_deletion_path
--- a/web/data/cubicweb.css	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/data/cubicweb.css	Thu Jul 02 10:35:03 2009 +0200
@@ -445,16 +445,15 @@
 }
 
 div.sideBoxTitle {
-  padding: 0.2em 0px;
   background: #cfceb7;
   display: block;
   font: bold 100% Georgia;
+  padding : 2px 0;
 }
 
 div.sideBox {
-  padding: 0.2em 0px;
+  padding: 0 0 0.2em;
   margin-bottom: 0.5em;
-  background: #eeedd9;
   min-width: 21em;
   max-width: 50em;
 }
@@ -467,6 +466,7 @@
 
 div.sideBoxBody {
   padding: 0.2em 5px;
+  background: #eeedd9;
 }
 
 div.sideBoxBody a {
--- a/web/data/cubicweb.htmlhelpers.js	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js	Thu Jul 02 10:35:03 2009 +0200
@@ -254,7 +254,7 @@
 
 //============= page loading events ==========================================//
 function roundedCornersOnLoad() {
-    jQuery('div.sideBox').corner('bottom 6px');
+    jQuery('div.sideBoxBody').corner('bottom 6px');
     jQuery('div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month').corner('top 6px');
 }
 
--- a/web/formfields.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/formfields.py	Thu Jul 02 10:35:03 2009 +0200
@@ -164,7 +164,12 @@
         widgets which desire it."""
         if self.choices is not None:
             if callable(self.choices):
-                vocab = self.choices(req=form.req)
+                try:
+                    vocab = self.choices(form=form)
+                except TypeError:
+                    warn('vocabulary method (eg field.choices) should now take '
+                         'the form instance as argument', DeprecationWarning)
+                    vocab = self.choices(req=form.req)
             else:
                 vocab = self.choices
             if vocab and not isinstance(vocab[0], (list, tuple)):
--- a/web/formwidgets.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/formwidgets.py	Thu Jul 02 10:35:03 2009 +0200
@@ -231,6 +231,7 @@
     """
     type = 'radio'
 
+
 # javascript widgets ###########################################################
 
 class DateTimePicker(TextInput):
--- a/web/request.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/request.py	Thu Jul 02 10:35:03 2009 +0200
@@ -150,6 +150,11 @@
                 del self.form[k]
             else:
                 self.form[k] = v
+        # special key for created entity, added in controller's reset method
+        if '__createdpath' in params:
+            self.message += ' (<a href="%s">%s</a>)' % (
+                self.build_url(params.pop('__createdpath')),
+                self._('click here to see created entity'))
 
     def no_script_form_param(self, param, default=None, value=None):
         """ensure there is no script in a user form param
--- a/web/test/unittest_form.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/test/unittest_form.py	Thu Jul 02 10:35:03 2009 +0200
@@ -5,6 +5,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
 
 from xml.etree.ElementTree import fromstring
 
@@ -98,6 +99,14 @@
         inputs = pageinfo.find_tag('input', False)
         self.failIf(any(attrs for t, attrs in inputs if attrs.get('name') == '__linkto'))
 
+    def test_reledit_composite_field(self):
+        rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"')
+        form = self.vreg.select_object('views', 'reledit', self.request(),
+                                       rset=rset, row=0, rtype='content')
+        data = form.render(row=0, rtype='content')
+        self.failUnless('edits-content' in data)
+        self.failUnless('edits-content_format' in data)
+
     # form view tests #########################################################
 
     def test_massmailing_formview(self):
--- a/web/test/unittest_views_baseviews.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/test/unittest_views_baseviews.py	Thu Jul 02 10:35:03 2009 +0200
@@ -103,9 +103,9 @@
 
     def test_sortvalue_with_display_col(self):
         e, rset, view = self._prepare_entity()
-        rqlstdescr = rset.syntax_tree().get_description()[0] # XXX missing Union support
+        labels = rset.column_labels()
         table = TableWidget(view)
-        table.columns = view.get_columns(rqlstdescr, [1, 2], None, None, None, None, 0)
+        table.columns = view.get_columns(labels, [1, 2], None, None, None, None, 0)
         expected = ['loo"ong blabla'[:10], e.creation_date.strftime('%Y-%m-%d %H:%M')]
         got = [loadjson(value) for _, value in table.itercols(0)]
         self.assertListEqual(got, expected)
--- a/web/uicfg.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/uicfg.py	Thu Jul 02 10:35:03 2009 +0200
@@ -129,6 +129,21 @@
         self._counter += 1
         tag.setdefault('order', self._counter)
 
+    def tag_subject_of(self, key, tag):
+        subj, rtype, obj = key
+        if obj != '*':
+            self.warning('using explict target type in display_ctrl.tag_subject_of() '
+                         'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
+                         subj, rtype, subj, rtype, obj)
+        super(DisplayCtrlRelationTags, self).tag_subject_of((subj, rtype, '*'), tag)
+
+    def tag_object_of(self, key, tag):
+        subj, rtype, obj = key
+        if subj != '*':
+            self.warning('using explict subject type in display_ctrl.tag_object_of() '
+                         'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
+                         rtype, obj, subj, rtype, obj)
+        super(DisplayCtrlRelationTags, self).tag_object_of(('*', rtype, obj), tag)
 
 def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
     if role == 'subject':
--- a/web/views/__init__.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/__init__.py	Thu Jul 02 10:35:03 2009 +0200
@@ -53,9 +53,15 @@
             return True
     return False
 
-VID_BY_MIMETYPE = {'text/xml': 'xml',
-                   # XXX rss, owl...
-                  }
+# FIXME: VID_BY_MIMETYPE is unfortunately a bit too naive since
+#        some browsers (e.g. FF2) send a bunch of mimetypes in
+#        the Accept header, for instance:
+#          text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,
+#          text/plain;q=0.8,image/png,*/*;q=0.5
+VID_BY_MIMETYPE = {
+    #'text/xml': 'xml',
+    # XXX rss, owl...
+}
 def vid_from_rset(req, rset, schema):
     """given a result set, return a view id"""
     if rset is None:
@@ -108,4 +114,7 @@
             self._generate(tmpfile)
             self.w(open(tmpfile).read())
         finally:
-            os.unlink(tmpfile)
+            try:
+                os.unlink(tmpfile)
+            except Exception, ex:
+                self.warning('cant delete %s: %s', tmpfile, ex)
--- a/web/views/basecomponents.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/basecomponents.py	Thu Jul 02 10:35:03 2009 +0200
@@ -110,11 +110,6 @@
             self.w(self.req._('anonymous'))
             self.w(u'''&nbsp;[<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
                    % (self.req._('i18n_login_popup')))
-            # FIXME maybe have an other option to explicitely authorise registration
-            #       also provide a working register view
-#             if self.config['anonymous-user']:
-#                 self.w(u'''&nbsp;[<a class="logout" href="?vid=register">%s</a>]'''
-#                        % (self.req._('i18n_register_user')))
         else:
             self.w(self.req._('anonymous'))
             self.w(u'&nbsp;[<a class="logout" href="%s">%s</a>]'
--- a/web/views/cwproperties.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/cwproperties.py	Thu Jul 02 10:35:03 2009 +0200
@@ -299,11 +299,10 @@
         _ = form.req._
         if entity.has_eid():
             return [(_(entity.pkey), entity.pkey)]
-        # key beginning with 'system.' should usually not be edited by hand
         choices = entity.vreg.user_property_keys()
         return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
 
-    
+
 class PropertyValueField(StringField):
     """specific field for CWProperty.value  which will be different according to
     the selected key type and vocabulary information
--- a/web/views/editforms.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/editforms.py	Thu Jul 02 10:35:03 2009 +0200
@@ -102,17 +102,25 @@
         if not default:
             default = self.req._('not specified')
         if rschema.is_final():
-            if getattr(entity, rtype) is None:
-                value = default
-            else:
-                value = entity.printable_value(rtype)
+            value = entity.printable_value(rtype)
+            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)
-        if not entity.has_perm('update'):
+        # 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)
--- a/web/views/facets.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/facets.py	Thu Jul 02 10:35:03 2009 +0200
@@ -38,6 +38,9 @@
     order = 1
     roundcorners = True
 
+    needs_css = 'cubicweb.facets.css'
+    needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
+
     def facetargs(self):
         """this method returns the list of extra arguments that should
         be used by the facet
@@ -56,8 +59,8 @@
 
     def call(self, view=None):
         req = self.req
-        req.add_js( ('cubicweb.ajax.js', 'cubicweb.formfilter.js') )
-        req.add_css('cubicweb.facets.css')
+        req.add_js( self.needs_js )
+        req.add_css( self.needs_css)
         if self.roundcorners:
             req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");')
         rset, vid, divid, paginate = self._get_context(view)
--- a/web/views/formrenderers.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/formrenderers.py	Thu Jul 02 10:35:03 2009 +0200
@@ -215,12 +215,29 @@
 
 
 class BaseFormRenderer(FormRenderer):
-    """use form_renderer_id = 'base' if you want base FormRenderer without
-    adaptation by selection
+    """use form_renderer_id = 'base' if you want base FormRenderer layout even
+    when selected for an entity
     """
     id = 'base'
 
 
+class EntityBaseFormRenderer(BaseFormRenderer):
+    """use form_renderer_id = 'base' if you want base FormRenderer layout even
+    when selected for an entity
+    """
+    __select__ = entity_implements('Any')
+
+    def display_field(self, form, field):
+        if not super(EntityBaseFormRenderer, self).display_field(form, field):
+            if isinstance(field, HiddenInitialValueField):
+                field = field.visible_field
+            ismeta = form.edited_entity.e_schema.is_metadata(field.name)
+            return ismeta is not None and (
+                ismeta[0] in self.display_fields or
+                (ismeta[0], 'subject') in self.display_fields)
+        return True
+
+
 class HTableFormRenderer(FormRenderer):
     """display fields horizontally in a table
 
@@ -310,9 +327,11 @@
         w(u'</tr>')
 
 
-class EntityFormRenderer(FormRenderer):
+class EntityFormRenderer(EntityBaseFormRenderer):
     """specific renderer for entity edition form (edition)"""
-    __select__ = entity_implements('Any') & yes()
+    id = 'default'
+    # needs some additional points in some case (XXX explain cases)
+    __select__ = EntityBaseFormRenderer.__select__ & yes()
 
     _options = FormRenderer._options + ('display_relations_form',)
     display_relations_form = True
--- a/web/views/primary.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/primary.py	Thu Jul 02 10:35:03 2009 +0200
@@ -14,10 +14,10 @@
 
 from cubicweb import Unauthorized
 from cubicweb.view import EntityView
+from cubicweb.schema import display_name
 from cubicweb.web import uicfg
 
 
-
 class PrimaryView(EntityView):
     """the full view of an non final entity"""
     id = 'primary'
@@ -119,7 +119,19 @@
 
     def render_entity_attributes(self, entity, siderelations=None):
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
-            vid =  dispctrl.get('vid', 'reledit')
+            # 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)
             if rschema.is_final() or vid == 'reledit':
                 value = entity.view(vid, rtype=rschema.type, role=role)
             else:
--- a/web/views/schema.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/schema.py	Thu Jul 02 10:35:03 2009 +0200
@@ -49,6 +49,7 @@
                % (entity.dc_type().capitalize(),
                   html_escape(entity.dc_long_title())))
 
+
 # CWEType ######################################################################
 
 class CWETypeOneLineView(baseviews.OneLineView):
@@ -85,11 +86,10 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         self.w(u'<h2>%s</h2>' % _('Attributes'))
-        rset = self.req.execute('Any N,F,D,GROUP_CONCAT(C),I,J,DE,A '
-                                'GROUPBY N,F,D,AA,A,I,J,DE '
+        rset = self.req.execute('Any N,F,D,I,J,DE,A '
                                 'ORDERBY AA WHERE A is CWAttribute, '
                                 'A ordernum AA, A defaultval D, '
-                                'A constrained_by C?, A description DE, '
+                                'A description DE, '
                                 'A fulltextindexed I, A internationalizable J, '
                                 'A relation_type R, R name N, '
                                 'A to_entity O, O name F, '
@@ -97,25 +97,22 @@
                                 {'x': entity.eid})
         self.wview('editable-table', rset, 'null', displayfilter=True)
         self.w(u'<h2>%s</h2>' % _('Relations'))
-        rset = self.req.execute('Any N,C,F,M,K,D,A ORDERBY N '
-                                'WITH N,C,F,M,D,K,A BEING ('
-                                '(Any N,C,F,M,K,D,A '
-                                'ORDERBY N WHERE A is CWRelation, '
-                                'A description D, A composite K?, '
-                                'A relation_type R, R name N, '
-                                'A to_entity O, O name F, '
-                                'A cardinality C, O meta M, '
-                                'A from_entity S, S eid %(x)s)'
-                                ' UNION '
-                                '(Any N,C,F,M,K,D,A '
-                                'ORDERBY N WHERE A is CWRelation, '
-                                'A description D, A composite K?, '
-                                'A relation_type R, R name N, '
-                                'A from_entity S, S name F, '
-                                'A cardinality C, S meta M, '
-                                'A to_entity O, O eid %(x)s))'
-                                ,{'x': entity.eid})
-        self.wview('editable-table', rset, 'null', displayfilter=True)
+        rset = self.req.execute(
+            'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
+            'WHERE A is CWRelation, A description D, A composite K?, '
+            'A relation_type R, R name RN, A to_entity TT, TT name TTN, '
+            'A cardinality C, A from_entity S, S eid %(x)s',
+            {'x': entity.eid})
+        self.wview('editable-table', rset, 'null', displayfilter=True,
+                   displaycols=range(6), mainindex=5)
+        rset = self.req.execute(
+            'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
+            'WHERE A is CWRelation, A description D, A composite K?, '
+            'A relation_type R, R name RN, A from_entity TT, TT name TTN, '
+            'A cardinality C, A to_entity O, O eid %(x)s',
+            {'x': entity.eid})
+        self.wview('editable-table', rset, 'null', displayfilter=True,
+                   displaycols=range(6), mainindex=5)
 
 
 class CWETypeSImageView(EntityView):
--- a/web/views/startup.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/startup.py	Thu Jul 02 10:35:03 2009 +0200
@@ -194,15 +194,9 @@
     id = 'schema-text'
 
     def call(self):
-        self.w(u'<p>%s</p>' % _('This is the list of types defined in the data '
-                                'model ofin this application.'))
-        self.w(u'<p>%s</p>' % _('<em>meta</em> is True for types that are defined by the '
-                                'framework itself (e.g. User and Group). '
-                                '<em>final</em> is True for types that can not be the '
-                                'subject of a relation (e.g. Int and String).'))
-        rset = self.req.execute('Any X,M,F ORDERBY N WHERE X is CWEType, X name N, '
-                                'X meta M, X final F')
-        self.wview('editable-table', rset, displayfilter=True)
+        rset = self.req.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
+                                'X final FALSE')
+        self.wview('table', rset, displayfilter=True)
 
 
 class ManagerSchemaPermissionsView(StartupView, management.SecurityViewMixIn):
--- a/web/views/tableview.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/tableview.py	Thu Jul 02 10:35:03 2009 +0200
@@ -93,7 +93,7 @@
 
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
              displaycols=None, displayactions=None, actions=(), divid=None,
-             cellvids=None, cellattrs=None):
+             cellvids=None, cellattrs=None, mainindex=None):
         """Dumps a table displaying a composite query
 
         :param title: title added before table
@@ -101,19 +101,18 @@
         :param displayfilter: filter that selects rows to display
         :param headers: columns' titles
         """
-        rset = self.rset
         req = self.req
         req.add_js('jquery.tablesorter.js')
         req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
-        rqlst = rset.syntax_tree()
-        # get rql description first since the filter form may remove some
-        # necessary information
-        rqlstdescr = rqlst.get_description()[0] # XXX missing Union support
-        mainindex = self.main_var_index()
+        # compute label first  since the filter form may remove some necessary
+        # information from the rql syntax tree
+        if mainindex is None:
+            mainindex = self.main_var_index()
+        computed_labels = self.columns_labels(mainindex)
         hidden = True
         if not subvid and 'subvid' in req.form:
             subvid = req.form.pop('subvid')
-        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(rset))
+        divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.rset))
         actions = list(actions)
         if mainindex is None:
             displayfilter, displayactions = False, False
@@ -154,8 +153,8 @@
             self.render_actions(divid, actions)
         # render table
         table = TableWidget(self)
-        for column in self.get_columns(rqlstdescr, displaycols, headers, subvid,
-                                       cellvids, cellattrs, mainindex):
+        for column in self.get_columns(computed_labels, displaycols, headers,
+                                       subvid, cellvids, cellattrs, mainindex):
             table.append_column(column)
         table.render(self.w)
         self.w(u'</div>\n')
@@ -188,20 +187,15 @@
         box.render(w=self.w)
         self.w(u'<div class="clear"/>')
 
-    def get_columns(self, rqlstdescr, displaycols, headers, subvid, cellvids,
-                    cellattrs, mainindex):
+    def get_columns(self, computed_labels, displaycols, headers, subvid,
+                    cellvids, cellattrs, mainindex):
         columns = []
-        for colindex, attr in enumerate(rqlstdescr):
+        for colindex, label in enumerate(computed_labels):
             if colindex not in displaycols:
                 continue
             # compute column header
             if headers is not None:
                 label = headers[displaycols.index(colindex)]
-            elif colindex == 0 or attr == 'Any': # find a better label
-                label = ','.join(display_name(self.req, et)
-                                 for et in self.rset.column_types(colindex))
-            else:
-                label = display_name(self.req, attr)
             if colindex == mainindex:
                 label += ' (%s)' % self.rset.rowcount
             column = TableColumn(label, colindex)
@@ -214,7 +208,6 @@
                 column.append_renderer(self.finalview, colindex)
             else:
                 column.append_renderer(subvid or 'incontext', colindex)
-
             if cellattrs and colindex in cellattrs:
                 for name, value in cellattrs[colindex].iteritems():
                     column.add_attr(name, value)
@@ -297,7 +290,7 @@
     title = None
 
     def call(self, title=None, subvid=None, headers=None, divid=None,
-             displaycols=None, displayactions=None):
+             displaycols=None, displayactions=None, mainindex=None):
         """Dumps a table displaying a composite query"""
         actrql = self.req.form['actualrql']
         self.ensure_ro_rql(actrql)
@@ -312,7 +305,8 @@
             title = self.req.form.pop('title')
         if title:
             self.w(u'<h2>%s</h2>\n' % title)
-        mainindex = self.main_var_index()
+        if mainindex is None:
+            mainindex = self.main_var_index()
         if mainindex is not None:
             actions = self.form_filter(divid, displaycols, displayactions, True)
         else:
--- a/web/views/treeview.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/treeview.py	Thu Jul 02 10:35:03 2009 +0200
@@ -106,7 +106,8 @@
         liclasses = []
         is_last = row == len(self.rset) - 1
         is_open = self.open_state(entity.eid, treeid)
-        if not hasattr(entity, 'is_leaf') or entity.is_leaf():
+        is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf()
+        if is_leaf:
             if is_last:
                 liclasses.append('last')
             w(u'<li class="%s">' % u' '.join(liclasses))
@@ -145,7 +146,7 @@
                 w(u'<ul class="placeholder"><li>place holder</li></ul>')
         # the local node info
         self.wview(vid, self.rset, row=row, col=col)
-        if is_open: # => not leaf => rql is defined
+        if is_open and not is_leaf: #  => rql is defined
             self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
         w(u'</li>')
 
--- a/web/views/xmlrss.py	Mon Jun 29 14:12:18 2009 +0200
+++ b/web/views/xmlrss.py	Thu Jul 02 10:35:03 2009 +0200
@@ -77,7 +77,7 @@
         w = self.w
         rset, descr = self.rset, self.rset.description
         eschema = self.schema.eschema
-        labels = self.columns_labels(False)
+        labels = self.columns_labels(tr=False)
         w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
         w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
         for rowindex, row in enumerate(self.rset):