merge stable
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Mon, 06 Jul 2009 14:14:55 +0200
branchstable
changeset 2281 3b2c1d55090a
parent 2274 885873dc4361 (diff)
parent 2280 31269a9b9ec4 (current diff)
child 2282 8fcf86950155
merge
--- a/common/__init__.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/common/__init__.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/common/uilib.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/cwconfig.py	Mon Jul 06 14:14:55 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"""
@@ -190,6 +194,12 @@
           'help': 'web server root url',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('allow-email-login',
+         {'type' : 'yn',
+          'default': False,
+          'help': 'allow users to login with their primary email if set',
+          'group': 'main', 'inputlevel': 2,
+          }),
         ('use-request-subdomain',
          {'type' : 'yn',
           'default': None,
@@ -743,7 +753,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 +837,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 Jul 06 14:13:49 2009 +0200
+++ b/cwvreg.py	Mon Jul 06 14:14:55 2009 +0200
@@ -76,6 +76,11 @@
         clear_cache(self, 'rqlhelper')
         # now we can load application's web objects
         self.register_objects(self.config.vregistry_path())
+        # map lowered entity type names to their actual name
+        self.case_insensitive_etypes = {}
+        for etype in self.schema.entities():
+            etype = str(etype)
+            self.case_insensitive_etypes[etype.lower()] = etype
 
     def update_schema(self, schema):
         """update .schema attribute on registered objects, necessary for some
@@ -294,9 +299,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/dbapi.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/dbapi.py	Mon Jul 06 14:14:55 2009 +0200
@@ -63,19 +63,19 @@
                                   'application' % nsid)
         return core.getProxyForURI(uri)
 
-def repo_connect(repo, user, password, cnxprops=None):
+def repo_connect(repo, login, password, cnxprops=None):
     """Constructor to create a new connection to the CubicWeb repository.
 
     Returns a Connection instance.
     """
     cnxprops = cnxprops or ConnectionProperties('inmemory')
-    cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops)
+    cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops)
     cnx = Connection(repo, cnxid, cnxprops)
     if cnxprops.cnxtype == 'inmemory':
         cnx.vreg = repo.vreg
     return cnx
 
-def connect(database=None, user=None, password=None, host=None,
+def connect(database=None, login=None, password=None, host=None,
             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
             initlog=True):
     """Constructor for creating a connection to the CubicWeb repository.
@@ -111,11 +111,11 @@
         vreg.set_schema(schema)
     else:
         vreg = None
-    cnx = repo_connect(repo, user, password, cnxprops)
+    cnx = repo_connect(repo, login, password, cnxprops)
     cnx.vreg = vreg
     return cnx
 
-def in_memory_cnx(config, user, password):
+def in_memory_cnx(config, login, password):
     """usefull method for testing and scripting to get a dbapi.Connection
     object connected to an in-memory repository instance
     """
@@ -128,7 +128,7 @@
     repo = get_repository('inmemory', config=config, vreg=vreg)
     # connection to the CubicWeb repository
     cnxprops = ConnectionProperties('inmemory')
-    cnx = repo_connect(repo, user, password, cnxprops=cnxprops)
+    cnx = repo_connect(repo, login, password, cnxprops=cnxprops)
     return repo, cnx
 
 
@@ -245,7 +245,7 @@
     @property
     def user(self):
         if self._user is None and self.cnx:
-            self.set_user(self.cnx.user(self))
+            self.set_user(self.cnx.user(self, {'lang': self.lang}))
         return self._user
 
     def set_user(self, user):
@@ -367,6 +367,10 @@
         """raise `BadSessionId` if the connection is no more valid"""
         self._repo.check_session(self.sessionid)
 
+    def set_session_props(self, **props):
+        """raise `BadSessionId` if the connection is no more valid"""
+        self._repo.set_session_props(self.sessionid, props)
+
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
@@ -434,7 +438,8 @@
     def user(self, req=None, props=None):
         """return the User object associated to this connection"""
         # cnx validity is checked by the call to .user_info
-        eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
+        eid, login, groups, properties = self._repo.user_info(self.sessionid,
+                                                              props)
         if req is None:
             req = self.request()
         rset = req.eid_rset(eid, 'CWUser')
--- a/debian/control	Mon Jul 06 14:13:49 2009 +0200
+++ b/debian/control	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/devtools/apptest.py	Mon Jul 06 14:14:55 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):
@@ -462,14 +462,13 @@
         self.__close = repo.close
         self.cnxid = self.cnx.sessionid
         self.session = repo._sessions[self.cnxid]
-        # XXX copy schema since hooks may alter it and it may be not fully
-        #     cleaned (missing some schema synchronization support)
-        try:
-            origschema = repo.__schema
-        except AttributeError:
-            origschema = repo.schema
-            repo.__schema = origschema
         if self.copy_schema:
+            # XXX copy schema since hooks may alter it and it may be not fully
+            #     cleaned (missing some schema synchronization support)
+            try:
+                origschema = repo.__schema
+            except AttributeError:
+                repo.__schema = origschema = repo.schema
             repo.schema = deepcopy(origschema)
             repo.set_schema(repo.schema) # reset hooks
             repo.vreg.update_schema(repo.schema)
--- a/devtools/repotest.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/devtools/repotest.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/devtools/testlib.py	Mon Jul 06 14:14:55 2009 +0200
@@ -245,6 +245,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/doc/book/en/admin/setup.rst	Mon Jul 06 14:13:49 2009 +0200
+++ b/doc/book/en/admin/setup.rst	Mon Jul 06 14:14:55 2009 +0200
@@ -46,6 +46,13 @@
 
 There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball.
 
+The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update":
+1. become root using sudo 
+2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget
+3. run "apt-key add logilab-dists-key.asc"
+4. re-run apt-get update (manually or through the package manager, whichever you prefer)
+
+.. `Logilab's gnupg key` _http://ftp.logilab.org/dists/logilab-dists-key.asc
 
 Install from source
 ```````````````````
--- a/i18n/fr.po	Mon Jul 06 14:13:49 2009 +0200
+++ b/i18n/fr.po	Mon Jul 06 14:14:55 2009 +0200
@@ -964,7 +964,7 @@
 #, python-format
 msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
 msgstr ""
-"L'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
+"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
 "autre via la relation %(rtype)s"
 
 msgid "attribute"
--- a/schema.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/schema.py	Mon Jul 06 14:14:55 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
@@ -108,6 +109,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
@@ -120,7 +131,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
@@ -345,6 +356,7 @@
         """rql expression factory"""
         return ERQLExpression(expression, mainvars, eid)
 
+
 class CubicWebRelationSchema(RelationSchema):
     RelationSchema._RPROPERTIES['eid'] = None
     _perms_checked = False
--- a/schemas/workflow.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/schemas/workflow.py	Mon Jul 06 14:14:55 2009 +0200
@@ -51,7 +51,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/selectors.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/selectors.py	Mon Jul 06 14:14:55 2009 +0200
@@ -482,8 +482,7 @@
     def __call__(self, cls, req, *args, **kwargs):
         score = 0
         for param in self.expected:
-            val = req.form.get(param)
-            if not val:
+            if not param in req.form:
                 return 0
             score += 1
         return len(self.expected)
@@ -623,6 +622,14 @@
                 etype = kwargs['etype']
             except KeyError:
                 return 0
+        else:
+            # only check this is a known type if etype comes from req.form,
+            # else we want the error to propagate
+            try:
+                etype = cls.vreg.case_insensitive_etypes[etype.lower()]
+                req.form['etype'] = etype
+            except KeyError:
+                return 0
         return self.score_class(cls.vreg.etype_class(etype), req)
 
 
--- a/server/checkintegrity.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/checkintegrity.py	Mon Jul 06 14:14:55 2009 +0200
@@ -71,6 +71,14 @@
                                        uniquecstrcheck_before_modification)
     from cubicweb.server.repository import FTIndexEntityOp
     repo = session.repo
+    cursor = session.pool['system']
+    if not repo.system_source.indexer.has_fti_table(cursor):
+        from indexer import get_indexer
+        print 'no text index table'
+        indexer = get_indexer(repo.system_source.dbdriver)
+        # XXX indexer.init_fti(cursor) once index 0.7 is out
+        indexer.init_extensions(cursor)
+        cursor.execute(indexer.sql_init_fti())
     repo.hm.unregister_hook(setmtime_before_update_entity,
                             'before_update_entity', '')
     repo.hm.unregister_hook(uniquecstrcheck_before_modification,
--- a/server/hookhelper.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/hookhelper.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/server/hooks.py	Mon Jul 06 14:14:55 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):
 
@@ -222,9 +222,10 @@
             return
         if self.session.unsafe_execute(*self._rql()).rowcount < 1:
             etype = self.session.describe(self.eid)[0]
-            msg = self.session._('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
-            raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype,
-                                                                'etype': etype,
+            _ = self.session._
+            msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
+            raise ValidationError(self.eid, {self.rtype: msg % {'rtype': _(self.rtype),
+                                                                'etype': _(etype),
                                                                 'eid': self.eid}})
 
     def commit_event(self):
@@ -379,7 +380,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 Jul 06 14:13:49 2009 +0200
+++ b/server/repository.py	Mon Jul 06 14:14:55 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):
@@ -393,10 +392,23 @@
         except ZeroDivisionError:
             pass
 
+    def _login_from_email(self, login):
+        session = self.internal_session()
+        try:
+            rset = session.execute('Any L WHERE U login L, U primary_email M, '
+                                   'M address %(login)s', {'login': login})
+            if rset.rowcount == 1:
+                login = rset[0][0]
+        finally:
+            session.close()
+        return login
+
     def authenticate_user(self, session, login, password):
         """validate login / password, raise AuthenticationError on failure
         return associated CWUser instance on success
         """
+        if self.vreg.config['allow-email-login'] and '@' in login:
+            login = self._login_from_email(login)
         for source in self.sources:
             if source.support_entity('CWUser'):
                 try:
@@ -406,11 +418,11 @@
                     continue
         else:
             raise AuthenticationError('authentication failed with all sources')
-        euser = self._build_user(session, eid)
+        cwuser = self._build_user(session, eid)
         if self.config.consider_user_state and \
-               not euser.state in euser.AUTHENTICABLE_STATES:
+               not cwuser.state in cwuser.AUTHENTICABLE_STATES:
             raise AuthenticationError('user is not in authenticable state')
-        return euser
+        return cwuser
 
     def _build_user(self, session, eid):
         """return a CWUser entity for user with the given eid"""
@@ -418,13 +430,13 @@
         rql = cls.fetch_rql(session.user, ['X eid %(x)s'])
         rset = session.execute(rql, {'x': eid}, 'x')
         assert len(rset) == 1, rset
-        euser = rset.get_entity(0, 0)
+        cwuser = rset.get_entity(0, 0)
         # pylint: disable-msg=W0104
-        # prefetch / cache euser's groups and properties. This is especially
+        # prefetch / cache cwuser's groups and properties. This is especially
         # useful for internal sessions to avoid security insertions
-        euser.groups
-        euser.properties
-        return euser
+        cwuser.groups
+        cwuser.properties
+        return cwuser
 
     # public (dbapi) interface ################################################
 
@@ -665,13 +677,22 @@
           custom properties)
         """
         session = self._get_session(sessionid, setpool=False)
-        if props:
-            # update session properties
-            for prop, value in props.items():
-                session.change_property(prop, value)
+        if props is not None:
+            self.set_session_props(sessionid, props)
         user = session.user
         return user.eid, user.login, user.groups, user.properties
 
+    def set_session_props(self, sessionid, props):
+        """this method should be used by client to:
+        * check session id validity
+        * update user information on each user's request (i.e. groups and
+          custom properties)
+        """
+        session = self._get_session(sessionid, setpool=False)
+        # update session properties
+        for prop, value in props.items():
+            session.change_property(prop, value)
+
     # public (inter-repository) interface #####################################
 
     def entities_modified_since(self, etypes, mtime):
--- a/server/rqlannotation.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/rqlannotation.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/server/schemahooks.py	Mon Jul 06 14:14:55 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)
@@ -247,8 +247,7 @@
     """actually add the entity type to the application's schema"""
     eid = None # make pylint happy
     def commit_event(self):
-        eschema = self.schema.add_entity_type(self.kobj)
-        eschema.eid = self.eid
+        self.schema.add_entity_type(self.kobj)
 
 def before_add_eetype(session, entity):
     """before adding a CWEType entity:
@@ -299,7 +298,8 @@
     # register operation to modify the schema on commit
     # this have to be done before adding other relations definitions
     # or permission settings
-    AddCWETypeOp(session, etype, eid=entity.eid)
+    etype.eid = entity.eid
+    AddCWETypeOp(session, etype)
     # add meta creation_date, modification_date and owned_by relations
     for rql, kwargs in relrqls:
         session.execute(rql, kwargs)
@@ -311,7 +311,6 @@
     def commit_event(self):
         rschema = self.schema.add_relation_type(self.kobj)
         rschema.set_default_groups()
-        rschema.eid = self.eid
 
 def before_add_ertype(session, entity):
     """before adding a CWRType entity:
@@ -331,12 +330,13 @@
       schema on commit
     We don't know yeat this point if a table is necessary
     """
-    AddCWRTypeOp(session, RelationType(name=entity['name'],
-                                      description=entity.get('description'),
-                                      meta=entity.get('meta', False),
-                                      inlined=entity.get('inlined', False),
-                                      symetric=entity.get('symetric', False)),
-                eid=entity.eid)
+    rtype = RelationType(name=entity['name'],
+                         description=entity.get('description'),
+                         meta=entity.get('meta', False),
+                         inlined=entity.get('inlined', False),
+                         symetric=entity.get('symetric', False))
+    rtype.eid = entity.eid
+    AddCWRTypeOp(session, rtype)
 
 
 class AddErdefOp(EarlySchemaOperation):
--- a/server/session.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/session.py	Mon Jul 06 14:14:55 2009 +0200
@@ -266,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
@@ -307,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:
@@ -317,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[:] = []
--- a/server/sources/pyrorql.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/sources/pyrorql.py	Mon Jul 06 14:14:55 2009 +0200
@@ -164,7 +164,12 @@
         """
         self.info('synchronizing pyro source %s', self.uri)
         cnx = self.get_connection()
-        extrepo = cnx._repo
+        try:
+            extrepo = cnx._repo
+        except AttributeError:
+            # fake connection wrapper returned when we can't connect to the
+            # external source (hence we've no chance to synchronize...)
+            return
         etypes = self.support_entities.keys()
         if mtime is None:
             mtime = self.last_update_time()
@@ -212,7 +217,7 @@
         nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group']
         #cnxprops = ConnectionProperties(cnxtype=self.config['cnx-type'])
         return dbapi.connect(database=self.config['pyro-ns-id'],
-                             user=self.config['cubicweb-user'],
+                             login=self.config['cubicweb-user'],
                              password=self.config['cubicweb-password'],
                              host=nshost, port=nsport, group=nsgroup,
                              setvreg=False) #cnxprops=cnxprops)
--- a/server/sources/rql2sql.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/sources/rql2sql.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_extlite.py	Mon Jul 06 14:14:55 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_hooks.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_hooks.py	Mon Jul 06 14:14:55 2009 +0200
@@ -242,7 +242,7 @@
 
 
 class SchemaModificationHooksTC(RepositoryBasedTC):
-    copy_schema = True
+    #copy_schema = True
 
     def setUp(self):
         if not hasattr(self, '_repo'):
@@ -471,6 +471,10 @@
         self.commit()
         # should not be able anymore to add personne without prenom
         self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"')
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "prenom", E name "Personne"')
+        self.commit()
 
 
 class WorkflowHooksTC(RepositoryBasedTC):
--- a/server/test/unittest_migractions.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_migractions.py	Mon Jul 06 14:14:55 2009 +0200
@@ -23,7 +23,7 @@
 
 
 class MigrationCommandsTC(RepositoryBasedTC):
-    copy_schema = True
+    copy_schema = False
 
     def setUp(self):
         if not hasattr(self, '_repo'):
@@ -146,7 +146,7 @@
         for cstr in eschema.constraints('name'):
             self.failUnless(hasattr(cstr, 'eid'))
 
-    def test_drop_entity_type(self):
+    def test_add_drop_entity_type(self):
         self.mh.cmd_add_entity_type('Folder2')
         todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True)
         doneeid = self.mh.cmd_add_state(u'done', 'Folder2')
@@ -161,7 +161,7 @@
         self.failIf(self.execute('State X WHERE NOT X state_of ET'))
         self.failIf(self.execute('Transition X WHERE NOT X transition_of ET'))
 
-    def test_add_relation_type(self):
+    def test_add_drop_relation_type(self):
         self.mh.cmd_add_entity_type('Folder2', auto=False)
         self.mh.cmd_add_relation_type('filed_under2')
         self.failUnless('filed_under2' in self.schema)
@@ -169,52 +169,40 @@
                           ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
                            'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
         self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
-
-
-    def test_drop_relation_type(self):
-        self.mh.cmd_add_entity_type('Folder2', auto=False)
-        self.mh.cmd_add_relation_type('filed_under2')
-        self.failUnless('filed_under2' in self.schema)
         self.mh.cmd_drop_relation_type('filed_under2')
         self.failIf('filed_under2' in self.schema)
 
-    def test_add_relation_definition(self):
-        self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State')
-        self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()),
-                          ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision'])
-        self.assertEquals(self.schema['in_state'].objects(), ('State',))
-
     def test_add_relation_definition_nortype(self):
         self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire')
         self.assertEquals(self.schema['concerne2'].subjects(),
                           ('Personne',))
         self.assertEquals(self.schema['concerne2'].objects(), ('Affaire',))
+        self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire')
+        self.failIf('concerne2' in self.schema)
 
-    def test_drop_relation_definition1(self):
-        self.failUnless('concerne' in self.schema)
+    def test_drop_relation_definition_existant_rtype(self):
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
         self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision'])
+        self.mh.cmd_add_relation_definition('Personne', 'concerne', 'Affaire')
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+        # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+        self.maxeid = self.execute('Any MAX(X)')[0][0]
 
     def test_drop_relation_definition_with_specialization(self):
-        self.failUnless('concerne' in self.schema)
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
         self.mh.cmd_drop_relation_definition('Affaire', 'concerne', 'Societe')
-        self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note'])
-
-    def test_drop_relation_definition2(self):
-        self.failUnless('evaluee' in self.schema)
-        self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note')
-        self.failUnless('evaluee' in self.schema)
-        self.assertEquals(sorted(self.schema['evaluee'].subjects()),
-                          ['CWUser', 'Division', 'Societe', 'SubDivision'])
-        self.assertEquals(sorted(self.schema['evaluee'].objects()),
-                          ['Note'])
+        self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe')
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+        # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+        self.maxeid = self.execute('Any MAX(X)')[0][0]
 
     def test_rename_relation(self):
         self.skip('implement me')
@@ -455,7 +443,6 @@
         ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file')
         self.assertEquals(str(ex), "can't remove cube file, used as a dependency")
 
-
     def test_set_state(self):
         user = self.session.user
         self.mh.set_state(user.eid, 'deactivated')
--- a/server/test/unittest_repository.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_repository.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/server/test/unittest_schemaserial.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/sobjects/supervising.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/test/unittest_entity.py	Mon Jul 06 14:14:55 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/utils.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/utils.py	Mon Jul 06 14:14:55 2009 +0200
@@ -206,8 +206,13 @@
     def add_post_inline_script(self, content):
         self.post_inlined_scripts.append(content)
 
-    def add_onload(self, jscode):
-        self.add_post_inline_script(u"""jQuery(document).ready(function () {
+    def add_onload(self, jscode, jsoncall=False):
+        if jsoncall:
+            self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) {
+%s
+});""" % jscode)
+        else:
+            self.add_post_inline_script(u"""jQuery(document).ready(function () {
  %s
  });""" % jscode)
 
--- a/view.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/view.py	Mon Jul 06 14:14:55 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/application.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/application.py	Mon Jul 06 14:14:55 2009 +0200
@@ -14,10 +14,11 @@
 
 from cubicweb import set_log_methods
 from cubicweb import (ValidationError, Unauthorized, AuthenticationError,
-                   NoSelectableObject, RepositoryError)
+                      NoSelectableObject, RepositoryError)
 from cubicweb.cwvreg import CubicWebRegistry
-from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, NotFound,
-                       RemoteCallFailed, ExplicitLogin, InvalidSession)
+from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect,
+                          NotFound, RemoteCallFailed, ExplicitLogin,
+                          InvalidSession, RequestError)
 from cubicweb.web.component import Component
 
 # make session manager available through a global variable so the debug view can
@@ -348,7 +349,7 @@
                 raise
             except ValidationError, ex:
                 self.validation_error_handler(req, ex)
-            except (Unauthorized, BadRQLQuery), ex:
+            except (Unauthorized, BadRQLQuery, RequestError), ex:
                 self.error_handler(req, ex, tb=False)
             except Exception, ex:
                 self.error_handler(req, ex, tb=True)
@@ -404,8 +405,8 @@
         return self.vreg.main_template(req, template, view=view)
 
     def main_template_id(self, req):
-        template = req.property_value('ui.main-template')
-        if template not in self.vreg.registry('views') :
+        template = req.form.get('__template', req.property_value('ui.main-template'))
+        if template not in self.vreg.registry('views'):
             template = 'main-template'
         return template
 
--- a/web/component.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/component.py	Mon Jul 06 14:14:55 2009 +0200
@@ -6,6 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from logilab.common.deprecation import class_renamed
 from logilab.mtconverter import html_escape
@@ -18,7 +19,6 @@
     partial_has_related_entities, condition_compat, accepts_compat,
     has_relation_compat)
 
-_ = unicode
 
 class EntityVComponent(Component):
     """abstract base class for additinal components displayed in content
@@ -37,7 +37,7 @@
 
     property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
-                            help=_('display the box or not')),
+                            help=_('display the component or not')),
         _('order'):    dict(type='Int', default=99,
                             help=_('display order of the component')),
         _('context'):  dict(type='String', default='header',
--- a/web/controller.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/controller.py	Mon Jul 06 14:14:55 2009 +0200
@@ -182,6 +182,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.ajax.js	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Mon Jul 06 14:14:55 2009 +0200
@@ -11,15 +11,20 @@
 
 function _loadAjaxHtmlHead(node, head, tag, srcattr) {
     var loaded = [];
-    jQuery('head ' + tag).each(function(i) {
+    var jqtagfilter = tag + '[' + srcattr + ']';
+    jQuery('head ' + jqtagfilter).each(function(i) {
 	loaded.push(this.getAttribute(srcattr));
     });
     node.find(tag).each(function(i) {
-	if (!loaded.contains(this.getAttribute(srcattr))) {
+	if (this.getAttribute(srcattr)) {
+	    if (!loaded.contains(this.getAttribute(srcattr))) {
+		jQuery(this).appendTo(head);
+	    }
+	} else {
 	    jQuery(this).appendTo(head);
 	}
     });
-    node.find(tag).remove();
+    node.find(jqtagfilter).remove();
 }
 
 /*
--- a/web/data/cubicweb.css	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/data/cubicweb.css	Mon Jul 06 14:14:55 2009 +0200
@@ -445,18 +445,14 @@
 }
 
 div.sideBoxTitle {
-  padding: 0.2em 0px;
   background: #cfceb7;
   display: block;
   font: bold 100% Georgia;
 }
 
 div.sideBox {
-  padding: 0.2em 0px;
+  padding: 0 0 0.2em;
   margin-bottom: 0.5em;
-  background: #eeedd9;
-  min-width: 21em;
-  max-width: 50em;
 }
 
 ul.sideBox li{
@@ -467,6 +463,7 @@
 
 div.sideBoxBody {
   padding: 0.2em 5px;
+  background: #eeedd9;
 }
 
 div.sideBoxBody a {
@@ -575,8 +572,6 @@
 }
 
 div.primaryRight{
-  float:right;
-
  }
 
 div.metadata {
@@ -687,7 +682,7 @@
 /***************************************/
 
 table.listing {
- margin: 10px 0em;
+ padding: 10px 0em;
  color: #000;
  width: 100%;
  border-right: 1px solid #dfdfdf;
--- a/web/data/cubicweb.edition.js	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/data/cubicweb.edition.js	Mon Jul 06 14:14:55 2009 +0200
@@ -305,7 +305,9 @@
 }
 
 function _clearPreviousErrors(formid) {
+    jQuery('#' + formid + 'ErrorMessage').remove();
     jQuery('#' + formid + ' span.error').remove();
+    jQuery('#' + formid + ' .error').removeClass('error');
 }
 
 function _displayValidationerrors(formid, eid, errors) {
@@ -324,7 +326,7 @@
 	    field.before(span);
 	} else {
 	    firsterrfield = formid;
-	    globalerrors.push(fieldname + ': ' + errmsg);
+	    globalerrors.push(_(fieldname) + ' : ' + errmsg);
 	}
     }
     if (globalerrors.length) {
@@ -334,7 +336,7 @@
 	    var innernode = UL(null, map(LI, globalerrors));
 	}
 	// insert DIV and innernode before the form
-	var div = DIV({'class' : "errorMessage"});
+	var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'});
 	div.appendChild(innernode);
 	jQuery('#' + formid).before(div);
     }
--- a/web/data/cubicweb.htmlhelpers.js	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/web/formfields.py	Mon Jul 06 14:14:55 2009 +0200
@@ -7,6 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
 from datetime import datetime
 
 from logilab.mtconverter import html_escape
@@ -164,7 +165,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 Jul 06 14:13:49 2009 +0200
+++ b/web/formwidgets.py	Mon Jul 06 14:14:55 2009 +0200
@@ -231,6 +231,7 @@
     """
     type = 'radio'
 
+
 # javascript widgets ###########################################################
 
 class DateTimePicker(TextInput):
--- a/web/request.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/request.py	Mon Jul 06 14:14:55 2009 +0200
@@ -61,6 +61,7 @@
 
 class CubicWebRequestBase(DBAPIRequest):
     """abstract HTTP request, should be extended according to the HTTP backend"""
+    json_request = False # to be set to True by json controllers
 
     def __init__(self, vreg, https, form=None):
         super(CubicWebRequestBase, self).__init__(vreg)
@@ -91,7 +92,7 @@
         or an anonymous connection is open
         """
         super(CubicWebRequestBase, self).set_connection(cnx, user)
-        # get request language:
+        # set request language
         vreg = self.vreg
         if self.user:
             try:
@@ -114,6 +115,7 @@
     def set_language(self, lang):
         self._ = self.__ = self.translations[lang]
         self.lang = lang
+        self.cnx.set_session_props(lang=lang)
         self.debug('request language: %s', lang)
 
     # input form parameters management ########################################
@@ -150,6 +152,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
@@ -331,7 +338,6 @@
                 params[name] = value
         params['eid'] = eid
         if len(params) < minparams:
-            print eid, params
             raise RequestError(self._('missing parameters for entity %s') % eid)
         return params
 
@@ -447,6 +453,9 @@
 
     # high level methods for HTML headers management ##########################
 
+    def add_onload(self, jscode):
+        self.html_headers.add_onload(jscode, self.json_request)
+
     def add_js(self, jsfiles, localfile=True):
         """specify a list of JS files to include in the HTML headers
         :param jsfiles: a JS filename or a list of JS filenames
--- a/web/test/unittest_application.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/test/unittest_application.py	Mon Jul 06 14:14:55 2009 +0200
@@ -307,7 +307,7 @@
         self.assertEquals(cnx.password, origcnx.password)
         self.assertEquals(cnx.anonymous_connection, False)
         self.assertEquals(path, 'view')
-        self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login})
+        self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login})
 
     def _test_auth_fail(self, req):
         self.assertRaises(AuthenticationError, self.app.connect, req)
@@ -351,8 +351,8 @@
         req.form['__password'] = origcnx.password
         self._test_auth_fail(req)
         # option allow-email-login set
+        origcnx.login = address
         self.set_option('allow-email-login', True)
-        req, origcnx = self._init_auth('cookie')
         req.form['__login'] = address
         req.form['__password'] = origcnx.password
         self._test_auth_succeed(req, origcnx)
--- a/web/test/unittest_form.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/test/unittest_form.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/web/test/unittest_views_baseviews.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/web/uicfg.py	Mon Jul 06 14:14:55 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 Jul 06 14:13:49 2009 +0200
+++ b/web/views/__init__.py	Mon Jul 06 14:14:55 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/authentication.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/authentication.py	Mon Jul 06 14:14:55 2009 +0200
@@ -36,7 +36,10 @@
             # calling cnx.user() check connection validity, raise
             # BadConnectionId on failure
             user = cnx.user(req)
-            if login and user.login != login:
+            # check cnx.login and not user.login, since in case of login by
+            # email, login and cnx.login are the email while user.login is the
+            # actual user login
+            if login and cnx.login != login:
                 cnx.close()
                 raise InvalidSession('login mismatch')
         except BadConnectionId:
@@ -53,18 +56,6 @@
         req.set_connection(cnx, user)
         return cnx
 
-    def login_from_email(self, login):
-        # XXX should not be called from web interface
-        session = self.repo.internal_session()
-        try:
-            rset = session.execute('Any L WHERE U login L, U primary_email M, '
-                                   'M address %(login)s', {'login': login})
-            if rset.rowcount == 1:
-                login = rset[0][0]
-        finally:
-            session.close()
-        return login
-
     def authenticate(self, req, _login=None, _password=None):
         """authenticate user and return corresponding user object
 
@@ -79,8 +70,6 @@
             login, password = _login, _password
         else:
             login, password = req.get_authorization()
-        if self.vreg.config['allow-email-login'] and '@' in (login or u''):
-            login = self.login_from_email(login)
         if not login:
             # No session and no login -> try anonymous
             login, password = self.vreg.config.anonymous_user()
--- a/web/views/basecomponents.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/basecomponents.py	Mon Jul 06 14:14:55 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/basecontrollers.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/basecontrollers.py	Mon Jul 06 14:14:55 2009 +0200
@@ -178,37 +178,51 @@
                 req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y'))
 
 
+def _validation_error(req, ex):
+    forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True)
+    foreid = ex.entity
+    eidmap = req.data.get('eidmap', {})
+    for var, eid in eidmap.items():
+        if foreid == eid:
+            foreid = var
+            break
+    return (foreid, ex.errors)
+
+def _validate_form(req, vreg):
+    # XXX should use the `RemoteCallFailed` mechanism
+    try:
+        ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'),
+                           req=req)
+    except NoSelectableObject:
+        return (False, {None: req._('not authorized')})
+    try:
+        ctrl.publish(None)
+    except ValidationError, ex:
+        req.cnx.rollback()
+        return (False, _validation_error(req, ex))
+    except Redirect, ex:
+        try:
+            req.cnx.commit() # ValidationError may be raise on commit
+        except ValidationError, ex:
+            req.cnx.rollback()
+            return (False, _validation_error(req, ex))
+        else:
+            return (True, ex.location)
+    except Exception, ex:
+        req.cnx.rollback()
+        req.exception('unexpected error while validating form')
+        return (False, req._(str(ex).decode('utf-8')))
+    return (False, '???')
+
+
 class FormValidatorController(Controller):
     id = 'validateform'
 
     def publish(self, rset=None):
-        vreg = self.vreg
-        try:
-            ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'),
-                               req=self.req, appli=self.appli)
-        except NoSelectableObject:
-            status, args = (False, {None: self.req._('not authorized')})
-        else:
-            try:
-                ctrl.publish(None, fromjson=True)
-            except ValidationError, err:
-                status, args = self.validation_error(err)
-            except Redirect, err:
-                try:
-                    self.req.cnx.commit() # ValidationError may be raise on commit
-                except ValidationError, err:
-                    status, args = self.validation_error(err)
-                else:
-                    status, args = (True, err.location)
-            except Exception, err:
-                self.req.cnx.rollback()
-                self.exception('unexpected error in validateform')
-                try:
-                    status, args = (False, self.req._(unicode(err)))
-                except UnicodeError:
-                    status, args = (False, repr(err))
-            else:
-                status, args = (False, '???')
+        self.req.json_request = True
+        # XXX unclear why we have a separated controller here vs
+        # js_validate_form on the json controller
+        status, args = _validate_form(self.req, self.vreg)
         self.req.set_content_type('text/html')
         jsarg = simplejson.dumps( (status, args) )
         domid = self.req.form.get('__domid', 'entityForm').encode(
@@ -217,14 +231,6 @@
  window.parent.handleFormValidationResponse('%s', null, null, %s);
 </script>""" %  (domid, simplejson.dumps( (status, args) ))
 
-    def validation_error(self, err):
-        self.req.cnx.rollback()
-        try:
-            eid = err.entity.eid
-        except AttributeError:
-            eid = err.entity
-        return (False, (eid, err.errors))
-
 
 class JSonController(Controller):
     id = 'json'
@@ -238,6 +244,7 @@
         note: it's the responsability of js_* methods to set the correct
         response content type
         """
+        self.req.json_request = True
         self.req.pageid = self.req.form.get('pageid')
         try:
             fname = self.req.form['fname']
@@ -377,27 +384,8 @@
         return self.validate_form(action, names, values)
 
     def validate_form(self, action, names, values):
-        # XXX this method (and correspoding js calls) should use the new
-        #     `RemoteCallFailed` mechansim
         self.req.form = self._rebuild_posted_form(names, values, action)
-        vreg = self.vreg
-        try:
-            ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'),
-                               req=self.req)
-        except NoSelectableObject:
-            return (False, {None: self.req._('not authorized')})
-        try:
-            ctrl.publish(None, fromjson=True)
-        except ValidationError, err:
-            self.req.cnx.rollback()
-            return (False, (err.entity, err.errors))
-        except Redirect, redir:
-            return (True, redir.location)
-        except Exception, err:
-            self.req.cnx.rollback()
-            self.exception('unexpected error in js_validateform')
-            return (False, self.req._(str(err).decode('utf-8')))
-        return (False, '???')
+        return _validate_form(self.req, self.vreg)
 
     @jsonize
     def js_edit_field(self, action, names, values, rtype, eid, default):
--- a/web/views/boxes.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/boxes.py	Mon Jul 06 14:14:55 2009 +0200
@@ -18,10 +18,10 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.rtags import RelationTags
 from cubicweb.selectors import match_user_groups, non_final_entity
+from cubicweb.view import EntityView
+from cubicweb.schema import display_name
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.view import EntityView
 from cubicweb.web import uicfg
 from cubicweb.web.box import BoxTemplate
 
--- a/web/views/cwproperties.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/cwproperties.py	Mon Jul 06 14:14:55 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/editcontroller.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/editcontroller.py	Mon Jul 06 14:14:55 2009 +0200
@@ -25,9 +25,8 @@
 class EditController(ViewController):
     id = 'edit'
 
-    def publish(self, rset=None, fromjson=False):
+    def publish(self, rset=None):
         """edit / create / copy / delete entity / relations"""
-        self.fromjson = fromjson
         for key in self.req.form:
             # There should be 0 or 1 action
             if key.startswith('__action_'):
@@ -113,9 +112,8 @@
                 entity = execute(rql, formparams).get_entity(0, 0)
                 eid = entity.eid
             except ValidationError, ex:
-                # ex.entity may be an int or an entity instance
                 self._to_create[formparams['eid']] = ex.entity
-                if self.fromjson:
+                if self.req.json_request: # XXX (syt) why?
                     ex.entity = formparams['eid']
                 raise
             self._to_create[formparams['eid']] = eid
--- a/web/views/editforms.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/editforms.py	Mon Jul 06 14:14:55 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)
@@ -360,8 +368,13 @@
         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
         title = self.schema.rschema(rtype).display_name(self.req, role)
         removejs = self.removejs % (peid, rtype,entity.eid)
+        countkey = '%s_count' % rtype
+        try:
+            self.req.data[countkey] += 1
+        except:
+            self.req.data[countkey] = 1
         self.w(form.form_render(divid=divid, title=title, removejs=removejs,
-                                **kwargs))
+                                counter=self.req.data[countkey], **kwargs))
 
     def add_hiddens(self, form, entity, peid, rtype, role):
         # to ease overriding (see cubes.vcsfile.views.forms for instance)
--- a/web/views/editviews.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/editviews.py	Mon Jul 06 14:14:55 2009 +0200
@@ -6,6 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from simplejson import dumps
 
@@ -21,7 +22,6 @@
 from cubicweb.web.views.editforms import relation_id
 from cubicweb.web.views.baseviews import FinalView
 
-_ = unicode
 
 class SearchForAssociationView(EntityView):
     """view called by the edition view when the user asks to search for
--- a/web/views/facets.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/facets.py	Mon Jul 06 14:14:55 2009 +0200
@@ -38,6 +38,11 @@
     order = 1
     roundcorners = True
 
+    needs_css = 'cubicweb.facets.css'
+    needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
+
+    bkLinkBox_template = u'<div class="facetTitle">%s</div>'
+    
     def facetargs(self):
         """this method returns the list of extra arguments that should
         be used by the facet
@@ -56,8 +61,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)
@@ -77,18 +82,8 @@
                         widgets.append(wdg)
             if not widgets:
                 return
+            self.displayBookmarkLink(rset)
             w = self.w
-            eschema = self.schema.eschema('Bookmark')
-            if eschema.has_perm(req, 'add'):
-                bk_path = 'view?rql=%s' % rset.printable_rql()
-                bk_title = req._('my custom search')
-                linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
-                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)
-                w(u'<div class="facetTitle"><a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a></div>' % (
-                    html_escape(bk_base_url),
-                    html_escape(bk_add_url),
-                    req._('bookmark this search')))
             w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">'  % (
                 divid, html_escape(dumps([divid, vid, paginate, self.facetargs()]))))
             w(u'<fieldset>')
@@ -106,6 +101,20 @@
             import cubicweb
             cubicweb.info('after facets with rql: %s' % repr(rqlst))
 
+    def displayBookmarkLink(self, rset):
+        eschema = self.schema.eschema('Bookmark')
+        if eschema.has_perm(self.req, 'add'):
+            bk_path = 'view?rql=%s' % rset.printable_rql()
+            bk_title = self.req._('my custom search')
+            linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
+            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),
+                    self.req._('bookmark this search'))
+            self.w(self.bkLinkBox_template % bk_link)
+
     def get_facets(self, rset, mainvar):
         return self.vreg.possible_vobjects('facets', self.req, rset,
                                            context='facetbox',
--- a/web/views/formrenderers.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/formrenderers.py	Mon Jul 06 14:14:55 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
@@ -481,7 +500,7 @@
         w(u'<div class="iformBody">')
         values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema)
         w(u'<div class="iformTitle"><span>%(title)s</span> '
-          '#<span class="icounter">1</span> '
+          '#<span class="icounter">%(counter)s</span> '
           '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
           % values)
         # cleanup values
--- a/web/views/forms.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/forms.py	Mon Jul 06 14:14:55 2009 +0200
@@ -119,8 +119,8 @@
 
     def form_add_hidden(self, name, value=None, **kwargs):
         """add an hidden field to the form"""
-        field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
-                            **kwargs)
+        kwargs.setdefault('widget', fwdgs.HiddenInput)
+        field = StringField(name=name, initial=value, **kwargs)
         if 'id' in kwargs:
             # by default, hidden input don't set id attribute. If one is
             # explicitly specified, ensure it will be set
--- a/web/views/management.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/management.py	Mon Jul 06 14:14:55 2009 +0200
@@ -14,7 +14,7 @@
 from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
 from cubicweb.view import AnyRsetView, StartupView, EntityView
 from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.web import formwidgets
+from cubicweb.web import formwidgets as wdgs
 from cubicweb.web.formfields import guess_field
 
 SUBMIT_MSGID = _('Submit bug report')
@@ -110,7 +110,7 @@
         form = self.vreg.select_object('forms', 'base', self.req, entity=entity,
                                        form_renderer_id='base',
                                   submitmsg=msg,
-                                  form_buttons=[formwidgets.SubmitButton()],
+                                  form_buttons=[wdgs.SubmitButton()],
                                   domid='ownership%s' % entity.eid,
                                   __redirectvid='security',
                                   __redirectpath=entity.rest_path())
@@ -167,7 +167,7 @@
         newperm.eid = self.req.varmaker.next()
         w(u'<p>%s</p>' % _('add a new permission'))
         form = self.vreg.select_object('forms', 'base', self.req, entity=newperm,
-                                       form_buttons=[formwidgets.SubmitButton()],
+                                       form_buttons=[wdgs.SubmitButton()],
                                        domid='reqperm%s' % entity.eid,
                                        __redirectvid='security',
                                        __redirectpath=entity.rest_path())
@@ -177,7 +177,7 @@
         cwpermschema = newperm.e_schema
         if permnames is not None:
             field = guess_field(cwpermschema, self.schema.rschema('name'),
-                                widget=formwidgets.Select({'size': 1}),
+                                widget=wdgs.Select({'size': 1}),
                                 choices=permnames)
         else:
             field = guess_field(cwpermschema, self.schema.rschema('name'))
@@ -247,15 +247,17 @@
             form = self.vreg.select_object('forms', 'base', self.req, rset=None,
                                            mainform=False)
             binfo = text_error_description(ex, excinfo, req, eversion, cversions)
-            form.form_add_hidden('description', binfo)
+            form.form_add_hidden('description', binfo,
+                                 # we must use a text area to keep line breaks
+                                 widget=wdgs.TextArea({'class': 'hidden'}))
             form.form_add_hidden('__bugreporting', '1')
             if submitmail:
-                form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)]
+                form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
                 form.action = req.build_url('reportbug')
                 w(form.form_render())
             if submiturl:
                 form.form_add_hidden('description_format', 'text/rest')
-                form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)]
+                form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)]
                 form.action = submiturl
                 w(form.form_render())
 
--- a/web/views/navigation.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/navigation.py	Mon Jul 06 14:14:55 2009 +0200
@@ -6,6 +6,7 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from rql.nodes import VariableRef, Constant
 
@@ -19,8 +20,6 @@
 from cubicweb.common.uilib import cut
 from cubicweb.web.component import EntityVComponent, NavigationComponent
 
-_ = unicode
-
 
 class PageNavigation(NavigationComponent):
 
--- a/web/views/primary.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/primary.py	Mon Jul 06 14:14:55 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'
@@ -52,7 +52,6 @@
         boxes = self._prepare_side_boxes(entity)
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'<table width="100%"><tr><td style="width: 75%">')
-        self.w(u'<div>')
         self.w(u'<div class="mainInfo">')
         self.content_navigation_components('navcontenttop')
         try:
@@ -63,7 +62,6 @@
             warn('siderelations argument of render_entity_attributes is '
                  'deprecated (%s)' % self.__class__)
             self.render_entity_attributes(entity, [])
-        self.w(u'</div>')
         if self.main_related_section:
             try:
                 self.render_entity_relations(entity)
@@ -74,9 +72,9 @@
                      'deprecated')
                 self.render_entity_relations(entity, [])
         self.w(u'</div>')
+        # side boxes
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'</td><td>')
-            # side boxes
             self.w(u'<div class="primaryRight">')
             if hasattr(self, 'render_side_related'):
                 warn('render_side_related is deprecated')
@@ -119,7 +117,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 Jul 06 14:13:49 2009 +0200
+++ b/web/views/schema.py	Mon Jul 06 14:14:55 2009 +0200
@@ -42,6 +42,7 @@
                % (entity.dc_type().capitalize(),
                   html_escape(entity.dc_long_title())))
 
+
 # CWEType ######################################################################
 
 class CWETypeOneLineView(baseviews.OneLineView):
@@ -81,11 +82,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, '
@@ -93,25 +93,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 Jul 06 14:13:49 2009 +0200
+++ b/web/views/startup.py	Mon Jul 06 14:14:55 2009 +0200
@@ -14,6 +14,7 @@
 
 from cubicweb.view import StartupView
 from cubicweb.selectors import match_user_groups, implements
+from cubicweb.schema import display_name
 from cubicweb.common.uilib import ureport_as_html
 from cubicweb.web import ajax_replace_url, uicfg, httpcache
 from cubicweb.web.views import tabs
@@ -195,15 +196,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, SecurityViewMixIn):
--- a/web/views/tableview.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/tableview.py	Mon Jul 06 14:14:55 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/tabs.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/tabs.py	Mon Jul 06 14:14:55 2009 +0200
@@ -24,7 +24,7 @@
     """
 
     def _prepare_bindings(self, vid, reloadable):
-        self.req.html_headers.add_onload(u"""
+        self.req.add_onload(u"""
   jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
      load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
   });""" % {'event': 'load_%s' % vid, 'vid': vid,
@@ -59,7 +59,7 @@
         on dom readyness
         """
         self.req.add_js('cubicweb.lazy.js')
-        self.req.html_headers.add_onload("trigger_load('%s');" % vid)
+        self.req.add_onload("trigger_load('%s');" % vid)
 
 
 class TabsMixin(LazyViewMixin):
@@ -93,22 +93,11 @@
         return selected_tabs
 
     def render_tabs(self, tabs, default, entity=None):
-        # tabbed views do no support concatenation
-        # hence we delegate to the default tab if there is more than on entity
-        # in the result set
+        # delegate to the default tab if there is more than one entity
+        # in the result set (tabs are pretty useless there)
         if entity and len(self.rset) > 1:
             entity.view(default, w=self.w)
             return
-        # XXX (syt) fix below add been introduced at some point to fix something
-        # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean
-        # way. We must not consider form['rql'] here since it introduces some
-        # other failures on non rql queries (plain text, shortcuts,... handled by
-        # magicsearch) which has a single result whose primary view is using tabs
-        # (https://www.logilab.net/cwo/ticket/342789)
-        #rql = self.req.form.get('rql')
-        #if rql:
-        #    self.req.execute(rql).get_entity(0,0).view(default, w=self.w)
-        #    return
         self.req.add_css('ui.tabs.css')
         self.req.add_js(('ui.core.js', 'ui.tabs.js',
                          'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
@@ -143,7 +132,7 @@
             w(u'</div>')
         # call the set_tab() JS function *after* each tab is generated
         # because the callback binding needs to be done before
-        self.req.html_headers.add_onload(u"""
+        self.req.add_onload(u"""
    jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
    set_tab('%(vid)s', '%(cookiename)s');
  """ % {'tabindex'   : tabs.index(active_tab),
--- a/web/views/treeview.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/treeview.py	Mon Jul 06 14:14:55 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/urlpublishing.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/urlpublishing.py	Mon Jul 06 14:14:55 2009 +0200
@@ -144,18 +144,12 @@
         <etype>[[/<attribute name>]/<attribute value>]*
     """
     priority = 2
-    def __init__(self, urlpublisher):
-        super(RestPathEvaluator, self).__init__(urlpublisher)
-        self.etype_map = {}
-        for etype in self.schema.entities():
-            etype = str(etype)
-            self.etype_map[etype.lower()] = etype
 
     def evaluate_path(self, req, parts):
         if not (0 < len(parts) < 4):
             raise PathDontMatch()
         try:
-            etype = self.etype_map[parts.pop(0).lower()]
+            etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()]
         except KeyError:
             raise PathDontMatch()
         cls = self.vreg.etype_class(etype)
--- a/web/views/xmlrss.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/views/xmlrss.py	Mon Jul 06 14:14:55 2009 +0200
@@ -79,7 +79,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):
--- a/web/webconfig.py	Mon Jul 06 14:13:49 2009 +0200
+++ b/web/webconfig.py	Mon Jul 06 14:14:55 2009 +0200
@@ -80,12 +80,6 @@
           'if anonymous-user is set',
           'group': 'main', 'inputlevel': 1,
           }),
-        ('allow-email-login',
-         {'type' : 'yn',
-          'default': False,
-          'help': 'allow users to login with their primary email if set',
-          'group': 'main', 'inputlevel': 2,
-          }),
         ('query-log-file',
          {'type' : 'string',
           'default': None,