merge default heads
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 25 May 2011 11:42:31 +0200
changeset 7433 9aadb5a04b53
parent 7432 cab99ccdb774 (diff)
parent 7427 06444c1233e0 (current diff)
child 7436 28ec860c4436
merge default heads
web/data/cubicweb.ajax.js
--- a/dbapi.py	Wed May 25 11:28:58 2011 +0200
+++ b/dbapi.py	Wed May 25 11:42:31 2011 +0200
@@ -30,6 +30,7 @@
 from itertools import count
 from warnings import warn
 from os.path import join
+from uuid import uuid4
 
 from logilab.common.logging_ext import set_log_methods
 from logilab.common.decorators import monkeypatch
@@ -246,7 +247,7 @@
         if cnx is not None:
             self.sessionid = cnx.sessionid
         else:
-            self.sessionid = None
+            self.sessionid = uuid4().hex
 
     @property
     def anonymous_session(self):
--- a/entity.py	Wed May 25 11:28:58 2011 +0200
+++ b/entity.py	Wed May 25 11:42:31 2011 +0200
@@ -62,6 +62,23 @@
     return True
 
 
+def remove_ambiguous_rels(attr_set, subjtypes, schema):
+    '''remove from `attr_set` the relations of entity types `subjtypes` that have
+    different entity type sets as target'''
+    for attr in attr_set.copy():
+        rschema = schema.rschema(attr)
+        if rschema.final:
+            continue
+        ttypes = None
+        for subjtype in subjtypes:
+            cur_ttypes = rschema.objects(subjtype)
+            if ttypes is None:
+                ttypes = cur_ttypes
+            elif cur_ttypes != ttypes:
+                attr_set.remove(attr)
+                break
+
+
 class Entity(AppObject):
     """an entity instance has e_schema automagically set on
     the class and instances has access to their issuing cursor.
@@ -222,6 +239,7 @@
                 if len(targettypes) > 1:
                     # find fetch_attrs common to all destination types
                     fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes)
+                    remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema)
                 else:
                     fetchattrs = etypecls.fetch_attrs
                 etypecls._fetch_restrictions(var, varmaker, fetchattrs,
@@ -739,6 +757,10 @@
         etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0])
         if len(targettypes) > 1:
             fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes)
+            # XXX we should fetch ambiguous relation objects too but not
+            # recurse on them in _fetch_restrictions; it is easier to remove
+            # them completely for now, as it would require an deeper api rewrite
+            remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema)
         else:
             fetchattrs = etypecls.fetch_attrs
         rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs,
--- a/i18n/de.po	Wed May 25 11:28:58 2011 +0200
+++ b/i18n/de.po	Wed May 25 11:42:31 2011 +0200
@@ -4040,7 +4040,7 @@
 msgid "tr_count"
 msgstr ""
 
-msgid "transaction undoed"
+msgid "transaction undone"
 msgstr "Transaktion rückgängig gemacht"
 
 #, python-format
--- a/i18n/en.po	Wed May 25 11:28:58 2011 +0200
+++ b/i18n/en.po	Wed May 25 11:42:31 2011 +0200
@@ -3935,7 +3935,7 @@
 msgid "tr_count"
 msgstr "transition number"
 
-msgid "transaction undoed"
+msgid "transaction undone"
 msgstr ""
 
 #, python-format
--- a/i18n/es.po	Wed May 25 11:28:58 2011 +0200
+++ b/i18n/es.po	Wed May 25 11:42:31 2011 +0200
@@ -4090,7 +4090,7 @@
 msgid "tr_count"
 msgstr "n° de transición"
 
-msgid "transaction undoed"
+msgid "transaction undone"
 msgstr "Transacciones Anuladas"
 
 #, python-format
--- a/i18n/fr.po	Wed May 25 11:28:58 2011 +0200
+++ b/i18n/fr.po	Wed May 25 11:42:31 2011 +0200
@@ -4091,7 +4091,7 @@
 msgid "tr_count"
 msgstr "n° de transition"
 
-msgid "transaction undoed"
+msgid "transaction undone"
 msgstr "transaction annulées"
 
 #, python-format
--- a/selectors.py	Wed May 25 11:28:58 2011 +0200
+++ b/selectors.py	Wed May 25 11:42:31 2011 +0200
@@ -1342,6 +1342,8 @@
 
     @lltrace
     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        if not req.cnx:
+            return 0
         user = req.user
         if user is None:
             return int('guests' in self.expected)
--- a/server/sources/rql2sql.py	Wed May 25 11:28:58 2011 +0200
+++ b/server/sources/rql2sql.py	Wed May 25 11:42:31 2011 +0200
@@ -72,7 +72,9 @@
     stack.append(self.source_execute)
 FunctionDescr.update_cb_stack = default_update_cb_stack
 
-LENGTH = SQL_FUNCTIONS_REGISTRY.get_function('LENGTH')
+get_func_descr = SQL_FUNCTIONS_REGISTRY.get_function
+
+LENGTH = get_func_descr('LENGTH')
 def length_source_execute(source, session, value):
     return len(value.getvalue())
 LENGTH.source_execute = length_source_execute
@@ -271,7 +273,9 @@
         # when a query is grouped, ensure sort terms are grouped as well
         for sortterm in sorts:
             term = sortterm.term
-            if not isinstance(term, Constant):
+            if not (isinstance(term, Constant) or \
+                    (isinstance(term, Function) and
+                     get_func_descr(term.name).aggregat)):
                 for vref in term.iget_nodes(VariableRef):
                     if not vref in groups:
                         groups.append(vref)
@@ -308,12 +312,12 @@
             break
         if not isinstance(node, Function):
             raise QueryError()
-        func = SQL_FUNCTIONS_REGISTRY.get_function(node.name)
-        if func.source_execute is None:
+        funcd = get_func_descr(node.name)
+        if funcd.source_execute is None:
             raise QueryError('%s can not be called on mapped attribute'
                              % node.name)
         state.source_cb_funcs.add(node)
-        func.update_cb_stack(stack)
+        funcd.update_cb_stack(stack)
 
 
 # IGenerator implementation for RQL->SQL #######################################
--- a/server/test/unittest_rql2sql.py	Wed May 25 11:28:58 2011 +0200
+++ b/server/test/unittest_rql2sql.py	Wed May 25 11:42:31 2011 +0200
@@ -536,6 +536,14 @@
      '''SELECT _X.cw_eid
 FROM cw_CWEType AS _X
 WHERE _X.cw_description!=parent AND _X.cw_description!=_X.cw_name'''),
+
+    ('DISTINCT Any X, SUM(C) GROUPBY X ORDERBY SUM(C) DESC WHERE H todo_by X, H duration C',
+     '''SELECT DISTINCT rel_todo_by0.eid_to, SUM(_H.cw_duration)
+FROM cw_Affaire AS _H, todo_by_relation AS rel_todo_by0
+WHERE rel_todo_by0.eid_from=_H.cw_eid
+GROUP BY rel_todo_by0.eid_to
+ORDER BY 2 DESC'''),
+
     ]
 
 ADVANCED_WITH_GROUP_CONCAT = [
--- a/server/test/unittest_undo.py	Wed May 25 11:28:58 2011 +0200
+++ b/server/test/unittest_undo.py	Wed May 25 11:42:31 2011 +0200
@@ -169,7 +169,7 @@
                           ['CWUser'])
         self.assertEqual([et.name for et in toto.is_instance_of],
                           ['CWUser'])
-        # undoing shouldn't be visble in undoable transaction, and the undoed
+        # undoing shouldn't be visble in undoable transaction, and the undone
         # transaction should be removed
         txs = self.cnx.undoable_transactions()
         self.assertEqual(len(txs), 2)
--- a/test/data/schema.py	Wed May 25 11:28:58 2011 +0200
+++ b/test/data/schema.py	Wed May 25 11:42:31 2011 +0200
@@ -18,9 +18,11 @@
 
 from yams.buildobjs import (EntityType, String, SubjectRelation,
                             RelationDefinition)
+
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLVocabularyConstraint)
 
+
 class Personne(EntityType):
     nom = String(required=True)
     prenom = String()
@@ -36,22 +38,40 @@
             RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'),
             RQLVocabularyConstraint('S travaille P, P nom "tutu"')])
 
+
 class Societe(EntityType):
     nom = String()
     evaluee = SubjectRelation('Note')
+    fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*')
+
+
+class Service(EntityType):
+    fabrique_par = SubjectRelation('Personne', cardinality='1*')
+
+
+class Produit(EntityType):
+    fabrique_par = SubjectRelation('Usine', cardinality='1*')
+
+
+class Usine(EntityType):
+    lieu = String(required=True)
+
 
 class Note(EntityType):
     type = String()
     ecrit_par = SubjectRelation('Personne')
 
+
 class SubNote(Note):
     __specializes_schema__ = True
     description = String()
 
+
 class tags(RelationDefinition):
     subject = 'Tag'
     object = ('Personne', 'Note')
 
+
 class evaluee(RelationDefinition):
     subject = 'CWUser'
     object = 'Note'
@@ -59,5 +79,3 @@
 
 class StateFull(WorkflowableEntityType):
     name = String()
-
-
--- a/test/unittest_entity.py	Wed May 25 11:28:58 2011 +0200
+++ b/test/unittest_entity.py	Wed May 25 11:42:31 2011 +0200
@@ -250,7 +250,7 @@
                           'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, '
                           'X modification_date AB')
 
-    def test_related_rql_ambigous_cant_use_fetch_order(self):
+    def test_related_rql_ambiguous_cant_use_fetch_order(self):
         tag = self.vreg['etypes'].etype_class('Tag')(self.request())
         for ttype in self.schema['tags'].objects():
             self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',)
@@ -258,6 +258,18 @@
                           'Any X,AA ORDERBY AA DESC '
                           'WHERE E eid %(x)s, E tags X, X modification_date AA')
 
+    def test_related_rql_cant_fetch_ambiguous_rtype(self):
+        soc_etype = self.vreg['etypes'].etype_class('Societe')
+        soc = soc_etype(self.request())
+        soc_etype.fetch_attrs = ('fournit',)
+        self.vreg['etypes'].etype_class('Service').fetch_attrs = ('fabrique_par',)
+        self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',)
+        self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',)
+        self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',)
+        # XXX should be improved: we could fetch fabrique_par object too
+        self.assertEqual(soc.cw_related_rql('fournit', 'subject'),
+                         'Any X WHERE E eid %(x)s, E fournit X')
+
     def test_unrelated_rql_security_1_manager(self):
         user = self.request().user
         rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
--- a/test/unittest_schema.py	Wed May 25 11:28:58 2011 +0200
+++ b/test/unittest_schema.py	Wed May 25 11:42:31 2011 +0200
@@ -166,10 +166,11 @@
                              'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig',
                              'CWUniqueTogetherConstraint', 'CWUser',
                              'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note',
-                             'Password', 'Personne',
+                             'Password', 'Personne', 'Produit',
                              'RQLExpression',
-                             'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
+                             'Service', 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
                              'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo',
+                             'Usine',
                              'Workflow', 'WorkflowTransition']
         self.assertListEqual(sorted(expected_entities), entities)
         relations = sorted([str(r) for r in schema.relations()])
@@ -188,14 +189,14 @@
 
                               'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype',
 
-                              'final', 'firstname', 'for_user',
+                              'fabrique_par', 'final', 'firstname', 'for_user', 'fournit',
                               'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed',
 
                               'has_text',
                               'identity', 'in_group', 'in_state', 'indexed',
                               'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of',
 
-                              'label', 'last_login_time', 'latest_retrieval', 'login',
+                              'label', 'last_login_time', 'latest_retrieval', 'lieu', 'login',
 
                               'mainvars', 'match_host', 'modification_date',
 
--- a/web/application.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/application.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -204,17 +204,34 @@
             except InvalidSession:
                 # try to open a new session, so we get an anonymous session if
                 # allowed
-                try:
-                    session = self.open_session(req)
-                except AuthenticationError:
-                    req.remove_cookie(cookie, sessioncookie)
-                    raise
+                session = self.open_session(req)
+            else:
+                if not session.cnx:
+                    # session exists but is not bound to a connection. We should
+                    # try to authenticate
+                    loginsucceed = False
+                    try:
+                        if self.open_session(req, allow_no_cnx=False):
+                            loginsucceed = True
+                    except Redirect:
+                        # may be raised in open_session (by postlogin mechanism)
+                        # on successful connection
+                        loginsucceed = True
+                        raise
+                    except AuthenticationError:
+                        # authentication failed, continue to use this session
+                        req.set_session(session)
+                    finally:
+                        if loginsucceed:
+                            # session should be replaced by new session created
+                            # in open_session
+                            self.session_manager.close_session(session)
 
     def get_session(self, req, sessionid):
         return self.session_manager.get_session(req, sessionid)
 
-    def open_session(self, req):
-        session = self.session_manager.open_session(req)
+    def open_session(self, req, allow_no_cnx=True):
+        session = self.session_manager.open_session(req, allow_no_cnx=allow_no_cnx)
         cookie = req.get_cookie()
         sessioncookie = self.session_cookie(req)
         cookie[sessioncookie] = session.sessionid
@@ -279,10 +296,7 @@
         sessions (i.e. a new connection may be created or an already existing
         one may be reused
         """
-        try:
-            self.session_handler.set_session(req)
-        except AuthenticationError:
-            req.set_session(DBAPISession(None))
+        self.session_handler.set_session(req)
 
     # publish methods #########################################################
 
@@ -365,11 +379,12 @@
                 # redirect is raised by edit controller when everything went fine,
                 # so try to commit
                 try:
-                    txuuid = req.cnx.commit()
-                    if txuuid is not None:
-                        msg = u'<span class="undo">[<a href="%s">%s</a>]</span>' %(
-                            req.build_url('undo', txuuid=txuuid), req._('undo'))
-                        req.append_to_redirect_message(msg)
+                    if req.cnx:
+                        txuuid = req.cnx.commit()
+                        if txuuid is not None:
+                            msg = u'<span class="undo">[<a href="%s">%s</a>]</span>' %(
+                                req.build_url('undo', txuuid=txuuid), req._('undo'))
+                            req.append_to_redirect_message(msg)
                 except ValidationError, ex:
                     self.validation_error_handler(req, ex)
                 except Unauthorized, ex:
--- a/web/controller.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/controller.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -114,7 +114,7 @@
                           [recipient], body, subject)
         if not self._cw.vreg.config.sendmails([(msg, [recipient])]):
             msg = self._cw._('could not connect to the SMTP server')
-            url = self._cw.build_url(__message=msg)
+            url = self._cw.build_url(__message=msgid)
             raise Redirect(url)
 
     def reset(self):
@@ -123,8 +123,10 @@
         """
         newparams = {}
         # sets message if needed
-        if self._cw.message:
-            newparams['_cwmsgid'] = self._cw.set_redirect_message(self._cw.message)
+        # XXX - don't call .message twice since it pops the id
+        msg = self._cw.message
+        if msg:
+            newparams['_cwmsgid'] = self._cw.set_redirect_message(msg)
         if self._cw.form.has_key('__action_apply'):
             self._return_to_edition_view(newparams)
         if self._cw.form.has_key('__action_cancel'):
--- a/web/data/cubicweb.ajax.js	Wed May 25 11:28:58 2011 +0200
+++ b/web/data/cubicweb.ajax.js	Wed May 25 11:42:31 2011 +0200
@@ -781,43 +781,37 @@
     }
 );
 
-remoteExec = cw.utils.deprecatedFunction(
-    '[3.9] remoteExec() is deprecated, use loadRemote instead',
-    function(fname /* ... */) {
-        setProgressCursor();
-        var props = {
-            fname: fname,
-            pageid: pageid,
-            arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
-        };
-        var result = jQuery.ajax({
-            url: JSON_BASE_URL,
-            data: props,
-            async: false,
-            traditional: true
-        }).responseText;
-        if (result) {
-            result = cw.evalJSON(result);
-        }
-        resetCursor();
-        return result;
+function remoteExec(fname /* ... */) {
+    setProgressCursor();
+    var props = {
+        fname: fname,
+        pageid: pageid,
+        arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
+    };
+    var result = jQuery.ajax({
+        url: JSON_BASE_URL,
+        data: props,
+        async: false,
+        traditional: true
+    }).responseText;
+    if (result) {
+        result = cw.evalJSON(result);
     }
-);
+    resetCursor();
+    return result;
+}
 
-asyncRemoteExec = cw.utils.deprecatedFunction(
-    '[3.9] asyncRemoteExec() is deprecated, use loadRemote instead',
-    function(fname /* ... */) {
-        setProgressCursor();
-        var props = {
-            fname: fname,
-            pageid: pageid,
-            arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
-        };
-        // XXX we should inline the content of loadRemote here
-        var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
-        deferred = deferred.addErrback(remoteCallFailed);
-        deferred = deferred.addErrback(resetCursor);
-        deferred = deferred.addCallback(resetCursor);
-        return deferred;
-    }
-);
+function asyncRemoteExec(fname /* ... */) {
+    setProgressCursor();
+    var props = {
+        fname: fname,
+        pageid: pageid,
+        arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
+    };
+    // XXX we should inline the content of loadRemote here
+    var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
+    deferred = deferred.addErrback(remoteCallFailed);
+    deferred = deferred.addErrback(resetCursor);
+    deferred = deferred.addCallback(resetCursor);
+    return deferred;
+}
--- a/web/form.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/form.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -112,7 +112,12 @@
                     if value:
                         self.add_hidden(param, value)
         if submitmsg is not None:
-            self.add_hidden(u'__message', submitmsg)
+            self.set_message(submitmsg)
+
+    def set_message(self, submitmsg):
+        """sets a submitmsg if exists, using _cwmsgid mechanism """
+        cwmsgid = self._cw.set_redirect_message(submitmsg)
+        self.add_hidden(u'_cwmsgid', cwmsgid)
 
     @property
     def root_form(self):
--- a/web/request.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/request.py	Wed May 25 11:42:31 2011 +0200
@@ -214,6 +214,12 @@
             if param == '_cwmsgid':
                 self.set_message_id(val)
             elif param == '__message':
+                warn('[3.13] __message in request parameter is deprecated (may '
+                     'only be given to .build_url). Seeing this message usualy '
+                     'means your application hold some <form> where you should '
+                     'replace use of __message hidden input by form.set_message, '
+                     'so new _cwmsgid mechanism is properly used',
+                     DeprecationWarning)
                 self.set_message(val)
             else:
                 self.form[param] = val
@@ -264,7 +270,7 @@
     @property
     def message(self):
         try:
-            return self.session.data.pop(self._msgid, '')
+            return self.session.data.pop(self._msgid, u'')
         except AttributeError:
             try:
                 return self._msg
@@ -283,6 +289,7 @@
         return make_uid()
 
     def set_redirect_message(self, msg):
+        # TODO - this should probably be merged with append_to_redirect_message
         assert isinstance(msg, unicode)
         msgid = self.redirect_message_id()
         self.session.data[msgid] = msg
@@ -292,7 +299,7 @@
         msgid = self.redirect_message_id()
         currentmsg = self.session.data.get(msgid)
         if currentmsg is not None:
-            currentmsg = '%s %s' % (currentmsg, msg)
+            currentmsg = u'%s %s' % (currentmsg, msg)
         else:
             currentmsg = msg
         self.session.data[msgid] = currentmsg
@@ -624,6 +631,16 @@
 
     # urls/path management ####################################################
 
+    def build_url(self, *args, **kwargs):
+        """return an absolute URL using params dictionary key/values as URL
+        parameters. Values are automatically URL quoted, and the
+        publishing method to use may be specified or will be guessed.
+        """
+        if '__message' in kwargs:
+            msg = kwargs.pop('__message')
+            kwargs['_cwmsgid'] = self.set_redirect_message(msg)
+        return super(CubicWebRequestBase, self).build_url(*args, **kwargs)
+
     def url(self, includeparams=True):
         """return currently accessed url"""
         return self.base_url() + self.relative_path(includeparams)
--- a/web/views/basecomponents.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/views/basecomponents.py	Wed May 25 11:42:31 2011 +0200
@@ -29,8 +29,8 @@
 from logilab.common.deprecation import class_renamed
 from rql import parse
 
-from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params,
-                                match_context, configuration_values,
+from cubicweb.selectors import (yes, match_form_params, match_context,
+                                multi_etypes_rset, configuration_values,
                                 anonymous_user, authenticated_user)
 from cubicweb.schema import display_name
 from cubicweb.utils import wrap_on_write
@@ -88,6 +88,7 @@
 class ApplLogo(HeaderComponent):
     """build the instance logo, usually displayed in the header"""
     __regid__ = 'logo'
+    __select__ = yes() # no need for a cnx
     order = -1
 
     def render(self, w):
@@ -150,7 +151,7 @@
 
 class AnonUserStatusLink(HeaderComponent):
     __regid__ = 'userstatus'
-    __select__ = HeaderComponent.__select__ & anonymous_user()
+    __select__ = anonymous_user()
     context = _('header-right')
     order = HeaderComponent.order - 10
 
@@ -159,7 +160,7 @@
 
 
 class AuthenticatedUserStatus(AnonUserStatusLink):
-    __select__ = HeaderComponent.__select__ & authenticated_user()
+    __select__ = authenticated_user()
 
     def render(self, w):
         # display useractions and siteactions
@@ -185,9 +186,17 @@
     # don't want user to hide this component using an cwproperty
     cw_property_defs = {}
 
-    def call(self):
-        msgs = [msg for msg in (self._cw.get_shared_data('sources_error', pop=True),
-                                self._cw.message) if msg]
+    def call(self, msg=None):
+        if msg is None:
+            msgs = []
+            if self._cw.cnx:
+                srcmsg = self._cw.get_shared_data('sources_error', pop=True)
+                msgs.append(srcmsg)
+            reqmsg = self._cw.message # XXX don't call self._cw.message twice
+            if reqmsg:
+                msgs.append(reqmsg)
+        else:
+            msgs = [msg]
         self.w(u'<div id="appMsg" onclick="%s" class="%s">\n' %
                (toggle_action('appMsg'), (msgs and ' ' or 'hidden')))
         for msg in msgs:
--- a/web/views/basecontrollers.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/views/basecontrollers.py	Wed May 25 11:42:31 2011 +0200
@@ -102,7 +102,7 @@
         msg = self._cw._('you have been logged out')
         # force base_url so on dual http/https configuration, we generate an url
         # on the http version of the site
-        return self._cw.build_url('view', vid='index', __message=msg,
+        return self._cw.build_url('view', vid='loggedout',
                                   base_url=self._cw.vreg.config['base-url'])
 
 
--- a/web/views/basetemplates.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/views/basetemplates.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -25,7 +25,7 @@
 
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import match_kwargs, no_cnx, anonymous_user
-from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW
+from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW, StartupView
 from cubicweb.utils import UStringIO
 from cubicweb.schema import display_name
 from cubicweb.web import component, formfields as ff, formwidgets as fw
@@ -66,19 +66,19 @@
         self.wview('logform', rset=self.cw_rset, id='loginBox', klass='')
 
 
-class LoggedOutTemplate(LogInOutTemplate):
+class LoggedOutTemplate(StartupView):
     __regid__ = 'loggedout'
+    __select__ = anonymous_user()
     title = 'logged out'
 
-    def content(self, w):
-        # FIXME Deprecated code ?
+    def call(self):
         msg = self._cw._('you have been logged out')
-        w(u'<h2>%s</h2>\n' % msg)
-        if self._cw.vreg.config.anonymous_user()[0]:
-            indexurl = self._cw.build_url('view', vid='index', __message=msg)
-            w(u'<p><a href="%s">%s</a><p>' % (
-                xml_escape(indexurl),
-                self._cw._('go back to the index page')))
+        if self._cw.cnx:
+            comp = self._cw.vreg['components'].select('applmessages', self._cw)
+            comp.render(w=self.w, msg=msg)
+            self.wview('index')
+        else:
+            self.w(u'<h2>%s</h2>' % msg)
 
 
 @objectify_selector
--- a/web/views/debug.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/views/debug.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -133,6 +133,9 @@
             if sessions:
                 w(u'<ul>')
                 for session in sessions:
+                    if not session.cnx:
+                        w(u'<li>%s (NO CNX)</li>' % session.sessionid)
+                        continue
                     try:
                         last_usage_time = session.cnx.check()
                     except BadConnectionId:
--- a/web/views/sessions.py	Wed May 25 11:28:58 2011 +0200
+++ b/web/views/sessions.py	Wed May 25 11:42:31 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -21,7 +21,7 @@
 
 __docformat__ = "restructuredtext en"
 
-from cubicweb import RepositoryError, Unauthorized
+from cubicweb import RepositoryError, Unauthorized, AuthenticationError
 from cubicweb.web import InvalidSession, Redirect
 from cubicweb.web.application import AbstractSessionManager
 from cubicweb.dbapi import DBAPISession
@@ -49,28 +49,36 @@
 
     def get_session(self, req, sessionid):
         """return existing session for the given session identifier"""
-        if not sessionid in self._sessions:
+        if sessionid not in self._sessions:
             raise InvalidSession()
         session = self._sessions[sessionid]
-        try:
-            user = self.authmanager.validate_session(req, session)
-        except InvalidSession:
-            # invalid session
-            self.close_session(session)
-            raise
-        # associate the connection to the current request
-        req.set_session(session, user)
+        if session.cnx:
+            try:
+                user = self.authmanager.validate_session(req, session)
+            except InvalidSession:
+                # invalid session
+                self.close_session(session)
+                raise
+            # associate the connection to the current request
+            req.set_session(session, user)
         return session
 
-    def open_session(self, req):
+    def open_session(self, req, allow_no_cnx=True):
         """open and return a new session for the given request. The session is
         also bound to the request.
 
         raise :exc:`cubicweb.AuthenticationError` if authentication failed
         (no authentication info found or wrong user/password)
         """
-        cnx, login = self.authmanager.authenticate(req)
-        session = DBAPISession(cnx, login)
+        try:
+            cnx, login = self.authmanager.authenticate(req)
+        except AuthenticationError:
+            if allow_no_cnx:
+                session = DBAPISession(None)
+            else:
+                raise
+        else:
+            session = DBAPISession(cnx, login)
         self._sessions[session.sessionid] = session
         # associate the connection to the current request
         req.set_session(session)
@@ -89,15 +97,16 @@
         args = req.form
         for forminternal_key in ('__form_id', '__domid', '__errorurl'):
             args.pop(forminternal_key, None)
-        args['__message'] = req._('welcome %s !') % req.user.login
-        if 'vid' in req.form:
-            args['vid'] = req.form['vid']
-        if 'rql' in req.form:
-            args['rql'] = req.form['rql']
         path = req.relative_path(False)
         if path == 'login':
             path = 'view'
-        raise Redirect(req.build_url(path, **args))
+            args['__message'] = req._('welcome %s !') % req.user.login
+            if 'vid' in req.form:
+                args['vid'] = req.form['vid']
+            if 'rql' in req.form:
+                args['rql'] = req.form['rql']
+            raise Redirect(req.build_url(path, **args))
+        req.set_message(req._('welcome %s !') % req.user.login)
 
     def _update_last_login_time(self, req):
         # XXX should properly detect missing permission / non writeable source
@@ -120,10 +129,11 @@
         """
         self.info('closing http session %s' % session.sessionid)
         del self._sessions[session.sessionid]
-        try:
-            session.cnx.close()
-        except:
-            # already closed, may occurs if the repository session expired but
-            # not the web session
-            pass
-        session.cnx = None
+        if session.cnx:
+            try:
+                session.cnx.close()
+            except:
+                # already closed, may occur if the repository session expired
+                # but not the web session
+                pass
+            session.cnx = None