backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 23 Sep 2010 23:28:58 +0200
changeset 6333 e3994fcc21c3
parent 6293 df44d7163582 (current diff)
parent 6332 312acf211cbb (diff)
child 6366 1806148d6ce8
backport stable
dbapi.py
devtools/testlib.py
entity.py
hooks/syncschema.py
i18n/en.po
i18n/es.po
i18n/fr.po
server/migractions.py
server/repository.py
server/serverconfig.py
server/sources/native.py
server/sqlutils.py
web/application.py
web/component.py
web/formfields.py
web/uicfg.py
web/views/basecontrollers.py
web/views/primary.py
--- a/.hgtags	Tue Sep 21 16:35:37 2010 +0200
+++ b/.hgtags	Thu Sep 23 23:28:58 2010 +0200
@@ -153,3 +153,5 @@
 7d2cab567735a17cab391c1a7f1bbe39118308a2 cubicweb-debian-version-3.9.6-1
 de588e756f4fbe9c53c72159c6b96580a36d3fa6 cubicweb-version-3.9.7
 1c01f9dffd64d507863c9f8f68e3585b7aa24374 cubicweb-debian-version-3.9.7-1
+eed788018b595d46a55805bd8d2054c401812b2b cubicweb-version-3.9.8
+e4dba8ae963701a36be94ae58c790bc97ba029bb cubicweb-debian-version-3.9.8-1
--- a/__pkginfo__.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/__pkginfo__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 9, 7)
+numversion = (3, 9, 8)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -43,7 +43,7 @@
     'logilab-common': '>= 0.51.0',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.26.2',
-    'yams': '>= 0.30.0',
+    'yams': '>= 0.30.1',
     'docutils': '>= 0.6',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
@@ -52,7 +52,7 @@
     'Twisted': '',
     # XXX graphviz
     # server dependencies
-    'logilab-database': '>= 1.2.0',
+    'logilab-database': '>= 1.3.0',
     'pysqlite': '>= 2.5.5', # XXX install pysqlite2
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/clone_deps.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+import os
+import sys
+from subprocess import call, Popen, PIPE
+try:
+    from mercurial.dispatch import dispatch as hg_call
+except ImportError:
+    print '-' * 20
+    print "mercurial module is not reachable from this Python interpreter"
+    print "trying from command line ..."
+    tryhg = os.system('hg --version')
+    if tryhg:
+        print 'mercurial seems to unavailable, please install it'
+        raise
+    print 'found it, ok'
+    print '-' * 20
+    def hg_call(args):
+        call(['hg'] + args)
+from urllib import urlopen
+from os import path as osp, pardir
+from os.path import normpath, join, dirname
+
+BASE_URL = 'http://www.logilab.org/hg/'
+
+to_clone = ['fyzz', 'yams', 'rql',
+            'logilab/common', 'logilab/constraint', 'logilab/database',
+            'logilab/devtools', 'logilab/mtconverter',
+            'cubes/blog', 'cubes/calendar', 'cubes/card', 'cubes/comment',
+            'cubes/datafeed', 'cubes/email', 'cubes/file', 'cubes/folder',
+            'cubes/forgotpwd', 'cubes/keyword', 'cubes/link',
+            'cubes/mailinglist', 'cubes/nosylist', 'cubes/person',
+            'cubes/preview', 'cubes/registration', 'cubes/rememberme',
+            'cubes/tag', 'cubes/vcsfile', 'cubes/zone']
+
+# a couple of functions to be used to explore available
+# repositories and cubes
+def list_repos(repos_root):
+    assert repos_root.startswith('http://')
+    hgwebdir_repos = (repo.strip()
+                      for repo in urlopen(repos_root + '?style=raw').readlines()
+                      if repo.strip())
+    prefix = osp.commonprefix(hgwebdir_repos)
+    return (repo[len(prefix):].strip('/')
+            for repo in hgwebdir_repos)
+
+def list_all_cubes(base_url=BASE_URL):
+    all_repos = list_repos(base_url)
+    #search for cubes
+    for repo in all_repos:
+        if repo.startswith('cubes'):
+            to_clone.append(repo)
+
+def get_latest_debian_tag(path):
+    proc = Popen(['hg', '-R', path, 'tags'], stdout=PIPE)
+    out, _err = proc.communicate()
+    for line in out.splitlines():
+        if 'debian-version' in line:
+            return line.split()[0]
+
+def main():
+    if len(sys.argv) == 1:
+        base_url = BASE_URL
+    elif len(sys.argv) == 2:
+        base_url = sys.argv[1]
+    else:
+        print >> sys.stderr, 'usage %s [base_url]' %  sys.argv[0]
+        sys.exit(1)
+    print len(to_clone), 'repositories will be cloned'
+    base_dir = normpath(join(dirname(__file__), pardir, pardir))
+    os.chdir(base_dir)
+    not_updated = []
+    for repo in to_clone:
+        url = base_url + repo
+        if '/' not in repo:
+            target_path = repo
+        else:
+            assert repo.count('/') == 1, repo
+            directory, repo = repo.split('/')
+            if not osp.isdir(directory):
+                os.mkdir(directory)
+                open(join(directory, '__init__.py'), 'w').close()
+            target_path = osp.join(directory, repo)
+        if osp.exists(target_path):
+            print target_path, 'seems already cloned. Skipping it.'
+        else:
+            hg_call(['clone', '-U', url, target_path])
+            tag = get_latest_debian_tag(target_path)
+            if tag:
+                print 'updating to', tag
+                hg_call(['update', '-R', target_path, tag])
+            else:
+                not_updated.append(target_path)
+    print """
+CubicWeb dependencies and standard set of cubes have been fetched and
+update to the latest stable version.
+
+You should ensure your PYTHONPATH contains `%(basedir)s`.
+You might want to read the environment configuration section of the documentation
+at http://docs.cubicweb.org/admin/setup.html#environment-configuration
+
+You can find more cubes at http://www.cubicweb.org.
+Clone them from `%(baseurl)scubes/` into the `%(basedir)s%(sep)scubes%(sep)s` directory.
+
+To get started you may read http://docs.cubicweb.org/tutorials/base/index.html.
+""" % {'basedir': os.getcwd(), 'baseurl': base_url, 'sep': os.sep}
+    if not_updated:
+        print >> sys.stderr, 'WARNING: The following repositories were not updated (no debian tag found):'
+        for path in not_updated:
+            print >> sys.stderr, '\t-', path
+
+if __name__ == '__main__':
+    main()
+
+
+
--- a/dbapi.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/dbapi.py	Thu Sep 23 23:28:58 2010 +0200
@@ -645,9 +645,12 @@
         return self._repo.get_schema()
 
     @check_not_closed
-    def get_option_value(self, option):
-        """return the value for `option` in the repository configuration."""
-        return self._repo.get_option_value(option)
+    def get_option_value(self, option, foreid=None):
+        """Return the value for `option` in the configuration. If `foreid` is
+        specified, the actual repository to which this entity belongs is
+        dereferenced and the option value retrieved from it.
+        """
+        return self._repo.get_option_value(option, foreid)
 
     @check_not_closed
     def describe(self, eid):
--- a/debian/changelog	Tue Sep 21 16:35:37 2010 +0200
+++ b/debian/changelog	Thu Sep 23 23:28:58 2010 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.9.8-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Thu, 23 Sep 2010 18:45:23 +0200
+
 cubicweb (3.9.7-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Tue Sep 21 16:35:37 2010 +0200
+++ b/debian/control	Thu Sep 23 23:28:58 2010 +0200
@@ -33,7 +33,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.2.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
 Recommends: pyro, cubicweb-documentation (= ${source:Version})
 Description: server part of the CubicWeb framework
  CubicWeb is a semantic web application framework.
@@ -97,7 +97,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.51.0), python-yams (>= 0.30.0), python-rql (>= 0.26.3), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.51.0), python-yams (>= 0.30.1), python-rql (>= 0.26.3), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/__init__.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/devtools/__init__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Test tools for cubicweb
+"""Test tools for cubicweb"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 import os
@@ -183,6 +182,9 @@
     def available_languages(self, *args):
         return ('en', 'fr', 'de')
 
+    def default_base_url(self):
+        return BASE_URL
+
     def pyro_enabled(self):
         # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and
         # threads
--- a/devtools/cwwindmill.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/devtools/cwwindmill.py	Thu Sep 23 23:28:58 2010 +0200
@@ -31,6 +31,7 @@
 # imported by default to simplify further import statements
 from logilab.common.testlib import unittest_main
 
+import windmill
 from windmill.dep import functest
 
 from cubicweb.devtools.httptest import CubicWebServerTC
@@ -46,7 +47,6 @@
 
 class WindmillUnitTestCase(unittest.TestCase):
     def setUp(self):
-        import windmill
         windmill.stdout, windmill.stdin = sys.stdout, sys.stdin
         from windmill.bin.admin_lib import configure_global_settings, setup
         configure_global_settings()
@@ -90,12 +90,11 @@
     def testWindmill(self):
         if self.edit_test:
             # see windmill.bin.admin_options.Firebug
-            import windmill
             windmill.settings['INSTALL_FIREBUG'] = 'firebug'
-            windmill.settings['MOZILLA_PLUGINS'].append('/usr/share/mozilla-extensions/')
-            windmill.settings['MOZILLA_PLUGINS'].append('/usr/share/xul-ext/')
-
-        self.windmill_shell_objects['start_' + self.browser]()
+            windmill.settings.setdefault('MOZILLA_PLUGINS', []).extend(
+                '/usr/share/mozilla-extensions/',
+                '/usr/share/xul-ext/')
+        controller = self.windmill_shell_objects['start_' + self.browser]()
         self.windmill_shell_objects['do_test'](self.test_dir,
                                                load=self.edit_test,
                                                threaded=False)
@@ -104,6 +103,7 @@
             import pdb; pdb.set_trace()
             return
 
+        # reporter
         for test in unittestreporter.test_list:
             msg = ""
             self._testMethodDoc = getattr(test, "__doc__", None)
--- a/devtools/httptest.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/devtools/httptest.py	Thu Sep 23 23:28:58 2010 +0200
@@ -55,6 +55,7 @@
             s.close()
     raise RuntimeError('get_available_port([ports_range]) cannot find an available port')
 
+
 class CubicWebServerTC(CubicWebTC):
     """basic class for running test server
 
@@ -141,6 +142,7 @@
                                 (user, passwd))
         assert response.status == httplib.SEE_OTHER, response.status
         self._ident_cookie = response.getheader('Set-Cookie')
+        assert self._ident_cookie
         return True
 
     def web_logout(self, user='admin', pwd=None):
--- a/devtools/test/unittest_httptest.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/devtools/test/unittest_httptest.py	Thu Sep 23 23:28:58 2010 +0200
@@ -38,11 +38,11 @@
         # login
         self.web_login(self.admlogin, self.admpassword)
         response = self.web_get()
-        self.assertEquals(response.status, httplib.OK)
+        self.assertEquals(response.status, httplib.OK, response.body)
         # logout
         self.web_logout()
         response = self.web_get()
-        self.assertEquals(response.status, httplib.FORBIDDEN)
+        self.assertEquals(response.status, httplib.FORBIDDEN, response.body)
 
 
 
--- a/devtools/testlib.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/devtools/testlib.py	Thu Sep 23 23:28:58 2010 +0200
@@ -46,7 +46,7 @@
 from cubicweb.web import Redirect, application
 from cubicweb.server.session import security_enabled
 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
-from cubicweb.devtools import fake, htmlparser
+from cubicweb.devtools import BASE_URL, fake, htmlparser
 from cubicweb.utils import json
 
 # low-level utilities ##########################################################
@@ -225,8 +225,8 @@
         config.global_set_option('default-dest-addrs', send_to)
         config.global_set_option('sender-name', 'cubicweb-test')
         config.global_set_option('sender-addr', 'cubicweb-test@logilab.fr')
+        config.global_set_option('base-url', BASE_URL)
         # web resources
-        config.global_set_option('base-url', devtools.BASE_URL)
         try:
             config.global_set_option('embed-allowed', re.compile('.*'))
         except: # not in server only configuration
--- a/doc/book/README	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/README	Thu Sep 23 23:28:58 2010 +0200
@@ -46,6 +46,20 @@
 .. [foot note] the foot note content
 
 
+Boxes
+=====
+
+- warning box: 
+    .. warning::
+
+       Warning content
+- note box:
+    .. note::
+
+       Note content
+
+
+
 Cross references
 ================
 
--- a/doc/book/en/admin/setup.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/admin/setup.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -74,6 +74,12 @@
 
 .. _SourceInstallation:
 
+.. warning::
+
+  This method may still have hiccups. If it does not work for you,
+  please consider installing from version control system
+  (:ref:`MercurialInstallation`).
+
 Install from source
 ```````````````````
 
@@ -90,19 +96,37 @@
 Install from version control system
 ```````````````````````````````````
 
-You can keep up to date with on-going development by using Mercurial and its
-forest extension::
+You can keep up to date with on-going development by using Mercurial::
 
-  hg fclone http://www.logilab.org/hg/forests/cubicweb
+  hg clone http://www.logilab.org/hg/forests/cubicweb
 
 See :ref:`MercurialPresentation` for more details about Mercurial.
 
+A practical way to get many of CubicWeb's dependencies and a nice set
+of base cubes is to run the `clone_deps.py` script located in
+`cubicweb/bin/`::
+
+  python cubicweb/bin/clone_deps.py
+
+(Windows users should replace slashes with antislashes).
+
+This script will clone a set of mercurial repositories into in the
+directory containing the CubicWeb repository, and update them to the
+latest published version tag (if any).
+
 When cloning a repository, you might be set in a development branch
 (the 'default' branch). You should check that the branches of the
 repositories are set to 'stable' (using `hg up stable` for each one)
 if you do not intend to develop the framework itself.
 
-Do not forget to update the forest itself (using `cd path/to/forest ; hg up`).
+Even better, `hg tags` will display a list of tags in reverse
+chronological order. One reasonnable way to get to a working version
+is to pick the latest published version (as done by the `clone_deps`
+script). These look like `cubicweb-debian-version-3.9.7-1`. Typing::
+
+ hg update cubicweb-debian-version-3.9.7-1
+
+will update the repository files to this version.
 
 Make sure you also have all the :ref:`InstallDependencies`.
 
@@ -182,8 +206,7 @@
   http://www.graphviz.org/Download_windows.php
 
 Simplejson is needed when installing with Python 2.5, but included in the
-standard library for Python >= 2.6. It will be provided within the forest, but a
-win32 compiled version will run much faster::
+standard library for Python >= 2.6. Get it from there::
 
   http://www.osuch.org/python-simplejson%3Awin32
 
@@ -211,33 +234,10 @@
 Getting the sources
 ~~~~~~~~~~~~~~~~~~~
 
-You can either download the latest release (see :ref:`SourceInstallation`) or
-get the development version using Mercurial (see
-:ref:`MercurialInstallation` and below).
-
-To enable the Mercurial forest extension on Windows, edit the file::
-
-  C:\Program Files\TortoiseHg\Mercurial.ini
-
-In the [extensions] section, add the following line::
-
-  forest=C:\Program Files\TortoiseHg\ext\forest\forest.py
-
-Now, you need to clone the cubicweb repository. We assume that you use
-Eclipse. From the IDE, choose File -> Import. In the box, select `Mercurial/Clone
-repository using MercurialEclipse`.
-
-In the import main panel you just have to:
-
-* fill the URL field with http://www.logilab.org/hg/forests/cubicwin32
-
-* check the 'Repository is a forest' box.
-
-Then, click on 'Finish'. It might take some time to get it all. Note that the
-`cubicwin32` forest contains additional python packages such as yapps, vobject,
-simplejson and twisted-web2 which are not provided with Python(x,y). This is
-provided for convenience, as we do not ensure the up-to-dateness of these
-packages, especially with respect to security fixes.
+You can either download the latest release (see
+:ref:`SourceInstallation`) or get the development version using
+Mercurial (see :ref:`MercurialInstallation` and below), which is more
+convenient.
 
 Environment variables
 ~~~~~~~~~~~~~~~~~~~~~
@@ -274,7 +274,7 @@
 C:\\etc\\cubicweb.d.
 
 For a cube 'my_instance', you will then find
-C:\\etc\\cubicweb.d\\my_instance\\win32svc.py that has to be used thusly::
+C:\\etc\\cubicweb.d\\my_instance\\win32svc.py that has to be used as follows::
 
   win32svc install
 
@@ -303,14 +303,17 @@
 Databases configuration
 -----------------------
 
-Whatever the backend used, database connection information are stored in the
-instance's :file:`sources` file. Currently cubicweb has been tested using
-Postgresql (recommended), MySQL, SQLServer and SQLite.
+Each instance can be configured with its own database connection information,
+that will be stored in the instance's :file:`sources` file. The database to use
+will be chosen when creating the instance. Currently cubicweb has been tested
+using Postgresql (recommended), MySQL, SQLServer and SQLite.
 
 Other possible sources of data include CubicWeb, Subversion, LDAP and Mercurial,
-but at least one relational database is required for CubicWeb to work. SQLite is
-not fit for production use, but it works for testing and ships with Python,
-which saves installation time when you want to get started quickly.
+but at least one relational database is required for CubicWeb to work. You do
+not need to install a backend that you do not intend to use for one of your
+instances. SQLite is not fit for production use, but it works well for testing
+and ships with Python, which saves installation time when you want to get
+started quickly.
 
 .. _PostgresqlConfiguration:
 
--- a/doc/book/en/annexes/rql/language.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/annexes/rql/language.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -107,12 +107,13 @@
 Operators priority
 ``````````````````
 
-1. '*', '/'
-2. '+', '-'
-3. 'NOT'
-4. 'AND'
-5. 'OR'
-6. ','
+#. "(", ")"
+#. '*', '/'
+#. '+', '-'
+#. 'NOT'
+#. 'AND'
+#. 'OR'
+#. ','
 
 
 Search Query
--- a/doc/book/en/devrepo/entityclasses/adapters.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devrepo/entityclasses/adapters.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -3,19 +3,18 @@
 Interfaces and Adapters
 -----------------------
 
-Interfaces are the same thing as object-oriented programming
-`interfaces`_. Adapter refers to a well-known `adapter`_ design
-pattern that helps separating concerns in object oriented
-applications.
+Interfaces are the same thing as object-oriented programming `interfaces`_.
+Adapter refers to a well-known `adapter`_ design pattern that helps separating
+concerns in object oriented applications.
 
 .. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
 .. _`adapter`: http://en.wikipedia.org/wiki/Adapter_pattern
 
-In |cubicweb| adapters provide logical functionalities
-to entity types. They are introduced in version `3.9`. Before that one
-had to implement Interfaces in entity classes to achieve a similar goal. However,
-hte problem with this approch is that is clutters the entity class's namespace, exposing
-name collision risks with schema attributes/relations or even methods names
+In |cubicweb| adapters provide logical functionalities to entity types. They
+are introduced in version `3.9`. Before that one had to implement Interfaces in
+entity classes to achieve a similar goal. However, the problem with this
+approach is that is clutters the entity class's namespace, exposing name
+collision risks with schema attributes/relations or even methods names
 (different interfaces may define the same method with not necessarily the same
 behaviour expected).
 
@@ -128,7 +127,7 @@
 
     class MyEntity(AnyEntity):
         __regid__ = 'MyEntity'
-	__implements__ = AnyEntity.__implements__ + (IFoo,)
+        __implements__ = AnyEntity.__implements__ + (IFoo,)
 
         def bar(self, *args):
             return sum(captain.age for captain in self.captains)
--- a/doc/book/en/devrepo/repo/sessions.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devrepo/repo/sessions.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -3,15 +3,16 @@
 Sessions
 ========
 
-There are three kinds of sessions.
-
-* `user sessions` are the most common: they are related to users and
-  carry security checks coming with user credentials
+Sessions are object carrying the `.execute` method to query the data
+sources.
 
-* `super sessions` are children of ordinary user sessions and allow to
-  bypass security checks (they are created by calling unsafe_execute
-  on a user session); this is often convenient in hooks which may
-  touch data that is not directly updatable by users
+Kinds of sessions
+-----------------
+
+There are two kinds of sessions.
+
+* `normal sessions` are the most common: they are related to users and
+  carry security checks coming with user credentials
 
 * `internal sessions` have all the powers; they are also used in only a
   few situations where you don't already have an adequate session at
@@ -20,8 +21,181 @@
 
 .. note::
   Do not confuse the session type with their connection mode, for
-  instance : 'in memory' or 'pyro'.
+  instance : `in memory` or `pyro`.
+
+Normal sessions are typically named `_cw` in most appobjects or
+sometimes just `session`.
+
+Internal sessions are available from the `Repository` object and are
+to be used like this:
+
+.. sourcecode:: python
+
+   session = self.repo.internal_session()
+   try:
+       do_stuff_with(session)
+   finally:
+       session.close()
+
+.. warning::
+  Do not forget to close such a session after use for a session leak
+  will quickly lead to an application crash.
+
+Authentication and management of sessions
+-----------------------------------------
+
+The authentication process is a ballet involving a few dancers:
+
+* through its `connect` method the top-level application object (the
+  `CubicWebPublisher`) will open a session whenever a web request
+  comes in; it asks the `session manager` to open a session (giving
+  the web request object as context) using `open_session`
+
+  * the session manager asks its authentication manager (which is a
+    `component`) to authenticate the request (using `authenticate`)
+
+    * the authentication manager asks, in order, to its authentication
+      information retrievers, a login and an opaque object containing
+      other credentials elements (calling `authentication_information`),
+      giving the request object each time
+
+      * the default retriever (bizarrely named
+        `LoginPaswordRetreiver`) will in turn defer login and password
+        fetching to the request object (which, depending on the
+        authentication mode (`cookie` or `http`), will do the
+        appropriate things and return a login and a password)
+
+    * the authentication manager, on success, asks the `Repository`
+      object to connect with the found credentials (using `connect`)
+
+      * the repository object asks authentication to all of its
+        sources which support the `CWUser` entity with the given
+        credentials; when successful it can build the cwuser entity,
+        from which a regular `Session` object is made; it returns the
+        session id
+
+        * the source in turn will defer work to an authentifier class
+          that define the ultimate `authenticate` method (for instance
+          the native source will query the database against the
+          provided credentials)
+
+    * the authentication manager, on success, will call back _all_
+      retrievers with `authenticated` and return its authentication
+      data (on failure, it will try the anonymous login or, if the
+      configuration forbids it, raise an `AuthenticationError`)
+
+Writing authentication plugins
+------------------------------
+
+Sometimes CubicWeb's out-of-the-box authentication schemes (cookie and
+http) are not sufficient. Nowadays there is a plethore of such schemes
+and the framework cannot provide them all, but as the sequence above
+shows, it is extensible.
+
+Two levels have to be considered when writing an authentication
+plugin: the web client and the repository.
+
+We invented a scenario where it makes sense to have a new plugin in
+each side: some middleware will do pre-authentication and under the
+right circumstances add a new HTTP `x-foo-user` header to the query
+before it reaches the CubicWeb instance. For a concrete example of
+this, see the `apachekerberos`_ cube.
+
+.. _`apachekerberos`: http://www.cubicweb.org/project/cubicweb-apachekerberos
+
+Repository authentication plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On the repository side, it is possible to register a source
+authentifier using the following kind of code:
 
-[WRITE ME]
+.. sourcecode:: python
+
+ from cubicweb.server.sources import native
+
+ class FooAuthentifier(native.LoginPasswordAuthentifier):
+     """ a source authentifier plugin
+     if 'foo' in authentication information, no need to check
+     password
+     """
+     auth_rql = 'Any X WHERE X is CWUser, X login %(login)s'
+
+     def authenticate(self, session, login, **kwargs):
+         """return CWUser eid for the given login
+         if this account is defined in this source,
+         else raise `AuthenticationError`
+         """
+         session.debug('authentication by %s', self.__class__.__name__)
+         if 'foo' not in kwargs:
+             return super(FooAuthentifier, self).authenticate(session, login, **kwargs)
+         try:
+             rset = session.execute(self.auth_rql, {'login': login})
+             return rset[0][0]
+         except Exception, exc:
+             session.debug('authentication failure (%s)', exc)
+         raise AuthenticationError('foo user is unknown to us')
+
+Since repository authentifiers are not appobjects, we have to register
+them through a `server_startup` hook.
+
+.. sourcecode:: python
+
+ class ServerStartupHook(hook.Hook):
+     """ register the foo authenticator """
+     __regid__ = 'fooauthenticatorregisterer'
+     events = ('server_startup',)
+
+     def __call__(self):
+         self.debug('registering foo authentifier')
+         self.repo.system_source.add_authentifier(FooAuthentifier())
+
+Web authentication plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. sourcecode:: python
 
-* authentication and management of sessions
+ class XFooUserRetriever(authentication.LoginPasswordRetreiver):
+     """ authenticate by the x-foo-user http header
+     or just do normal login/password authentication
+     """
+     __regid__ = 'x-foo-user'
+     order = 0
+
+     def authentication_information(self, req):
+         """retrieve authentication information from the given request, raise
+         NoAuthInfo if expected information is not found
+         """
+         self.debug('web authenticator building auth info')
+         try:
+            login = req.get_header('x-foo-user')
+            if login:
+                return login, {'foo': True}
+            else:
+                return super(XFooUserRetriever, self).authentication_information(self, req)
+         except Exception, exc:
+            self.debug('web authenticator failed (%s)', exc)
+         raise authentication.NoAuthInfo()
+
+     def authenticated(self, retriever, req, cnx, login, authinfo):
+         """callback when return authentication information have opened a
+         repository connection successfully. Take care req has no session
+         attached yet, hence req.execute isn't available.
+
+         Here we set a flag on the request to indicate that the user is
+         foo-authenticated. Can be used by a selector
+         """
+         self.debug('web authenticator running post authentication callback')
+         cnx.foo_user = authinfo.get('foo')
+
+In the `authenticated` method we add (in an admitedly slightly hackish
+way) an attribute to the connection object. This, in turn, can be used
+to build a selector dispatching on the fact that the user was
+preauthenticated or not.
+
+.. sourcecode:: python
+
+ @objectify_selector
+ def foo_authenticated(cls, req, rset=None, **kwargs):
+     if hasattr(req.cnx, 'foo_user') and req.foo_user:
+         return 1
+     return 0
--- a/doc/book/en/devrepo/testing.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devrepo/testing.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -6,24 +6,24 @@
 Unit tests
 ----------
 
-The *CubicWeb* framework provides the `CubicWebTC` test base class in
-the module `cubicweb.devtools.testlib`.
+The *CubicWeb* framework provides the
+:class:`cubicweb.devtools.testlib.CubicWebTC` test base class .
 
 Tests shall be put into the mycube/test directory. Additional test
 data shall go into mycube/test/data.
 
-It is much advised to write tests concerning entities methods, hooks
-and operations, security. The CubicWebTC base class has convenience
-methods to help test all of this.
-
-.. note::
+It is much advised to write tests concerning entities methods,
+actions, hooks and operations, security. The
+:class:`~cubicweb.devtools.testlib.CubicWebTC` base class has
+convenience methods to help test all of this.
 
-  In the realm of views, there is not much to do but check that the
-  views are valid XHTML.  See :ref:`automatic_views_tests` for
-  details. Integration of CubicWeb tests with UI testing tools such as
-  `selenium`_ are currently under invesitgation.
+In the realm of views, automatic tests check that views are valid
+XHTML. See :ref:`automatic_views_tests` for details. Since 3.9, bases
+for web functional testing using `windmill
+<http://www.getwindmill.com/>`_ are set. See test cases in
+cubicweb/web/test/windmill and python wrapper in
+cubicweb/web/test_windmill/ if you want to use this in your own cube.
 
-.. _selenium: http://seleniumhq.org/projects/ide/
 
 Most unit tests need a live database to work against. This is achieved
 by CubicWeb using automatically sqlite (bundled with Python, see
@@ -77,13 +77,21 @@
             self.kw1.set_relations(subkeyword_of=kw3)
             self.assertRaises(ValidationError, self.commit)
 
-The test class defines a `setup_database` method which populates the
+The test class defines a :meth:`setup_database` method which populates the
 database with initial data. Each test of the class runs with this
-pre-populated database.
+pre-populated database. A commit is done automatically after the
+:meth:`setup_database` call. You don't have to call it explicitely.
 
 The test case itself checks that an Operation does it job of
 preventing cycles amongst Keyword entities.
 
+.. note::
+
+   :meth:`commit` method is not called automatically in test_XXX
+   methods. You have to call it explicitely if needed (notably to test
+   operations). It is a good practice to call :meth:`clear_all_caches`
+   on entities after a commit to avoid request cache effects.
+
 You can see an example of security tests in the
 :ref:`adv_tuto_security`.
 
@@ -149,7 +157,7 @@
 
 When running tests potentially generated e-mails are not really sent
 but is found in the list `MAILBOX` of module
-`cubicweb.devtools.testlib`.
+:mod:`cubicweb.devtools.testlib`.
 
 You can test your notifications by analyzing the contents of this list, which
 contains objects with two attributes:
--- a/doc/book/en/devweb/views/basetemplates.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devweb/views/basetemplates.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -11,17 +11,33 @@
 in :ref:`views_base_class`, there are two kinds of views: the
 templatable and non-templatable.
 
-Non-templatable views are standalone. They are responsible for all the
-details such as setting a proper content type (or mime type), the
-proper document headers, namespaces, etc. Examples are pure xml views
-such as RSS or Semantic Web views (`SIOC`_, `DOAP`_, `FOAF`_, `Linked
-Data`_, etc.).
+
+Non-templatable views
+---------------------
+
+Non-templatable views are standalone. They are responsible for all the details
+such as setting a proper content type (or mime type), the proper document
+headers, namespaces, etc. Examples are pure xml views such as RSS or Semantic Web
+views (`SIOC`_, `DOAP`_, `FOAF`_, `Linked Data`_, etc.), and views which generate
+binary files (pdf, excel files, etc.)
 
 .. _`SIOC`: http://sioc-project.org/
 .. _`DOAP`: http://trac.usefulinc.com/doap
 .. _`FOAF`: http://www.foaf-project.org/
 .. _`Linked Data`: http://linkeddata.org/
 
+
+To notice that a view is not templatable, you just have to set the
+view's class attribute `templatable` to `False`. In this case, it
+should set the `content_type` class attribute to the correct MIME
+type. By default, it is text/xhtml. Additionally, if your view
+generate a binary file, you have to set the view's class attribute
+`binary` to `True` too.
+
+
+Templatable views
+-----------------
+
 Templatable views are not concerned with such pesky details. They
 leave it to the template. Conversely, the template's main job is to:
 
@@ -30,14 +46,14 @@
 * invoke adequate views in the various sections of the document
 
 
-Look at :mod:`cubicweb.web.views.basetemplates` and you will find the
-base templates used to generate (X)HTML for your application. The most
-important template there is `TheMainTemplate`.
+Look at :mod:`cubicweb.web.views.basetemplates` and you will find the base
+templates used to generate (X)HTML for your application. The most important
+template there is :class:`~cubicweb.web.views.basetemplates.TheMainTemplate`.
 
 .. _the_main_template_layout:
 
 TheMainTemplate
----------------
+~~~~~~~~~~~~~~~
 
 .. _the_main_template_sections:
 
@@ -88,28 +104,60 @@
   How and why a view object is given to the main template is explained
   in the :ref:`publisher` chapter.
 
-Class attributes
-````````````````
+Configure the main template
+```````````````````````````
+
+You can overload some methods of the
+:class:`~cubicweb.web.views.basetemplates.TheMainTemplate`, in order to fulfil
+your needs. There are also some attributes and methods which can be defined on a
+view to modify the base template behaviour:
+
+* `paginable`: if the result set is bigger than a configurable size, your result
+  page will be paginated by default. You can set this attribute to `False` to
+  avoid this.
+
+* `binary`: boolean flag telling if the view generates some text or a binary
+  stream.  Default to False. When view generates text argument given to `self.w`
+  **must be an unicode string**, encoded string otherwise.
 
-We can also control certain aspects of the main template thanks to the following
-forms parameters:
+* `content_type`, view's content type, default to 'text/xhtml'
+
+* `templatable`, boolean flag telling if the view's content should be returned
+  directly (when `False`) or included in the main template layout (including
+  header, boxes and so on).
+
+* `page_title()`, method that should return a title that will be set as page
+  title in the html headers.
+
+* `html_headers()`, method that should return a list of HTML headers to be
+  included the html headers.
+
+
+You can also modify certain aspects of the main template of a page
+when building an url or setting these parameters in the req.form:
 
 * `__notemplate`, if present (whatever the value assigned), only the content view
   is returned
-* `__force_display`, if present and its value is not null, no navigation
-  whatever the number of entities to display
+
+* `__force_display`, if present and its value is not null, no pagination whatever
+  the number of entities to display (e.g. similar effect as view's `paginable`
+  attribute described above.
+
 * `__method`, if the result set to render contains only one entity and this
-  parameter is set, it refers to a method to call on the entity by passing it
-  the dictionary of the forms parameters, before going the classic way (through
-  step 1 and 2 described juste above)
+  parameter is set, it refers to a method to call on the entity by passing it the
+  dictionary of the forms parameters, before going the classic way (through step
+  1 and 2 described juste above)
+
+* `vtitle`, a title to be set as <h1> of the content
 
 Other templates
----------------
+~~~~~~~~~~~~~~~
 
-Other standard templates include:
+There are also the following other standard templates:
 
-* `login` and `logout`
-
-* `error-template` specializes TheMainTemplate to do proper end-user
-  output if an error occurs during the computation of TheMainTemplate
-  (it is a fallback view).
+* :class:`cubicweb.web.views.basetemplates.LogInTemplate`
+* :class:`cubicweb.web.views.basetemplates.LogOutTemplate`
+* :class:`cubicweb.web.views.basetemplates.ErrorTemplate` specializes
+  :class:`~cubicweb.web.views.basetemplates.TheMainTemplate` to do
+  proper end-user output if an error occurs during the computation of
+  TheMainTemplate (it is a fallback view).
--- a/doc/book/en/devweb/views/baseviews.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devweb/views/baseviews.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -4,11 +4,12 @@
 ----------
 
 *CubicWeb* provides a lot of standard views, that can be found in
- :mod:`cubicweb.web.views` and :mod:`cubicweb.web.views.baseviews`.
+:mod:`cubicweb.web.views` sub-modules.
 
-A certain number of views are used to build the web interface, which
-apply to one or more entities. Their identifier is what distinguish
-them from each others and the main ones are:
+A certain number of views are used to build the web interface, which apply to one
+or more entities. As other appobject, Their identifier is what distinguish them
+from each others. The most generic ones, found in
+:mod:`cubicweb.web.views.baseviews`, are described below.
 
 HTML views
 ~~~~~~~~~~
@@ -32,53 +33,105 @@
     This view is the default view used when nothing needs to be rendered.
     It is always applicable.
 
+
 Entity views
 ````````````
 
 *incontext, outofcontext*
-    Those are used to display a link to an entity, depending on the
-    entity having to be displayed in or out of context
-    (of another entity).  By default it respectively produces the
-    result of `textincontext` and `textoutofcontext` wrapped in a link
-    leading to the primary view of the entity.
+
+    Those are used to display a link to an entity, whose label depends on the
+    entity having to be displayed in or out of context (of another entity): some
+    entities make sense in the context of another entity. For instance, the
+    `Version` of a `Project` in forge. So one may expect that 'incontext' will
+    be called when display a version from within the context of a project, while
+    'outofcontext"' will be called in other cases. In our example, the
+    'incontext' view of the version would be something like '0.1.2', while the
+    'outofcontext' view would include the project name, e.g. 'baz 0.1.2' (since
+    only a version number without the associated project doesn't make sense if
+    you don't know yet that you're talking about the famous 'baz' project. |cubicweb|
+    tries to make guess and call 'incontext'/'outofcontext' nicely. When it can't
+    know, the 'oneline' view should be used.
+
+    By default it respectively produces the result of `textincontext` and
+    `textoutofcontext` wrapped in a link leading to the primary view of the
+    entity.
+
 
 *oneline*
+
     This view is used when we can't tell if the entity should be considered as
-    displayed in or out of context.  By default it produces the result of `text`
+    displayed in or out of context. By default it produces the result of `text`
     in a link leading to the primary view of the entity.
 
+
 List
 `````
 
 *list*
-    This view displays a list of entities by creating a HTML list (`<ul>`)
-    and call the view `listitem` for each entity of the result set.
+
+    This view displays a list of entities by creating a HTML list (`<ul>`) and
+    call the view `listitem` for each entity of the result set. The 'list' view
+    will generate html like:
+
+    .. sourcecode:: html
+
+      <ul class="section">
+        <li>"result of 'subvid' view for a row</li>
+        ...
+      </ul>
+
 
-*listitem*
-    This view redirects by default to the `outofcontext` view.
+*simplelist*
+
+  This view is not 'ul' based, and rely on div behaviour to separate items. html
+  will look like
+
+    .. sourcecode:: html
+
+      <div class="section">"result of 'subvid' view for a row</div>
+      ...
+
+
+  It relies on base :class:`~cubicweb.view.View` class implementation of the
+  :meth:`call` method to insert those <div>.
+
 
 *sameetypelist*
-    This view displays a list of entities of the same type, in HTML section (`<div>`)
-    and call the view `sameetypelistitem` for each entity of the result set.
 
-*sameetypelistitem*
-    This view redirects by default to the `listitem` view.
+    This view displays a list of entities of the same type, in HTML section
+    (`<div>`) and call the view `sameetypelistitem` for each entity of the result
+    set. It's designed to get a more adapted global list when displayed entities
+    are all of the same type.
+
 
 *csv*
-    This view applies to entity groups, which are individually
-    displayed using the `incontext` view. It displays each entity as a
-    coma separated list. It is NOT related to the well-known text file
-    format.
+
+    This view displays each entity in a coma separated list. It is NOT related to
+    the well-known text file format.
+
+
+Those list view can be given a 'subvid' arguments, telling the view to use of
+each item in the list. When not specified, the value of the 'redirect_vid'
+attribute of :class:`ListItemView` (for 'listview') or of :class:`SimpleListView`
+will be used. This default to 'outofcontext' for 'list' / 'incontext' for
+'simplelist'
+
 
 Text entity views
 ~~~~~~~~~~~~~~~~~
 
+Basic html view have some variantsto be used when generating raw text, not html
+(for notifications for instance).
+
 *text*
+
     This is the simplest text view for an entity. By default it returns the
     result of the `.dc_title` method, which is cut to fit the
     `navigation.short-line-size` property if necessary.
 
 *textincontext, textoutofcontext*
-    Similar to the `text` view, but called when an entity is considered out or
-    in context. By default it returns respectively the result of the
-    methods `.dc_title` and `.dc_long_title` of the entity.
+
+    Similar to the `text` view, but called when an entity is considered out or in
+    context (see description of incontext/outofcontext html views for more
+    information on this). By default it returns respectively the result of the
+    methods `.dc_title()` and `.dc_long_title()` of the entity.
--- a/doc/book/en/devweb/views/primary.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/devweb/views/primary.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -36,15 +36,16 @@
 Attributes/relations display location
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-In the primary view, there are 3 sections where attributes and
+In the primary view, there are three sections where attributes and
 relations can be displayed (represented in pink in the image above):
 
-* attributes
-* relations
-* sideboxes
+* 'attributes'
+* 'relations'
+* 'sideboxes'
 
 **Attributes** can only be displayed in the attributes section (default
-  behavior). They can also be hidden.
+  behavior). They can also be hidden. By default, attributes of type `Password`
+  and `Bytes` are hidden.
 
 For instance, to hide the ``title`` attribute of the ``Blog`` entity:
 
@@ -95,6 +96,10 @@
 * ``order``: int used to control order within a section. When not specified,
   automatically set according to order in which tags are added.
 
+* ``label``: label for the relations section or side box
+
+* ``showlabel``: boolean telling whether the label is displayed
+
 .. sourcecode:: python
 
    # let us remind the schema of a blog entry
@@ -110,15 +115,31 @@
    for index, attr in enumerate('title', 'content', 'publish_date'):
        view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index})
 
-Keys for relations only:
+By default, relations displayed in the 'relations' section are being displayed by
+the 'autolimited' view. This view will use comma separated values, or list view
+and/or limit your rset if there is too much items in it (and generate the "view
+all" link in this case).
 
-* ``label``: label for the relations section or side box
+You can control this view by setting the following values in the
+`primaryview_display_ctrl` relation tag:
+
+* `limit`, maximum number of entities to display. The value of the
+  'navigation.related-limit'  cwproperty is used by default (which is 8 by default).
+  If None, no limit.
 
-* ``showlabel``: boolean telling whether the label is displayed
+* `use_list_limit`, number of entities until which they should be display as a list
+  (eg using the 'list' view). Below that limit, the 'csv' view is used. If None,
+  display using 'csv' anyway.
+
+* `subvid`, the subview identifier (eg view that should be used of each item in the
+  list)
 
-* ``limit``: boolean telling if the results should be limited. If so, a link to all results is displayed
+Notice you can also use the `filter` key to set up a callback taking the related
+result set as argument and returning it filtered, to do some arbitrary filtering
+that can't be done using rql for instance.
 
-* ``filter``: callback taking the related result set as argument and returning it filtered
+
+
 
 .. sourcecode:: python
 
@@ -153,22 +174,19 @@
 are:
 
 *render_entity_title(self, entity)*
-    Renders the entity title using the ``def dc_title(self)`` method.
-
-*render_entity_metadata(self, entity)*
-    Renders the entity metadata by calling the ``metadata`` view on the
-    entity. This generic view is in cubicweb.views.baseviews.
+    Renders the entity title, by default using entity's :meth:`dc_title()` method.
 
 *render_entity_attributes(self, entity)*
-    Renders all the attribute of an entity with the exception of
-    attribute of type `Password` and `Bytes`. The skip_none class
-    attribute controls the display of None valued attributes.
+    Renders all attributes and relations in the 'attributes' section . The
+    :attr:`skip_none` attribute controls the display of `None` valued attributes.
 
 *render_entity_relations(self, entity)*
-    Renders all the relations of the entity in the main section of the page.
+    Renders all relations in the 'relations' section.
 
 *render_side_boxes(self, entity, boxes)*
-    Renders relations of the entity in a side box.
+    Renders side boxes on the right side of the content. This will generate a box
+    for each relation in the 'sidebox' section, as well as explicit box
+    appobjects selectable in this context.
 
 The placement of relations in the relations section or in side boxes
 can be controlled through the :ref:`primary_view_configuration` mechanism.
@@ -184,24 +202,25 @@
 subclass, you can already customize some of the rendering:
 
 *show_attr_label*
-    Renders the attribute label next to the attribute value if set to True.
+    Renders the attribute label next to the attribute value if set to `True`.
     Otherwise, does only display the attribute value.
 
 *show_rel_label*
-    Renders the relation label next to the relation value if set to True.
+    Renders the relation label next to the relation value if set to `True`.
     Otherwise, does only display the relation value.
 
 *skip_none*
-    Does not render an attribute value that is None if set to True.
+    Does not render an attribute value that is None if set to `True`.
 
 *main_related_section*
-    Renders the relations of the entity if set to True.
+    Renders the relations of the entity if set to `True`.
 
 A good practice is for you to identify the content of your entity type for which
 the default rendering does not answer your need so that you can focus on the specific
 method (from the list above) that needs to be modified. We do not advise you to
 overwrite ``render_entity`` unless you want a completely different layout.
 
+
 Example of customization and creation
 `````````````````````````````````````
 
--- a/doc/book/en/tutorials/tools/windmill.rst	Tue Sep 21 16:35:37 2010 +0200
+++ b/doc/book/en/tutorials/tools/windmill.rst	Thu Sep 23 23:28:58 2010 +0200
@@ -150,6 +150,25 @@
 windmill recorded use cases.
 
 
+Caveats
+=======
+
+File Upload
+-----------
+
+Windmill can't do file uploads. This is a limitation of browser Javascript
+support / sandboxing, not of Windmill per se.  It would be nice if there were
+some command that would prime the Windmill HTTP proxy to add a particular file
+to the next HTTP request that comes through, so that uploads could at least be
+faked.
+
+.. http://groups.google.com/group/windmill-dev/browse_thread/thread/cf9dc969722bd6bb/01aa18fdd652f7ff?lnk=gst&q=input+type+file#01aa18fdd652f7ff
+
+.. http://davisagli.com/blog/in-browser-integration-testing-with-windmill
+
+.. http://groups.google.com/group/windmill-dev/browse_thread/thread/b7bebcc38ed30dc7
+
+
 Preferences
 ===========
 
--- a/entity.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/entity.py	Thu Sep 23 23:28:58 2010 +0200
@@ -393,12 +393,13 @@
         # in linksearch mode, we don't want external urls else selecting
         # the object for use in the relation is tricky
         # XXX search_state is web specific
-        if 'base-url' not in kwargs and \
+        use_ext_id = False
+        if 'base_url' not in kwargs and \
                getattr(self._cw, 'search_state', ('normal',))[0] == 'normal':
-            kwargs['base_url'] = self.cw_metainformation()['source'].get('base-url')
-            use_ext_id = bool(kwargs['base_url'])
-        else:
-            use_ext_id = False
+            baseurl = self.cw_metainformation()['source'].get('base-url')
+            if baseurl:
+                kwargs['base_url'] = baseurl
+                use_ext_id = True
         if method in (None, 'view'):
             try:
                 kwargs['_restpath'] = self.rest_path(use_ext_id)
--- a/hooks/syncschema.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/hooks/syncschema.py	Thu Sep 23 23:28:58 2010 +0200
@@ -705,8 +705,9 @@
         cols = ['%s%s' % (prefix, r.rtype.name)
                 for r in self.entity.relations]
         dbhelper= session.pool.source('system').dbhelper
-        sql = dbhelper.sql_create_multicol_unique_index(table, cols)
-        session.system_sql(sql)
+        sqls = dbhelper.sqls_create_multicol_unique_index(table, cols)
+        for sql in sqls:
+            session.system_sql(sql)
 
     # XXX revertprecommit_event
 
@@ -724,8 +725,9 @@
         table = '%s%s' % (prefix, self.entity.type)
         dbhelper= session.pool.source('system').dbhelper
         cols = ['%s%s' % (prefix, c) for c in self.cols]
-        sql = dbhelper.sql_drop_multicol_unique_index(table, cols)
-        session.system_sql(sql)
+        sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols)
+        for sql in sqls:
+            session.system_sql(sql)
 
     # XXX revertprecommit_event
 
--- a/i18n/en.po	Tue Sep 21 16:35:37 2010 +0200
+++ b/i18n/en.po	Thu Sep 23 23:28:58 2010 +0200
@@ -182,6 +182,9 @@
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
 msgstr ""
 
+msgid "<not specified>"
+msgstr ""
+
 msgid "?*"
 msgstr "0..1 0..n"
 
--- a/i18n/es.po	Tue Sep 21 16:35:37 2010 +0200
+++ b/i18n/es.po	Thu Sep 23 23:28:58 2010 +0200
@@ -191,6 +191,9 @@
 "pero se puede ver a un <a href=\"%s\">modelo completo con meta-datos</a>.</"
 "div>"
 
+msgid "<not specified>"
+msgstr ""
+
 msgid "?*"
 msgstr "0..1 0..n"
 
--- a/i18n/fr.po	Tue Sep 21 16:35:37 2010 +0200
+++ b/i18n/fr.po	Thu Sep 23 23:28:58 2010 +0200
@@ -189,6 +189,9 @@
 "<div>Ce schéma du modèle de données <em>exclue</em> les méta-données, mais "
 "vous pouvez afficher un <a href=\"%s\">schéma complet</a>.</div>"
 
+msgid "<not specified>"
+msgstr "<non spécifié>"
+
 msgid "?*"
 msgstr "0..1 0..n"
 
--- a/req.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/req.py	Thu Sep 23 23:28:58 2010 +0200
@@ -174,6 +174,8 @@
         """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.
+
+        raises :exc:`ValueError` if None is found in arguments
         """
         # use *args since we don't want first argument to be "anonymous" to
         # avoid potential clash with kwargs
@@ -201,7 +203,6 @@
             return u'%s%s' % (base_url, path)
         return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
 
-
     def build_url_params(self, **kwargs):
         """return encoded params to incorporate them in an URL"""
         args = []
@@ -209,6 +210,8 @@
             if not isinstance(values, (list, tuple)):
                 values = (values,)
             for value in values:
+                if value is None:
+                    raise ValueError(_('unauthorized value'))
                 args.append(u'%s=%s' % (param, self.url_quote(value)))
         return '&'.join(args)
 
--- a/server/migractions.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/migractions.py	Thu Sep 23 23:28:58 2010 +0200
@@ -207,7 +207,7 @@
                          askconfirm=True):
         # check
         if not osp.exists(backupfile):
-            raise Exception("Backup file %s doesn't exist" % backupfile)
+            raise ExecutionError("Backup file %s doesn't exist" % backupfile)
         if askconfirm and not self.confirm('Restore %s database from %s ?'
                                            % (self.config.appid, backupfile)):
             return
@@ -221,7 +221,7 @@
         else:
             for name in bkup.getnames():
                 if name[0] in '/.':
-                    raise Exception('Security check failed, path starts with "/" or "."')
+                    raise ExecutionError('Security check failed, path starts with "/" or "."')
             bkup.close() # XXX seek error if not close+open !?!
             bkup = tarfile.open(backupfile, 'r|gz')
             bkup.extractall(path=tmpdir)
@@ -793,8 +793,8 @@
             try:
                 specialized.eid = instschema[specialized].eid
             except KeyError:
-                raise Exception('trying to add entity type but parent type is '
-                                'not yet in the database schema')
+                raise ExecutionError('trying to add entity type but parent type is '
+                                     'not yet in the database schema')
             self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
         # register entity's attributes
         for rschema, attrschema in eschema.attribute_definitions():
--- a/server/repository.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/repository.py	Thu Sep 23 23:28:58 2010 +0200
@@ -464,13 +464,24 @@
         cubes.remove('cubicweb')
         return cubes
 
-    def get_option_value(self, option):
-        """Return the value for `option` in the configuration.
+    def get_option_value(self, option, foreid=None):
+        """Return the value for `option` in the configuration. If `foreid` is
+        specified, the actual repository to which this entity belongs is
+        derefenced and the option value retrieved from it.
 
         This is a public method, not requiring a session id.
         """
         # XXX we may want to check we don't give sensible information
-        return self.config[option]
+        if foreid is None:
+            return self.config[option]
+        _, sourceuri, extid = self.type_and_source_from_eid(foreid)
+        if sourceuri == 'system':
+            return self.config[option]
+        pool = self._get_pool()
+        try:
+            return pool.connection(sourceuri).get_option_value(option, extid)
+        finally:
+            self._free_pool(pool)
 
     @cached
     def get_versions(self, checkversions=False):
--- a/server/rqlannotation.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/rqlannotation.py	Thu Sep 23 23:28:58 2010 +0200
@@ -17,8 +17,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Functions to add additional annotations on a rql syntax tree to ease later
 code generation.
+"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from logilab.common.compat import any
--- a/server/serverconfig.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/serverconfig.py	Thu Sep 23 23:28:58 2010 +0200
@@ -45,15 +45,27 @@
     )
 
 class SourceConfiguration(Configuration):
-    def __init__(self, appid, options):
-        self.appid = appid # has to be done before super call
+    def __init__(self, appconfig, options):
+        self.appconfig = appconfig # has to be done before super call
         super(SourceConfiguration, self).__init__(options=options)
 
     # make Method('default_instance_id') usable in db option defs (in native.py)
     def default_instance_id(self):
-        return self.appid
+        return self.appconfig.appid
 
-def generate_sources_file(appid, sourcesfile, sourcescfg, keys=None):
+    def input_option(self, option, optdict, inputlevel):
+        if self['db-driver'] == 'sqlite':
+            if option in ('db-user', 'db-password'):
+                return
+            if option == 'db-name':
+                optdict = optdict.copy()
+                optdict['help'] = 'path to the sqlite database'
+                optdict['default'] = join(self.appconfig.appdatahome,
+                                          self.appconfig.appid + '.sqlite')
+        super(SourceConfiguration, self).input_option(option, optdict, inputlevel)
+
+
+def generate_sources_file(appconfig, sourcesfile, sourcescfg, keys=None):
     """serialize repository'sources configuration into a INI like file
 
     the `keys` parameter may be used to sort sections
@@ -73,7 +85,7 @@
                 options = USER_OPTIONS
             else:
                 options = SOURCE_TYPES[sconfig['adapter']].options
-            _sconfig = SourceConfiguration(appid, options=options)
+            _sconfig = SourceConfiguration(appconfig, options=options)
             for attr, val in sconfig.items():
                 if attr == 'uri':
                     continue
@@ -278,7 +290,7 @@
         if exists(sourcesfile):
             import shutil
             shutil.copy(sourcesfile, sourcesfile + '.bak')
-        generate_sources_file(self.appid, sourcesfile, sourcescfg,
+        generate_sources_file(self, sourcesfile, sourcescfg,
                               ['admin', 'system'])
         restrict_perms_to_user(sourcesfile)
 
--- a/server/serverctl.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/serverctl.py	Thu Sep 23 23:28:58 2010 +0200
@@ -174,7 +174,7 @@
         sourcesfile = config.sources_file()
         # XXX hack to make Method('default_instance_id') usable in db option
         # defs (in native.py)
-        sconfig = SourceConfiguration(config.appid,
+        sconfig = SourceConfiguration(config,
                                       options=SOURCE_TYPES['native'].options)
         sconfig.adapter = 'native'
         sconfig.input_config(inputlevel=inputlevel)
@@ -234,6 +234,9 @@
         dbname = source['db-name']
         helper = get_db_helper(source['db-driver'])
         if ASK.confirm('Delete database %s ?' % dbname):
+            if source['db-driver'] == 'sqlite':
+                os.unlink(source['db-name'])
+                return
             user = source['db-user'] or None
             cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
             cursor = cnx.cursor()
--- a/server/sources/native.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/sources/native.py	Thu Sep 23 23:28:58 2010 +0200
@@ -675,7 +675,7 @@
                         index_name = mo.group(0)
                         elements = index_name.rstrip('_idx').split('_cw_')[1:]
                         etype = elements[0]
-                        rtypes = elements[1:]                        
+                        rtypes = elements[1:]
                         raise UniqueTogetherError(etype, rtypes)
                     mo = re.search('columns (.*) are not unique', arg)
                     if mo is not None: # sqlite in use
@@ -867,7 +867,6 @@
             cnx.commit()
             return eid
 
-
     def add_info(self, session, entity, source, extid, complete):
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
--- a/server/sources/pyrorql.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/sources/pyrorql.py	Thu Sep 23 23:28:58 2010 +0200
@@ -197,7 +197,8 @@
         """method called by the repository once ready to handle request"""
         interval = int(self.config.get('synchronization-interval', 5*60))
         self.repo.looping_task(interval, self.synchronize)
-        self.repo.looping_task(self._query_cache.ttl.seconds/10, self._query_cache.clear_expired)
+        self.repo.looping_task(self._query_cache.ttl.seconds/10,
+                               self._query_cache.clear_expired)
 
     def synchronize(self, mtime=None):
         """synchronize content known by this repository with content in the
--- a/server/sources/rql2sql.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/sources/rql2sql.py	Thu Sep 23 23:28:58 2010 +0200
@@ -761,6 +761,8 @@
             restrictions.append(restriction)
         restriction = ' AND '.join(restrictions)
         if not restriction:
+            if tables:
+                return 'SELECT 1 FROM %s' % ', '.join(tables)
             return ''
         if not tables:
             # XXX could leave surrounding EXISTS() in this case no?
@@ -1141,11 +1143,11 @@
 
     def visit_constant(self, constant):
         """generate SQL name for a constant"""
-        value = constant.value
         if constant.type is None:
             return 'NULL'
+        value = constant.value
         if constant.type == 'Int' and  isinstance(constant.parent, SortTerm):
-            return constant.value
+            return value
         if constant.type in ('Date', 'Datetime'):
             rel = constant.relation()
             if rel is not None:
@@ -1158,7 +1160,7 @@
                 # we may found constant from simplified var in varmap
                 return self._mapped_term(constant, '%%(%s)s' % value)[0]
             except KeyError:
-                _id = constant.value
+                _id = value
                 if isinstance(_id, unicode):
                     _id = _id.encode()
         else:
--- a/server/sqlutils.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/sqlutils.py	Thu Sep 23 23:28:58 2010 +0200
@@ -261,9 +261,8 @@
         attrs = {}
         eschema = entity.e_schema
         for attr, value in entity.cw_edited.iteritems():
-            rschema = eschema.subjrels[attr]
-            if rschema.final:
-                atype = str(eschema.destination(attr))
+            if value is not None and eschema.subjrels[attr].final:
+                atype = str(entity.e_schema.destination(attr))
                 if atype == 'Boolean':
                     value = self.dbhelper.boolean_value(value)
                 elif atype == 'Password':
--- a/server/test/unittest_rql2sql.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/server/test/unittest_rql2sql.py	Thu Sep 23 23:28:58 2010 +0200
@@ -567,6 +567,11 @@
 GROUP BY T1.C0,T1.C2
 ORDER BY T1.C2'''),
 
+    ('Any 1 WHERE X in_group G, X is CWUser',
+     '''SELECT 1
+FROM in_group_relation AS rel_in_group0'''),
+
+
     ]
 
 
@@ -1466,6 +1471,15 @@
 FROM cw_CWUser AS _X
 WHERE ((CAST(EXTRACT(YEAR from _X.cw_creation_date) AS INTEGER)=2010) OR (_X.cw_creation_date IS NULL))''')
 
+    def test_not_no_where(self):
+        # XXX will check if some in_group relation exists, that's it.
+        # We  can't actually know if we want to check if there are some
+        # X without in_group relation, or some G without it.
+        self._check('Any 1 WHERE NOT X in_group G, X is CWUser',
+                    '''SELECT 1
+WHERE NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group0))''')
+
+
 
 class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):
     backend = 'sqlite'
@@ -1686,6 +1700,13 @@
 WHERE ((EXTRACT(YEAR from _X.cw_creation_date)=2010) OR (_X.cw_creation_date IS NULL))''')
 
 
+    def test_not_no_where(self):
+        self._check('Any 1 WHERE NOT X in_group G, X is CWUser',
+                    '''SELECT 1
+FROM (SELECT 1) AS _T
+WHERE NOT (EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group0))''')
+
+
 class removeUnsusedSolutionsTC(TestCase):
     def test_invariant_not_varying(self):
         rqlst = mock_object(defined_vars={})
--- a/setup.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/setup.py	Thu Sep 23 23:28:58 2010 +0200
@@ -47,7 +47,7 @@
 import __pkginfo__
 if USE_SETUPTOOLS:
     requires = {}
-    for entry in ("__depends__", "__recommends__"):
+    for entry in ("__depends__",): # "__recommends__"):
         requires.update(getattr(__pkginfo__, entry, {}))
     install_requires = [("%s %s" % (d, v and v or "")).strip()
                        for d, v in requires.iteritems()]
--- a/skeleton/MANIFEST.in	Tue Sep 21 16:35:37 2010 +0200
+++ b/skeleton/MANIFEST.in	Thu Sep 23 23:28:58 2010 +0200
@@ -1,5 +1,5 @@
 include *.py
 include */*.py
-recursive-include data external_resources *.gif *.png *.css *.ico *.js
-recursive-include i18n *.pot *.po
+recursive-include data *.gif *.png *.ico *.css *.js
+recursive-include i18n *.po
 recursive-include wdoc *
--- a/skeleton/setup.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/skeleton/setup.py	Thu Sep 23 23:28:58 2010 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# pylint: disable-msg=W0404,W0622,W0704,W0613,W0152
+# pylint: disable=W0404,W0622,W0704,W0613
 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/comment/__init__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/comment/__pkginfo__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,25 @@
+# pylint: disable-msg=W0622
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-comment packaging information"""
+
+distname = "cubicweb-comment"
+modname = distname.split('-', 1)[1]
+
+numversion = (1, 4, 3)
+version = '.'.join(str(num) for num in numversion)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/email/__init__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/email/__pkginfo__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,30 @@
+# pylint: disable-msg=W0622
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-email packaging information"""
+
+distname = "cubicweb-email"
+modname = distname.split('-', 1)[1]
+
+numversion = (1, 4, 3)
+version = '.'.join(str(num) for num in numversion)
+
+
+__depends__ = {'cubicweb': None,
+               'cubicweb-file': None}
+__recommends__ = {'cubicweb-comment': None}
--- a/test/data/cubes/file/__pkginfo__.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/test/data/cubes/file/__pkginfo__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -16,9 +16,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb-file packaging information
-
-"""
+"""cubicweb-file packaging information"""
 
 distname = "cubicweb-file"
 modname = distname.split('-', 1)[1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/forge/__init__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/cubes/forge/__pkginfo__.py	Thu Sep 23 23:28:58 2010 +0200
@@ -0,0 +1,32 @@
+# pylint: disable-msg=W0622
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-forge packaging information"""
+
+distname = "cubicweb-forge"
+modname = distname.split('-', 1)[1]
+
+numversion = (1, 4, 3)
+version = '.'.join(str(num) for num in numversion)
+
+
+__depends__ = {'cubicweb': None,
+               'cubicweb-file': None,
+               'cubicweb-email': None,
+               'cubicweb-comment': None,
+               }
--- a/test/unittest_cwconfig.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/test/unittest_cwconfig.py	Thu Sep 23 23:28:58 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
+"""cubicweb.cwconfig unit tests"""
 
-"""
 import sys
 import os
 import tempfile
@@ -51,7 +50,9 @@
         ApptestConfiguration.CUBES_PATH = []
 
     def test_reorder_cubes(self):
-        # jpl depends on email and file and comment
+        self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
+        self.config.adjust_sys_path()
+        # forge depends on email and file and comment
         # email depends on file
         self.assertEquals(self.config.reorder_cubes(['file', 'email', 'forge']),
                           ('forge', 'email', 'file'))
@@ -67,6 +68,8 @@
                           ('forge', 'email', 'file'))
 
     def test_reorder_cubes_recommends(self):
+        self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
+        self.config.adjust_sys_path()
         from cubes.comment import __pkginfo__ as comment_pkginfo
         comment_pkginfo.__recommends_cubes__ = {'file': None}
         try:
@@ -130,6 +133,7 @@
         from cubes import file
         self.assertEquals(file.__path__, [join(CUSTOM_CUBES_DIR, 'file')])
 
+
 class FindPrefixTC(TestCase):
     def make_dirs(self, *args):
         path = join(tempfile.tempdir, *args)
--- a/test/unittest_req.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/test/unittest_req.py	Thu Sep 23 23:28:58 2010 +0200
@@ -17,9 +17,11 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.req import RequestSessionBase
+from cubicweb.devtools.testlib import CubicWebTC
+
 
 class RebuildURLTC(TestCase):
-    def test(self):
+    def test_rebuild_url(self):
         rebuild_url = RequestSessionBase(None).rebuild_url
         self.assertEquals(rebuild_url('http://logilab.fr?__message=pouet', __message='hop'),
                           'http://logilab.fr?__message=hop')
@@ -28,6 +30,18 @@
         self.assertEquals(rebuild_url('http://logilab.fr?vid=index', __message='hop'),
                           'http://logilab.fr?__message=hop&vid=index')
 
+    def test_build_url(self):
+        req = RequestSessionBase(None)
+        req.from_controller = lambda : 'view'
+        req.relative_path = lambda includeparams=True: None
+        req.base_url = lambda : 'http://testing.fr/cubicweb/'
+        self.assertEqual(req.build_url(), u'http://testing.fr/cubicweb/view')
+        self.assertEqual(req.build_url(None), u'http://testing.fr/cubicweb/view')
+        self.assertEqual(req.build_url('one'), u'http://testing.fr/cubicweb/one')
+        self.assertEqual(req.build_url(param='ok'), u'http://testing.fr/cubicweb/view?param=ok')
+        self.assertRaises(AssertionError, req.build_url, 'one', 'two not allowed')
+        self.assertRaises(ValueError, req.build_url, 'view', test=None)
+
 
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_schema.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/test/unittest_schema.py	Thu Sep 23 23:28:58 2010 +0200
@@ -173,7 +173,8 @@
                              'Date', 'Datetime', 'Decimal',
                              'CWCache', 'CWConstraint', 'CWConstraintType', 'CWEType',
                              'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation',
-                             'CWPermission', 'CWProperty', 'CWRType', 'CWUser',
+                             'CWPermission', 'CWProperty', 'CWRType',
+                             'CWUniqueTogetherConstraint', 'CWUser',
                              'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note',
                              'Password', 'Personne',
                              'RQLExpression',
@@ -187,8 +188,10 @@
                               'bookmarked_by', 'by_transition',
 
                               'cardinality', 'comment', 'comment_format',
-                              'composite', 'condition', 'connait', 'constrained_by', 'content',
-                              'content_format', 'created_by', 'creation_date', 'cstrtype', 'custom_workflow', 'cwuri',
+                              'composite', 'condition', 'connait',
+                              'constrained_by', 'constraint_of',
+                              'content', 'content_format',
+                              'created_by', 'creation_date', 'cstrtype', 'custom_workflow', 'cwuri',
 
                               'data', 'data_encoding', 'data_format', 'data_name', 'default_workflow', 'defaultval', 'delete_permission',
                               'description', 'description_format', 'destination_state',
@@ -212,7 +215,7 @@
 
                               'path', 'pkey', 'prefered_form', 'prenom', 'primary_email',
 
-                              'read_permission', 'relation_type', 'require_group',
+                              'read_permission', 'relation_type', 'relations', 'require_group',
 
                               'specializes', 'state_of', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis',
 
--- a/web/application.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/application.py	Thu Sep 23 23:28:58 2010 +0200
@@ -360,7 +360,8 @@
         """
         path = path or 'view'
         # don't log form values they may contains sensitive information
-        self.info('publish "%s" (form params: %s)', path, req.form.keys())
+        self.info('publish "%s" (%s, form params: %s)',
+                  path, req.session.sessionid, req.form.keys())
         # remove user callbacks on a new request (except for json controllers
         # to avoid callbacks being unregistered before they could be called)
         tstart = clock()
--- a/web/component.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/component.py	Thu Sep 23 23:28:58 2010 +0200
@@ -110,6 +110,10 @@
             url = self.ajax_page_url(**params)
         else:
             url = self._cw.build_url(path, **params)
+        # XXX hack to avoid opening a new page containing the evaluation of the
+        # js expression on ajax call
+        if url.startswith('javascript:'):
+            url += '; noop();'
         return url
 
     def ajax_page_url(self, **params):
@@ -120,10 +124,6 @@
 
     def page_link(self, path, params, start, stop, content):
         url = xml_escape(self.page_url(path, params, start, stop))
-        # XXX hack to avoid opening a new page containing the evaluation of the
-        # js expression on ajax call
-        if url.startswith('javascript:'):
-            url += '; noop();'
         if start == self.starting_from:
             return self.selected_page_link_templ % (url, content, content)
         return self.page_link_templ % (url, content, content)
--- a/web/data/cubicweb.reledit.js	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/data/cubicweb.reledit.js	Thu Sep 23 23:28:58 2010 +0200
@@ -63,12 +63,12 @@
      * @param reload: boolean to reload page if true (when changing URL dependant data)
      * @param default_value : value if the field is empty
      */
-    loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid, default_value) {
+    loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid) {
         var args = {fname: 'reledit_form', rtype: rtype, role: role,
                     pageid: pageid,
                     eid: eid, divid: divid, formid: formid,
-                    reload: reload, vid: vid, default_value: default_value,
-                    callback: function () {cw.reledit.showInlineEditionForm(divid);}};
-       jQuery('#'+divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
+                    reload: reload, vid: vid};
+        var d = jQuery('#'+divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
+        d.addCallback(function () {cw.reledit.showInlineEditionForm(divid);});
     }
 });
--- a/web/formfields.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/formfields.py	Thu Sep 23 23:28:58 2010 +0200
@@ -877,6 +877,15 @@
         # XXX empty string for 'no' in that case for bw compat
         return [(form._cw._('yes'), '1'), (form._cw._('no'), '')]
 
+    def format_single_value(self, req, value):
+        """return value suitable for display"""
+        if self.allow_none:
+            if value is None:
+                return u''
+            if value is False:
+                return '0'
+        return super(BooleanField, self).format_single_value(req, value)
+
     def _ensure_correctly_typed(self, form, value):
         if self.allow_none:
             if value:
--- a/web/test/unittest_reledit.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/test/unittest_reledit.py	Thu Sep 23 23:28:58 2010 +0200
@@ -33,73 +33,71 @@
 class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC):
 
     def test_default_config(self):
-        reledit = {'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, false, &#39;&#39;, &#39;&amp;lt;title not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-                   'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue">&lt;long_desc not specified&gt;</div><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;long_desc not specified&amp;gt;&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
-                   'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue">&lt;manager not specified&gt;</div><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-                   'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
-                   'concerns': """&lt;concerns_object not specified&gt;"""}
+        reledit = {'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, false, &#39;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                   'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
+                   'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                   'composite_card11_2ttypes': """&lt;not specified&gt;""",
+                   'concerns': """&lt;not specified&gt;"""}
 
         for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
             if rschema not in reledit:
                 continue
             rtype = rschema.type
-            self.assertTextEquals(reledit[rtype], self.proj.view('reledit', rtype=rtype, role=role), rtype)
+            self.assertTextEquals(reledit[rtype] % {'eid': self.proj.eid}, self.proj.view('reledit', rtype=rtype, role=role), rtype)
 
     def test_default_forms(self):
-        doreledit = {'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="title-subject-917-form" onsubmit="return freezeFormButtons(&#39;title-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+        doreledit = {'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="title-subject-%(eid)s-form" onsubmit="return freezeFormButtons(&#39;title-subject-%(eid)s-form&#39;);" class="releditForm" cubicweb:target="eformframe">
 <fieldset>
 <input name="__form_id" type="hidden" value="base" />
-<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#title-subject-917-form" />
-<input name="__domid" type="hidden" value="title-subject-917-form" />
-<input name="__type:917" type="hidden" value="Project" />
-<input name="eid" type="hidden" value="917" />
-<input name="__maineid" type="hidden" value="917" />
-<input name="__reledit|default_value" type="hidden" value="&amp;lt;title not specified&amp;gt;" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#title-subject-%(eid)s-form" />
+<input name="__domid" type="hidden" value="title-subject-%(eid)s-form" />
+<input name="__type:%(eid)s" type="hidden" value="Project" />
+<input name="eid" type="hidden" value="%(eid)s" />
+<input name="__maineid" type="hidden" value="%(eid)s" />
 <input name="__reledit|vid" type="hidden" value="" />
 <input name="__reledit|rtype" type="hidden" value="title" />
-<input name="__reledit|divid" type="hidden" value="title-subject-917" />
+<input name="__reledit|divid" type="hidden" value="title-subject-%(eid)s" />
 <input name="__reledit|formid" type="hidden" value="base" />
 <input name="__reledit|reload" type="hidden" value="false" />
 <input name="__reledit|role" type="hidden" value="subject" />
-<input name="__reledit|eid" type="hidden" value="917" />
-<input name="_cw_edited_fields:917" type="hidden" value="title-subject,__type" />
+<input name="__reledit|eid" type="hidden" value="%(eid)s" />
+<input name="_cw_edited_fields:%(eid)s" type="hidden" value="title-subject,__type" />
 <fieldset class="default">
 <table class="">
 <tr class="title_subject_row">
 <td
 >
-<input id="title-subject:917" maxlength="32" name="title-subject:917" size="32" tabindex="1" type="text" value="cubicweb-world-domination" />
+<input id="title-subject:%(eid)s" maxlength="32" name="title-subject:%(eid)s" size="32" tabindex="1" type="text" value="cubicweb-world-domination" />
 </td></tr>
 </table></fieldset>
 <table class="buttonbar">
 <tr>
 
-<td><button class="validateButton" tabindex="2" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" tabindex="2" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
 
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;title-subject-917&#39;)" tabindex="3" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;title-subject-%(eid)s&#39;)" tabindex="3" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 
 </tr></table>
 </fieldset>
-</form><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, false, &#39;&#39;, &#39;&amp;lt;title not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+</form><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, false, &#39;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
 
-                     'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue">&lt;long_desc not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="long_desc-subject-917-form" onsubmit="return freezeFormButtons(&#39;long_desc-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+                     'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="long_desc-subject-%(eid)s-form" onsubmit="return freezeFormButtons(&#39;long_desc-subject-%(eid)s-form&#39;);" class="releditForm" cubicweb:target="eformframe">
 <fieldset>
 <input name="__form_id" type="hidden" value="edition" />
-<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#long_desc-subject-917-form" />
-<input name="__domid" type="hidden" value="long_desc-subject-917-form" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#long_desc-subject-%(eid)s-form" />
+<input name="__domid" type="hidden" value="long_desc-subject-%(eid)s-form" />
 <input name="__type:A" type="hidden" value="Blog" />
 <input name="eid" type="hidden" value="A" />
 <input name="__maineid" type="hidden" value="A" />
-<input name="__linkto" type="hidden" value="long_desc:917:object" />
+<input name="__linkto" type="hidden" value="long_desc:%(eid)s:object" />
 <input name="__message" type="hidden" value="entity linked" />
-<input name="__reledit|default_value" type="hidden" value="&amp;lt;long_desc not specified&amp;gt;" />
-<input name="__reledit|vid" type="hidden" value="incontext" />
+<input name="__reledit|vid" type="hidden" value="autolimited" />
 <input name="__reledit|rtype" type="hidden" value="long_desc" />
-<input name="__reledit|divid" type="hidden" value="long_desc-subject-917" />
+<input name="__reledit|divid" type="hidden" value="long_desc-subject-%(eid)s" />
 <input name="__reledit|formid" type="hidden" value="edition" />
 <input name="__reledit|reload" type="hidden" value="false" />
 <input name="__reledit|role" type="hidden" value="subject" />
-<input name="__reledit|eid" type="hidden" value="917" />
+<input name="__reledit|eid" type="hidden" value="%(eid)s" />
 <input name="_cw_edited_fields:A" type="hidden" value="title-subject,rss_url-subject,__type,description-subject" />
 <fieldset class="default">
 <table class="attributeForm">
@@ -125,62 +123,61 @@
 <table class="buttonbar">
 <tr>
 
-<td><button class="validateButton" tabindex="7" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" tabindex="7" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
 
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;long_desc-subject-917&#39;)" tabindex="8" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;long_desc-subject-%(eid)s&#39;)" tabindex="8" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 
 </tr></table>
 </fieldset>
-</form><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;long_desc not specified&amp;gt;&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
+</form><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
 
-                     'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue">&lt;manager not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="manager-subject-917-form" onsubmit="return freezeFormButtons(&#39;manager-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+                     'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue">&lt;not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="manager-subject-%(eid)s-form" onsubmit="return freezeFormButtons(&#39;manager-subject-%(eid)s-form&#39;);" class="releditForm" cubicweb:target="eformframe">
 <fieldset>
 <input name="__form_id" type="hidden" value="base" />
-<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#manager-subject-917-form" />
-<input name="__domid" type="hidden" value="manager-subject-917-form" />
-<input name="__type:917" type="hidden" value="Project" />
-<input name="eid" type="hidden" value="917" />
-<input name="__maineid" type="hidden" value="917" />
-<input name="__linkto" type="hidden" value="long_desc:917:object" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#manager-subject-%(eid)s-form" />
+<input name="__domid" type="hidden" value="manager-subject-%(eid)s-form" />
+<input name="__type:%(eid)s" type="hidden" value="Project" />
+<input name="eid" type="hidden" value="%(eid)s" />
+<input name="__maineid" type="hidden" value="%(eid)s" />
+<input name="__linkto" type="hidden" value="long_desc:%(eid)s:object" />
 <input name="__message" type="hidden" value="entity linked" />
-<input name="__reledit|default_value" type="hidden" value="&amp;lt;manager not specified&amp;gt;" />
-<input name="__reledit|vid" type="hidden" value="incontext" />
+<input name="__reledit|vid" type="hidden" value="autolimited" />
 <input name="__reledit|rtype" type="hidden" value="manager" />
-<input name="__reledit|divid" type="hidden" value="manager-subject-917" />
+<input name="__reledit|divid" type="hidden" value="manager-subject-%(eid)s" />
 <input name="__reledit|formid" type="hidden" value="base" />
 <input name="__reledit|reload" type="hidden" value="false" />
 <input name="__reledit|role" type="hidden" value="subject" />
-<input name="__reledit|eid" type="hidden" value="917" />
-<input name="_cw_edited_fields:917" type="hidden" value="manager-subject,__type" />
+<input name="__reledit|eid" type="hidden" value="%(eid)s" />
+<input name="_cw_edited_fields:%(eid)s" type="hidden" value="manager-subject,__type" />
 <fieldset class="default">
 <table class="">
 <tr class="manager_subject_row">
 <td
 >
-<select id="manager-subject:917" name="manager-subject:917" size="1" tabindex="9">
+<select id="manager-subject:%(eid)s" name="manager-subject:%(eid)s" size="1" tabindex="9">
 <option value="__cubicweb_internal_field__"></option>
-<option value="919">Toto</option>
+<option value="%(toto)s">Toto</option>
 </select>
 </td></tr>
 </table></fieldset>
 <table class="buttonbar">
 <tr>
 
-<td><button class="validateButton" tabindex="10" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" tabindex="10" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
 
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;manager-subject-917&#39;)" tabindex="11" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;manager-subject-%(eid)s&#39;)" tabindex="11" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 
 </tr></table>
 </fieldset>
-</form><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-                     'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
-                     'concerns': """&lt;concerns_object not specified&gt;"""
+</form><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                     'composite_card11_2ttypes': """&lt;not specified&gt;""",
+                     'concerns': """&lt;not specified&gt;"""
             }
         for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
             if rschema not in doreledit:
                 continue
             rtype = rschema.type
-            self.assertTextEquals(doreledit[rtype],
+            self.assertTextEquals(doreledit[rtype] % {'eid': self.proj.eid, 'toto': self.toto.eid},
                                   self.proj.view('doreledit', rtype=rtype, role=role,
                                                  formid='edition' if rtype == 'long_desc' else 'base'),
                                   rtype)
@@ -195,10 +192,10 @@
     def test_with_uicfg(self):
         old_rctl = reledit_ctrl._tagdefs.copy()
         reledit_ctrl.tag_attribute(('Project', 'title'),
-                                   {'default_value': '<title is required>', 'reload': True})
+                                   {'novalue_label': '<title is required>', 'reload': True})
         reledit_ctrl.tag_subject_of(('Project', 'long_desc', '*'),
                                     {'reload': True, 'edit_target': 'rtype',
-                                     'default_value': u'<long_desc is required>'})
+                                     'novalue_label': u'<long_desc is required>'})
         reledit_ctrl.tag_subject_of(('Project', 'manager', '*'),
                                    {'edit_target': 'related'})
         reledit_ctrl.tag_subject_of(('Project', 'composite_card11_2ttypes', '*'),
@@ -206,17 +203,17 @@
         reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'),
                                    {'edit_target': 'rtype'})
         reledit = {
-            'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, true, &#39;&#39;, &#39;&lt;title is required&gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-            'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue"><long_desc is required></div><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, true, &#39;incontext&#39;, &#39;&lt;long_desc is required&gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
-            'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/919" title="">Toto</a></div><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-917-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;deleteconf&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to delete this value"><img title="click to delete this value" src="data/cancel.png" alt="click to delete this value"/></div></div></div>""",
-            'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
-            'concerns': """<div id="concerns-object-917-reledit" onmouseout="jQuery('#concerns-object-917').addClass('hidden')" onmouseover="jQuery('#concerns-object-917').removeClass('hidden')" class="releditField"><div id="concerns-object-917-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/918" title="">write the code</a></div><div id="concerns-object-917" class="editableField hidden"><div id="concerns-object-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;concerns&#39;, &#39;object&#39;, &#39;concerns-object-917&#39;, false, &#39;csv&#39;, &#39;&amp;lt;concerns_object not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
+            'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-%(eid)s&#39;, true, &#39;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue">&lt;long_desc is required&gt;</div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-%(eid)s&#39;, true, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/%(toto)s" title="">Toto</a></div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-%(eid)s-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;deleteconf&#39;, %(eid)s, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to delete this value"><img title="click to delete this value" src="data/cancel.png" alt="click to delete this value"/></div></div></div>""",
+            'composite_card11_2ttypes': """&lt;not specified&gt;""",
+            'concerns': """<div id="concerns-object-%(eid)s-reledit" onmouseout="jQuery('#concerns-object-%(eid)s').addClass('hidden')" onmouseover="jQuery('#concerns-object-%(eid)s').removeClass('hidden')" class="releditField"><div id="concerns-object-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/%(tick)s" title="">write the code</a></div><div id="concerns-object-%(eid)s" class="editableField hidden"><div id="concerns-object-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, %(eid)s, &#39;concerns&#39;, &#39;object&#39;, &#39;concerns-object-%(eid)s&#39;, false, &#39;autolimited&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
             }
         for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
             if rschema not in reledit:
                 continue
             rtype = rschema.type
-            self.assertTextEquals(reledit[rtype],
+            self.assertTextEquals(reledit[rtype] % {'eid': self.proj.eid, 'toto': self.toto.eid, 'tick': self.tick.eid},
                                   self.proj.view('reledit', rtype=rtype, role=role),
                                   rtype)
         reledit_ctrl.clear()
--- a/web/test/unittest_urlpublisher.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/test/unittest_urlpublisher.py	Thu Sep 23 23:28:58 2010 +0200
@@ -16,9 +16,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Unit tests for url publishing service
-
-"""
+"""Unit tests for url publishing service"""
 
 import re
 
@@ -69,29 +67,29 @@
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.description[0][0], 'CWUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"')
+        self.assertEquals(rset.printable_rql(), 'Any X,AA,AB,AC,AD WHERE X login "admin", X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD')
         ctrl, rset = self.process('cwuser/admin')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.description[0][0], 'CWUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X login "admin"')
+        self.assertEquals(rset.printable_rql(), 'Any X,AA,AB,AC,AD WHERE X login "admin", X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD')
         ctrl, rset = self.process('cwuser/eid/%s'%rset[0][0])
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.description[0][0], 'CWUser')
-        self.assertEquals(rset.printable_rql(), 'Any X WHERE X is CWUser, X eid 5')
+        self.assertEquals(rset.printable_rql(), 'Any X,AA,AB,AC,AD WHERE X eid 5, X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD')
         # test non-ascii paths
         ctrl, rset = self.process('CWUser/login/%C3%BFsa%C3%BFe')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.description[0][0], 'CWUser')
-        self.assertEquals(rset.printable_rql(), u'Any X WHERE X is CWUser, X login "ÿsaÿe"')
+        self.assertEquals(rset.printable_rql(), u'Any X,AA,AB,AC,AD WHERE X login "\xffsa\xffe", X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD')
         # test quoted paths
         ctrl, rset = self.process('BlogEntry/title/hell%27o')
         self.assertEquals(ctrl, 'view')
         self.assertEquals(len(rset), 1)
         self.assertEquals(rset.description[0][0], 'BlogEntry')
-        self.assertEquals(rset.printable_rql(), u'Any X WHERE X is BlogEntry, X title "hell\'o"')
+        self.assertEquals(rset.printable_rql(), u'Any X,AA,AB,AC WHERE X title "hell\'o", X is BlogEntry, X creation_date AA, X title AB, X modification_date AC')
         # errors
         self.assertRaises(NotFound, self.process, 'CWUser/eid/30000')
         self.assertRaises(NotFound, self.process, 'Workcases')
--- a/web/test/unittest_views_editforms.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/test/unittest_views_editforms.py	Thu Sep 23 23:28:58 2010 +0200
@@ -127,7 +127,8 @@
                                ])
         self.assertListEquals(rbc(e, 'main', 'relations'),
                               [('travaille', 'subject'),
-                               ('connait', 'object')
+                               ('manager', 'object'),
+                               ('connait', 'object'),
                                ])
         self.assertListEquals(rbc(e, 'main', 'hidden'),
                               [])
--- a/web/uicfg.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/uicfg.py	Thu Sep 23 23:28:58 2010 +0200
@@ -42,8 +42,6 @@
    # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
    # primary view
    uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
-
-
 """
 __docformat__ = "restructuredtext en"
 
@@ -372,22 +370,33 @@
 autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
 
 class ReleditTags(NoTargetRelationTagsDict):
+    """Associate to relation a dictionnary to control `reledit` (e.g. edition of
+    attributes / relations from within views).
+
+    Possible keys and associated values are:
+
+    * `novalue_label`, alternative default value (shown when there is no value).
+
+    * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean
+      flag control wether the generated default value should contains the
+      relation label or not. Will be the opposite of the `showlabel` value found
+      in the `primaryview_display_ctrl` rtag by default.
+
+    * `reload`, boolean, eid (to reload to) or function taking subject and
+      returning bool/eid. This is useful when editing a relation (or attribute)
+      that impacts the url or another parts of the current displayed
+      page. Defaults to False.
+
+    * `rvid`, alternative view id (as str) for relation or composite edition.
+      Default is 'autolimited'.
+
+    * `edit_target`, may be either 'rtype' (to edit the relation) or 'related'
+      (to edit the related entity).  This controls whether to edit the relation
+      or the target entity of the relation.  Currently only one-to-one relations
+      support target entity edition. By default, the 'related' option is taken
+      whenever the relation is composite.
     """
-    default_value: alternative default value
-      The default value is what is shown when there is no value.
-    reload: boolean, eid (to reload to) or function taking subject and returning bool/eid
-      This is useful when editing a relation (or attribute) that impacts the url
-      or another parts of the current displayed page. Defaults to False.
-    rvid: alternative view id (as str) for relation or composite edition
-      Default is 'incontext' or 'csv' depending on the cardinality. They can also be
-      statically changed by subclassing ClickAndEditFormView and redefining _one_rvid
-      (resp. _many_rvid).
-    edit_target: 'rtype' (to edit the relation) or 'related' (to edit the related entity)
-      This controls whether to edit the relation or the target entity of the relation.
-      Currently only one-to-one relations support target entity edition. By default,
-      the 'related' option is taken whenever the relation is composite and one-to-one.
-    """
-    _keys = frozenset('default_value reload rvid edit_target'.split())
+    _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
 
     def tag_relation(self, key, tag):
         for tagkey in tag.iterkeys():
@@ -412,6 +421,11 @@
         edittarget = 'related' if composite else 'rtype'
         rtag.tag_relation((sschema, rschema, oschema, role),
                           {'edit_target': edittarget})
+    if not 'novalue_include_rtype' in values:
+        showlabel = primaryview_display_ctrl.get(
+            sschema, rschema, oschema, role).get('showlabel', True)
+        rtag.tag_relation((sschema, rschema, oschema, role),
+                          {'novalue_include_rtype': not showlabel})
 
 reledit_ctrl = ReleditTags('reledit', init_reledit_ctrl)
 
--- a/web/views/basecontrollers.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/views/basecontrollers.py	Thu Sep 23 23:28:58 2010 +0200
@@ -160,7 +160,7 @@
         return view, rset
 
     def add_to_breadcrumbs(self, view):
-        # update breadcrumps **before** validating cache, unless the view
+        # update breadcrumbs **before** validating cache, unless the view
         # specifies explicitly it should not be added to breadcrumb or the
         # view is a binary view
         if view.add_to_breadcrumbs and not view.binary:
@@ -463,7 +463,7 @@
     def js_reledit_form(self):
         req = self._cw
         args = dict((x, req.form[x])
-                    for x in ('formid', 'rtype', 'role', 'reload', 'default_value'))
+                    for x in ('formid', 'rtype', 'role', 'reload'))
         rset = req.eid_rset(typed_eid(self._cw.form['eid']))
         try:
             args['reload'] = json.loads(args['reload'])
--- a/web/views/cwuser.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/views/cwuser.py	Thu Sep 23 23:28:58 2010 +0200
@@ -19,6 +19,8 @@
 
 __docformat__ = "restructuredtext en"
 
+import hashlib
+
 from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import one_line_rset, is_instance, match_user_groups
@@ -68,21 +70,22 @@
 
     def cell_call(self, row, col):
         entity = self.cw_rset.complete_entity(row, col)
-        self.w(u'''<foaf:PersonalProfileDocument rdf:about="">
-                      <foaf:maker rdf:resource="%s"/>
-                      <foaf:primaryTopic rdf:resource="%s"/>
-                   </foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
-        self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
-        self.w(u'<foaf:name>%s</foaf:name>\n' % xml_escape(entity.dc_long_title()))
+        # account
+        self.w(u'<foaf:OnlineAccount rdf:about="%s">\n' % entity.absolute_url())
+        self.w(u'  <foaf:accountName>%s</foaf:accountName>\n' % entity.login)
+        self.w(u'</foaf:OnlineAccount>\n')
+        # person
+        self.w(u'<foaf:Person rdf:about="%s#user">\n' % entity.absolute_url())
+        self.w(u'  <foaf:account rdf:resource="%s" />\n' % entity.absolute_url())
         if entity.surname:
-            self.w(u'<foaf:family_name>%s</foaf:family_name>\n'
+            self.w(u'<foaf:familyName>%s</foaf:familyName>\n'
                    % xml_escape(entity.surname))
         if entity.firstname:
-            self.w(u'<foaf:givenname>%s</foaf:givenname>\n'
+            self.w(u'<foaf:givenName>%s</foaf:givenName>\n'
                    % xml_escape(entity.firstname))
         emailaddr = entity.cw_adapt_to('IEmailable').get_email()
         if emailaddr:
-            self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % xml_escape(emailaddr))
+            self.w(u'<foaf:mbox_sha1sum>%s</foaf:mbox_sha1sum>\n' % hashlib.sha1(emailaddr).hexdigest())
         self.w(u'</foaf:Person>\n')
 
 
--- a/web/views/primary.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/views/primary.py	Thu Sep 23 23:28:58 2010 +0200
@@ -54,7 +54,6 @@
     def cell_call(self, row, col):
         self.cw_row = row
         self.cw_col = col
-        self.maxrelated = self._cw.property_value('navigation.related-limit')
         entity = self.cw_rset.complete_entity(row, col)
         self.render_entity(entity)
 
@@ -260,8 +259,7 @@
 
     def _relation_rset(self, entity, rschema, role, dispctrl):
         try:
-            dispctrl.setdefault('limit', self.maxrelated)
-            rset = entity.related(rschema.type, role, limit=dispctrl['limit']+1)
+            rset = entity.related(rschema.type, role)
         except Unauthorized:
             return
         if 'filter' in dispctrl:
@@ -295,22 +293,29 @@
 
 
 class RelatedView(EntityView):
+    """Display a rset, usually containing entities linked to another entity
+    being displayed.
+
+    It will try to display nicely according to the number of items in the result
+    set.
+    """
     __regid__ = 'autolimited'
 
     def call(self, **kwargs):
-        # nb: rset is retreived using entity.related with limit + 1 if any.
-        # Because of that, we know that rset.printable_rql() will return rql
-        # with no limit set anyway (since it's handled manually)
         if 'dispctrl' in self.cw_extra_kwargs:
-            limit = self.cw_extra_kwargs['dispctrl'].get('limit')
+            if 'limit' in self.cw_extra_kwargs['dispctrl']:
+                limit = self.cw_extra_kwargs['dispctrl']['limit']
+            else:
+                limit = self._cw.property_value('navigation.related-limit')
+            list_limit = self.cw_extra_kwargs['dispctrl'].get('use_list_limit', 5)
             subvid = self.cw_extra_kwargs['dispctrl'].get('subvid', 'incontext')
         else:
-            limit = None
+            limit = list_limit = None
             subvid = 'incontext'
         if limit is None or self.cw_rset.rowcount <= limit:
             if self.cw_rset.rowcount == 1:
                 self.wview(subvid, self.cw_rset, row=0)
-            elif 1 < self.cw_rset.rowcount <= 5:
+            elif list_limit is None or 1 < self.cw_rset.rowcount <= list_limit:
                 self.wview('csv', self.cw_rset, subvid=subvid)
             else:
                 self.w(u'<div>')
@@ -320,12 +325,18 @@
         else:
             rql = self.cw_rset.printable_rql()
             self.cw_rset.limit(limit) # remove extra entity
-            self.w(u'<div>')
-            self.wview('simplelist', self.cw_rset, subvid=subvid)
-            self.w(u'[<a href="%s">%s</a>]' % (
-                xml_escape(self._cw.build_url(rql=rql, vid=subvid)),
-                self._cw._('see them all')))
-            self.w(u'</div>')
+            if list_limit is None:
+                self.wview('csv', self.cw_rset, subvid=subvid)
+                self.w(u'[<a href="%s">%s</a>]' % (
+                    xml_escape(self._cw.build_url(rql=rql, vid=subvid)),
+                    self._cw._('see them all')))
+            else:
+                self.w(u'<div>')
+                self.wview('simplelist', self.cw_rset, subvid=subvid)
+                self.w(u'[<a href="%s">%s</a>]' % (
+                    xml_escape(self._cw.build_url(rql=rql, vid=subvid)),
+                    self._cw._('see them all')))
+                self.w(u'</div>')
 
 
 class URLAttributeView(EntityView):
--- a/web/views/reledit.py	Tue Sep 21 16:35:37 2010 +0200
+++ b/web/views/reledit.py	Thu Sep 23 23:28:58 2010 +0200
@@ -19,6 +19,7 @@
 """
 
 import copy
+from warnings import warn
 
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import deprecated
@@ -49,7 +50,7 @@
 
     # ui side continuations
     _onclick = (u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', "
-                "'%(divid)s', %(reload)s, '%(vid)s', '%(default_value)s');")
+                "'%(divid)s', %(reload)s, '%(vid)s');")
     _cancelclick = "cw.reledit.cleanupAfterCancel('%s')"
 
     # ui side actions/buttons
@@ -60,10 +61,6 @@
     _editzone = u'<img title="%(msg)s" src="data/pen_icon.png" alt="%(msg)s"/>'
     _editzonemsg = _('click to edit this field')
 
-    # default relation vids according to cardinality
-    # can be changed per rtype using reledit_ctrl rtag
-    _one_rvid = 'incontext'
-    _many_rvid = 'csv'
     # renderer
     _form_renderer_id = 'base'
 
@@ -83,82 +80,75 @@
         entity = self.cw_rset.get_entity(row, col)
         rschema = self._cw.vreg.schema[rtype]
         self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
+        if rvid is not None or default_value is not None:
+            warn('[3.9] specifying rvid/default_value on select is deprecated, '
+                 'reledit_ctrl rtag to control this' % self, DeprecationWarning)
         reload = self._compute_reload(entity, rschema, role, reload)
-        default_value = self._compute_default_value(entity, rschema, role, default_value)
         divid = self._build_divid(rtype, role, entity.eid)
         if rschema.final:
-            self._handle_attribute(entity, rschema, role, divid, reload, default_value)
+            self._handle_attribute(entity, rschema, role, divid, reload)
         else:
             if self._is_composite():
-                self._handle_composite(entity, rschema, role, divid, reload, default_value, formid)
+                self._handle_composite(entity, rschema, role, divid, reload, formid)
             else:
-                self._handle_relation(entity, rschema, role, divid, reload, default_value, formid)
+                self._handle_relation(entity, rschema, role, divid, reload, formid)
 
-    def _handle_attribute(self, entity, rschema, role, divid, reload, default_value):
+    def _handle_attribute(self, entity, rschema, role, divid, reload):
         rtype = rschema.type
         value = entity.printable_value(rtype)
         if not self._should_edit_attribute(entity, rschema):
             self.w(value)
             return
-
         display_label, related_entity = self._prepare_form(entity, rtype, role)
-        form, renderer = self._build_form(entity, rtype, role, divid, 'base', default_value,
+        form, renderer = self._build_form(entity, rtype, role, divid, 'base',
                                           reload, display_label, related_entity)
-        value = value or default_value
+        value = value or self._compute_default_value(rschema, role)
         self.view_form(divid, value, form, renderer)
 
-    def _compute_formid_value(self, entity, rschema, role, default_value, rvid, formid):
+    def _compute_formid_value(self, entity, rschema, role, rvid, formid):
         related_rset = entity.related(rschema.type, role)
         if related_rset:
             value = self._cw.view(rvid, related_rset)
         else:
-            value = default_value
+            value = self._compute_default_value(rschema, role)
         if not self._should_edit_relation(entity, rschema, role):
             return None, value
         return formid, value
 
-    def _handle_relation(self, entity, rschema, role, divid, reload, default_value, formid):
-        rvid = self._compute_best_vid(entity.e_schema, rschema, role)
-        formid, value = self._compute_formid_value(entity, rschema, role, default_value, rvid, formid)
+    def _handle_relation(self, entity, rschema, role, divid, reload, formid):
+        rvid = self._rules.get('rvid', 'autolimited')
+        formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
         if formid is None:
             return self.w(value)
-
         rtype = rschema.type
         display_label, related_entity = self._prepare_form(entity, rtype, role)
-        form, renderer = self._build_form(entity, rtype, role, divid, formid, default_value, reload,
+        form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
                                           display_label, related_entity, dict(vid=rvid))
         self.view_form(divid, value, form, renderer)
 
-    def _handle_composite(self, entity, rschema, role, divid, reload, default_value, formid):
+    def _handle_composite(self, entity, rschema, role, divid, reload, formid):
         # this is for attribute-like composites (1 target type, 1 related entity at most, for now)
         ttypes = self._compute_ttypes(rschema, role)
         related_rset = entity.related(rschema.type, role)
         add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes)
         edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes)
         delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role)
-
-        rvid = self._compute_best_vid(entity.e_schema, rschema, role)
-        formid, value = self._compute_formid_value(entity, rschema, role, default_value, rvid, formid)
+        rvid = self._rules.get('rvid', 'autolimited')
+        formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
         if formid is None or not (edit_related or add_related):
             # till we learn to handle cases where not (edit_related or add_related)
             self.w(value)
             return
-
         rtype = rschema.type
         ttype = ttypes[0]
-        _fdata = self._prepare_composite_form(entity, rtype, role, edit_related, add_related and ttype)
+        _fdata = self._prepare_composite_form(entity, rtype, role, edit_related,
+                                              add_related and ttype)
         display_label, related_entity = _fdata
-        form, renderer = self._build_form(entity, rtype, role, divid, formid, default_value, reload,
+        form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
                                           display_label, related_entity, dict(vid=rvid))
         self.view_form(divid, value, form, renderer,
                        edit_related, add_related, delete_related)
 
-    def _compute_best_vid(self, eschema, rschema, role):
-        rvid = self._one_rvid
-        if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
-            rvid = self._many_rvid
-        return self._rules.get('rvid', rvid)
-
     def _compute_ttypes(self, rschema, role):
         dual_role = neg_role(role)
         return getattr(rschema, '%ss' % dual_role)()
@@ -171,15 +161,15 @@
             ctrl_reload = self._cw.build_url(ctrl_reload)
         return ctrl_reload
 
-    def _compute_default_value(self, entity, rschema, role, default_value):
-        etype = entity.e_schema.type
-        ctrl_default = self._rules.get('default_value', default_value)
-        if ctrl_default:
-            return ctrl_default
-        if default_value is None:
-            return xml_escape(self._cw._('<%s not specified>') %
-                              display_name(self._cw, rschema.type, role))
-        return default_value
+    def _compute_default_value(self, rschema, role):
+        default = self._rules.get('novalue_label')
+        if default is None:
+            if self._rules.get('novalue_include_rtype'):
+                default = self._cw._('<%s not specified>') % display_name(
+                    self._cw, rschema.type, role)
+            else:
+                default = self._cw._('<not specified>')
+        return xml_escape(default)
 
     def _is_composite(self):
         return self._rules.get('edit_target') == 'related'
@@ -231,11 +221,11 @@
         """ builds an id for the root div of a reledit widget """
         return '%s-%s-%s' % (rtype, role, entity_eid)
 
-    def _build_args(self, entity, rtype, role, formid, default_value, reload,
+    def _build_args(self, entity, rtype, role, formid, reload,
                     extradata=None):
         divid = self._build_divid(rtype, role, entity.eid)
         event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid,
-                      'reload' : json_dumps(reload), 'default_value' : default_value,
+                      'reload' : json_dumps(reload),
                       'role' : role, 'vid' : u''}
         if extradata:
             event_args.update(extradata)
@@ -247,11 +237,10 @@
         return display_label, related_entity
 
     def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related):
+        display_label = True
         if edit_related and not add_related:
-            display_label = True
             related_entity = entity.related(rtype, role).get_entity(0, 0)
         elif add_related:
-            display_label = True
             _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw)
             _new_entity.eid = self._cw.varmaker.next()
             related_entity = _new_entity
@@ -268,9 +257,9 @@
             display_help=False, button_bar_class='buttonbar',
             display_progress_div=False)
 
-    def _build_form(self, entity, rtype, role, divid, formid, default_value, reload,
+    def _build_form(self, entity, rtype, role, divid, formid, reload,
                     display_label, related_entity, extradata=None, **formargs):
-        event_args = self._build_args(entity, rtype, role, formid, default_value,
+        event_args = self._build_args(entity, rtype, role, formid,
                                       reload, extradata)
         cancelclick = self._cancelclick % divid
         form = self._cw.vreg['forms'].select(
@@ -381,9 +370,9 @@
 class AutoClickAndEditFormView(ClickAndEditFormView):
     __regid__ = 'reledit'
 
-    def _build_form(self, entity, rtype, role, divid, formid, default_value, reload,
+    def _build_form(self, entity, rtype, role, divid, formid, reload,
                     display_label, related_entity, extradata=None, **formargs):
-        event_args = self._build_args(entity, rtype, role, 'base', default_value,
+        event_args = self._build_args(entity, rtype, role, 'base',
                                       reload, extradata)
         form = _DummyForm()
         form.event_args = event_args