merge stable branch back into default branch
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Thu, 21 May 2009 00:50:24 +0200
changeset 1889 874a055c373b
parent 1888 f36d43f00f32 (current diff)
parent 1885 c2011d238e98 (diff)
child 1891 dd7c1d7715e7
merge stable branch back into default branch
web/views/basecontrollers.py
--- a/dbapi.py	Thu May 21 00:44:57 2009 +0200
+++ b/dbapi.py	Thu May 21 00:50:24 2009 +0200
@@ -50,7 +50,7 @@
         # resolve the Pyro object
         try:
             nshost, nsport = config['pyro-ns-host'], config['pyro-ns-port']
-            uri = locator.getNS(nshost, nsport) .resolve(nsid)
+            uri = locator.getNS(nshost, nsport).resolve(nsid)
         except ProtocolError:
             raise ConnectionError('Could not connect to the Pyro name server '
                                   '(host: %s:%i)' % (nshost, nsport))
@@ -325,6 +325,11 @@
         self.vreg = None
         # session's data
         self.data = {}
+        # XXX < 3.2 bw compat
+        if 'EUser' in self._repo.get_schema():
+            self._user_etype = 'EUser'
+        else:
+            self._user_etype = 'CWUser'
 
     def __repr__(self):
         if self.anonymous_connection:
@@ -430,9 +435,9 @@
         eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
         if req is None:
             req = self.request()
-        rset = req.eid_rset(eid, 'CWUser')
-        user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups,
-                                               properties=properties)
+        rset = req.eid_rset(eid, self._user_etype)
+        user = self.vreg.etype_class(self._user_etype)(req, rset, row=0, groups=groups,
+                                                       properties=properties)
         user['login'] = login # cache login
         return user
 
--- a/debian/cubicweb-common.install.in	Thu May 21 00:44:57 2009 +0200
+++ b/debian/cubicweb-common.install.in	Thu May 21 00:50:24 2009 +0200
@@ -1,4 +1,5 @@
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/common/ usr/lib/PY_VERSION/site-packages/cubicweb
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/entities/ usr/lib/PY_VERSION/site-packages/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/ usr/lib/PY_VERSION/site-packages/cubicweb
 debian/tmp/usr/share/cubicweb/cubes/shared/i18n usr/share/cubicweb/cubes/shared/
 debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/*.py usr/share/pyshared/cubicweb
--- a/devtools/devctl.py	Thu May 21 00:44:57 2009 +0200
+++ b/devtools/devctl.py	Thu May 21 00:50:24 2009 +0200
@@ -77,11 +77,11 @@
             if mod.__file__.startswith(path):
                 del sys.modules[name]
                 break
-        # fresh rtags
-        from cubicweb import rtags
-        from cubicweb.web import uicfg
-        rtags.RTAGS[:] = []
-        reload(uicfg)
+    # fresh rtags
+    from cubicweb import rtags
+    from cubicweb.web import uicfg
+    rtags.RTAGS[:] = []
+    reload(uicfg)
 
 def generate_schema_pot(w, cubedir=None):
     """generate a pot file with schema specific i18n messages
--- a/entities/__init__.py	Thu May 21 00:44:57 2009 +0200
+++ b/entities/__init__.py	Thu May 21 00:50:24 2009 +0200
@@ -57,16 +57,6 @@
             return '%s DESC' % var
         return None
 
-    @classmethod
-    def __initialize__(cls):
-        super(ANYENTITY, cls).__initialize__() # XXX
-        # set a default_ATTR method for rich text format fields
-        # XXX move this away once the old widgets have been dropped!
-        eschema = cls.e_schema
-        for metaattr, (metadata, attr) in eschema.meta_attributes().iteritems():
-            if metadata == 'format' and not hasattr(cls, 'default_%s' % metaattr):
-                setattr(cls, 'default_%s' % metaattr, cls._default_format)
-
     # meta data api ###########################################################
 
     def dc_title(self):
@@ -269,9 +259,6 @@
         from cubicweb.web.views.autoform import AutomaticEntityForm
         return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
 
-    def _default_format(self):
-        return self.req.property_value('ui.default-text-format')
-
     def attribute_values(self, attrname):
         if self.has_eid() or attrname in self:
             try:
--- a/entity.py	Thu May 21 00:44:57 2009 +0200
+++ b/entity.py	Thu May 21 00:50:24 2009 +0200
@@ -284,6 +284,23 @@
         parents.append(cls.vreg.etype_class('Any'))
         return parents
 
+    @classmethod
+    @cached
+    def _rest_attr_info(cls):
+        mainattr, needcheck = 'eid', True
+        if cls.rest_attr:
+            mainattr = cls.rest_attr
+            needcheck = not cls.e_schema.has_unique_values(mainattr)
+        else:
+            for rschema in cls.e_schema.subject_relations():
+                if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
+                    mainattr = str(rschema)
+                    needcheck = False
+                    break
+        if mainattr == 'eid':
+            needcheck = False
+        return mainattr, needcheck
+
     def __init__(self, req, rset=None, row=None, col=0):
         AppRsetObject.__init__(self, req, rset, row, col)
         dict.__init__(self)
@@ -363,46 +380,41 @@
         if getattr(self.req, 'search_state', ('normal',))[0] == 'normal':
             kwargs['base_url'] = self.metainformation()['source'].get('base-url')
         if method is None or method == 'view':
-            kwargs['_restpath'] = self.rest_path()
+            try:
+                kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
+            except TypeError:
+                warn('%s: rest_path() now take use_ext_eid argument, '
+                     'please update' % self.id, DeprecationWarning)
+                kwargs['_restpath'] = self.rest_path()
         else:
             kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
         return self.build_url(method, **kwargs)
 
-    def rest_path(self):
+    def rest_path(self, use_ext_eid=False):
         """returns a REST-like (relative) path for this entity"""
         mainattr, needcheck = self._rest_attr_info()
         etype = str(self.e_schema)
-        if mainattr == 'eid':
-            value = self.eid
-        else:
+        path = etype.lower()
+        if mainattr != 'eid':
             value = getattr(self, mainattr)
             if value is None:
-                return '%s/eid/%s' % (etype.lower(), self.eid)
-        if needcheck:
-            # make sure url is not ambiguous
-            rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
-            if value is not None:
-                nbresults = self.req.execute(rql, {'value' : value})[0][0]
-                # may an assertion that nbresults is not 0 would be a good idea
-                if nbresults != 1: # no ambiguity
-                    return '%s/eid/%s' % (etype.lower(), self.eid)
-        return '%s/%s' % (etype.lower(), self.req.url_quote(value))
-
-    @classmethod
-    def _rest_attr_info(cls):
-        mainattr, needcheck = 'eid', True
-        if cls.rest_attr:
-            mainattr = cls.rest_attr
-            needcheck = not cls.e_schema.has_unique_values(mainattr)
-        else:
-            for rschema in cls.e_schema.subject_relations():
-                if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
-                    mainattr = str(rschema)
-                    needcheck = False
-                    break
+                mainattr = 'eid'
+                path += '/eid'
+            elif needcheck:
+                # make sure url is not ambiguous
+                rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (
+                    etype, mainattr)
+                if value is not None:
+                    nbresults = self.req.execute(rql, {'value' : value})[0][0]
+                    if nbresults != 1: # ambiguity?
+                        mainattr = 'eid'
+                        path += '/eid'
         if mainattr == 'eid':
-            needcheck = False
-        return mainattr, needcheck
+            if use_ext_eid:
+                value = self.metainformation()['extid']
+            else:
+                value = self.eid
+        return '%s/%s' % (path, self.req.url_quote(value))
 
     def attr_metadata(self, attr, metadata):
         """return a metadata for an attribute (None if unspecified)"""
--- a/rtags.py	Thu May 21 00:44:57 2009 +0200
+++ b/rtags.py	Thu May 21 00:50:24 2009 +0200
@@ -24,7 +24,8 @@
     This class associates a single tag to each key.
     """
     _allowed_values = None
-    def __init__(self, initfunc=None, allowed_values=None):
+    def __init__(self, name=None, initfunc=None, allowed_values=None):
+        self._name = name or '<unknown>'
         self._tagdefs = {}
         if allowed_values is not None:
             self._allowed_values = allowed_values
@@ -32,7 +33,7 @@
         register_rtag(self)
 
     def __repr__(self):
-        return repr(self._tagdefs)
+        return '%s: %s' % (self._name, repr(self._tagdefs))
 
     # dict compat
     def __getitem__(self, key):
@@ -95,7 +96,9 @@
         #else:
         stype, rtype, otype, tagged = [str(k) for k in key]
         if self._allowed_values is not None:
-            assert tag in self._allowed_values, '%r is not an allowed tag' % tag
+            assert tag in self._allowed_values, \
+                   '%r is not an allowed tag (should be in %s)' % (
+                tag, self._allowed_values)
         self._tagdefs[(rtype, tagged, stype, otype)] = tag
 
     # rtag runtime api ########################################################
--- a/server/repository.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/repository.py	Thu May 21 00:50:24 2009 +0200
@@ -217,6 +217,7 @@
         self._get_pool().close(True)
         for i in xrange(config['connections-pool-size']):
             self._available_pools.put_nowait(ConnectionsPool(self.sources))
+        self._shutting_down = False
 
     # internals ###############################################################
 
@@ -349,6 +350,7 @@
         """called on server stop event to properly close opened sessions and
         connections
         """
+        self._shutting_down = True
         if isinstance(self._looping_tasks, tuple): # if tasks have been started
             for looptask in self._looping_tasks:
                 self.info('canceling task %s...', looptask.name)
@@ -618,7 +620,7 @@
         """commit transaction for the session with the given id"""
         self.debug('begin commit for session %s', sessionid)
         try:
-            self._get_session(sessionid, setpool=True).commit()
+            self._get_session(sessionid).commit()
         except (ValidationError, Unauthorized):
             raise
         except:
@@ -629,7 +631,7 @@
         """commit transaction for the session with the given id"""
         self.debug('begin rollback for session %s', sessionid)
         try:
-            self._get_session(sessionid, setpool=True).rollback()
+            self._get_session(sessionid).rollback()
         except:
             self.exception('unexpected error')
             raise
@@ -720,6 +722,8 @@
 
     def _get_session(self, sessionid, setpool=False):
         """return the user associated to the given session identifier"""
+        if self._shutting_down:
+            raise Exception('Repository is shutting down')
         try:
             session = self._sessions[sessionid]
         except KeyError:
--- a/server/schemaserial.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/schemaserial.py	Thu May 21 00:50:24 2009 +0200
@@ -14,6 +14,7 @@
 from yams import schema as schemamod, buildobjs as ybo
 
 from cubicweb.schema import CONSTRAINTS, ETYPE_NAME_MAP
+from cubicweb.server import sqlutils
 
 def group_mapping(cursor, interactive=True):
     """create a group mapping from an rql cursor
@@ -124,7 +125,8 @@
         if etype in ETYPE_NAME_MAP: # XXX <2.45 bw compat
             print 'fixing etype name from %s to %s' % (etype, ETYPE_NAME_MAP[etype])
             # can't use write rql queries at this point, use raw sql
-            session.system_sql('UPDATE CWEType SET name=%(n)s WHERE eid=%(x)s',
+            session.system_sql('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s'
+                               % {'p': sqlutils.SQL_PREFIX},
                                {'x': eid, 'n': ETYPE_NAME_MAP[etype]})
             session.system_sql('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
                                {'x': etype, 'n': ETYPE_NAME_MAP[etype]})
--- a/server/session.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/session.py	Thu May 21 00:50:24 2009 +0200
@@ -65,6 +65,8 @@
         # i18n initialization
         self.set_language(cnxprops.lang)
         self._threaddata = threading.local()
+        self._threads_in_transaction = set()
+        self._closed = False
 
     def get_mode(self):
         return getattr(self._threaddata, 'mode', 'read')
@@ -150,6 +152,8 @@
 
     def set_pool(self):
         """the session need a pool to execute some queries"""
+        if self._closed:
+            raise Exception('try to set pool on a closed session')
         if self.pool is None:
             self._threaddata.pool = self.repo._get_pool()
             try:
@@ -158,6 +162,7 @@
                 self.repo._free_pool(self.pool)
                 self._threaddata.pool = None
                 raise
+            self._threads_in_transaction.add(threading.currentThread())
         return self._threaddata.pool
 
     def reset_pool(self):
@@ -167,6 +172,7 @@
         # or rollback
         if self.pool is not None and self.mode == 'read':
             # even in read mode, we must release the current transaction
+            self._threads_in_transaction.remove(threading.currentThread())
             self.repo._free_pool(self.pool)
             self.pool.pool_reset(self)
             self._threaddata.pool = None
@@ -343,6 +349,23 @@
 
     def close(self):
         """do not close pool on session close, since they are shared now"""
+        self._closed = True
+        # copy since _threads_in_transaction maybe modified while waiting
+        for thread in self._threads_in_transaction.copy():
+            if thread is threading.currentThread():
+                continue
+            self.info('waiting for thread %s', thread)
+            # do this loop/break instead of a simple join(10) in case thread is
+            # the main thread (in which case it will be removed from
+            # self._threads_in_transaction but still be alive...)
+            for i in xrange(10):
+                thread.join(1)
+                if not (thread.isAlive() and
+                        thread in self._threads_in_transaction):
+                    break
+            else:
+                self.error('thread %s still alive after 10 seconds, will close '
+                           'session anyway', thread)
         self.rollback()
 
     # transaction data/operations management ##################################
--- a/server/sources/rql2sql.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/sources/rql2sql.py	Thu May 21 00:50:24 2009 +0200
@@ -901,6 +901,7 @@
     def visit_comparison(self, cmp, contextrels=None):
         """generate SQL for a comparaison"""
         if len(cmp.children) == 2:
+            # XXX occurs ?
             lhs, rhs = cmp.children
         else:
             lhs = None
@@ -911,6 +912,11 @@
                 operator = ' LIKE '
             else:
                 operator = ' %s ' % operator
+        elif (operator == '=' and isinstance(rhs, Constant)
+              and rhs.eval(self._args) is None):
+            if lhs is None:
+                return ' IS NULL'
+            return '%s IS NULL' % lhs.accept(self, contextrels)
         elif isinstance(rhs, Function) and rhs.name == 'IN':
             assert operator == '='
             operator = ' '
--- a/server/test/data/sources_multi	Thu May 21 00:44:57 2009 +0200
+++ b/server/test/data/sources_multi	Thu May 21 00:50:24 2009 +0200
@@ -14,6 +14,7 @@
 cubicweb-user = admin
 cubicweb-password = gingkow
 mapping-file = extern_mapping.py
+base-url=http://extern.org/
 
 [extern-multi]
 adapter = pyrorql
--- a/server/test/unittest_multisources.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_multisources.py	Thu May 21 00:50:24 2009 +0200
@@ -235,9 +235,21 @@
         states.remove((aff1stateeid, aff1statename))
         notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
                                                        {'x': aff1}, 'x'))
-        self.set_debug(False)
         self.assertSetEquals(notstates, states)
 
+    def test_absolute_url_base_url(self):
+        ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
+        cnx2.commit()
+        lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+        self.assertEquals(lc.absolute_url(), 'http://extern.org/card/eid/%s' % ceid)
+
+    def test_absolute_url_no_base_url(self):
+        cu = cnx3.cursor()
+        ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
+        cnx3.commit()
+        lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+        self.assertEquals(lc.absolute_url(), 'http://testing.fr/cubicweb/card/eid/%s' % lc.eid)
+
     def test_nonregr1(self):
         ueid = self.session.user.eid
         affaire = self.execute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
@@ -251,7 +263,6 @@
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.rows[0], [self.session.user.eid])
 
-
     def test_nonregr3(self):
         self.execute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
 
--- a/server/test/unittest_repository.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_repository.py	Thu May 21 00:50:24 2009 +0200
@@ -199,6 +199,26 @@
     def test_transaction_interleaved(self):
         self.skip('implement me')
 
+    def test_close_wait_processing_request(self):
+        repo = self.repo
+        cnxid = repo.connect(*self.default_user_password())
+        repo.execute(cnxid, 'INSERT CWUser X: X login "toto", X upassword "tutu", X in_group G WHERE G name "users"')
+        repo.commit(cnxid)
+        # close has to be in the thread due to sqlite limitations
+        def close_in_a_few_moment():
+            time.sleep(0.1)
+            repo.close(cnxid)
+        t = threading.Thread(target=close_in_a_few_moment)
+        t.start()
+        try:
+            print 'execute'
+            repo.execute(cnxid, 'DELETE CWUser X WHERE X login "toto"')
+            print 'commit'
+            repo.commit(cnxid)
+            print 'commited'
+        finally:
+            t.join()
+
     def test_initial_schema(self):
         schema = self.repo.schema
         # check order of attributes is respected
@@ -238,31 +258,29 @@
     def test_pyro(self):
         import Pyro
         Pyro.config.PYRO_MULTITHREADED = 0
-        lock = threading.Lock()
+        done = []
         # the client part has to be in the thread due to sqlite limitations
-        t = threading.Thread(target=self._pyro_client, args=(lock,))
+        t = threading.Thread(target=self._pyro_client, args=(done,))
         try:
             daemon = self.repo.pyro_register()
             t.start()
-            # connection
-            daemon.handleRequests(1.0)
-            daemon.handleRequests(1.0)
-            daemon.handleRequests(1.0)
-            # get schema
-            daemon.handleRequests(1.0)
-            # execute
-            daemon.handleRequests(1.0)
-            t.join()
+            while not done:
+                daemon.handleRequests(1.0)
+            t.join(1)
+            if t.isAlive():
+                self.fail('something went wrong, thread still alive')
         finally:
             repository.pyro_unregister(self.repo.config)
 
-    def _pyro_client(self, lock):
+    def _pyro_client(self, done):
         cnx = connect(self.repo.config.appid, u'admin', 'gingkow')
         # check we can get the schema
         schema = cnx.get_schema()
         self.assertEquals(schema.__hashmode__, None)
-        rset = cnx.cursor().execute('Any U,G WHERE U in_group G')
-
+        cu = cnx.cursor()
+        rset = cu.execute('Any U,G WHERE U in_group G')
+        cnx.close()
+        done.append(True)
 
     def test_internal_api(self):
         repo = self.repo
--- a/server/test/unittest_rql2sql.py	Thu May 21 00:44:57 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Thu May 21 00:50:24 2009 +0200
@@ -1,3 +1,4 @@
+
 """unit tests for module cubicweb.server.sources.rql2sql"""
 
 import sys
@@ -1211,6 +1212,14 @@
 WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid''',
                     varmap={'X': 'T00.x', 'X.login': 'T00.l'})
 
+    def test_is_null_transform(self):
+        union = self._prepare('Any X WHERE X login %(login)s')
+        r, args = self.o.generate(union, {'login': None})
+        self.assertLinesEquals((r % args).strip(),
+                               '''SELECT X.cw_eid
+FROM cw_CWUser AS X
+WHERE X.cw_login IS NULL''')
+
     def test_parser_parse(self):
         for t in self._parse(PARSER):
             yield t
--- a/web/box.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/box.py	Thu May 21 00:50:24 2009 +0200
@@ -152,7 +152,7 @@
         entity = self.entity(row, col)
         limit = self.req.property_value('navigation.related-limit') + 1
         role = get_role(self)
-        self.w(u'<div class="sideRelated">')
+        self.w(u'<div class="sideBox">')
         self.wview('sidebox', entity.related(self.rtype, role, limit=limit),
                    title=display_name(self.req, self.rtype, role))
         self.w(u'</div>')
--- a/web/data/cubicweb.css	Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.css	Thu May 21 00:50:24 2009 +0200
@@ -455,6 +455,8 @@
   padding: 0.2em 0px;
   margin-bottom: 0.5em;
   background: #eeedd9;
+  min-width: 21em;
+  max-width: 50em;
 }
 
 ul.sideBox li{ 
@@ -572,24 +574,11 @@
   padding: 0.5em 0.2em 0.2em;
 }
 
-div.sideRelated h4,
-div.sideRelated h5 {
-  margin-top: 0px;
-  margin-bottom: 0px;
-}
-
 div.primaryRight{
   float:right;
   
  }
 
-div.sideRelated {
-  margin-right: 1em;
-  padding: 12px 0px 12px 12px;
-  min-width: 21em;
-  max-width: 50em;
-}
-
 div.metadata {
   font-size: 90%;
   margin: 5px 0px 3px;
--- a/web/data/cubicweb.edition.js	Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.edition.js	Thu May 21 00:50:24 2009 +0200
@@ -342,11 +342,11 @@
 }
 
 
-function handleFormValidationResponse(formid, onsuccess, result) {
+function handleFormValidationResponse(formid, onsuccess, onfailure, result) {
     // Success
     if (result[0]) {
 	if (onsuccess) {
-	    return onsuccess(result[1]);
+	    return onsuccess(result[1], formid);
 	} else {
 	    document.location.href = result[1];
 	    return ;
@@ -365,6 +365,9 @@
     _displayValidationerrors(formid, descr[0], descr[1]);
     updateMessage(_("please correct errors below"));
     document.location.hash = '#header';
+    if (onfailure){
+	onfailure(formid);
+    }
     return false;
 }
 
@@ -426,7 +429,7 @@
  * to the appropriate URL. Otherwise, the validation errors are displayed
  * around the corresponding input fields.
  */
-function validateForm(formid, action, onsuccess) {
+function validateForm(formid, action, onsuccess, onfailure) {
     try {
 	var zipped = formContents(formid);
 	var d = asyncRemoteExec('validate_form', action, zipped[0], zipped[1]);
@@ -435,7 +438,7 @@
 	return false;
     }
     function _callback(result, req) {
-	handleFormValidationResponse(formid, onsuccess, result);
+	handleFormValidationResponse(formid, onsuccess, onfailure, result);
     }
     d.addCallback(_callback);
     return false;
@@ -468,7 +471,7 @@
 	return false;
     }
     d.addCallback(function (result, req) {
-        handleFormValidationResponse(formid, noop, result);
+    handleFormValidationResponse(formid, noop, noop, result);
 	if (reload) {
 	    document.location.href = result[1];
 	} else {
@@ -502,7 +505,7 @@
 	return false;
     }
     d.addCallback(function (result, req) {
-        handleFormValidationResponse(formid, noop, result);
+	    handleFormValidationResponse(formid, noop, noop, result);
 	var fieldview = getNode(divid);
         fieldview.innerHTML = result[2];
 	// switch inline form off only if no error
--- a/web/data/cubicweb.preferences.css	Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.preferences.css	Thu May 21 00:50:24 2009 +0200
@@ -6,98 +6,105 @@
  */
 
 
-table.preferences td{ 
- padding: 0 0.5em 1em;
+.preferences .validateButton{
+ margin-top:0px;
  }
 
-fieldset.preferences{  
+fieldset.preferences{
  border : 1px solid #CFCEB7;
- margin:1em 0;
- padding:0 1em 1em;
+ margin:7px 1em 0;
+ padding:2px 6px 6px;
 }
 
-div.preffield {
- margin-bottom: 0.8em ;
+div.component {
+ margin-left: 1em;
 }
 
-/*
-div.preffield label{ 
- font-size:110%
- }
-*/
-
-div.prefinput{ 
- margin:.3em 0;
-}
-
-div.componentLink{ 
+div.componentLink{
  margin-top:0.3em;
  }
 
 a.componentTitle{
  font-weight:bold;
- color: #000;
+ color: #000/*#0083AB;*/
  }
 
 a.componentTitle:visited{
  color: #000;
 }
 
-
-
 h2.propertiesform a{
  display:block;
  margin: 10px 0px 6px 0px;
  font-weight: bold;
- color: #222211;
+ color: #000;
  padding: 0.2em 0.2em 0.2em 16px;
  background:#eeedd9 url("puce_down.png") 3px center no-repeat;
- font-size:76%;
+ font-size:89%;
 }
 
 h2.propertiesform a:hover{
- color:#000;
  background-color:#cfceb7;
+}
+
+h2.propertiesform a:hover,
+h2.propertiesform a:visited{
  text-decoration:none;
+ color: #000;
 }
 
+div.preffield {
+ margin-bottom: 5px;
+ padding:2px 5px;
+ background:#eeedd9;
+}
+
+div.prefinput{
+ margin:.3em;
+}
+
+
 div.prefinput select.changed,
-div.prefinput input.changed{ 
- background:#eeedd9;
- border: 1px solid #eeedd9;
+div.prefinput input.changed{
+ border: 1px solid #000;
+ font-weight:bold;
+
 }
 
 div.prefinput select,
-div.prefinput input{ 
+div.prefinput input{
  background:#fff;
  border: 1px solid #CFCEB7;
 }
 
 .prefinput input.error {
- background:transparent url(error.png) no-repeat scroll 100% 50% !important;
+ /* background:#fff url(error.png) no-repeat scroll 100% 50% !important; */
+ border:1px solid red !important;
+ color:red;
+ padding-right:1em;
 }
 
 
-div.formsg{ 
+div.formsg{
  font-weight:bold;
  margin:0.5em 0px;
- }
+}
 
 
-div.formsg .critical{ 
+div.critical{
  color:red;
  padding-left:20px;
  background:#fff url(critical.png) no-repeat;
  }
 
-div.formsg .message{ 
+div.formsg .msg{
  color : green;
 }
 
 .helper{
   font-size: 96%;
   color: #555544;
-  padding:0; 
+  padding:0;
 }
 
 div.prefinput .helper:hover {
@@ -105,11 +112,6 @@
   cursor: default;
 }
 
-.error{ 
- color:red;
- padding-right:1em;
+div.openlink{
+ display:inline;
  }
-
-div.openlink{ 
- display:inline;
- }
\ No newline at end of file
--- a/web/data/cubicweb.preferences.js	Thu May 21 00:44:57 2009 +0200
+++ b/web/data/cubicweb.preferences.js	Thu May 21 00:50:24 2009 +0200
@@ -4,23 +4,21 @@
  *     move me in a more appropriate place
  */
 
-function toggleVisibility(elemId, cookiename) {
-    _clearPreviousMessages();
-    jqNode(elemId).toggleClass('hidden');
-    asyncRemoteExec('set_cookie', cookiename,
-                      jQuery('#' + elemId).attr('class'));
+function togglePrefVisibility(elemId) {
+    clearPreviousMessages();
+    jQuery('#' + elemId).toggleClass('hidden');
 }
 
 function closeFieldset(fieldsetid){
     var linklabel = _('open all');
-    var linkhref = 'javascript:openFieldset("' +fieldsetid + '")'
-    _toggleFieldset(fieldsetid, 1, linklabel, linkhref)
+    var linkhref = 'javascript:openFieldset("' +fieldsetid + '")';
+    _toggleFieldset(fieldsetid, 1, linklabel, linkhref);
 }
 
 function openFieldset(fieldsetid){
     var linklabel = _('close all');
-    var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")'
-    _toggleFieldset(fieldsetid, 0, linklabel, linkhref)
+    var linkhref = 'javascript:closeFieldset("'+ fieldsetid + '")';
+    _toggleFieldset(fieldsetid, 0, linklabel, linkhref);
 }
 
 
@@ -28,13 +26,13 @@
     jQuery('#'+fieldsetid).find('div.openlink').each(function(){
 	    var link = A({'href' : "javascript:noop();",
 			  'onclick' : linkhref},
-			  linklabel)
+			  linklabel);
 	    jQuery(this).empty().append(link);
 	});
     jQuery('#'+fieldsetid).find('fieldset[id]').each(function(){
 	    var fieldset = jQuery(this);
 	    if(closeaction){
-		fieldset.addClass('hidden')
+		fieldset.addClass('hidden');
 	    }else{
 		fieldset.removeClass('hidden');
 		linkLabel = (_('open all'));
@@ -43,44 +41,24 @@
 }
 
 function validatePrefsForm(formid){
-    var form = getNode(formid);
-    freezeFormButtons(formid);
-    try {
-	var d = _sendForm(formid, null);
-    } catch (ex) {
-	log('got exception', ex);
-	return false;
-    }
-    function _callback(result, req) {
-	_clearPreviousMessages();
-	_clearPreviousErrors(formid);
-	// success
-	if(result[0]){
-	    return submitSucces(formid)
-	}
- 	// Failures
-	unfreezeFormButtons(formid);
-	var descr = result[1];
-        if (!isArrayLike(descr) || descr.length != 2) {
-	   log('got strange error :', descr);
-	   updateMessage(descr);
-	   return ;
-	}
-        _displayValidationerrors(formid, descr[0], descr[1]);
-	var dom = DIV({'class':'critical'},
-		      _("please correct errors below"));
-	jQuery(form).find('div.formsg').empty().append(dom);
-	updateMessage(_(""));
-	return false;
-    }
-    d.addCallback(_callback);
-    return false;
+    clearPreviousMessages();
+    clearPreviousErrors(formid);
+    return validateForm(formid, null,  submitSucces, submitFailure);
 }
 
-function submitSucces(formid){
+function submitFailure(formid){
+    var form = jQuery('#'+formid);
+    var dom = DIV({'class':'critical'},
+		  _("please correct errors below"));
+    jQuery(form).find('div.formsg').empty().append(dom);
+    // clearPreviousMessages()
+    jQuery(form).find('span.error').next().focus();
+}
+
+function submitSucces(url, formid){
     var form = jQuery('#'+formid);
     setCurrentValues(form);
-    var dom = DIV({'class':'message'},
+    var dom = DIV({'class':'msg'},
 		  _("changes applied"));
     jQuery(form).find('div.formsg').empty().append(dom);
     jQuery(form).find('input').removeClass('changed');
@@ -88,64 +66,63 @@
     return;
 }
 
-function _clearPreviousMessages() {
+function clearPreviousMessages() {
     jQuery('div#appMsg').addClass('hidden');
     jQuery('div.formsg').empty();
 }
 
-function _clearPreviousErrors(formid) {
-    jQuery('#' + formid + ' span.error').remove();
+function clearPreviousErrors(formid) {
+    jQuery('#err-value:' + formid).remove();
 }
 
 
 function checkValues(form, success){
     var unfreezeButtons = false;
-    jQuery(form).find('select').each(function () { 
+    jQuery(form).find('select').each(function () {
 	    unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
 	});
     jQuery(form).find('[type=text]').each(function () {
 	    unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
 	});
-    jQuery(form).find('input[type=radio]').each(function () { 
+    jQuery(form).find('input[type=radio]').each(function () {
 	    if (jQuery(this).attr('checked')){
 		unfreezeButtons = _checkValue(jQuery(this), unfreezeButtons);
 	    }
-     }); 
-   
+     });
+
     if (unfreezeButtons){
 	unfreezeFormButtons(form.attr('id'));
     }else{
 	if (!success){
-	    _clearPreviousMessages();
+	    clearPreviousMessages();
 	}
-	_clearPreviousErrors(form.attr('id'));
+	clearPreviousErrors(form.attr('id'));
 	freezeFormButtons(form.attr('id'));
     }
 }
 
 function _checkValue(input, unfreezeButtons){
-     var currentValueInput = jQuery("input[id=current-" + input.attr('name') + "]");
+     var currentValueInput = jQuery("input[name=current-" + input.attr('name') + "]");
      if (currentValueInput.attr('value') != input.attr('value')){
 	 input.addClass('changed');
 	 unfreezeButtons = true;
      }else{
 	 input.removeClass('changed');
 	 jQuery("span[id=err-" + input.attr('id') + "]").remove();
-     }	
+     }
      input.removeClass('error');
-     return unfreezeButtons
+     return unfreezeButtons;
 }
 
 
 function setCurrentValues(form){
-    jQuery(form).find('input[id^=current-value]').each(function () { 
+    jQuery(form).find('input[name^=current-value]').each(function () {
 	    var currentValueInput = jQuery(this);
-	    var name = currentValueInput.attr('id').split('-')[1];
+	    var name = currentValueInput.attr('name').split('-')[1];
 	    jQuery(form).find("[name=" + name + "]").each(function (){
 		    var input = jQuery(this);
 		    if(input.attr('type')=='radio'){
 			if(input.attr('checked')){
-			    log(input.attr('value'));
 			    currentValueInput.attr('value', input.attr('value'));
 			}
 		    }else{
@@ -155,20 +132,20 @@
     });
 }
 
-
 function initEvents(){
-  jQuery('form').each(function() { 
+  jQuery('form').each(function() {
 	  var form = jQuery(this);
 	  freezeFormButtons(form.attr('id'));
-	  form.find('input[type=text]').keyup(function(){  
-		  checkValues(form);	   
+	  form.find('input[type=text]').keyup(function(){
+		  checkValues(form);
           });
-	  form.find('input[type=radio]').change(function(){  
-		  checkValues(form);	   
+	  form.find('input[type=radio]').change(function(){
+		  checkValues(form);
           });
-	  form.find('select').change(function(){  
-		  checkValues(form);	 
+	  form.find('select').change(function(){
+		  checkValues(form);
           });
+	  setCurrentValues(form);
     });
 }
 
--- a/web/form.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/form.py	Thu May 21 00:50:24 2009 +0200
@@ -372,10 +372,15 @@
             return self.form_previous_values[qname]
         if qname in self.req.form:
             return self.req.form[qname]
+        if field.name in self.req.form:
+            return self.req.form[field.name]
         return None
 
     def form_field_value(self, field, load_bytes=False):
         """return field's *typed* value"""
+        myattr = '%s_%s_default' % (field.role, field.name)
+        if hasattr(self, myattr):
+            return getattr(self, myattr)()
         value = field.initial
         if callable(value):
             value = value(self)
--- a/web/formfields.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/formfields.py	Thu May 21 00:50:24 2009 +0200
@@ -213,20 +213,21 @@
         try:
             return req.data[self]
         except KeyError:
+            fkwargs = {}
             if self.use_fckeditor(form):
                 # if fckeditor is used and format field isn't explicitly
                 # deactivated, we want an hidden field for the format
-                widget = HiddenInput()
-                choices = None
+                fkwargs['widget'] = HiddenInput()
+                fkwargs['initial'] = 'text/html'
             else:
                 # else we want a format selector
-                # XXX compute vocabulary
-                widget = Select()
+                fkwargs['widget'] = Select()
+                fkwargs['widget'].attrs['size'] = 1
                 fcstr = FormatConstraint()
-                choices = [(req._(fmt), fmt) for fmt in fcstr.vocabulary(req=req)]
-                widget.attrs['size'] = 1
-            field = StringField(name=self.name + '_format', widget=widget,
-                                choices=choices)
+                fkwargs['choices'] = fcstr.vocabulary(req=req)
+                fkwargs['internationalizable'] = True
+                fkwargs['initial'] = lambda f: f.form_field_format(self)
+            field = StringField(name=self.name + '_format', **fkwargs)
             req.data[self] = field
             return field
 
@@ -471,7 +472,10 @@
                     kwargs.setdefault('widget', Select())
                     kwargs.setdefault('choices', cstr.vocabulary)
                     if card in '?1':
+                        if isinstance(kwargs['widget'], type):
+                            kwargs['widget'] = kwargs['widget']()
                         kwargs['widget'].attrs.setdefault('size', 1)
+            for cstr in constraints:
                 if isinstance(cstr, SizeConstraint) and cstr.max is not None:
                     if cstr.max < 257:
                         kwargs.setdefault('widget', TextInput())
--- a/web/formrenderers.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/formrenderers.py	Thu May 21 00:50:24 2009 +0200
@@ -83,13 +83,13 @@
         return tags.label(label, **attrs)
 
     def render_help(self, form, field):
-        help = [ u'<br/>' ]
+	help = []
         descr = field.help
         if descr:
-            help.append('<span class="helper">%s</span>' % form.req._(descr))
+            help.append('<div class="helper">%s</div>' % form.req._(descr))
         example = field.example_format(form.req)
         if example:
-            help.append('<span class="helper">(%s: %s)</span>'
+            help.append('<div class="helper">(%s: %s)</div>'
                         % (form.req._('sample format'), example))
         return u'&nbsp;'.join(help)
 
--- a/web/formwidgets.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/formwidgets.py	Thu May 21 00:50:24 2009 +0200
@@ -199,27 +199,8 @@
 
     def render(self, form, field):
         name, curvalues, attrs = self._render_attrs(form, field)
-        options = []
-        for label, value in field.vocabulary(form):
-            if value in curvalues:
-                tag = tags.input(name=name, value=value, type=self.type,
-                                 checked='checked', **attrs)
-            else:
-                tag = tags.input(name=name, value=value, type=self.type,
-                                 **attrs)
-            options.append(tag + label)
-        return '<br/>\n'.join(options)
-
-
-class Radio(Input):
-    """<input type='radio'>, for field having a specific vocabulary. One
-    input will be generated for each possible value.
-    """
-    type = 'radio'
-
-    def render(self, form, field):
-        name, curvalues, attrs = self._render_attrs(form, field)
         domid = attrs.pop('id', None)
+        sep = attrs.pop('separator', u'<br/>')
         options = []
         for i, (label, value) in enumerate(field.vocabulary(form)):
             iattrs = attrs.copy()
@@ -228,10 +209,16 @@
             if value in curvalues:
                 iattrs['checked'] = u'checked'
             tag = tags.input(name=name, type=self.type, value=value, **iattrs)
-            options.append(tag + label + '<br/>')
+            options.append(tag + label + sep)
         return '\n'.join(options)
 
 
+class Radio(CheckBox):
+    """<input type='radio'>, for field having a specific vocabulary. One
+    input will be generated for each possible value.
+    """
+    type = 'radio'
+
 # javascript widgets ###########################################################
 
 class DateTimePicker(TextInput):
@@ -318,20 +305,21 @@
             values = (INTERNAL_FIELD_VALUE,)
         init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
         # XXX entity form specific
-        attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity)
+        attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
         return name, values, attrs
 
-    def _get_url(self, entity):
-        return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
-                                pageid=entity.req.pageid, mode='remote')
+    def _get_url(self, entity, field):
+        fname = entity.autocomplete_initfuncs[field.name]
+        return entity.req.build_url('json', fname=fname, mode='remote',
+                                    pageid=entity.req.pageid)
 
 
 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
     """XXX describe me"""
     wdgtype = 'StaticFileSuggestField'
 
-    def _get_url(self, entity):
-        return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
+    def _get_url(self, entity, field):
+        return entity.req.datadir_url + entity.autocomplete_initfuncs[field.name]
 
 
 class RestrictedAutoCompletionWidget(AutoCompletionWidget):
--- a/web/htmlwidgets.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/htmlwidgets.py	Thu May 21 00:50:24 2009 +0200
@@ -57,6 +57,7 @@
     def append(self, item):
         self.items.append(item)
 
+    title_class = 'boxTitle'
     main_div_class = 'boxContent'
     listing_class = 'boxListing'
 
@@ -82,7 +83,7 @@
                 title = '<span>%s</span>' % html_escape(self.title)
             else:
                 title = '<span>%s</span>' % self.title
-            self.w(u'<div class="boxTitle">%s</div>' % title)
+            self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
         if self.items:
             self.box_begin_content()
             for item in self.items:
@@ -93,6 +94,7 @@
 
 class SideBoxWidget(BoxWidget):
     """default CubicWeb's sidebox widget"""
+    title_class = u'sideBoxTitle'
     main_div_class = u'sideBoxBody'
     listing_class = ''
 
--- a/web/test/data/schema/testschema.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/test/data/schema/testschema.py	Thu May 21 00:50:24 2009 +0200
@@ -1,6 +1,7 @@
 class Salesterm(EntityType):
     described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
     amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
+    reason = String(maxsize=20, vocabulary=[u'canceled', u'sold'])
 
 class tags(RelationDefinition):
     subject = 'Tag'
--- a/web/test/unittest_form.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/test/unittest_form.py	Thu May 21 00:50:24 2009 +0200
@@ -64,7 +64,14 @@
         self.assertEquals(len(states), 1)
         self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
 
-
+    def test_consider_req_form_params(self):
+        e = self.etype_instance('CWUser')
+        e.eid = 'A'
+        form = EntityFieldsForm(self.request(login=u'toto'), None, entity=e)
+        field = StringField(name='login', eidparam=True)
+        form.append_field(field)
+        form.form_build_context({})
+        self.assertEquals(form.form_field_display_value(field, {}), 'toto')
 
     # form view tests #########################################################
 
--- a/web/test/unittest_formfields.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/test/unittest_formfields.py	Thu May 21 00:50:24 2009 +0200
@@ -1,11 +1,15 @@
 """unittests for cw.web.formfields"""
 
 from logilab.common.testlib import TestCase, unittest_main
+
+from yams.constraints import StaticVocabularyConstraint, SizeConstraint
+
 from cubicweb.devtools import TestServerConfiguration
-from cubicweb.web.formwidgets import PasswordInput, TextArea
+from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.web.form import EntityFieldsForm
+from cubicweb.web.formwidgets import PasswordInput, TextArea, Select
 from cubicweb.web.formfields import *
-from cubicweb.entities.wfobjs import State
-from cubicweb.entities.authobjs import CWUser
+
 from cubes.file.entities import File
 
 config = TestServerConfiguration('data')
@@ -14,6 +18,7 @@
 state_schema = schema['State']
 cwuser_schema = schema['CWUser']
 file_schema = schema['File']
+salesterm_schema = schema['Salesterm']
 
 class GuessFieldTC(TestCase):
 
@@ -41,12 +46,13 @@
         self.assertEquals(description_format_field.sort, True)
         self.assertEquals(description_format_field.initial(None), 'text/rest')
 
+
 #         wikiid_field = guess_field(state_schema, schema['wikiid'])
 #         self.assertIsInstance(wikiid_field, StringField)
 #         self.assertEquals(wikiid_field.required, False)
 
 
-    def test_euser_fields(self):
+    def test_cwuser_fields(self):
         upassword_field = guess_field(cwuser_schema, schema['upassword'])
         self.assertIsInstance(upassword_field, StringField)
         self.assertIsInstance(upassword_field.widget, PasswordInput)
@@ -80,5 +86,28 @@
         self.assertIsInstance(data_field.format_field, StringField)
         self.assertIsInstance(data_field.encoding_field, StringField)
 
+    def test_constraints_priority(self):
+        salesterm_field = guess_field(salesterm_schema, schema['reason'])
+        constraints = schema['reason'].rproperty('Salesterm', 'String', 'constraints')
+        self.assertEquals([c.__class__ for c in constraints],
+                          [SizeConstraint, StaticVocabularyConstraint])
+        self.assertIsInstance(salesterm_field.widget, Select)
+
+class MoreFieldsTC(EnvBasedTC):
+    def test_rtf_format_field(self):
+        req = self.request()
+        req.use_fckeditor = lambda: False
+        e = self.etype_instance('State')
+        form = EntityFieldsForm(req, entity=e)
+        description_field = guess_field(state_schema, schema['description'])
+        description_format_field = description_field.get_format_field(form)
+        self.assertEquals(description_format_field.internationalizable, True)
+        self.assertEquals(description_format_field.sort, True)
+        # unlike below, initial is bound to form.form_field_format
+        self.assertEquals(description_format_field.initial(form), 'text/html')
+        self.execute('INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
+        self.commit()
+        self.assertEquals(description_format_field.initial(form), 'text/rest')
+
 if __name__ == '__main__':
     unittest_main()
--- a/web/uicfg.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/uicfg.py	Thu May 21 00:50:24 2009 +0200
@@ -97,8 +97,9 @@
             section = 'sideboxes'
         rtag.tag_relation((sschema, rschema, oschema, role), section)
 
-primaryview_section = RelationTags(init_primaryview_section,
-                                    frozenset(('attributes', 'relations',
+primaryview_section = RelationTags('primaryview_section',
+                                   init_primaryview_section,
+                                   frozenset(('attributes', 'relations',
                                                'sideboxes', 'hidden')))
 for rtype in ('eid', 'creation_date', 'modification_date',
               'is', 'is_instance_of', 'identity',
@@ -108,6 +109,8 @@
               'see_also'):
     primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
     primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
+primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
+primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
 
 for attr in ('name', 'meta', 'final'):
     primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
@@ -140,7 +143,8 @@
         rtag.tag_relation((sschema, rschema, oschema, role), displayinfo)
     displayinfo.setdefault('label', label)
 
-primaryview_display_ctrl = DisplayCtrlRelationTags(init_primaryview_display_ctrl)
+primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
+                                                   init_primaryview_display_ctrl)
 
 
 # index view configuration ####################################################
@@ -178,7 +182,7 @@
             section = 'generic'
         rtag.tag_relation((sschema, rschema, oschema, role), section)
 
-autoform_section = RelationTags(init_autoform_section,
+autoform_section = RelationTags('autoform_section', init_autoform_section,
                                 set(('primary', 'secondary', 'generic',
                                      'metadata', 'generated')))
 # use primary and not generated for eid since it has to be an hidden
@@ -217,7 +221,7 @@
 
 
 # relations'field class
-autoform_field = RelationTags()
+autoform_field = RelationTags('autoform_field')
 
 # relations'field explicit kwargs (given to field's __init__)
 autoform_field_kwargs = RelationTags()
@@ -231,7 +235,7 @@
 # inlined view flag for non final relations: when True for an entry, the
 # entity(ies) at the other end of the relation will be editable from the
 # form of the edited entity
-autoform_is_inlined = RelationTagsBool()
+autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
 autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
 autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
 autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
@@ -241,7 +245,7 @@
 # set of tags of the form <action>_on_new on relations. <action> is a
 # schema action (add/update/delete/read), and when such a tag is found
 # permissions checking is by-passed and supposed to be ok
-autoform_permissions_overrides = RelationTagsSet()
+autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
 
 
 # boxes.EditBox configuration #################################################
@@ -254,7 +258,8 @@
                rschema.rproperty(sschema, oschema, 'composite') == role:
             rtag.tag_relation((sschema, rschema, oschema, role), True)
 
-actionbox_appearsin_addmenu = RelationTagsBool(init_actionbox_appearsin_addmenu)
+actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
+                                               init_actionbox_appearsin_addmenu)
 actionbox_appearsin_addmenu.tag_subject_of(('*', 'is', '*'), False)
 actionbox_appearsin_addmenu.tag_object_of(('*', 'is', '*'), False)
 actionbox_appearsin_addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
@@ -279,3 +284,4 @@
 actionbox_appearsin_addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
 actionbox_appearsin_addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
 actionbox_appearsin_addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
+
--- a/web/views/actions.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/actions.py	Thu May 21 00:50:24 2009 +0200
@@ -9,7 +9,7 @@
 from cubicweb.vregistry import objectify_selector
 from cubicweb.selectors import (EntitySelector,
     one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
-    non_final_entity,
+    nonempty_rset, non_final_entity,
     authenticated_user, match_user_groups, match_search_state,
     has_permission, has_add_permission,
     )
@@ -74,7 +74,7 @@
     if accept match.
     """
     id = 'select'
-    __select__ = match_search_state('linksearch') & match_searched_etype()
+    __select__ = match_search_state('linksearch') & nonempty_rset() & match_searched_etype()
 
     title = _('select')
     category = 'mainactions'
--- a/web/views/basecontrollers.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/basecontrollers.py	Thu May 21 00:50:24 2009 +0200
@@ -18,7 +18,7 @@
 from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
 from cubicweb.utils import strptime
 from cubicweb.selectors import yes, match_user_groups
-from cubicweb.view import STRICT_DOCTYPE
+from cubicweb.view import STRICT_DOCTYPE, STRICT_DOCTYPE_NOEXT
 from cubicweb.common.mail import format_mail
 from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
 from cubicweb.web.formrenderers import FormRenderer
@@ -32,8 +32,13 @@
     HAS_SEARCH_RESTRICTION = False
 
 
-def xhtml_wrap(source):
-    head = u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE
+def xhtml_wrap(self, source):
+    # XXX factor out, watch view.py ~ Maintemplate.doctype
+    if self.req.xhtml_browser():
+        dt = STRICT_DOCTYPE
+    else:
+        dt = STRICT_DOCTYPE_NOEXT
+    head = u'<?xml version="1.0"?>\n' + dt
     return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
 
 def jsonize(func):
@@ -51,7 +56,7 @@
     def wrapper(self, *args, **kwargs):
         self.req.set_content_type(self.req.html_content_type())
         result = func(self, *args, **kwargs)
-        return xhtml_wrap(result)
+        return xhtml_wrap(self, result)
     wrapper.__name__ = func.__name__
     return wrapper
 
@@ -205,7 +210,7 @@
         self.req.set_content_type('text/html')
         jsarg = simplejson.dumps( (status, args) )
         return """<script type="text/javascript">
- window.parent.handleFormValidationResponse('entityForm', null, %s);
+ window.parent.handleFormValidationResponse('entityForm', null, null, %s);
 </script>""" %  simplejson.dumps( (status, args) )
 
     def validation_error(self, err):
@@ -379,11 +384,7 @@
             ctrl.publish(None, fromjson=True)
         except ValidationError, err:
             self.req.cnx.rollback()
-            if not err.entity or isinstance(err.entity, (long, int)):
-                eid = err.entity
-            else:
-                eid = err.entity.eid
-            return (False, (eid, err.errors))
+            return (False, (err.entity, err.errors))
         except Redirect, redir:
             return (True, redir.location)
         except Exception, err:
@@ -484,6 +485,24 @@
         rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
         self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
 
+    def js_node_clicked(self, treeid, nodeeid):
+        """add/remove eid in treestate cookie"""
+        from cubicweb.web.views.treeview import treecookiename
+        cookies = self.req.get_cookie()
+        statename = treecookiename(treeid)
+        treestate = cookies.get(statename)
+        if treestate is None:
+            cookies[statename] = nodeeid
+            self.req.set_cookie(cookies, statename)
+        else:
+            marked = set(filter(None, treestate.value.split(';')))
+            if nodeeid in marked:
+                marked.remove(nodeeid)
+            else:
+                marked.add(nodeeid)
+            cookies[statename] = ';'.join(marked)
+            self.req.set_cookie(cookies, statename)
+
     def js_set_cookie(self, cookiename, cookievalue):
         # XXX we should consider jQuery.Cookie
         cookiename, cookievalue = str(cookiename), str(cookievalue)
--- a/web/views/basetemplates.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/basetemplates.py	Thu May 21 00:50:24 2009 +0200
@@ -7,7 +7,6 @@
 """
 __docformat__ = "restructuredtext en"
 
-
 from logilab.mtconverter import html_escape
 
 from cubicweb.vregistry import objectify_selector
--- a/web/views/baseviews.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/baseviews.py	Thu May 21 00:50:24 2009 +0200
@@ -11,8 +11,8 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 #from __future__ import with_statement
-
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from rql import nodes
 
@@ -23,7 +23,6 @@
 from cubicweb.view import EntityView, AnyRsetView, View
 from cubicweb.common.uilib import cut, printable_value
 
-_ = unicode
 
 class NullView(AnyRsetView):
     """default view when no result has been found"""
@@ -204,9 +203,10 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         desc = cut(entity.dc_description(), 50)
-        self.w(u'<a href="%s" title="%s">' % (html_escape(entity.absolute_url()),
-                                              html_escape(desc)))
-        self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
+        self.w(u'<a href="%s" title="%s">' % (
+            html_escape(entity.absolute_url()), html_escape(desc)))
+        self.w(html_escape(self.view('textincontext', self.rset,
+                                     row=row, col=col)))
         self.w(u'</a>')
 
 
@@ -214,8 +214,12 @@
     id = 'outofcontext'
 
     def cell_call(self, row, col):
-        self.w(u'<a href="%s">' % self.entity(row, col).absolute_url())
-        self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
+        entity = self.entity(row, col)
+        desc = cut(entity.dc_description(), 50)
+        self.w(u'<a href="%s" title="%s">' % (
+            html_escape(entity.absolute_url()), html_escape(desc)))
+        self.w(html_escape(self.view('textoutofcontext', self.rset,
+                                     row=row, col=col)))
         self.w(u'</a>')
 
 
--- a/web/views/boxes.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/boxes.py	Thu May 21 00:50:24 2009 +0200
@@ -13,6 +13,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from logilab.mtconverter import html_escape
 
@@ -23,7 +24,6 @@
 from cubicweb.web import uicfg
 from cubicweb.web.box import BoxTemplate
 
-_ = unicode
 
 class EditBox(BoxTemplate):
     """
@@ -99,10 +99,10 @@
         eschema = entity.e_schema
         for rschema, teschema, x in self.add_related_schemas(entity):
             if x == 'subject':
-                label = '%s %s %s %s' % (eschema, rschema, teschema, x)
+                label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'object')
             else:
-                label = '%s %s %s %s' % (teschema, rschema, eschema, x)
+                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'subject')
             actions.append(self.mk_action(_(label), url))
         return actions
--- a/web/views/calendar.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/calendar.py	Thu May 21 00:50:24 2009 +0200
@@ -4,6 +4,8 @@
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
+__docformat__ = "restructuredtext en"
+_ = unicode
 
 from datetime import datetime, date, timedelta
 
@@ -14,7 +16,6 @@
 from cubicweb.utils import strptime, date_range, todate, todatetime
 from cubicweb.view import EntityView
 
-_ = unicode
 
 # useful constants & functions ################################################
 
@@ -81,7 +82,8 @@
             task = self.complete_entity(i)
             self.w(u'<div class="vevent">')
             self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
-            self.w(u'<div class="description">%s</div>' % html_escape(task.dc_description()))
+            self.w(u'<div class="description">%s</div>'
+                   % task.dc_description(format='text/html'))
             if task.start:
                 self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self.format_date(task.start)))
             if task.stop:
--- a/web/views/cwproperties.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/cwproperties.py	Thu May 21 00:50:24 2009 +0200
@@ -17,12 +17,13 @@
 from cubicweb.view import StartupView
 from cubicweb.web import uicfg, stdmsgs
 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
+from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.formfields import FIELDS, StringField
 from cubicweb.web.formwidgets import Select, Button, SubmitButton
 from cubicweb.web.views import primary
 
 
-# some string we want to be internationalizable for nicer display of eproperty
+# some string we want to be internationalizable for nicer display of property
 # groups
 _('navigation')
 _('ui')
@@ -48,10 +49,9 @@
 _('category')
 
 
-def make_togglable_link(nodeid, label, cookiename):
+def make_togglable_link(nodeid, label):
     """builds a HTML link that switches the visibility & remembers it"""
-    action = u"javascript: toggleVisibility('%s', '%s')" % \
-        (nodeid, cookiename)
+    action = u"javascript: togglePrefVisibility('%s')" % nodeid
     return u'<a href="%s">%s</a>' % (action, label)
 
 def css_class(someclass):
@@ -63,8 +63,8 @@
     skip_none = False
 
 
-class SystemEPropertiesForm(FormViewMixIn, StartupView):
-    id = 'systemepropertiesform'
+class SystemCWPropertiesForm(FormViewMixIn, StartupView):
+    id = 'systempropertiesform'
     __select__ = none_rset() & match_user_groups('managers')
 
     title = _('site configuration')
@@ -95,15 +95,15 @@
 
     def call(self, **kwargs):
         """The default view representing the application's index"""
-        self.req.add_js('cubicweb.preferences.js')
+        self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
         self.req.add_css('cubicweb.preferences.css')
         vreg = self.vreg
         values = self.defined_keys
         groupedopts = {}
         mainopts = {}
-        # "self.id=='systemepropertiesform'" to skip site wide properties on
+        # "self.id=='systempropertiesform'" to skip site wide properties on
         # user's preference but not site's configuration
-        for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
+        for key in vreg.user_property_keys(self.id=='systempropertiesform'):
             parts = key.split('.')
             if parts[0] in vreg:
                 # appobject configuration
@@ -113,61 +113,73 @@
                 mainopts.setdefault(parts[0], []).append(key)
         # precompute form to consume error message
         for group, keys in mainopts.items():
-            mainopts[group] = self.form(keys, True)
+            mainopts[group] = self.form(group, keys, False)
+
         for group, objects in groupedopts.items():
             for oid, keys in objects.items():
-                groupedopts[group][oid] = self.form(keys, True)
+                groupedopts[group][oid] = self.form(group + '-' + oid, keys, True)
+
         w = self.w
         req = self.req
         _ = req._
         w(u'<h1>%s</h1>\n' % _(self.title))
-        # we don't want this in each sub-forms
-        w(u'<div id="progress">%s</div>' % self.req._('validating...'))
         for label, group, form in sorted((_(g), g, f)
                                          for g, f in mainopts.iteritems()):
             status = css_class(self._group_status(group))
             w(u'<h2 class="propertiesform">%s</h2>\n' %
-              (make_togglable_link('fieldset_' + group, label,
-                                   self._cookie_name(group))))
+            (make_togglable_link('fieldset_' + group, label.capitalize())))
             w(u'<div id="fieldset_%s" %s>' % (group, status))
+            w(u'<fieldset class="preferences">')
             w(form)
-            w(u'</div>')
+            w(u'</fieldset></div>')
+
         for label, group, objects in sorted((_(g), g, o)
                                             for g, o in groupedopts.iteritems()):
             status = css_class(self._group_status(group))
             w(u'<h2 class="propertiesform">%s</h2>\n' %
-              (make_togglable_link('fieldset_' + group, label,
-                                   self._cookie_name(group))))
+              (make_togglable_link('fieldset_' + group, label.capitalize())))
             w(u'<div id="fieldset_%s" %s>' % (group, status))
-            for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
-                                           for o, f in objects.iteritems()):
-                w(u'<fieldset class="subentity">')
-                w(u'<legend class="componentTitle">%s</legend>\n' % label)
+
+            # create selection
+            sorted_objects =  sorted((self.req.__('%s_%s' % (group, o)), o, f)
+                                           for o, f in objects.iteritems())
+            for label, oid, form in sorted_objects:
+                w(u'<div class="component">')
+                w(u'''<div class="componentLink"><a href="javascript:noop();"
+                           onclick="javascript:toggleVisibility('field_%(oid)s_%(group)s')"
+                           class="componentTitle">%(label)s</a>''' % {'label':label, 'oid':oid, 'group':group})
+                w(u''' (<div class="openlink"><a href="javascript:noop();"
+                             onclick="javascript:openFieldset('fieldset_%(group)s')">%(label)s</a></div>)'''
+                  % {'label':_('open all'), 'group':group})
+                w(u'</div>')
                 docmsgid = '%s_%s_description' % (group, oid)
                 doc = _(docmsgid)
                 if doc != docmsgid:
-                    w(u'<p class="description">%s</p>' % html_escape(doc))
+                    w(u'<div class="helper">%s</div>' % html_escape(doc).capitalize())
+                w(u'</div>')
+                w(u'<fieldset id="field_%(oid)s_%(group)s" class="%(group)s preferences hidden">'
+                  % {'oid':oid, 'group':group})
                 w(form)
                 w(u'</fieldset>')
             w(u'</div>')
 
     @property
     @cached
-    def eprops_rset(self):
+    def cwprops_rset(self):
         return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
                                 'P value V, NOT P for_user U')
 
     @property
     def defined_keys(self):
         values = {}
-        for i, entity in enumerate(self.eprops_rset.entities()):
+        for i, entity in enumerate(self.cwprops_rset.entities()):
             values[entity.pkey] = i
         return values
 
     def entity_for_key(self, key):
         values = self.defined_keys
         if key in values:
-            entity = self.eprops_rset.get_entity(values[key], 0)
+            entity = self.cwprops_rset.get_entity(values[key], 0)
         else:
             entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
             entity.eid = self.req.varmaker.next()
@@ -175,11 +187,11 @@
             entity['value'] = self.vreg.property_value(key)
         return entity
 
-    def form(self, keys, splitlabel=False):
-        buttons = [SubmitButton(),
-                   Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
-        form = CompositeForm(self.req, domid=None, action=self.build_url(),
+    def form(self, formid, keys, splitlabel=False):
+        buttons = [SubmitButton()]
+        form = CompositeForm(self.req, domid=formid, action=self.build_url(),
                              form_buttons=buttons,
+                             onsubmit="return validatePrefsForm('%s')" % formid,
                              submitmsg=self.req._('changes applied'))
         path = self.req.relative_path()
         if '?' in path:
@@ -188,7 +200,9 @@
         form.form_add_hidden('__redirectpath', path)
         for key in keys:
             self.form_row(form, key, splitlabel)
-        return form.form_render(display_progress_div=False)
+        renderer = CWPropertiesFormRenderer()
+        return form.form_render(display_progress_div=False,
+                                renderer=renderer)
 
     def form_row(self, form, key, splitlabel):
         entity = self.entity_for_key(key)
@@ -197,20 +211,21 @@
         else:
             label = key
         subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False)
+
         subform.append_field(PropertyValueField(name='value', label=label,
                                                 eidparam=True))
         subform.vreg = self.vreg
         subform.form_add_hidden('pkey', key, eidparam=True)
+ 	subform.form_add_hidden("current-value:%s" % entity.eid,)
         form.form_add_subform(subform)
         return subform
 
-
 def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
     return req.user.eid == rset[row or 0][col]
 
 
-class EPropertiesForm(SystemEPropertiesForm):
-    id = 'epropertiesform'
+class CWPropertiesForm(SystemCWPropertiesForm):
+    id = 'propertiesform'
     __select__ = (
         # we don't want guests to be able to come here
         match_user_groups('users', 'managers') &
@@ -228,19 +243,19 @@
 
     @property
     @cached
-    def eprops_rset(self):
+    def cwprops_rset(self):
         return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
                                 'P for_user U, U eid %(x)s', {'x': self.user.eid})
 
     def form_row(self, form, key, splitlabel):
-        subform = super(EPropertiesForm, self).form_row(form, key, splitlabel)
+        subform = super(CWPropertiesForm, self).form_row(form, key, splitlabel)
         # if user is in the managers group and the property is being created,
         # we have to set for_user explicitly
         if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
             subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
 
 
-# eproperty form objects ######################################################
+# cwproperty form objects ######################################################
 
 class PlaceHolderWidget(object):
 
@@ -341,3 +356,29 @@
 
 uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
 uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
+
+
+class CWPropertiesFormRenderer(FormRenderer):
+    """specific renderer for properties"""
+
+    def open_form(self, form, values):
+        err = '<div class="formsg"></div>'
+        return super(CWPropertiesFormRenderer, self).open_form(form, values) + err
+
+    def _render_fields(self, fields, w, form):
+        for field in fields:
+            w(u'<div class="preffield">\n')
+            if self.display_label:
+                w(u'%s' % self.render_label(form, field))
+            error = form.form_field_error(field)
+            w(u'%s' % self.render_help(form, field))
+            w(u'<div class="prefinput">')
+            w(field.render(form, self))
+            w(u'</div>')
+            w(u'</div>')
+
+    def render_buttons(self, w, form):
+        w(u'<div>\n')
+        for button in form.form_buttons:
+            w(u'%s\n' % button.render(form))
+        w(u'</div>')
--- a/web/views/editforms.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/editforms.py	Thu May 21 00:50:24 2009 +0200
@@ -52,7 +52,7 @@
     # else we will only delete the displayed page
     need_navigation = False
 
-    def call(self):
+    def call(self, onsubmit=None):
         """ask for confirmation before real deletion"""
         req, w = self.req, self.w
         _ = req._
@@ -61,7 +61,7 @@
         # XXX above message should have style of a warning
         w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
         form = CompositeForm(req, domid='deleteconf', copy_nav_params=True,
-                             action=self.build_url('edit'), onsubmit=None,
+                             action=self.build_url('edit'), onsubmit=onsubmit,
                              form_buttons=[Button(stdmsgs.YES, cwaction='delete'),
                                            Button(stdmsgs.NO, cwaction='cancel')])
         done = set()
@@ -350,7 +350,8 @@
     def render_form(self, entity, peid, rtype, role, **kwargs):
         """fetch and render the form"""
         form = self.vreg.select_object('forms', 'edition', self.req, None,
-                                       entity=entity, set_error_url=False)
+                                       entity=entity, set_error_url=False,
+                                       copy_nav_params=False)
         self.add_hiddens(form, entity, peid, rtype, role)
         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
         title = self.schema.rschema(rtype).display_name(self.req, role)
--- a/web/views/ibreadcrumbs.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/ibreadcrumbs.py	Thu May 21 00:50:24 2009 +0200
@@ -5,6 +5,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from logilab.mtconverter import html_escape
 
@@ -16,7 +17,6 @@
 from cubicweb.common.uilib import cut
 from cubicweb.web.component import EntityVComponent
 
-_ = unicode
 
 def bc_title(entity):
     textsize = entity.req.property_value('navigation.short-line-size')
@@ -80,7 +80,6 @@
 
     def cell_call(self, row, col):
         entity = self.entity(row, col)
-        desc = cut(entity.dc_description(), 50)
-        self.w(u'<a href="%s" title="%s">%s</a>' % (html_escape(entity.absolute_url()),
-                                                    html_escape(desc),
-                                                    bc_title(entity)))
+        desc = html_escape(cut(entity.dc_description(), 50))
+        self.w(u'<a href="%s" title="%s">%s</a>' % (
+            html_escape(entity.absolute_url()), desc, bc_title(entity)))
--- a/web/views/idownloadable.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/idownloadable.py	Thu May 21 00:50:24 2009 +0200
@@ -26,7 +26,7 @@
 
 def download_box(w, entity, title=None, label=None):
     req = entity.req
-    w(u'<div class="sideRelated">')
+    w(u'<div class="sideBox">')
     if title is None:
         title = req._('download')
     w(u'<div class="sideBoxTitle downloadBoxTitle"><span>%s</span></div>'
--- a/web/views/management.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/management.py	Thu May 21 00:50:24 2009 +0200
@@ -10,7 +10,7 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.selectors import yes, none_rset, match_user_groups
+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
@@ -65,6 +65,8 @@
 class SecurityManagementView(EntityView, SecurityViewMixIn):
     """display security information for a given entity"""
     id = 'security'
+    __select__ = EntityView.__select__ & authenticated_user()
+
     title = _('security')
     def call(self):
         self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
@@ -181,7 +183,6 @@
         self.w(form.form_render(renderer=HTableFormRenderer(display_progress_div=False)))
 
 
-
 class ErrorView(AnyRsetView):
     """default view when no result has been found"""
     __select__ = yes()
@@ -271,6 +272,7 @@
     binfo += '\n'
     return binfo
 
+
 class ProcessInformationView(StartupView):
     id = 'info'
     __select__ = none_rset() & match_user_groups('managers')
--- a/web/views/primary.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/primary.py	Thu May 21 00:50:24 2009 +0200
@@ -141,7 +141,7 @@
         for box in boxes:
             if isinstance(box, tuple):
                 label, rset, vid  = box
-                self.w(u'<div class="sideRelated">')
+                self.w(u'<div class="sideBox">')
                 self.wview(vid, rset, title=label)
                 self.w(u'</div>')
             else:
--- a/web/views/timeline.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/timeline.py	Thu May 21 00:50:24 2009 +0200
@@ -16,6 +16,7 @@
 from cubicweb.selectors import implements
 from cubicweb.view import EntityView, StartupView
 
+_ = unicode
 
 class TimelineJsonView(EntityView):
     """generates a json file to feed Timeline.loadJSON()
@@ -67,7 +68,7 @@
             return None
         event_data = {'start': start.strftime(self.date_fmt),
                       'title': html_escape(entity.dc_title()),
-                      'description': entity.dc_description(),
+                      'description': entity.dc_description(format='text/html'),
                       'link': entity.absolute_url(),
                       }
         onclick = self.onclick(entity)
@@ -101,6 +102,7 @@
 class TimelineView(TimelineViewMixIn, EntityView):
     """builds a cubicweb timeline widget node"""
     id = 'timeline'
+    title = _('timeline')
     __select__ = implements(ICalendarable)
     need_navigation = False
     def call(self, tlunit=None):
--- a/web/views/treeview.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/treeview.py	Thu May 21 00:50:24 2009 +0200
@@ -1,22 +1,22 @@
 """Set of tree-building widgets, based on jQuery treeview plugin
 
 :organization: Logilab
-:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.common.decorators import monkeypatch
 from logilab.mtconverter import html_escape
 
+from cubicweb.utils import make_uid
 from cubicweb.interfaces import ITree
 from cubicweb.selectors import implements
 from cubicweb.view import EntityView
-from cubicweb.utils import make_uid
 
 def treecookiename(treeid):
     return str('treestate-%s' % treeid)
 
-
 class TreeView(EntityView):
     id = 'treeview'
     itemvid = 'treeitemview'
@@ -25,26 +25,22 @@
 
     def call(self, subvid=None, treeid=None, initial_load=True):
         if subvid is None:
-            if 'subvid' in self.req.form:
-                subvid = self.req.form.pop('subvid') # consume it
-            else:
-                subvid = 'oneline'
+            subvid = self.req.form.pop('subvid', 'oneline') # consume it
         if treeid is None:
-            if 'treeid' in self.req.form:
-                treeid = self.req.form.pop('treeid')
-            else:
+            treeid = self.req.form.pop('treeid', None)
+            if treeid is None:
+                self.warning('Tree state won\'t be properly restored after next reload')
                 treeid = make_uid('throw away uid')
-                self.warning('Tree state won\'t be properly restored after next reload')
-        if initial_load:
+        self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
+        for rowidx in xrange(len(self.rset)):
+            self.wview(self.itemvid, self.rset, row=rowidx, col=0,
+                       vid=subvid, parentvid=self.id, treeid=treeid)
+        self.w(u'</ul>')
+        if initial_load and not self.req.form.get('fname'):
             self.req.add_css('jquery.treeview.css')
             self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
             self.req.html_headers.add_onload(u"""
 jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
-        self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
-        for rowidx in xrange(len(self.rset)):
-            self.wview(self.itemvid, self.rset, row=rowidx, col=0,
-                       vid=subvid, parentvid=self.id)
-        self.w(u'</ul>')
 
 
 class FileTreeView(TreeView):
@@ -57,8 +53,6 @@
     def call(self, subvid=None, treeid=None, initial_load=True):
         super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
 
-
-
 class FileItemInnerView(EntityView):
     """inner view used by the TreeItemView instead of oneline view
 
@@ -70,17 +64,18 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
-            self.w(u'<div class="folder">%s</div>' % entity.view('oneline'))
+            self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline'))
         else:
             # XXX define specific CSS classes according to mime types
-            self.w(u'<div class="file">%s</div>' % entity.view('oneline'))
+            self.w(u'<div class="file">%s</div>\n' % entity.view('oneline'))
 
 
 class DefaultTreeViewItemView(EntityView):
     """default treeitem view for entities which don't implement ITree"""
     id = 'treeitemview'
 
-    def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
+    def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
+        assert treeid is not None
         entity = self.entity(row, col)
         itemview = self.view(vid, self.rset, row=row, col=col)
         if row == len(self.rset) - 1:
@@ -95,32 +90,61 @@
     (each item should be expandable if it's not a tree leaf)
     """
     id = 'treeitemview'
-    __select__ = implements(ITree)
+    __select__ = EntityView.__select__ & implements(ITree) # XXX
 
-    def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
+    def open_state(self, eeid, treeid):
+        cookies = self.req.get_cookie()
+        treestate = cookies.get(treecookiename(treeid))
+        if treestate:
+            return str(eeid) in treestate.value.split(';')
+        return False
+
+    def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview'):
+        w = self.w
         entity = self.entity(row, col)
-        cssclasses = []
-        is_leaf = False
-        if row == len(self.rset) - 1:
-            is_leaf = True
+        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():
-            if is_leaf : cssclasses.append('last')
-            self.w(u'<li class="%s">' % u' '.join(cssclasses))
+            if is_last:
+                liclasses.append('last')
+            w(u'<li class="%s">' % u' '.join(liclasses))
         else:
             rql = entity.children_rql() % {'x': entity.eid}
             url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
                                              pageid=self.req.pageid,
-                                             subvid=vid,
-                                             noautoload=True))
-            cssclasses.append('expandable')
-            divclasses = ['hitarea expandable-hitarea']
-            if is_leaf :
-                cssclasses.append('lastExpandable')
-                divclasses.append('lastExpandable-hitarea')
-            self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses)))
-            self.w(u'<div class="%s"> </div>' % u' '.join(divclasses))
+                                             treeid=treeid,
+                                             fname='view',
+                                             subvid=vid))
+            divclasses = ['hitarea']
+            if is_open:
+                liclasses.append('collapsable')
+                divclasses.append('collapsable-hitarea')
+            else:
+                liclasses.append('expandable')
+                divclasses.append('expandable-hitarea')
+            if is_last:
+                if is_open:
+                    liclasses.append('lastCollapsable')
+                    divclasses.append('lastCollapsable-hitarea')
+                else:
+                    liclasses.append('lastExpandable')
+                    divclasses.append('lastExpandable-hitarea')
+            if is_open:
+                w(u'<li class="%s">' % u' '.join(liclasses))
+            else:
+                w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses)))
+            divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ %\
+                (treeid, entity.eid)
+            w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
+
             # add empty <ul> because jquery's treeview plugin checks for
             # sublists presence
-            self.w(u'<ul class="placeholder"><li>place holder</li></ul>')
+            if not is_open:
+                w(u'<ul class="placeholder"><li>place holder</li></ul>')
+        # the local node info
         self.wview(vid, self.rset, row=row, col=col)
-        self.w(u'</li>')
+        if is_open: # => not leaf => rql is defined
+            self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
+        w(u'</li>')
+
--- a/web/views/urlrewrite.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/urlrewrite.py	Thu May 21 00:50:24 2009 +0200
@@ -73,8 +73,8 @@
     rules = [
         ('/schema',  dict(vid='schema')),
         ('/index', dict(vid='index')),
-        ('/myprefs', dict(vid='epropertiesform')),
-        ('/siteconfig', dict(vid='systemepropertiesform')),
+        ('/myprefs', dict(vid='propertiesform')),
+        ('/siteconfig', dict(vid='systempropertiesform')),
         ('/manage', dict(vid='manage')),
         ('/notfound', dict(vid='404')),
         ('/error', dict(vid='error')),
--- a/web/views/xmlrss.py	Thu May 21 00:44:57 2009 +0200
+++ b/web/views/xmlrss.py	Thu May 21 00:50:24 2009 +0200
@@ -5,6 +5,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
+_ = unicode
 
 from time import timezone
 
@@ -17,8 +18,6 @@
 from cubicweb.web.box import BoxTemplate
 from cubicweb.common.uilib import simple_sgml_tag
 
-_ = unicode
-
 
 # base xml views ##############################################################