backport stable branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 06 Jul 2009 19:55:18 +0200
changeset 2293 7ded2a1416e4
parent 2235 d5987f75c97c (current diff)
parent 2292 8240a7d0d52a (diff)
child 2294 e846aa2824dd
backport stable branch
cwvreg.py
devtools/apptest.py
schema.py
selectors.py
web/application.py
web/component.py
web/test/unittest_form.py
web/views/basecontrollers.py
web/views/editforms.py
web/views/facets.py
web/views/formrenderers.py
web/views/forms.py
web/views/management.py
web/views/navigation.py
web/views/primary.py
web/views/schema.py
web/views/startup.py
web/views/tabs.py
web/views/urlpublishing.py
--- a/.hgtags	Thu Jul 02 10:36:25 2009 +0200
+++ b/.hgtags	Mon Jul 06 19:55:18 2009 +0200
@@ -43,3 +43,8 @@
 a356da3e725bfcb59d8b48a89d04be05ea261fd3 3.3.1
 e3aeb6e6c3bb5c18e8dcf61bae9d654beda6c036 cubicweb-version-3_3_2
 bef5e74e53f9de8220451dca4b5863a24a0216fb cubicweb-debian-version-3_3_2-1
+1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
+81973c897c9e78e5e52643e03628654916473196 cubicweb-debian-version-3_3_3-1
+1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
+47b5236774a0cf3b1cfe75f6d4bd2ec989644ace cubicweb-version-3_3_3
+2ba27ce8ecd9828693ec53c517e1c8810cbbe33e cubicweb-debian-version-3_3_3-2
--- a/__pkginfo__.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/__pkginfo__.py	Mon Jul 06 19:55:18 2009 +0200
@@ -7,7 +7,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 3, 2)
+numversion = (3, 3, 3)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL v2'
--- a/common/migration.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/common/migration.py	Mon Jul 06 19:55:18 2009 +0200
@@ -20,24 +20,6 @@
 from cubicweb import ConfigurationError
 
 
-def migration_files(config, toupgrade):
-    """return an orderer list of path of scripts to execute to upgrade
-    an installed application according to installed cube and cubicweb versions
-    """
-    merged = []
-    for cube, fromversion, toversion in toupgrade:
-        if cube == 'cubicweb':
-            migrdir = config.migration_scripts_dir()
-        else:
-            migrdir = config.cube_migration_scripts_dir(cube)
-        scripts = filter_scripts(config, migrdir, fromversion, toversion)
-        merged += [s[1] for s in scripts]
-    if config.accept_mode('Any'):
-        migrdir = config.migration_scripts_dir()
-        merged.insert(0, join(migrdir, 'bootstrapmigration_repository.py'))
-    return merged
-
-
 def filter_scripts(config, directory, fromversion, toversion, quiet=True):
     """return a list of paths of migration files to consider to upgrade
     from a version to a greater one
@@ -126,6 +108,18 @@
                           'interactive_mode': interactive,
                           }
 
+    def __getattribute__(self, name):
+        try:
+            return object.__getattribute__(self, name)
+        except AttributeError:
+            cmd = 'cmd_%s' % name
+            if hasattr(self, cmd):
+                meth = getattr(self, cmd)
+                return lambda *args, **kwargs: self.interact(args, kwargs,
+                                                             meth=meth)
+            raise
+        raise AttributeError(name)
+
     def repo_connect(self):
         return self.config.repository()
 
@@ -144,31 +138,35 @@
                     return False
                 return orig_accept_mode(mode)
             self.config.accept_mode = accept_mode
-        scripts = migration_files(self.config, toupgrade)
-        if scripts:
-            vmap = dict( (pname, (fromver, tover)) for pname, fromver, tover in toupgrade)
-            self.__context.update({'applcubicwebversion': vcconf['cubicweb'],
-                                   'cubicwebversion': self.config.cubicweb_version(),
-                                   'versions_map': vmap})
-            self.scripts_session(scripts)
-        else:
-            print 'no migration script to execute'
+        # may be an iterator
+        toupgrade = tuple(toupgrade)
+        vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
+        ctx = self.__context
+        ctx['versions_map'] = vmap
+        if self.config.accept_mode('Any') and 'cubicweb' in vmap:
+            migrdir = self.config.migration_scripts_dir()
+            self.process_script(join(migrdir, 'bootstrapmigration_repository.py'))
+        for cube, fromversion, toversion in toupgrade:
+            if cube == 'cubicweb':
+                migrdir = self.config.migration_scripts_dir()
+            else:
+                migrdir = self.config.cube_migration_scripts_dir(cube)
+            scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
+            if scripts:
+                for version, script in scripts:
+                    self.process_script(script)
+                    self.cube_upgraded(cube, version)
+                if version != toversion:
+                    self.cube_upgraded(cube, toversion)
+            else:
+                self.cube_upgraded(cube, toversion)
+
+    def cube_upgraded(self, cube, version):
+        pass
 
     def shutdown(self):
         pass
 
-    def __getattribute__(self, name):
-        try:
-            return object.__getattribute__(self, name)
-        except AttributeError:
-            cmd = 'cmd_%s' % name
-            if hasattr(self, cmd):
-                meth = getattr(self, cmd)
-                return lambda *args, **kwargs: self.interact(args, kwargs,
-                                                             meth=meth)
-            raise
-        raise AttributeError(name)
-
     def interact(self, args, kwargs, meth):
         """execute the given method according to user's confirmation"""
         msg = 'execute command: %s(%s) ?' % (
@@ -205,7 +203,6 @@
         if answer in ('r', 'retry'):
             return 2
         if answer in ('a', 'abort'):
-            self.rollback()
             raise SystemExit(1)
         if shell and answer in ('s', 'shell'):
             self.interactive_shell()
@@ -284,16 +281,6 @@
                     return None
                 return func(*args, **kwargs)
 
-    def scripts_session(self, migrscripts):
-        """execute some scripts in a transaction"""
-        try:
-            for migrscript in migrscripts:
-                self.process_script(migrscript)
-            self.commit()
-        except:
-            self.rollback()
-            raise
-
     def cmd_option_renamed(self, oldname, newname):
         """a configuration option has been renamed"""
         self._option_changes.append(('renamed', oldname, newname))
--- a/common/test/unittest_migration.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/common/test/unittest_migration.py	Mon Jul 06 19:55:18 2009 +0200
@@ -13,7 +13,8 @@
 from cubicweb.devtools.apptest import TestEnvironment
 
 from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.common.migration import migration_files, filter_scripts
+from cubicweb.common.migration import MigrationHelper, filter_scripts
+from cubicweb.server.migractions import ServerMigrationHelper
 
 
 class Schema(dict):
@@ -39,82 +40,53 @@
         self.config.__class__.cubicweb_vobject_path = frozenset()
         self.config.__class__.cube_vobject_path = frozenset()
 
-    def test_migration_files_base(self):
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)),
-                                                            ('TEMPLATE', (0,0,2), (0,0,3))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.0.3_Any.py'])
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,4,0), (2,5,0)),
-                                                            ('TEMPLATE', (0,0,2), (0,0,3))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               SMIGRDIR+'2.5.0_Any.sql',
-                               TMIGRDIR+'0.0.3_Any.py'])
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,5,0), (2,6,0)),
-                                                            ('TEMPLATE', (0,0,3), (0,0,4))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               SMIGRDIR+'2.6.0_Any.sql',
-                               TMIGRDIR+'0.0.4_Any.py'])
+    def test_filter_scripts_base(self):
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
+                              [])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
+                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
+                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
+                               ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
+                              [])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
+                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
 
-##     def test_migration_files_overlap(self):
-##         self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2),
-##                                               (0,0,2), (0,1,2)),
-##                               [SMIGRDIR+'bootstrapmigration_repository.py',
-##                                TMIGRDIR+'0.0.3_Any.py',
-##                                TMIGRDIR+'0.0.4_Any.py',
-##                                SMIGRDIR+'2.4.0_2.5.0_Any.sql',
-##                                SMIGRDIR+'2.5.1_2.6.0_Any.sql',
-##                                TMIGRDIR+'0.1.0_Any.py',
-##                                TMIGRDIR+'0.1.0_common.py',
-##                                TMIGRDIR+'0.1.0_repository.py',
-##                                TMIGRDIR+'0.1.2_Any.py',
-##                                SMIGRDIR+'2.10.1_2.10.2_Any.sql'])
+        self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
+                              [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
+        self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
+                              [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
+                               ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
 
-    def test_migration_files_for_mode(self):
-        from cubicweb.server.migractions import ServerMigrationHelper
+    def test_filter_scripts_for_mode(self):
         self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
-        from cubicweb.common.migration import MigrationHelper
         config = CubicWebConfiguration('data')
         config.verbosity = 0
         self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
         self.assertIsInstance(config.migration_handler(), MigrationHelper)
         config = self.config
         config.__class__.name = 'twisted'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_web.py'])
-        config.__class__.name = 'repository'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.1.0_Any.py',
-                               TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_repository.py'])
-        config.__class__.name = 'all-in-one'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.1.0_Any.py',
-                               TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_repository.py',
-                               TMIGRDIR+'0.1.0_web.py'])
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
         config.__class__.name = 'repository'
-
-    def test_filter_scripts(self):
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
-                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
-                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
-                               ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
-                              [])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
-                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,10,2)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
-                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
+        config.__class__.name = 'all-in-one'
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+        config.__class__.name = 'repository'
 
 
 from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
--- a/cwconfig.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/cwconfig.py	Mon Jul 06 19:55:18 2009 +0200
@@ -194,6 +194,12 @@
           'help': 'web server root url',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('allow-email-login',
+         {'type' : 'yn',
+          'default': False,
+          'help': 'allow users to login with their primary email if set',
+          'group': 'main', 'inputlevel': 2,
+          }),
         ('use-request-subdomain',
          {'type' : 'yn',
           'default': None,
--- a/cwctl.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/cwctl.py	Mon Jul 06 19:55:18 2009 +0200
@@ -655,7 +655,7 @@
         else:
             applcubicwebversion = vcconf.get('cubicweb')
         if cubicwebversion > applcubicwebversion:
-            toupgrade.append( ('cubicweb', applcubicwebversion, cubicwebversion) )
+            toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
         if not self.config.fs_only and not toupgrade:
             print 'no software migration needed for application %s' % appid
             return
@@ -682,7 +682,6 @@
                            'continue anyway ?'):
                 print 'migration not completed'
                 return
-        mih.rewrite_vcconfiguration()
         mih.shutdown()
         print
         print 'application migrated'
@@ -733,7 +732,8 @@
         config.set_sources_mode(sources)
         mih = config.migration_handler()
         if args:
-            mih.scripts_session(args)
+            for arg in args:
+                mih.process_script(script)
         else:
             mih.interactive_shell()
         mih.shutdown()
--- a/cwvreg.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/cwvreg.py	Mon Jul 06 19:55:18 2009 +0200
@@ -96,6 +96,11 @@
         clear_cache(self, 'rqlhelper')
         # now we can load application's web objects
         self.register_objects(self.config.vregistry_path())
+        # map lowered entity type names to their actual name
+        self.case_insensitive_etypes = {}
+        for etype in self.schema.entities():
+            etype = str(etype)
+            self.case_insensitive_etypes[etype.lower()] = etype
 
     def update_schema(self, schema):
         """update .schema attribute on registered objects, necessary for some
--- a/dbapi.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/dbapi.py	Mon Jul 06 19:55:18 2009 +0200
@@ -63,19 +63,19 @@
                                   'application' % nsid)
         return core.getProxyForURI(uri)
 
-def repo_connect(repo, user, password, cnxprops=None):
+def repo_connect(repo, login, password, cnxprops=None):
     """Constructor to create a new connection to the CubicWeb repository.
 
     Returns a Connection instance.
     """
     cnxprops = cnxprops or ConnectionProperties('inmemory')
-    cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops)
+    cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops)
     cnx = Connection(repo, cnxid, cnxprops)
     if cnxprops.cnxtype == 'inmemory':
         cnx.vreg = repo.vreg
     return cnx
 
-def connect(database=None, user=None, password=None, host=None,
+def connect(database=None, login=None, password=None, host=None,
             group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
             initlog=True):
     """Constructor for creating a connection to the CubicWeb repository.
@@ -111,11 +111,11 @@
         vreg.set_schema(schema)
     else:
         vreg = None
-    cnx = repo_connect(repo, user, password, cnxprops)
+    cnx = repo_connect(repo, login, password, cnxprops)
     cnx.vreg = vreg
     return cnx
 
-def in_memory_cnx(config, user, password):
+def in_memory_cnx(config, login, password):
     """usefull method for testing and scripting to get a dbapi.Connection
     object connected to an in-memory repository instance
     """
@@ -128,7 +128,7 @@
     repo = get_repository('inmemory', config=config, vreg=vreg)
     # connection to the CubicWeb repository
     cnxprops = ConnectionProperties('inmemory')
-    cnx = repo_connect(repo, user, password, cnxprops=cnxprops)
+    cnx = repo_connect(repo, login, password, cnxprops=cnxprops)
     return repo, cnx
 
 
@@ -245,7 +245,7 @@
     @property
     def user(self):
         if self._user is None and self.cnx:
-            self.set_user(self.cnx.user(self))
+            self.set_user(self.cnx.user(self, {'lang': self.lang}))
         return self._user
 
     def set_user(self, user):
@@ -367,6 +367,10 @@
         """raise `BadSessionId` if the connection is no more valid"""
         self._repo.check_session(self.sessionid)
 
+    def set_session_props(self, **props):
+        """raise `BadSessionId` if the connection is no more valid"""
+        self._repo.set_session_props(self.sessionid, props)
+
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
@@ -434,7 +438,8 @@
     def user(self, req=None, props=None):
         """return the User object associated to this connection"""
         # cnx validity is checked by the call to .user_info
-        eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
+        eid, login, groups, properties = self._repo.user_info(self.sessionid,
+                                                              props)
         if req is None:
             req = self.request()
         rset = req.eid_rset(eid, 'CWUser')
--- a/debian/changelog	Thu Jul 02 10:36:25 2009 +0200
+++ b/debian/changelog	Mon Jul 06 19:55:18 2009 +0200
@@ -1,3 +1,16 @@
+cubicweb (3.3.3-2) unstable; urgency=low
+
+  * re-release with "from __future__ import with_statement" commented out to
+    avoid broken installation if 2.4 is installed
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 06 Jul 2009 17:33:15 +0200
+
+cubicweb (3.3.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 06 Jul 2009 13:24:29 +0200
+
 cubicweb (3.3.2-1) unstable; urgency=low
 
   * new upstream release
--- a/devtools/apptest.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/devtools/apptest.py	Mon Jul 06 19:55:18 2009 +0200
@@ -462,14 +462,13 @@
         self.__close = repo.close
         self.cnxid = self.cnx.sessionid
         self.session = repo._sessions[self.cnxid]
-        # XXX copy schema since hooks may alter it and it may be not fully
-        #     cleaned (missing some schema synchronization support)
-        try:
-            origschema = repo.__schema
-        except AttributeError:
-            origschema = repo.schema
-            repo.__schema = origschema
         if self.copy_schema:
+            # XXX copy schema since hooks may alter it and it may be not fully
+            #     cleaned (missing some schema synchronization support)
+            try:
+                origschema = repo.__schema
+            except AttributeError:
+                repo.__schema = origschema = repo.schema
             repo.schema = deepcopy(origschema)
             repo.set_schema(repo.schema) # reset hooks
             repo.vreg.update_schema(repo.schema)
--- a/doc/book/en/admin/setup.rst	Thu Jul 02 10:36:25 2009 +0200
+++ b/doc/book/en/admin/setup.rst	Mon Jul 06 19:55:18 2009 +0200
@@ -46,6 +46,13 @@
 
 There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball.
 
+The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update":
+1. become root using sudo 
+2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget
+3. run "apt-key add logilab-dists-key.asc"
+4. re-run apt-get update (manually or through the package manager, whichever you prefer)
+
+.. `Logilab's gnupg key` _http://ftp.logilab.org/dists/logilab-dists-key.asc
 
 Install from source
 ```````````````````
--- a/doc/book/en/development/cubes/cc-newcube.rst	Thu Jul 02 10:36:25 2009 +0200
+++ b/doc/book/en/development/cubes/cc-newcube.rst	Mon Jul 06 19:55:18 2009 +0200
@@ -13,16 +13,15 @@
   hg add .
   hg ci
 
-If all went well, you should see the cube you just create in the list
-returned by `cubicweb-ctl list` in the section *Available components*,
+If all went well, you should see the cube you just created in the list
+returned by ``cubicweb-ctl list`` in the section *Available components*,
 and if it is not the case please refer to :ref:`ConfigurationEnv`.
 
-To use a cube, you have to list it in the variable ``__use__``
-of the file ``__pkginfo__.py`` of the instance.
-This variable is used for the instance packaging (dependencies
-handled by system utility tools such as APT) and the usable cubes
-at the time the base is created (import_erschema('MyCube') will
-not properly work otherwise).
+To reuse an existing cube, add it to the list named ``__use__`` and defined in
+:file:`__pkginfo__.py`.  This variable is used for the instance packaging
+(dependencies handled by system utility tools such as APT) and the usable cubes
+at the time the base is created (import_erschema('MyCube') will not properly
+work otherwise).
 
 .. note::
     Please note that if you do not wish to use default directory
--- a/doc/book/en/intro/concepts/index.rst	Thu Jul 02 10:36:25 2009 +0200
+++ b/doc/book/en/intro/concepts/index.rst	Mon Jul 06 19:55:18 2009 +0200
@@ -1,5 +1,7 @@
 .. -*- coding: utf-8 -*-
 
+.. _Concepts:
+
 The Core Concepts of CubicWeb
 =============================
 
@@ -13,8 +15,9 @@
 Cubes
 -----
 
-A cube is a software component composed of three parts: its data model (schema),
-its logic (entities) and its user interface (views).
+A cube is a software component made of three parts: its data model
+(:file:`schema`), its logic (:file:`entities`) and its user interface
+(:file:`views`).
 
 A cube can use other cubes as building blocks and assemble them to provide
 a whole with richer functionnalities than its parts. The cubes `cubicweb-blog`_
@@ -24,12 +27,14 @@
 The `CubicWeb Forge`_ offers a large number of cubes developed by the community
 and available under a free software license.
 
-Available cubes on your system are usually stored in the directory
-:file:`/usr/share/cubicweb/cubes` when using a unix system wide
-installation. During development, the cubes are found in the
-:file:`/path/to/cubicweb_forest/cubes` directory. You can specify additional
-locations using the :envvar:`CW_CUBES_PATH` environment variable, using ':' as a
-separator.
+The command ``cubicweb-ctl list`` displays the list of cubes installed on your
+system.
+
+On a Unix system, the available cubes are usually stored in the directory
+:file:`/usr/share/cubicweb/cubes`. During development, the cubes are commonly
+found in the directory :file:`/path/to/cubicweb_forest/cubes`. The environment
+variable :envvar:`CW_CUBES_PATH` gives additionnal locations where to search for
+cubes.
 
 .. _`CubicWeb Forge`: http://www.cubicweb.org/project/
 .. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
@@ -37,124 +42,113 @@
 
 
 Instances
-----------
+---------
 
 An instance is a runnable application installed on a computer and based on a
 cube.
 
-The instance directory includes the configuration files. Several instances can
-be created based on the same cube. For exemple, several software forges can be
-set up on one computer system based on the `cubicweb-forge`_ cube.
+The instance directory contains the configuration files. Several instances can
+be created and based on the same cube. For exemple, several software forges can
+be set up on one computer system based on the `cubicweb-forge`_ cube.
 
 .. _`cubicweb-forge`: http://www.cubicweb.org/project/cubicweb-forge
 
-Instances can be of different types: all-in-one, web engine or data repository. For
-applications that support high traffic, several web (front-end) and data
-(back-end) instances can be set-up to share the load.
+Instances can be of three different types: all-in-one, web engine or data
+repository. For applications that support high traffic, several web (front-end)
+and data (back-end) instances can be set-up to share the load.
 
 .. image:: ../../images/archi_globale.en.png
 
+The command ``cubicweb-ctl list`` displays the list of instances installed on
+your system.
+
+On a Unix system, the instances are usually stored in the directory
+:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/`
+directory is looked up, as well as the paths in :envvar:`CW_REGISTRY`
+environment variable.
+
 The term application can refer to an instance or to a cube, depending on the
 context. This book will try to avoid using this term and use *cube* and
 *instance* as appropriate.
 
-(Data) Repository
-~~~~~~~~~~~~~~~~~~
+Data Repository
+---------------
 
-The repository (Be carefull not to get confused with a Mercurial repository or a
-debian repository!) manages all interactions with various data sources by
-providing access to them using uniformly using the Relation Query Language (RQL).  The
-web interface and the repository communicate using this language.
+The data repository [#]_ provides access to one or more data sources (including
+SQL databases, LDAP repositories, Mercurial or Subversion version control
+systems, other CubicWeb repositories, GAE's DataStore, etc).
 
-Usually, the web server and repository sides are integrated in the same process and
-interact directly, without the need for distant calls using Pyro. But, it is
-important to note that those two sides, client/server, are disjointed and it is
-possible to execute a couple of calls in distinct processes to balance the load
-of your web site on one or more machines.
-
+All interactions with the repository are done using the Relation Query Language
+(RQL). The repository federates the data sources and hides them from the
+querier, which does not realize when a query spans accross several data sources
+and requires running sub-queries and merges to complete.
 
-A data source is a container of data integrated in the *CubicWeb* repository. A
-repository has at least one source, named `system`, which contains the schema of
-the application, plain-text index and other vital informations for the
-system. You'll find source for SQL databases, LDAP servers, other RQL
-repositories and even mercurial /svn repositories or `Google App Engine`'s
-datastore.
+It is common to run the web engine and the repository in the same process (see
+instances of type all-in-one above), but this is not a requirement. A repository
+can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and
+act as a server.
 
-Web interface
-~~~~~~~~~~~~~
-By default the web server provides a generated interface based on its schema.
-Entities can be created, displayed, updated and deleted. As display views are not
-very fancy, it is usually necessary to develop your own.
+Some logic can be attached to events that happen in the repository, like
+creation of entities, deletion of relations, etc. This is used for example to
+send email notifications when the state of an object changes. See `Hooks` below.
+
+.. _[#]: not to be confused with a Mercurial repository or a Debian repository.
+.. _`Python Remote Objects`: http://pyro.sourceforge.net/
 
-Instances are defined on your system in the directory :file:`/etc/cubicweb.d` when
-using a system wide installation.  For people using the mercurial repository of
-cubicweb, the :file:`etc` directory is searched in the user home directory. You can
-also specify an alternative directory using the :envvar:`CW_REGISTRY` environment
-variable.
-
-
+Web Engine
+----------
 
-Schema
-------
-** *CubicWeb* is schema driven **
+The web engine replies to http requests and runs the user interface and most of
+the application logic.
 
-The schema describes the persistent data model using entities and
-relations. It is modeled with a comprehensive language made of Python classes based on
-the `yams`_ library.
+By default the web engine provides a generated user interface based on the data
+model of the instance. Entities can be created, displayed, updated and
+deleted. As the default user interface is not very fancy, it is usually
+necessary to develop your own.
 
-When you create a new cubicweb instance, the schema is stored in the database,
-and it will usually evolves as you upgrade cubicweb and used cubes.
+Schema (Data Model)
+-------------------
 
-*CubicWeb* provides a certain number of system entities included
-sytematically (necessary for the core of *CubicWeb*, notably the schema itself).
-You will also find a library of cubes which defines more piece of schema for standard needs.
-necessary.
+The data model of a cube is described as an entity-relationship schema using a
+comprehensive language made of Python classes imported from the yams_ library.
 
-*CubicWeb* add some metadata to every entity type, such as the eid (a global
-  identifier, unique into an instance), entity's creation date...
-
+.. _yams: http://www.logilab.org/project/yams/
 
-Attributes may be of the following types:
-  `String`, `Int`, `Float`, `Boolean`, `Date`, `Time`, `Datetime`,
-  `Interval`, `Password`, `Bytes`.
-
-New in 3.2: RichString
+An `entity type` defines a set of attributes and is used in some relations.
+Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`,
+`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See
+:ref:`yams.BASE_TYPES` for details.
 
-see :ref:`yams.BASE_TYPES`
-
-Data level security is defined by setting permissions on entity and relation types.
-
-A schema consist of parts detailed below.
+A `relation type` is used to define a binary oriented relation between two
+entity types.  The left-hand part of a relation is named the `subject` and the
+right-hand part is named the `object`.
 
+A `relation definition` is a triple (*subject entity type*, *relation type*, *object
+entity type*) associated with a set of properties such as cardinality,
+constraints, etc.
 
-Entity type
-~~~~~~~~~~~
-An *entity type* defines set of attributes and is used in some relations. It may
-have some permissions telling who can read/add/update/delete entities of this type.
-
-Relation type
-~~~~~~~~~~~~~
-A *relation type* is used to define a semantic relation between two entity types.
-It may have some permissions telling who can read/add/delete relation of this type.
+Permissions can be set on entity types and relation types to control who will be
+able to create, read, update or delete entities and relations.
 
-In *CubicWeb* relations are ordered and binary: by convention we name the first
-item of a relation the `subject` and the second the `object`.
+Some meta-data necessary to the system is added to the data model. That includes
+entities like users and groups, the entities used to store the data model
+itself and attributes like unique identifier, creation date, creator, etc.
 
-Relation definition
-~~~~~~~~~~~~~~~~~~~
-A *relation definition* is a 3-uple (*subject entity type*, *relation type*, *object
-entity type*), with an associated set of property such as cardinality, constraints...
+When you create a new *CubicWeb* instance, the schema is stored in the database.
+When the cubes the instance is based on evolve, they may change their data model
+and provide migration scripts that will be executed when the administrator will
+run the upgrade process for the instance.
 
-
+Registries and Objects
+----------------------
 
-Dynamic objects for reusable components
----------------------------------------
-** Dynamic objects management or how CubicWeb provides really reusable components **
+XXX registry, register, registries, registers ???
 
 Application objects
 ~~~~~~~~~~~~~~~~~~~
+
 Beside a few core functionalities, almost every feature of the framework is
-acheived by dynamic objects (`application objects` or `appobjects`) stored in a
+achieved by dynamic objects (`application objects` or `appobjects`) stored in a
 two-levels registry (the `vregistry`). Each object is affected to a registry with
 an identifier in this registry. You may have more than one object sharing an
 identifier in the same registry, At runtime, appobjects are selected in the
@@ -168,6 +162,7 @@
 
 The `vregistry`
 ~~~~~~~~~~~~~~~
+
 At startup, the `registry` or registers base, inspects a number of directories
 looking for compatible classes definition. After a recording process, the objects
 are assigned to registers so that they can be selected dynamically while the
@@ -175,23 +170,22 @@
 
 Selectors
 ~~~~~~~~~
-Each appobject has a selector, which is used to score how well it suits to a
-given context by returning a score.  A score of 0 means the object doesn't apply
-to the context. The score is used to choose the most pertinent object: the "more"
-the appobject suits the context the higher the score.
+
+Each appobject has a selector, that is used to compute how well the object fits
+a given context. The better the object fits the context, the higher the score.
 
-CubicWeb provides a set of basic selectors which may be parametrized and combined
-using binary `&` and `|` operators to provide a custom selector (which can be
-itself reused...).
+CubicWeb provides a set of basic selectors that may be parametrized. Selectors
+can be combined with the binary operators `&` and `|` to build more complex
+selector that can be combined too.
 
-There is 3 current ways to retreive some appobject from the repository:
+There are three common ways to retrieve some appobject from the repository:
 
 * get the most appropriate objects by specifying a registry and an identifier. In
   that case, the object with the greatest score is selected. There should always
   be a single appobject with a greater score than others.
 
 * get all appobjects applying to a context by specifying a registry.In
-  that case, every objects with the a postive score are selected.
+  that case, every object with the a postive score is selected.
 
 * get the object within a particular registry/identifier. In that case no
   selection process is involved, the vregistry will expect to find a single
@@ -218,9 +212,10 @@
 
 The RQL query language
 ----------------------
-**No needs for a complicated ORM when you've a powerful query language**
 
-All the persistant data in a CubicWeb application is retreived and modified by using the
+**No need for a complicated ORM when you have a powerful query language**
+
+All the persistant data in a CubicWeb application is retrieved and modified by using the
 Relation Query Language.
 
 This query language is inspired by SQL but is on a higher level in order to
@@ -228,16 +223,19 @@
 
 db-api
 ~~~~~~
+
 The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
 XXX feed me
 
 Result set
 ~~~~~~~~~~
+
 XXX feed me
 
 
 Views
 -----
+
 ** *CubicWeb* is data driven **
 
 XXX feed me.
@@ -248,7 +246,3 @@
 ** *CubicWeb* provides an extensible data repository **
 
 XXX feed me.
-
-
-.. _`Python Remote Object`: http://pyro.sourceforge.net/
-.. _`yams`: http://www.logilab.org/project/yams/
--- a/doc/book/en/intro/tutorial/components.rst	Thu Jul 02 10:36:25 2009 +0200
+++ b/doc/book/en/intro/tutorial/components.rst	Mon Jul 06 19:55:18 2009 +0200
@@ -23,6 +23,8 @@
 
 * classtags: Tag (to tag anything)
 
+* comment: Comment (to attach comment threads to entities)
+
 * file: File (to allow users to upload and store binary or text files)
 
 * link: Link (to collect links to web resources)
@@ -37,10 +39,6 @@
 * zone: Zone (to define places within larger places, for example a
   city in a state in a country)
 
-The available system entities are:
-
-* comment: Comment (to attach comment threads to entities)
-
 
 Adding comments to BlogDemo
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/i18n/en.po	Thu Jul 02 10:36:25 2009 +0200
+++ b/i18n/en.po	Mon Jul 06 19:55:18 2009 +0200
@@ -154,6 +154,12 @@
 msgid "1?"
 msgstr "1 0..1"
 
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -169,12 +175,18 @@
 msgid "AND"
 msgstr ""
 
+msgid "Add permissions"
+msgstr ""
+
 msgid "Any"
 msgstr ""
 
 msgid "Application"
 msgstr ""
 
+msgid "Attributes"
+msgstr ""
+
 msgid "Bookmark"
 msgstr "Bookmark"
 
@@ -284,6 +296,9 @@
 msgid "Decimal_plural"
 msgstr "Decimal numbers"
 
+msgid "Delete permissions"
+msgstr ""
+
 msgid "Do you want to delete the following element(s) ?"
 msgstr ""
 
@@ -395,6 +410,9 @@
 msgid "RQLExpression_plural"
 msgstr "RQL expressions"
 
+msgid "Read permissions"
+msgstr ""
+
 msgid "Recipients:"
 msgstr ""
 
@@ -408,6 +426,9 @@
 msgid "Schema %s"
 msgstr ""
 
+msgid "Schema of the data model"
+msgstr ""
+
 msgid "Search for"
 msgstr ""
 
@@ -449,6 +470,9 @@
 msgid "The view %s could not be found"
 msgstr ""
 
+msgid "There is no workflow defined for this entity."
+msgstr ""
+
 #, python-format
 msgid "This %s"
 msgstr ""
@@ -526,6 +550,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr ""
 
+msgid "Update permissions"
+msgstr ""
+
 msgid "Used by:"
 msgstr ""
 
@@ -981,6 +1008,9 @@
 msgid "bookmarks"
 msgstr ""
 
+msgid "bookmarks are used to have user's specific internal links"
+msgstr ""
+
 msgid "boxes"
 msgstr ""
 
@@ -1111,6 +1141,9 @@
 msgid "changes applied"
 msgstr ""
 
+msgid "click here to see created entity"
+msgstr ""
+
 msgid "click on the box to cancel the deletion"
 msgstr ""
 
@@ -1132,12 +1165,6 @@
 msgid "components_appliname_description"
 msgstr "display the application title in the page's header"
 
-msgid "components_applmessages"
-msgstr "application messages"
-
-msgid "components_applmessages_description"
-msgstr "display the application messages"
-
 msgid "components_breadcrumbs"
 msgstr "breadcrumbs"
 
@@ -1419,6 +1446,18 @@
 msgid "currently attached file: %s"
 msgstr ""
 
+msgid "cwetype-schema-image"
+msgstr "schema"
+
+msgid "cwetype-schema-permissions"
+msgstr "permissions"
+
+msgid "cwetype-schema-text"
+msgstr "description"
+
+msgid "cwetype-workflow"
+msgstr "workflow"
+
 msgid "data directory url"
 msgstr ""
 
@@ -1538,9 +1577,6 @@
 msgid "detach attached file %s"
 msgstr ""
 
-msgid "detailed schema view"
-msgstr ""
-
 msgid "display order of the action"
 msgstr ""
 
@@ -1624,6 +1660,9 @@
 msgid "entity edited"
 msgstr ""
 
+msgid "entity linked"
+msgstr ""
+
 msgid "entity type"
 msgstr ""
 
@@ -1676,6 +1715,18 @@
 msgid "facets_created_by-facet_description"
 msgstr ""
 
+msgid "facets_cwfinal-facet"
+msgstr "\"final entity or relation type\" facet"
+
+msgid "facets_cwfinal-facet_description"
+msgstr ""
+
+msgid "facets_cwmeta-facet"
+msgstr ""
+
+msgid "facets_cwmeta-facet_description"
+msgstr ""
+
 msgid "facets_etype-facet"
 msgstr "\"entity type\" facet"
 
@@ -1820,9 +1871,6 @@
 msgid "hide filter form"
 msgstr ""
 
-msgid "hide meta-data"
-msgstr "hide meta entities and relations"
-
 msgid "home"
 msgstr ""
 
@@ -2484,6 +2532,12 @@
 msgid "schema's permissions definitions"
 msgstr ""
 
+msgid "schema-image"
+msgstr "schema"
+
+msgid "schema-text"
+msgstr "description"
+
 msgid "search"
 msgstr ""
 
@@ -2569,9 +2623,6 @@
 msgid "show filter form"
 msgstr ""
 
-msgid "show meta-data"
-msgstr "show the complete schema"
-
 msgid "sioc"
 msgstr ""
 
@@ -2685,6 +2736,9 @@
 msgid "thursday"
 msgstr ""
 
+msgid "timeline"
+msgstr ""
+
 msgid "timestamp"
 msgstr ""
 
@@ -2957,14 +3011,26 @@
 #~ msgid "add a Card"
 #~ msgstr "add a card"
 
+#~ msgid "components_applmessages"
+#~ msgstr "application messages"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "display the application messages"
+
 #~ msgid "content_format"
 #~ msgstr "content format"
 
+#~ msgid "hide meta-data"
+#~ msgstr "hide meta entities and relations"
+
 #~ msgid "planned_delivery"
 #~ msgstr "planned delivery"
 
 #~ msgid "remove this Card"
 #~ msgstr "remove this card"
 
+#~ msgid "show meta-data"
+#~ msgstr "show the complete schema"
+
 #~ msgid "wikiid"
 #~ msgstr "wiki identifier"
--- a/i18n/es.po	Thu Jul 02 10:36:25 2009 +0200
+++ b/i18n/es.po	Mon Jul 06 19:55:18 2009 +0200
@@ -105,7 +105,7 @@
 
 #, python-format
 msgid "%d&nbsp;years"
-msgstr ""
+msgstr "%d&nbsp;años"
 
 #, python-format
 msgid "%s error report"
@@ -159,6 +159,14 @@
 msgid "1?"
 msgstr "1 0..1"
 
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+"<div>Este esquema del modelo de datos <em>no incluye</em> los meta-datos, pero "
+"se puede ver a un <a href=\"%s\">modelo completo con meta-datos</a>.</div>"
+
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -174,12 +182,18 @@
 msgid "AND"
 msgstr "Y"
 
+msgid "Add permissions"
+msgstr "Añadir autorizaciónes"
+
 msgid "Any"
 msgstr "Cualquiera"
 
 msgid "Application"
 msgstr "Aplicación"
 
+msgid "Attributes"
+msgstr "Atributos"
+
 msgid "Bookmark"
 msgstr "Favorito"
 
@@ -289,6 +303,9 @@
 msgid "Decimal_plural"
 msgstr "Decimales"
 
+msgid "Delete permissions"
+msgstr "Autorización de suprimir"
+
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Desea suprimir el(los) elemento(s) siguiente(s)"
 
@@ -311,7 +328,7 @@
 msgstr "Números flotantes"
 
 msgid "From:"
-msgstr ""
+msgstr "De: "
 
 msgid "Int"
 msgstr "Número entero"
@@ -400,8 +417,11 @@
 msgid "RQLExpression_plural"
 msgstr "Expresiones RQL"
 
+msgid "Read permissions"
+msgstr "Autorización de leer"
+
 msgid "Recipients:"
-msgstr ""
+msgstr "Destinatarios"
 
 msgid "Relations"
 msgstr "Relaciones"
@@ -413,6 +433,9 @@
 msgid "Schema %s"
 msgstr "Esquema %s"
 
+msgid "Schema of the data model"
+msgstr "Esquema del modelo de datos"
+
 msgid "Search for"
 msgstr "Buscar"
 
@@ -435,7 +458,7 @@
 msgstr "Cadenas de caracteres"
 
 msgid "Subject:"
-msgstr ""
+msgstr "Sujeto:"
 
 msgid "Submit bug report"
 msgstr "Enviar un reporte de error (bug)"
@@ -454,6 +477,9 @@
 msgid "The view %s could not be found"
 msgstr "La vista %s no ha podido ser encontrada"
 
+msgid "There is no workflow defined for this entity."
+msgstr "No hay workflow para este entidad"
+
 #, python-format
 msgid "This %s"
 msgstr "Este %s"
@@ -531,6 +557,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr "No encontramos el nombre \"%s\" en el esquema"
 
+msgid "Update permissions"
+msgstr "Autorización de modificar"
+
 msgid "Used by:"
 msgstr "Utilizado por :"
 
@@ -683,7 +712,7 @@
 msgstr ""
 
 msgid "actions_managepermission"
-msgstr ""
+msgstr "Administración de autorizaciónes"
 
 msgid "actions_managepermission_description"
 msgstr ""
@@ -973,7 +1002,7 @@
 msgstr "Atributo"
 
 msgid "attributes with modified permissions:"
-msgstr ""
+msgstr "atributos con autorizaciónes modificadas:"
 
 msgid "august"
 msgstr "Agosto"
@@ -1008,6 +1037,9 @@
 msgid "bookmarks"
 msgstr "Favoritos"
 
+msgid "bookmarks are used to have user's specific internal links"
+msgstr "favoritos son usados para que un usuario recorde ligas"
+
 msgid "boxes"
 msgstr "Cajas"
 
@@ -1132,7 +1164,7 @@
 msgstr "cardinalidad"
 
 msgid "category"
-msgstr ""
+msgstr "categoria"
 
 #, python-format
 msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
@@ -1141,6 +1173,9 @@
 msgid "changes applied"
 msgstr "Cambios realizados"
 
+msgid "click here to see created entity"
+msgstr "ver la entidad creada"
+
 msgid "click on the box to cancel the deletion"
 msgstr "Seleccione la zona de edición para cancelar la eliminación"
 
@@ -1162,12 +1197,6 @@
 msgid "components_appliname_description"
 msgstr "Muestra el título de la aplicación en el encabezado de la página"
 
-msgid "components_applmessages"
-msgstr "Mensajes de la aplicación"
-
-msgid "components_applmessages_description"
-msgstr "Muestra los mensajes de la aplicación"
-
 msgid "components_breadcrumbs"
 msgstr "Ruta de Navegación"
 
@@ -1474,7 +1503,19 @@
 
 #, python-format
 msgid "currently attached file: %s"
-msgstr ""
+msgstr "archivo adjunto: %s"
+
+msgid "cwetype-schema-image"
+msgstr "Esquema"
+
+msgid "cwetype-schema-permissions"
+msgstr "Autorizaciónes"
+
+msgid "cwetype-schema-text"
+msgstr "Modelo de datos"
+
+msgid "cwetype-workflow"
+msgstr "Workflow"
 
 msgid "data directory url"
 msgstr "Url del repertorio de datos"
@@ -1608,9 +1649,6 @@
 msgid "detach attached file %s"
 msgstr "Quitar archivo adjunto %s"
 
-msgid "detailed schema view"
-msgstr "Vista detallada del esquema"
-
 msgid "display order of the action"
 msgstr "Orden de aparición de la acción"
 
@@ -1685,16 +1723,19 @@
 msgstr "Entidades eliminadas"
 
 msgid "entity copied"
-msgstr ""
+msgstr "entidad copiada"
 
 msgid "entity created"
-msgstr ""
+msgstr "entidad creada"
 
 msgid "entity deleted"
 msgstr "Entidad eliminada"
 
 msgid "entity edited"
-msgstr ""
+msgstr "entidad modificada"
+
+msgid "entity linked"
+msgstr "entidad asociada"
 
 msgid "entity type"
 msgstr "Tipo de entidad"
@@ -1753,6 +1794,18 @@
 msgid "facets_created_by-facet_description"
 msgstr "faceta creado por"
 
+msgid "facets_cwfinal-facet"
+msgstr "faceta \"final\""
+
+msgid "facets_cwfinal-facet_description"
+msgstr "faceta para las entidades \"finales\""
+
+msgid "facets_cwmeta-facet"
+msgstr "faceta \"meta\""
+
+msgid "facets_cwmeta-facet_description"
+msgstr "faceta para las entidades \"meta\""
+
 msgid "facets_etype-facet"
 msgstr "faceta \"es de tipo\""
 
@@ -1809,7 +1862,7 @@
 
 #, python-format
 msgid "from %(date)s"
-msgstr ""
+msgstr "de %(date)s"
 
 msgid "from_entity"
 msgstr "De la entidad"
@@ -1836,7 +1889,7 @@
 msgstr "Trazado de curbas estándares"
 
 msgid "generic relation to link one entity to another"
-msgstr ""
+msgstr "relación generica para ligar entidades"
 
 msgid "go back to the index page"
 msgstr "Regresar a la página de inicio"
@@ -1897,9 +1950,6 @@
 msgid "hide filter form"
 msgstr "Esconder el filtro"
 
-msgid "hide meta-data"
-msgstr "Esconder los meta-datos"
-
 msgid "home"
 msgstr "Inicio"
 
@@ -2106,7 +2156,8 @@
 msgid ""
 "link a permission to the entity. This permission should be used in the "
 "security definition of the entity's type to be useful."
-msgstr ""
+msgstr "relaciónar una autorización con la entidad. Este autorización debe "
+"ser usada en la definición de la entidad para ser utíl."
 
 msgid ""
 "link a property to the user which want this property customization. Unless "
@@ -2150,7 +2201,7 @@
 msgstr "Clave de acesso"
 
 msgid "login or email"
-msgstr ""
+msgstr "Clave de acesso o dirección de correo"
 
 msgid "login_action"
 msgstr "Ingresa tus datos"
@@ -2259,15 +2310,19 @@
 
 msgid "navigation.combobox-limit"
 msgstr ""
+# msgstr "Navegación: numero maximo de elementos en una caja de elección (combobox)"
 
 msgid "navigation.page-size"
 msgstr ""
+# msgstr "Navegación: numero maximo de elementos por pagina"
 
 msgid "navigation.related-limit"
 msgstr ""
+# msgstr "Navegación: numero maximo de elementos relacionados"
 
 msgid "navigation.short-line-size"
 msgstr ""
+#msgstr "Navegación: numero maximo de caracteres en una linéa corta"
 
 msgid "navtop"
 msgstr "Encabezado del contenido principal"
@@ -2282,7 +2337,7 @@
 msgstr "no"
 
 msgid "no associated permissions"
-msgstr ""
+msgstr "no autorización relacionada"
 
 msgid "no possible transition"
 msgstr "transición no posible"
@@ -2322,7 +2377,7 @@
 msgstr "objeto"
 
 msgid "object_plural:"
-msgstr ""
+msgstr "objetos:"
 
 msgid "october"
 msgstr "octubre"
@@ -2340,7 +2395,7 @@
 msgstr "solo estan permitidas consultas de lectura"
 
 msgid "open all"
-msgstr ""
+msgstr "abrir todos"
 
 msgid "order"
 msgstr "orden"
@@ -2349,10 +2404,10 @@
 msgstr "orden"
 
 msgid "owl"
-msgstr ""
+msgstr "owl"
 
 msgid "owlabox"
-msgstr ""
+msgstr "owlabox"
 
 msgid "owned_by"
 msgstr "pertenece a"
@@ -2385,10 +2440,10 @@
 msgstr "Permiso"
 
 msgid "permissions for entities"
-msgstr ""
+msgstr "autorizaciónes para entidades"
 
 msgid "permissions for relations"
-msgstr ""
+msgstr "autorizaciónes para relaciones"
 
 msgid "permissions for this entity"
 msgstr "Permisos para esta entidad"
@@ -2400,7 +2455,7 @@
 msgstr "Seleccione los favoritos existentes"
 
 msgid "pkey"
-msgstr ""
+msgstr "pkey"
 
 msgid "please correct errors below"
 msgstr "Favor de corregir errores"
@@ -2458,7 +2513,7 @@
 msgstr "Definición"
 
 msgid "relations"
-msgstr ""
+msgstr "relaciones"
 
 msgid "relations deleted"
 msgstr "Relaciones eliminadas"
@@ -2518,16 +2573,16 @@
 msgstr "Eliminar esta transición"
 
 msgid "require_group"
-msgstr "Requiere_grupo"
+msgstr "Requiere grupo"
 
 msgid "require_group_object"
-msgstr "Objeto_grupo_requerido"
+msgstr "Requerido por grupo"
 
 msgid "require_permission"
-msgstr ""
+msgstr "Requiere autorización"
 
 msgid "require_permission_object"
-msgstr ""
+msgstr "Requerido por autorización"
 
 msgid "required attribute"
 msgstr "Atributo requerido"
@@ -2582,6 +2637,12 @@
 msgid "schema's permissions definitions"
 msgstr "definiciones de permisos del esquema"
 
+msgid "schema-image"
+msgstr "esquema imagen"
+
+msgid "schema-text"
+msgstr "esquema text"
+
 msgid "search"
 msgstr "buscar"
 
@@ -2601,7 +2662,7 @@
 msgstr "Ver todos"
 
 msgid "see_also"
-msgstr ""
+msgstr "Ver tambíen"
 
 msgid "select"
 msgstr "Seleccionar"
@@ -2610,7 +2671,7 @@
 msgstr "seleccione un"
 
 msgid "select a key first"
-msgstr ""
+msgstr "seleccione una clave"
 
 msgid "select a relation"
 msgstr "seleccione una relación"
@@ -2670,9 +2731,6 @@
 msgid "show filter form"
 msgstr "afficher le filtre"
 
-msgid "show meta-data"
-msgstr "mostrar meta-data"
-
 msgid "sioc"
 msgstr ""
 
@@ -2730,7 +2788,7 @@
 msgstr "cardinalidad sujeto/objeto"
 
 msgid "subject_plural:"
-msgstr ""
+msgstr "sujetos:"
 
 msgid "sunday"
 msgstr "domingo"
@@ -2787,6 +2845,9 @@
 msgid "thursday"
 msgstr "jueves"
 
+msgid "timeline"
+msgstr ""
+
 msgid "timestamp"
 msgstr "fecha"
 
@@ -2804,7 +2865,7 @@
 
 #, python-format
 msgid "to %(date)s"
-msgstr ""
+msgstr "a %(date)s"
 
 msgid "to associate with"
 msgstr "a asociar con"
@@ -2825,7 +2886,7 @@
 msgstr "a hacer por"
 
 msgid "toggle check boxes"
-msgstr ""
+msgstr "cambiar valor"
 
 msgid "transition is not allowed"
 msgstr "transition no permitida"
@@ -2894,7 +2955,7 @@
 msgstr "propiedad desconocida"
 
 msgid "up"
-msgstr ""
+msgstr "arriba"
 
 msgid "upassword"
 msgstr "clave de acceso"
@@ -3055,7 +3116,7 @@
 msgstr "ha terminado la sesion"
 
 msgid "you should probably delete that property"
-msgstr ""
+msgstr "deberia probablamente suprimir esta propriedad"
 
 #~ msgid "%s constraint failed"
 #~ msgstr "La contrainte %s n'est pas satisfaite"
@@ -3121,6 +3182,12 @@
 #~ msgid "cancel edition"
 #~ msgstr "annuler l'Èdition"
 
+#~ msgid "components_applmessages"
+#~ msgstr "Mensajes de la aplicación"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "Muestra los mensajes de la aplicación"
+
 #~ msgid "components_rss_feed_url"
 #~ msgstr "RSS FEED URL"
 
@@ -3140,6 +3207,9 @@
 #~ "langue par dÈfaut (regarder le rÈpertoire i18n de l'application pour voir "
 #~ "les langues disponibles)"
 
+#~ msgid "detailed schema view"
+#~ msgstr "Vista detallada del esquema"
+
 #~ msgid "filter"
 #~ msgstr "filtrer"
 
@@ -3149,6 +3219,9 @@
 #~ msgid "header"
 #~ msgstr "en-tÍte de page"
 
+#~ msgid "hide meta-data"
+#~ msgstr "Esconder los meta-datos"
+
 #~ msgid "iCal"
 #~ msgstr "iCal"
 
@@ -3190,6 +3263,9 @@
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
+#~ msgid "show meta-data"
+#~ msgstr "mostrar meta-data"
+
 #~ msgid "status will change from %s to %s"
 #~ msgstr "l'Ètat va passer de %s ‡ %s"
 
--- a/i18n/fr.po	Thu Jul 02 10:36:25 2009 +0200
+++ b/i18n/fr.po	Mon Jul 06 19:55:18 2009 +0200
@@ -159,6 +159,14 @@
 msgid "1?"
 msgstr "1 0..1"
 
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+"<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 "?*"
 msgstr "0..1 0..n"
 
@@ -174,12 +182,18 @@
 msgid "AND"
 msgstr "ET"
 
+msgid "Add permissions"
+msgstr "Permissions d'ajouter"
+
 msgid "Any"
 msgstr "N'importe"
 
 msgid "Application"
 msgstr "Application"
 
+msgid "Attributes"
+msgstr "Attributs"
+
 msgid "Bookmark"
 msgstr "Signet"
 
@@ -289,6 +303,9 @@
 msgid "Decimal_plural"
 msgstr "Nombres décimaux"
 
+msgid "Delete permissions"
+msgstr "Permissions de supprimer"
+
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Voulez vous supprimer le(s) élément(s) suivant(s)"
 
@@ -400,6 +417,9 @@
 msgid "RQLExpression_plural"
 msgstr "Expressions RQL"
 
+msgid "Read permissions"
+msgstr "Permissions de lire"
+
 msgid "Recipients:"
 msgstr "Destinataires :"
 
@@ -413,6 +433,9 @@
 msgid "Schema %s"
 msgstr "Schéma %s"
 
+msgid "Schema of the data model"
+msgstr "Schéma du modèle de données"
+
 msgid "Search for"
 msgstr "Rechercher"
 
@@ -454,6 +477,9 @@
 msgid "The view %s could not be found"
 msgstr "La vue %s est introuvable"
 
+msgid "There is no workflow defined for this entity."
+msgstr "Il n'y a pas de workflow défini pour ce type d'entité"
+
 #, python-format
 msgid "This %s"
 msgstr "Ce %s"
@@ -531,6 +557,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr "Rien de nommé \"%s\" dans le schéma"
 
+msgid "Update permissions"
+msgstr "Permissions de modifier"
+
 msgid "Used by:"
 msgstr "Utilisé par :"
 
@@ -604,6 +633,9 @@
 "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
 "get_cache() method."
 msgstr ""
+"une simple entité de cache, caractérisées par un nom et une date de validité. L'application "
+"est responsable de la mise à jour de la date quand il est nécessaire d'invalider le cache (typiquement dans les crochets). "
+"Voir aussi la méthode get_cache() sur la classe AppRsetObject."
 
 msgid "about this site"
 msgstr "à propos de ce site"
@@ -645,7 +677,7 @@
 msgstr ""
 
 msgid "actions_download_as_owl"
-msgstr ""
+msgstr "télécharger en owl"
 
 msgid "actions_download_as_owl_description"
 msgstr ""
@@ -681,7 +713,7 @@
 msgstr ""
 
 msgid "actions_managepermission"
-msgstr ""
+msgstr "gestion des permissions"
 
 msgid "actions_managepermission_description"
 msgstr ""
@@ -964,7 +996,7 @@
 #, python-format
 msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
 msgstr ""
-"L'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
+"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
 "autre via la relation %(rtype)s"
 
 msgid "attribute"
@@ -1006,6 +1038,9 @@
 msgid "bookmarks"
 msgstr "signets"
 
+msgid "bookmarks are used to have user's specific internal links"
+msgstr "les signets sont utilisés pour gérer des liens internes par utilisateur"
+
 msgid "boxes"
 msgstr "boîtes"
 
@@ -1139,6 +1174,9 @@
 msgid "changes applied"
 msgstr "changements appliqués"
 
+msgid "click here to see created entity"
+msgstr "cliquez ici pour voir l'entité créée"
+
 msgid "click on the box to cancel the deletion"
 msgstr "cliquer dans la zone d'édition pour annuler la suppression"
 
@@ -1160,12 +1198,6 @@
 msgid "components_appliname_description"
 msgstr "affiche le titre de l'application dans l'en-tête de page"
 
-msgid "components_applmessages"
-msgstr "messages applicatifs"
-
-msgid "components_applmessages_description"
-msgstr "affiche les messages applicatifs"
-
 msgid "components_breadcrumbs"
 msgstr "fil d'ariane"
 
@@ -1475,6 +1507,18 @@
 msgid "currently attached file: %s"
 msgstr "fichie actuellement attaché %s"
 
+msgid "cwetype-schema-image"
+msgstr "schéma"
+
+msgid "cwetype-schema-permissions"
+msgstr "permissions"
+
+msgid "cwetype-schema-text"
+msgstr "description"
+
+msgid "cwetype-workflow"
+msgstr "workflow"
+
 msgid "data directory url"
 msgstr "url du répertoire de données"
 
@@ -1606,9 +1650,6 @@
 msgid "detach attached file %s"
 msgstr "détacher le fichier existant %s"
 
-msgid "detailed schema view"
-msgstr "vue détaillée du schéma"
-
 msgid "display order of the action"
 msgstr "ordre d'affichage de l'action"
 
@@ -1694,6 +1735,9 @@
 msgid "entity edited"
 msgstr "entité éditée"
 
+msgid "entity linked"
+msgstr "entité liée"
+
 msgid "entity type"
 msgstr "type d'entité"
 
@@ -1750,6 +1794,18 @@
 msgid "facets_created_by-facet_description"
 msgstr ""
 
+msgid "facets_cwfinal-facet"
+msgstr "facette \"type d'entité ou de relation final\""
+
+msgid "facets_cwfinal-facet_description"
+msgstr ""
+
+msgid "facets_cwmeta-facet"
+msgstr ""
+
+msgid "facets_cwmeta-facet_description"
+msgstr ""
+
 msgid "facets_etype-facet"
 msgstr "facette \"est de type\""
 
@@ -1895,9 +1951,6 @@
 msgid "hide filter form"
 msgstr "cacher le filtre"
 
-msgid "hide meta-data"
-msgstr "cacher les entités et relations \"méta\""
-
 msgid "home"
 msgstr "maison"
 
@@ -2106,8 +2159,8 @@
 "link a permission to the entity. This permission should be used in the "
 "security definition of the entity's type to be useful."
 msgstr ""
-"lie une permission à une entité. Cette permission doit généralement être utilisée "
-"dans la définition de sécurité du type d'entité pour être utile."
+"lie une permission à une entité. Cette permission doit généralement être "
+"utilisée dans la définition de sécurité du type d'entité pour être utile."
 
 msgid ""
 "link a property to the user which want this property customization. Unless "
@@ -2590,6 +2643,12 @@
 msgid "schema's permissions definitions"
 msgstr "permissions définies dans le schéma"
 
+msgid "schema-image"
+msgstr "schéma"
+
+msgid "schema-text"
+msgstr "description"
+
 msgid "search"
 msgstr "rechercher"
 
@@ -2678,9 +2737,6 @@
 msgid "show filter form"
 msgstr "afficher le filtre"
 
-msgid "show meta-data"
-msgstr "afficher le schéma complet"
-
 msgid "sioc"
 msgstr "sioc"
 
@@ -2795,6 +2851,9 @@
 msgid "thursday"
 msgstr "jeudi"
 
+msgid "timeline"
+msgstr "échelle de temps"
+
 msgid "timestamp"
 msgstr "date"
 
@@ -3133,6 +3192,12 @@
 #~ msgid "close all"
 #~ msgstr "tout fermer"
 
+#~ msgid "components_applmessages"
+#~ msgstr "messages applicatifs"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "affiche les messages applicatifs"
+
 #~ msgid "components_rss_feed_url"
 #~ msgstr "syndication rss"
 
@@ -3149,6 +3214,9 @@
 #~ "langue par défaut (regarder le répertoire i18n de l'application pour voir "
 #~ "les langues disponibles)"
 
+#~ msgid "detailed schema view"
+#~ msgstr "vue détaillée du schéma"
+
 #~ msgid "filter"
 #~ msgstr "filtrer"
 
@@ -3158,6 +3226,9 @@
 #~ msgid "header"
 #~ msgstr "en-tête de page"
 
+#~ msgid "hide meta-data"
+#~ msgstr "cacher les entités et relations \"méta\""
+
 #~ msgid "iCal"
 #~ msgstr "iCal"
 
@@ -3193,6 +3264,9 @@
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
+#~ msgid "show meta-data"
+#~ msgstr "afficher le schéma complet"
+
 #~ msgid "status will change from %s to %s"
 #~ msgstr "l'état va passer de %s à %s"
 
--- a/misc/migration/bootstrapmigration_repository.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Jul 06 19:55:18 2009 +0200
@@ -8,6 +8,8 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
+applcubicwebversion, cubicwebversion = versions_map['cubicweb']
+
 if applcubicwebversion < (3, 2, 2) and cubicwebversion >= (3, 2, 1):
    from base64 import b64encode
    for table in ('entities', 'deleted_entities'):
--- a/schema.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/schema.py	Mon Jul 06 19:55:18 2009 +0200
@@ -354,6 +354,7 @@
         """rql expression factory"""
         return ERQLExpression(expression, mainvars, eid)
 
+
 class CubicWebRelationSchema(RelationSchema):
     RelationSchema._RPROPERTIES['eid'] = None
     _perms_checked = False
--- a/selectors.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/selectors.py	Mon Jul 06 19:55:18 2009 +0200
@@ -482,8 +482,7 @@
     def __call__(self, cls, req, *args, **kwargs):
         score = 0
         for param in self.expected:
-            val = req.form.get(param)
-            if not val:
+            if not param in req.form:
                 return 0
             score += 1
         return len(self.expected)
@@ -623,6 +622,14 @@
                 etype = kwargs['etype']
             except KeyError:
                 return 0
+        else:
+            # only check this is a known type if etype comes from req.form,
+            # else we want the error to propagate
+            try:
+                etype = cls.vreg.case_insensitive_etypes[etype.lower()]
+                req.form['etype'] = etype
+            except KeyError:
+                return 0
         return self.score_class(cls.vreg.etype_class(etype), req)
 
 
--- a/server/checkintegrity.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/checkintegrity.py	Mon Jul 06 19:55:18 2009 +0200
@@ -71,6 +71,14 @@
                                        uniquecstrcheck_before_modification)
     from cubicweb.server.repository import FTIndexEntityOp
     repo = session.repo
+    cursor = session.pool['system']
+    if not repo.system_source.indexer.has_fti_table(cursor):
+        from indexer import get_indexer
+        print 'no text index table'
+        indexer = get_indexer(repo.system_source.dbdriver)
+        # XXX indexer.init_fti(cursor) once index 0.7 is out
+        indexer.init_extensions(cursor)
+        cursor.execute(indexer.sql_init_fti())
     repo.hm.unregister_hook(setmtime_before_update_entity,
                             'before_update_entity', '')
     repo.hm.unregister_hook(uniquecstrcheck_before_modification,
--- a/server/hooks.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/hooks.py	Mon Jul 06 19:55:18 2009 +0200
@@ -222,9 +222,10 @@
             return
         if self.session.unsafe_execute(*self._rql()).rowcount < 1:
             etype = self.session.describe(self.eid)[0]
-            msg = self.session._('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
-            raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype,
-                                                                'etype': etype,
+            _ = self.session._
+            msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
+            raise ValidationError(self.eid, {self.rtype: msg % {'rtype': _(self.rtype),
+                                                                'etype': _(etype),
                                                                 'eid': self.eid}})
 
     def commit_event(self):
--- a/server/migractions.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/migractions.py	Mon Jul 06 19:55:18 2009 +0200
@@ -64,25 +64,48 @@
         self.fs_schema = schema
         self._synchronized = set()
 
+    # overriden from base MigrationHelper ######################################
+
     @cached
     def repo_connect(self):
         self.repo = get_repository(method='inmemory', config=self.config)
         return self.repo
 
+    def cube_upgraded(self, cube, version):
+        self.cmd_set_property('system.version.%s' % cube.lower(),
+                              unicode(version))
+        self.commit()
+
     def shutdown(self):
         if self.repo is not None:
             self.repo.shutdown()
 
-    def rewrite_vcconfiguration(self):
-        """write current installed versions (of cubicweb software
-        and of each used cube) into the database
+    def migrate(self, vcconf, toupgrade, options):
+        if not options.fs_only:
+            if options.backup_db is None:
+                self.backup_database()
+            elif options.backup_db:
+                self.backup_database(askconfirm=False)
+        super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
+
+    def process_script(self, migrscript, funcname=None, *args, **kwargs):
+        """execute a migration script
+        in interactive mode,  display the migration script path, ask for
+        confirmation and execute it if confirmed
         """
-        self.cmd_set_property('system.version.cubicweb',
-                              self.config.cubicweb_version())
-        for pkg in self.config.cubes():
-            pkgversion = self.config.cube_version(pkg)
-            self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
-        self.commit()
+        try:
+            if migrscript.endswith('.sql'):
+                if self.execscript_confirm(migrscript):
+                    sqlexec(open(migrscript).read(), self.session.system_sql)
+            else:
+                return super(ServerMigrationHelper, self).process_script(
+                    migrscript, funcname, *args, **kwargs)
+            self.commit()
+        except:
+            self.rollback()
+            raise
+
+    # server specific migration methods ########################################
 
     def backup_database(self, backupfile=None, askconfirm=True):
         config = self.config
@@ -142,26 +165,6 @@
                         break
             print 'database restored'
 
-    def migrate(self, vcconf, toupgrade, options):
-        if not options.fs_only:
-            if options.backup_db is None:
-                self.backup_database()
-            elif options.backup_db:
-                self.backup_database(askconfirm=False)
-        super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
-
-    def process_script(self, migrscript, funcname=None, *args, **kwargs):
-        """execute a migration script
-        in interactive mode,  display the migration script path, ask for
-        confirmation and execute it if confirmed
-        """
-        if migrscript.endswith('.sql'):
-            if self.execscript_confirm(migrscript):
-                sqlexec(open(migrscript).read(), self.session.system_sql)
-        else:
-            return super(ServerMigrationHelper, self).process_script(
-                migrscript, funcname, *args, **kwargs)
-
     @property
     def cnx(self):
         """lazy connection"""
--- a/server/repository.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/repository.py	Mon Jul 06 19:55:18 2009 +0200
@@ -392,10 +392,23 @@
         except ZeroDivisionError:
             pass
 
+    def _login_from_email(self, login):
+        session = self.internal_session()
+        try:
+            rset = session.execute('Any L WHERE U login L, U primary_email M, '
+                                   'M address %(login)s', {'login': login})
+            if rset.rowcount == 1:
+                login = rset[0][0]
+        finally:
+            session.close()
+        return login
+
     def authenticate_user(self, session, login, password):
         """validate login / password, raise AuthenticationError on failure
         return associated CWUser instance on success
         """
+        if self.vreg.config['allow-email-login'] and '@' in login:
+            login = self._login_from_email(login)
         for source in self.sources:
             if source.support_entity('CWUser'):
                 try:
@@ -405,11 +418,11 @@
                     continue
         else:
             raise AuthenticationError('authentication failed with all sources')
-        euser = self._build_user(session, eid)
+        cwuser = self._build_user(session, eid)
         if self.config.consider_user_state and \
-               not euser.state in euser.AUTHENTICABLE_STATES:
+               not cwuser.state in cwuser.AUTHENTICABLE_STATES:
             raise AuthenticationError('user is not in authenticable state')
-        return euser
+        return cwuser
 
     def _build_user(self, session, eid):
         """return a CWUser entity for user with the given eid"""
@@ -417,13 +430,13 @@
         rql = cls.fetch_rql(session.user, ['X eid %(x)s'])
         rset = session.execute(rql, {'x': eid}, 'x')
         assert len(rset) == 1, rset
-        euser = rset.get_entity(0, 0)
+        cwuser = rset.get_entity(0, 0)
         # pylint: disable-msg=W0104
-        # prefetch / cache euser's groups and properties. This is especially
+        # prefetch / cache cwuser's groups and properties. This is especially
         # useful for internal sessions to avoid security insertions
-        euser.groups
-        euser.properties
-        return euser
+        cwuser.groups
+        cwuser.properties
+        return cwuser
 
     # public (dbapi) interface ################################################
 
@@ -664,13 +677,22 @@
           custom properties)
         """
         session = self._get_session(sessionid, setpool=False)
-        if props:
-            # update session properties
-            for prop, value in props.items():
-                session.change_property(prop, value)
+        if props is not None:
+            self.set_session_props(sessionid, props)
         user = session.user
         return user.eid, user.login, user.groups, user.properties
 
+    def set_session_props(self, sessionid, props):
+        """this method should be used by client to:
+        * check session id validity
+        * update user information on each user's request (i.e. groups and
+          custom properties)
+        """
+        session = self._get_session(sessionid, setpool=False)
+        # update session properties
+        for prop, value in props.items():
+            session.change_property(prop, value)
+
     # public (inter-repository) interface #####################################
 
     def entities_modified_since(self, etypes, mtime):
--- a/server/schemahooks.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/schemahooks.py	Mon Jul 06 19:55:18 2009 +0200
@@ -247,8 +247,7 @@
     """actually add the entity type to the application's schema"""
     eid = None # make pylint happy
     def commit_event(self):
-        eschema = self.schema.add_entity_type(self.kobj)
-        eschema.eid = self.eid
+        self.schema.add_entity_type(self.kobj)
 
 def before_add_eetype(session, entity):
     """before adding a CWEType entity:
@@ -299,7 +298,8 @@
     # register operation to modify the schema on commit
     # this have to be done before adding other relations definitions
     # or permission settings
-    AddCWETypeOp(session, etype, eid=entity.eid)
+    etype.eid = entity.eid
+    AddCWETypeOp(session, etype)
     # add meta creation_date, modification_date and owned_by relations
     for rql, kwargs in relrqls:
         session.execute(rql, kwargs)
@@ -311,7 +311,6 @@
     def commit_event(self):
         rschema = self.schema.add_relation_type(self.kobj)
         rschema.set_default_groups()
-        rschema.eid = self.eid
 
 def before_add_ertype(session, entity):
     """before adding a CWRType entity:
@@ -331,12 +330,13 @@
       schema on commit
     We don't know yeat this point if a table is necessary
     """
-    AddCWRTypeOp(session, RelationType(name=entity['name'],
-                                      description=entity.get('description'),
-                                      meta=entity.get('meta', False),
-                                      inlined=entity.get('inlined', False),
-                                      symetric=entity.get('symetric', False)),
-                eid=entity.eid)
+    rtype = RelationType(name=entity['name'],
+                         description=entity.get('description'),
+                         meta=entity.get('meta', False),
+                         inlined=entity.get('inlined', False),
+                         symetric=entity.get('symetric', False))
+    rtype.eid = entity.eid
+    AddCWRTypeOp(session, rtype)
 
 
 class AddErdefOp(EarlySchemaOperation):
--- a/server/sources/pyrorql.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/sources/pyrorql.py	Mon Jul 06 19:55:18 2009 +0200
@@ -164,7 +164,12 @@
         """
         self.info('synchronizing pyro source %s', self.uri)
         cnx = self.get_connection()
-        extrepo = cnx._repo
+        try:
+            extrepo = cnx._repo
+        except AttributeError:
+            # fake connection wrapper returned when we can't connect to the
+            # external source (hence we've no chance to synchronize...)
+            return
         etypes = self.support_entities.keys()
         if mtime is None:
             mtime = self.last_update_time()
@@ -212,7 +217,7 @@
         nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group']
         #cnxprops = ConnectionProperties(cnxtype=self.config['cnx-type'])
         return dbapi.connect(database=self.config['pyro-ns-id'],
-                             user=self.config['cubicweb-user'],
+                             login=self.config['cubicweb-user'],
                              password=self.config['cubicweb-password'],
                              host=nshost, port=nsport, group=nsgroup,
                              setvreg=False) #cnxprops=cnxprops)
--- a/server/test/unittest_hooks.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/test/unittest_hooks.py	Mon Jul 06 19:55:18 2009 +0200
@@ -242,7 +242,7 @@
 
 
 class SchemaModificationHooksTC(RepositoryBasedTC):
-    copy_schema = True
+    #copy_schema = True
 
     def setUp(self):
         if not hasattr(self, '_repo'):
@@ -471,6 +471,10 @@
         self.commit()
         # should not be able anymore to add personne without prenom
         self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"')
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "prenom", E name "Personne"')
+        self.commit()
 
 
 class WorkflowHooksTC(RepositoryBasedTC):
--- a/server/test/unittest_migractions.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/server/test/unittest_migractions.py	Mon Jul 06 19:55:18 2009 +0200
@@ -23,7 +23,7 @@
 
 
 class MigrationCommandsTC(RepositoryBasedTC):
-    copy_schema = True
+    copy_schema = False
 
     def setUp(self):
         if not hasattr(self, '_repo'):
@@ -146,7 +146,7 @@
         for cstr in eschema.constraints('name'):
             self.failUnless(hasattr(cstr, 'eid'))
 
-    def test_drop_entity_type(self):
+    def test_add_drop_entity_type(self):
         self.mh.cmd_add_entity_type('Folder2')
         todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True)
         doneeid = self.mh.cmd_add_state(u'done', 'Folder2')
@@ -161,7 +161,7 @@
         self.failIf(self.execute('State X WHERE NOT X state_of ET'))
         self.failIf(self.execute('Transition X WHERE NOT X transition_of ET'))
 
-    def test_add_relation_type(self):
+    def test_add_drop_relation_type(self):
         self.mh.cmd_add_entity_type('Folder2', auto=False)
         self.mh.cmd_add_relation_type('filed_under2')
         self.failUnless('filed_under2' in self.schema)
@@ -169,52 +169,40 @@
                           ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
                            'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
         self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
-
-
-    def test_drop_relation_type(self):
-        self.mh.cmd_add_entity_type('Folder2', auto=False)
-        self.mh.cmd_add_relation_type('filed_under2')
-        self.failUnless('filed_under2' in self.schema)
         self.mh.cmd_drop_relation_type('filed_under2')
         self.failIf('filed_under2' in self.schema)
 
-    def test_add_relation_definition(self):
-        self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State')
-        self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()),
-                          ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision'])
-        self.assertEquals(self.schema['in_state'].objects(), ('State',))
-
     def test_add_relation_definition_nortype(self):
         self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire')
         self.assertEquals(self.schema['concerne2'].subjects(),
                           ('Personne',))
         self.assertEquals(self.schema['concerne2'].objects(), ('Affaire',))
+        self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire')
+        self.failIf('concerne2' in self.schema)
 
-    def test_drop_relation_definition1(self):
-        self.failUnless('concerne' in self.schema)
+    def test_drop_relation_definition_existant_rtype(self):
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
         self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision'])
+        self.mh.cmd_add_relation_definition('Personne', 'concerne', 'Affaire')
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+        # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+        self.maxeid = self.execute('Any MAX(X)')[0][0]
 
     def test_drop_relation_definition_with_specialization(self):
-        self.failUnless('concerne' in self.schema)
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
         self.mh.cmd_drop_relation_definition('Affaire', 'concerne', 'Societe')
-        self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe')
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
         self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note'])
-
-    def test_drop_relation_definition2(self):
-        self.failUnless('evaluee' in self.schema)
-        self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note')
-        self.failUnless('evaluee' in self.schema)
-        self.assertEquals(sorted(self.schema['evaluee'].subjects()),
-                          ['CWUser', 'Division', 'Societe', 'SubDivision'])
-        self.assertEquals(sorted(self.schema['evaluee'].objects()),
-                          ['Note'])
+        self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe')
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
+        self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+        # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+        self.maxeid = self.execute('Any MAX(X)')[0][0]
 
     def test_rename_relation(self):
         self.skip('implement me')
@@ -455,7 +443,6 @@
         ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file')
         self.assertEquals(str(ex), "can't remove cube file, used as a dependency")
 
-
     def test_set_state(self):
         user = self.session.user
         self.mh.set_state(user.eid, 'deactivated')
--- a/test/unittest_rset.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/test/unittest_rset.py	Mon Jul 06 19:55:18 2009 +0200
@@ -6,7 +6,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from __future__ import with_statement
+#from __future__ import with_statement
 
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/utils.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/utils.py	Mon Jul 06 19:55:18 2009 +0200
@@ -206,8 +206,13 @@
     def add_post_inline_script(self, content):
         self.post_inlined_scripts.append(content)
 
-    def add_onload(self, jscode):
-        self.add_post_inline_script(u"""jQuery(document).ready(function () {
+    def add_onload(self, jscode, jsoncall=False):
+        if jsoncall:
+            self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) {
+%s
+});""" % jscode)
+        else:
+            self.add_post_inline_script(u"""jQuery(document).ready(function () {
  %s
  });""" % jscode)
 
--- a/web/application.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/application.py	Mon Jul 06 19:55:18 2009 +0200
@@ -21,8 +21,7 @@
 from cubicweb.web import LOGGER, component
 from cubicweb.web import (
     StatusResponse, DirectResponse, Redirect, NotFound,
-    RemoteCallFailed, ExplicitLogin, InvalidSession)
-from cubicweb.web.component import Component
+    RemoteCallFailed, ExplicitLogin, InvalidSession, RequestError)
 
 # make session manager available through a global variable so the debug view can
 # print information about web session
@@ -340,7 +339,7 @@
                 raise
             except ValidationError, ex:
                 self.validation_error_handler(req, ex)
-            except (Unauthorized, BadRQLQuery), ex:
+            except (Unauthorized, BadRQLQuery, RequestError), ex:
                 self.error_handler(req, ex, tb=False)
             except Exception, ex:
                 self.error_handler(req, ex, tb=True)
@@ -396,8 +395,8 @@
         return self.vreg.main_template(req, template, view=view)
 
     def main_template_id(self, req):
-        template = req.property_value('ui.main-template')
-        if template not in self.vreg.registry('views') :
+        template = req.form.get('__template', req.property_value('ui.main-template'))
+        if template not in self.vreg.registry('views'):
             template = 'main-template'
         return template
 
--- a/web/component.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/component.py	Mon Jul 06 19:55:18 2009 +0200
@@ -37,7 +37,7 @@
 
     property_defs = {
         _('visible'):  dict(type='Boolean', default=True,
-                            help=_('display the box or not')),
+                            help=_('display the component or not')),
         _('order'):    dict(type='Int', default=99,
                             help=_('display order of the component')),
         _('context'):  dict(type='String', default='header',
--- a/web/data/cubicweb.ajax.js	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/data/cubicweb.ajax.js	Mon Jul 06 19:55:18 2009 +0200
@@ -11,15 +11,20 @@
 
 function _loadAjaxHtmlHead(node, head, tag, srcattr) {
     var loaded = [];
-    jQuery('head ' + tag).each(function(i) {
+    var jqtagfilter = tag + '[' + srcattr + ']';
+    jQuery('head ' + jqtagfilter).each(function(i) {
 	loaded.push(this.getAttribute(srcattr));
     });
     node.find(tag).each(function(i) {
-	if (!loaded.contains(this.getAttribute(srcattr))) {
+	if (this.getAttribute(srcattr)) {
+	    if (!loaded.contains(this.getAttribute(srcattr))) {
+		jQuery(this).appendTo(head);
+	    }
+	} else {
 	    jQuery(this).appendTo(head);
 	}
     });
-    node.find(tag).remove();
+    node.find(jqtagfilter).remove();
 }
 
 /*
--- a/web/data/cubicweb.css	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/data/cubicweb.css	Mon Jul 06 19:55:18 2009 +0200
@@ -448,14 +448,11 @@
   background: #cfceb7;
   display: block;
   font: bold 100% Georgia;
-  padding : 2px 0;
 }
 
 div.sideBox {
   padding: 0 0 0.2em;
   margin-bottom: 0.5em;
-  min-width: 21em;
-  max-width: 50em;
 }
 
 ul.sideBox li{
@@ -575,8 +572,6 @@
 }
 
 div.primaryRight{
-  float:right;
-
  }
 
 div.metadata {
@@ -687,7 +682,7 @@
 /***************************************/
 
 table.listing {
- margin: 10px 0em;
+ padding: 10px 0em;
  color: #000;
  width: 100%;
  border-right: 1px solid #dfdfdf;
--- a/web/data/cubicweb.edition.js	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/data/cubicweb.edition.js	Mon Jul 06 19:55:18 2009 +0200
@@ -305,7 +305,9 @@
 }
 
 function _clearPreviousErrors(formid) {
+    jQuery('#' + formid + 'ErrorMessage').remove();
     jQuery('#' + formid + ' span.error').remove();
+    jQuery('#' + formid + ' .error').removeClass('error');
 }
 
 function _displayValidationerrors(formid, eid, errors) {
@@ -324,7 +326,7 @@
 	    field.before(span);
 	} else {
 	    firsterrfield = formid;
-	    globalerrors.push(fieldname + ': ' + errmsg);
+	    globalerrors.push(_(fieldname) + ' : ' + errmsg);
 	}
     }
     if (globalerrors.length) {
@@ -334,7 +336,7 @@
 	    var innernode = UL(null, map(LI, globalerrors));
 	}
 	// insert DIV and innernode before the form
-	var div = DIV({'class' : "errorMessage"});
+	var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'});
 	div.appendChild(innernode);
 	jQuery('#' + formid).before(div);
     }
--- a/web/formfields.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/formfields.py	Mon Jul 06 19:55:18 2009 +0200
@@ -7,6 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
 from datetime import datetime
 
 from logilab.mtconverter import html_escape
--- a/web/request.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/request.py	Mon Jul 06 19:55:18 2009 +0200
@@ -61,6 +61,7 @@
 
 class CubicWebRequestBase(DBAPIRequest):
     """abstract HTTP request, should be extended according to the HTTP backend"""
+    json_request = False # to be set to True by json controllers
 
     def __init__(self, vreg, https, form=None):
         super(CubicWebRequestBase, self).__init__(vreg)
@@ -91,7 +92,7 @@
         or an anonymous connection is open
         """
         super(CubicWebRequestBase, self).set_connection(cnx, user)
-        # get request language:
+        # set request language
         vreg = self.vreg
         if self.user:
             try:
@@ -114,6 +115,7 @@
     def set_language(self, lang):
         self._ = self.__ = self.translations[lang]
         self.lang = lang
+        self.cnx.set_session_props(lang=lang)
         self.debug('request language: %s', lang)
 
     # input form parameters management ########################################
@@ -336,7 +338,6 @@
                 params[name] = value
         params['eid'] = eid
         if len(params) < minparams:
-            print eid, params
             raise RequestError(self._('missing parameters for entity %s') % eid)
         return params
 
@@ -452,6 +453,9 @@
 
     # high level methods for HTML headers management ##########################
 
+    def add_onload(self, jscode):
+        self.html_headers.add_onload(jscode, self.json_request)
+
     def add_js(self, jsfiles, localfile=True):
         """specify a list of JS files to include in the HTML headers
         :param jsfiles: a JS filename or a list of JS filenames
--- a/web/test/unittest_application.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/test/unittest_application.py	Mon Jul 06 19:55:18 2009 +0200
@@ -307,7 +307,7 @@
         self.assertEquals(cnx.password, origcnx.password)
         self.assertEquals(cnx.anonymous_connection, False)
         self.assertEquals(path, 'view')
-        self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login})
+        self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login})
 
     def _test_auth_fail(self, req):
         self.assertRaises(AuthenticationError, self.app.connect, req)
@@ -351,8 +351,8 @@
         req.form['__password'] = origcnx.password
         self._test_auth_fail(req)
         # option allow-email-login set
+        origcnx.login = address
         self.set_option('allow-email-login', True)
-        req, origcnx = self._init_auth('cookie')
         req.form['__login'] = address
         req.form['__password'] = origcnx.password
         self._test_auth_succeed(req, origcnx)
--- a/web/test/unittest_form.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/test/unittest_form.py	Mon Jul 06 19:55:18 2009 +0200
@@ -5,7 +5,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from __future__ import with_statement
+#from __future__ import with_statement
 
 from xml.etree.ElementTree import fromstring
 
--- a/web/views/authentication.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/authentication.py	Mon Jul 06 19:55:18 2009 +0200
@@ -36,7 +36,10 @@
             # calling cnx.user() check connection validity, raise
             # BadConnectionId on failure
             user = cnx.user(req)
-            if login and user.login != login:
+            # check cnx.login and not user.login, since in case of login by
+            # email, login and cnx.login are the email while user.login is the
+            # actual user login
+            if login and cnx.login != login:
                 cnx.close()
                 raise InvalidSession('login mismatch')
         except BadConnectionId:
@@ -53,18 +56,6 @@
         req.set_connection(cnx, user)
         return cnx
 
-    def login_from_email(self, login):
-        # XXX should not be called from web interface
-        session = self.repo.internal_session()
-        try:
-            rset = session.execute('Any L WHERE U login L, U primary_email M, '
-                                   'M address %(login)s', {'login': login})
-            if rset.rowcount == 1:
-                login = rset[0][0]
-        finally:
-            session.close()
-        return login
-
     def authenticate(self, req, _login=None, _password=None):
         """authenticate user and return corresponding user object
 
@@ -79,8 +70,6 @@
             login, password = _login, _password
         else:
             login, password = req.get_authorization()
-        if self.vreg.config['allow-email-login'] and '@' in (login or u''):
-            login = self.login_from_email(login)
         if not login:
             # No session and no login -> try anonymous
             login, password = self.vreg.config.anonymous_user()
--- a/web/views/basecontrollers.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/basecontrollers.py	Mon Jul 06 19:55:18 2009 +0200
@@ -178,37 +178,50 @@
                 req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y'))
 
 
+def _validation_error(req, ex):
+    req.cnx.rollback()
+    forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True)
+    foreid = ex.entity
+    eidmap = req.data.get('eidmap', {})
+    for var, eid in eidmap.items():
+        if foreid == eid:
+            foreid = var
+            break
+    return (foreid, ex.errors)
+
+def _validate_form(req, vreg):
+    # XXX should use the `RemoteCallFailed` mechanism
+    try:
+        ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'),
+                           req=req)
+    except NoSelectableObject:
+        return (False, {None: req._('not authorized')})
+    try:
+        ctrl.publish(None)
+    except ValidationError, ex:
+        return (False, _validation_error(req, ex))
+    except Redirect, ex:
+        try:
+            req.cnx.commit() # ValidationError may be raise on commit
+        except ValidationError, ex:
+            return (False, _validation_error(req, ex))
+        else:
+            return (True, ex.location)
+    except Exception, ex:
+        req.cnx.rollback()
+        req.exception('unexpected error while validating form')
+        return (False, req._(str(ex).decode('utf-8')))
+    return (False, '???')
+
+
 class FormValidatorController(Controller):
     id = 'validateform'
 
     def publish(self, rset=None):
-        vreg = self.vreg
-        try:
-            ctrl = vreg.select('controllers', 'edit', self.req,
-                               appli=self.appli)
-        except NoSelectableObject:
-            status, args = (False, {None: self.req._('not authorized')})
-        else:
-            try:
-                ctrl.publish(None, fromjson=True)
-            except ValidationError, err:
-                status, args = self.validation_error(err)
-            except Redirect, err:
-                try:
-                    self.req.cnx.commit() # ValidationError may be raise on commit
-                except ValidationError, err:
-                    status, args = self.validation_error(err)
-                else:
-                    status, args = (True, err.location)
-            except Exception, err:
-                self.req.cnx.rollback()
-                self.exception('unexpected error in validateform')
-                try:
-                    status, args = (False, self.req._(unicode(err)))
-                except UnicodeError:
-                    status, args = (False, repr(err))
-            else:
-                status, args = (False, '???')
+        self.req.json_request = True
+        # XXX unclear why we have a separated controller here vs
+        # js_validate_form on the json controller
+        status, args = _validate_form(self.req, self.vreg)
         self.req.set_content_type('text/html')
         jsarg = simplejson.dumps( (status, args) )
         domid = self.req.form.get('__domid', 'entityForm').encode(
@@ -217,14 +230,6 @@
  window.parent.handleFormValidationResponse('%s', null, null, %s);
 </script>""" %  (domid, simplejson.dumps( (status, args) ))
 
-    def validation_error(self, err):
-        self.req.cnx.rollback()
-        try:
-            eid = err.entity.eid
-        except AttributeError:
-            eid = err.entity
-        return (False, (eid, err.errors))
-
 
 class JSonController(Controller):
     id = 'json'
@@ -238,6 +243,7 @@
         note: it's the responsability of js_* methods to set the correct
         response content type
         """
+        self.req.json_request = True
         self.req.pageid = self.req.form.get('pageid')
         try:
             fname = self.req.form['fname']
@@ -377,26 +383,8 @@
         return self.validate_form(action, names, values)
 
     def validate_form(self, action, names, values):
-        # XXX this method (and correspoding js calls) should use the new
-        #     `RemoteCallFailed` mechansim
         self.req.form = self._rebuild_posted_form(names, values, action)
-        vreg = self.vreg
-        try:
-            ctrl = vreg.select('controllers', 'edit', self.req)
-        except NoSelectableObject:
-            return (False, {None: self.req._('not authorized')})
-        try:
-            ctrl.publish(None, fromjson=True)
-        except ValidationError, err:
-            self.req.cnx.rollback()
-            return (False, (err.entity, err.errors))
-        except Redirect, redir:
-            return (True, redir.location)
-        except Exception, err:
-            self.req.cnx.rollback()
-            self.exception('unexpected error in js_validateform')
-            return (False, self.req._(str(err).decode('utf-8')))
-        return (False, '???')
+        return _validate_form(self.req, self.vreg)
 
     @jsonize
     def js_edit_field(self, action, names, values, rtype, eid, default):
--- a/web/views/boxes.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/boxes.py	Mon Jul 06 19:55:18 2009 +0200
@@ -18,10 +18,10 @@
 
 from logilab.mtconverter import html_escape
 
-from cubicweb.rtags import RelationTags
 from cubicweb.selectors import match_user_groups, non_final_entity
+from cubicweb.view import EntityView
+from cubicweb.schema import display_name
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.view import EntityView
 from cubicweb.web import uicfg
 from cubicweb.web.box import BoxTemplate
 
--- a/web/views/editcontroller.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/editcontroller.py	Mon Jul 06 19:55:18 2009 +0200
@@ -25,9 +25,8 @@
 class EditController(ViewController):
     id = 'edit'
 
-    def publish(self, rset=None, fromjson=False):
+    def publish(self, rset=None):
         """edit / create / copy / delete entity / relations"""
-        self.fromjson = fromjson
         for key in self.req.form:
             # There should be 0 or 1 action
             if key.startswith('__action_'):
@@ -113,9 +112,8 @@
                 entity = execute(rql, formparams).get_entity(0, 0)
                 eid = entity.eid
             except ValidationError, ex:
-                # ex.entity may be an int or an entity instance
                 self._to_create[formparams['eid']] = ex.entity
-                if self.fromjson:
+                if self.req.json_request: # XXX (syt) why?
                     ex.entity = formparams['eid']
                 raise
             self._to_create[formparams['eid']] = eid
--- a/web/views/editforms.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/editforms.py	Mon Jul 06 19:55:18 2009 +0200
@@ -368,8 +368,13 @@
         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
         title = self.schema.rschema(rtype).display_name(self.req, role)
         removejs = self.removejs % (peid, rtype,entity.eid)
+        countkey = '%s_count' % rtype
+        try:
+            self.req.data[countkey] += 1
+        except:
+            self.req.data[countkey] = 1
         self.w(form.form_render(divid=divid, title=title, removejs=removejs,
-                                **kwargs))
+                                counter=self.req.data[countkey], **kwargs))
 
     def add_hiddens(self, form, entity, peid, rtype, role):
         # to ease overriding (see cubes.vcsfile.views.forms for instance)
--- a/web/views/facets.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/facets.py	Mon Jul 06 19:55:18 2009 +0200
@@ -41,6 +41,8 @@
     needs_css = 'cubicweb.facets.css'
     needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
 
+    bkLinkBox_template = u'<div class="facetTitle">%s</div>'
+
     def facetargs(self):
         """this method returns the list of extra arguments that should
         be used by the facet
@@ -80,18 +82,8 @@
                         widgets.append(wdg)
             if not widgets:
                 return
+            self.displayBookmarkLink(rset)
             w = self.w
-            eschema = self.schema.eschema('Bookmark')
-            if eschema.has_perm(req, 'add'):
-                bk_path = 'view?rql=%s' % rset.printable_rql()
-                bk_title = req._('my custom search')
-                linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
-                bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
-                bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
-                w(u'<div class="facetTitle"><a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a></div>' % (
-                    html_escape(bk_base_url),
-                    html_escape(bk_add_url),
-                    req._('bookmark this search')))
             w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">'  % (
                 divid, html_escape(dumps([divid, vid, paginate, self.facetargs()]))))
             w(u'<fieldset>')
@@ -109,6 +101,20 @@
             import cubicweb
             cubicweb.info('after facets with rql: %s' % repr(rqlst))
 
+    def displayBookmarkLink(self, rset):
+        eschema = self.schema.eschema('Bookmark')
+        if eschema.has_perm(self.req, 'add'):
+            bk_path = 'view?rql=%s' % rset.printable_rql()
+            bk_title = self.req._('my custom search')
+            linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
+            bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
+            bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
+            bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
+                    html_escape(bk_base_url),
+                    html_escape(bk_add_url),
+                    self.req._('bookmark this search'))
+            self.w(self.bkLinkBox_template % bk_link)
+
     def get_facets(self, rset, mainvar):
         return self.vreg.possible_vobjects('facets', self.req, rset=rset,
                                            context='facetbox',
--- a/web/views/formrenderers.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/formrenderers.py	Mon Jul 06 19:55:18 2009 +0200
@@ -503,7 +503,7 @@
         w(u'<div class="iformBody">')
         values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema)
         w(u'<div class="iformTitle"><span>%(title)s</span> '
-          '#<span class="icounter">1</span> '
+          '#<span class="icounter">%(counter)s</span> '
           '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
           % values)
         # cleanup values
--- a/web/views/forms.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/forms.py	Mon Jul 06 19:55:18 2009 +0200
@@ -119,8 +119,8 @@
 
     def form_add_hidden(self, name, value=None, **kwargs):
         """add an hidden field to the form"""
-        field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
-                            **kwargs)
+        kwargs.setdefault('widget', fwdgs.HiddenInput)
+        field = StringField(name=name, initial=value, **kwargs)
         if 'id' in kwargs:
             # by default, hidden input don't set id attribute. If one is
             # explicitly specified, ensure it will be set
--- a/web/views/management.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/management.py	Mon Jul 06 19:55:18 2009 +0200
@@ -14,7 +14,7 @@
 from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
 from cubicweb.view import AnyRsetView, StartupView, EntityView
 from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.web import formwidgets
+from cubicweb.web import formwidgets as wdgs
 from cubicweb.web.formfields import guess_field
 
 SUBMIT_MSGID = _('Submit bug report')
@@ -160,11 +160,9 @@
             self.w(self.req._('no associated permissions'))
 
     def require_permission_edit_form(self, entity):
-        w = self.w
-        _ = self.req._
         newperm = self.vreg.etype_class('CWPermission')(self.req, None)
         newperm.eid = self.req.varmaker.next()
-        w(u'<p>%s</p>' % _('add a new permission'))
+        self.w(u'<p>%s</p>' % self.req._('add a new permission'))
         form = self.vreg.select('forms', 'base', self.req, entity=newperm,
                                 form_buttons=[formwidgets.SubmitButton()],
                                 domid='reqperm%s' % entity.eid,
@@ -176,7 +174,7 @@
         cwpermschema = newperm.e_schema
         if permnames is not None:
             field = guess_field(cwpermschema, self.schema.rschema('name'),
-                                widget=formwidgets.Select({'size': 1}),
+                                widget=wdgs.Select({'size': 1}),
                                 choices=permnames)
         else:
             field = guess_field(cwpermschema, self.schema.rschema('name'))
@@ -246,15 +244,17 @@
             form = self.vreg.select('forms', 'base', self.req, rset=None,
                                     mainform=False)
             binfo = text_error_description(ex, excinfo, req, eversion, cversions)
-            form.form_add_hidden('description', binfo)
+            form.form_add_hidden('description', binfo,
+                                 # we must use a text area to keep line breaks
+                                 widget=wdgs.TextArea({'class': 'hidden'}))
             form.form_add_hidden('__bugreporting', '1')
             if submitmail:
-                form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)]
+                form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
                 form.action = req.build_url('reportbug')
                 w(form.form_render())
             if submiturl:
                 form.form_add_hidden('description_format', 'text/rest')
-                form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)]
+                form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)]
                 form.action = submiturl
                 w(form.form_render())
 
--- a/web/views/primary.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/primary.py	Mon Jul 06 19:55:18 2009 +0200
@@ -52,7 +52,6 @@
         boxes = self._prepare_side_boxes(entity)
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'<table width="100%"><tr><td style="width: 75%">')
-        self.w(u'<div>')
         self.w(u'<div class="mainInfo">')
         self.content_navigation_components('navcontenttop')
         try:
@@ -63,7 +62,6 @@
             warn('siderelations argument of render_entity_attributes is '
                  'deprecated (%s)' % self.__class__)
             self.render_entity_attributes(entity, [])
-        self.w(u'</div>')
         if self.main_related_section:
             try:
                 self.render_entity_relations(entity)
@@ -74,9 +72,9 @@
                      'deprecated')
                 self.render_entity_relations(entity, [])
         self.w(u'</div>')
+        # side boxes
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'</td><td>')
-            # side boxes
             self.w(u'<div class="primaryRight">')
             if hasattr(self, 'render_side_related'):
                 warn('render_side_related is deprecated')
--- a/web/views/schema.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/schema.py	Mon Jul 06 19:55:18 2009 +0200
@@ -248,12 +248,7 @@
 
 ### facets
 
-class CWMetaFacet(AttributeFacet):
-    id = 'cwmeta-facet'
-    __select__ = AttributeFacet.__select__ & implements('CWEType')
-    rtype = 'meta'
-
 class CWFinalFacet(AttributeFacet):
     id = 'cwfinal-facet'
-    __select__ = AttributeFacet.__select__ & implements('CWEType')
+    __select__ = AttributeFacet.__select__ & implements('CWEType', 'CWRType')
     rtype = 'final'
--- a/web/views/startup.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/startup.py	Mon Jul 06 19:55:18 2009 +0200
@@ -14,7 +14,7 @@
 
 from cubicweb.view import StartupView
 from cubicweb.selectors import match_user_groups, implements
-from cubicweb.schema import META_RELATIONS_TYPES
+from cubicweb.schema import META_RELATIONS_TYPES, display_name
 from cubicweb.common.uilib import ureport_as_html
 from cubicweb.web import ajax_replace_url, uicfg, httpcache
 from cubicweb.web.views import tabs, management, schema as schemamod
--- a/web/views/tabs.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/tabs.py	Mon Jul 06 19:55:18 2009 +0200
@@ -24,7 +24,7 @@
     """
 
     def _prepare_bindings(self, vid, reloadable):
-        self.req.html_headers.add_onload(u"""
+        self.req.add_onload(u"""
   jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
      load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
   });""" % {'event': 'load_%s' % vid, 'vid': vid,
@@ -59,7 +59,7 @@
         on dom readyness
         """
         self.req.add_js('cubicweb.lazy.js')
-        self.req.html_headers.add_onload("trigger_load('%s');" % vid)
+        self.req.add_onload("trigger_load('%s');" % vid)
 
 
 class TabsMixin(LazyViewMixin):
@@ -93,22 +93,11 @@
         return selected_tabs
 
     def render_tabs(self, tabs, default, entity=None):
-        # tabbed views do no support concatenation
-        # hence we delegate to the default tab if there is more than on entity
-        # in the result set
+        # delegate to the default tab if there is more than one entity
+        # in the result set (tabs are pretty useless there)
         if entity and len(self.rset) > 1:
             entity.view(default, w=self.w)
             return
-        # XXX (syt) fix below add been introduced at some point to fix something
-        # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean
-        # way. We must not consider form['rql'] here since it introduces some
-        # other failures on non rql queries (plain text, shortcuts,... handled by
-        # magicsearch) which has a single result whose primary view is using tabs
-        # (https://www.logilab.net/cwo/ticket/342789)
-        #rql = self.req.form.get('rql')
-        #if rql:
-        #    self.req.execute(rql).get_entity(0,0).view(default, w=self.w)
-        #    return
         self.req.add_css('tabs-no-images.css')
         self.req.add_js(('jquery.tools.min.js', 'cubicweb.htmlhelpers.js',
                          'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
@@ -145,11 +134,11 @@
         w(u'</div>')
         # call the set_tab() JS function *after* each tab is generated
         # because the callback binding needs to be done before
-        self.req.html_headers.add_onload(u"""
+        self.req.add_onload(u'''
     jQuery(function() {
       jQuery("#tabs-%(eeid)s").tabs("#panes-%(eeid)s > div", {initialIndex: %(tabindex)s});
       set_tab('%(vid)s', '%(cookiename)s');
-    });""" % {'eeid' : entity.eid,
+    });''' % {'eeid' : entity.eid,
               'vid'  : active_tab,
               'cookiename' : self.cookie_name,
               'tabindex' : tabs.index(active_tab)})
--- a/web/views/urlpublishing.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/views/urlpublishing.py	Mon Jul 06 19:55:18 2009 +0200
@@ -144,18 +144,12 @@
         <etype>[[/<attribute name>]/<attribute value>]*
     """
     priority = 2
-    def __init__(self, urlpublisher):
-        super(RestPathEvaluator, self).__init__(urlpublisher)
-        self.etype_map = {}
-        for etype in self.schema.entities():
-            etype = str(etype)
-            self.etype_map[etype.lower()] = etype
 
     def evaluate_path(self, req, parts):
         if not (0 < len(parts) < 4):
             raise PathDontMatch()
         try:
-            etype = self.etype_map[parts.pop(0).lower()]
+            etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()]
         except KeyError:
             raise PathDontMatch()
         cls = self.vreg.etype_class(etype)
--- a/web/webconfig.py	Thu Jul 02 10:36:25 2009 +0200
+++ b/web/webconfig.py	Mon Jul 06 19:55:18 2009 +0200
@@ -80,12 +80,6 @@
           'if anonymous-user is set',
           'group': 'main', 'inputlevel': 1,
           }),
-        ('allow-email-login',
-         {'type' : 'yn',
-          'default': False,
-          'help': 'allow users to login with their primary email if set',
-          'group': 'main', 'inputlevel': 2,
-          }),
         ('query-log-file',
          {'type' : 'string',
           'default': None,