backport stable to default
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 26 Feb 2010 17:39:33 +0100
changeset 4737 64143d458495
parent 4622 790181bfd19c (current diff)
parent 4736 b7749ae9572f (diff)
child 4762 8dce25da9d95
backport stable to default
embedded/README
goa/test/pytestconf.py
--- a/.hgtags	Thu Feb 18 09:22:04 2010 +0100
+++ b/.hgtags	Fri Feb 26 17:39:33 2010 +0100
@@ -100,3 +100,7 @@
 4281e1e2d76b9a37f38c0eeb1cbdcaa2fac6533c cubicweb-debian-version-3.5.12-1
 5f957e351b0a60d5c5fff60c560b04e666c3a8c6 cubicweb-version-3.6.0
 17e88f2485d1ea1fb8a3926a274637ce19e95d69 cubicweb-debian-version-3.6.0-1
+450804da3ab2476b7ede0c1f956235b4c239734f cubicweb-version-3.6.0
+d2ba93fcb8da95ceab08f48f8149a480215f149c cubicweb-debian-version-3.6.0-1
+4ae30c9ca11b1edad67d25b76fce672171d02023 cubicweb-version-3.6.1
+b9cdfe3341d1228687515d9af8686971ad5e6f5c cubicweb-debian-version-3.6.1-1
--- a/__pkginfo__.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/__pkginfo__.py	Fri Feb 26 17:39:33 2010 +0100
@@ -7,7 +7,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 6, 0)
+numversion = (3, 6, 1)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
--- a/appobject.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/appobject.py	Fri Feb 26 17:39:33 2010 +0100
@@ -13,12 +13,9 @@
 from logging import getLogger
 from warnings import warn
 
-from logilab.common.decorators import classproperty
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 
-from cubicweb import Unauthorized, NoSelectableObject
-
 
 # selector base classes and operations ########################################
 
@@ -268,7 +265,7 @@
             pdef['default'] = getattr(cls, propid, pdef['default'])
             pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
             registry.vreg.register_property(cls._cwpropkey(propid), **pdef)
-        assert callable(cls.__select__), obj
+        assert callable(cls.__select__), cls
         return cls
 
     def __init__(self, req, **extra):
--- a/cwconfig.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/cwconfig.py	Fri Feb 26 17:39:33 2010 +0100
@@ -236,7 +236,7 @@
     options = (
        ('log-threshold',
          {'type' : 'string', # XXX use a dedicated type?
-          'default': 'ERROR',
+          'default': 'WARNING',
           'help': 'server\'s log level',
           'group': 'main', 'inputlevel': 1,
           }),
--- a/cwctl.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/cwctl.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,16 +1,26 @@
-"""%%prog %s [options] %s
+"""the cubicweb-ctl tool, based on logilab.common.clcommands to
+provide a pluggable commands system.
+
 
-The CubicWeb swiss-knife.
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
 
-%s"""
-
+# *ctl module should limit the number of import to be imported as quickly as
+# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
+# completion). So import locally in command helpers.
 import sys
 from os import remove, listdir, system, pathsep
 try:
     from os import kill, getpgid
 except ImportError:
-    def kill(*args): pass
-    def getpgid(): pass
+    def kill(*args):
+        """win32 kill implementation"""
+    def getpgid():
+        """win32 getpgid implementation"""
 
 from os.path import exists, join, isfile, isdir, dirname, abspath
 
@@ -158,6 +168,79 @@
 
 # base commands ###############################################################
 
+def version_strictly_lower(a, b):
+    from logilab.common.changelog import Version
+    if a:
+        a = Version(a)
+    if b:
+        b = Version(b)
+    return a < b
+
+def max_version(a, b):
+    from logilab.common.changelog import Version
+    return str(max(Version(a), Version(b)))
+
+class ConfigurationProblem(object):
+    """Each cube has its own list of dependencies on other cubes/versions.
+
+    The ConfigurationProblem is used to record the loaded cubes, then to detect
+    inconsistencies in their dependencies.
+
+    See configuration management on wikipedia for litterature.
+    """
+
+    def __init__(self):
+        self.cubes = {}
+
+    def add_cube(self, name, info):
+        self.cubes[name] = info
+
+    def solve(self):
+        self.warnings = []
+        self.errors = []
+        self.read_constraints()
+        for cube, versions in sorted(self.constraints.items()):
+            oper, version = None, None
+            # simplify constraints
+            if versions:
+                for constraint in versions:
+                    op, ver = constraint.split()
+                    if oper is None:
+                        oper = op
+                        version = ver
+                    elif op == '>=' and oper == '>=':
+                        version = max_version(ver, version)
+                    else:
+                        print 'unable to handle this case', oper, version, op, ver
+            # "solve" constraint satisfaction problem
+            if cube not in self.cubes:
+                self.errors.append( ('add', cube, version) )
+            elif versions:
+                lower_strict = version_strictly_lower(self.cubes[cube].version, version)
+                if oper in ('>=','='):
+                    if lower_strict:
+                        self.errors.append( ('update', cube, version) )
+                else:
+                    print 'unknown operator', oper
+
+    def read_constraints(self):
+        self.constraints = {}
+        self.reverse_constraints = {}
+        for cube, info in self.cubes.items():
+            if hasattr(info,'__depends_cubes__'):
+                use = info.__depends_cubes__
+                if not isinstance(use, dict):
+                    use = dict((key, None) for key in use)
+                    self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list')
+            else:
+                self.warnings.append('cube %s should define __depends_cubes__' % cube)
+                use = dict((key, None) for key in info.__use__)
+            for name, constraint in use.items():
+                self.constraints.setdefault(name,set())
+                if constraint:
+                    self.constraints[name].add(constraint)
+                self.reverse_constraints.setdefault(name, set()).add(cube)
+
 class ListCommand(Command):
     """List configurations, cubes and instances.
 
@@ -185,6 +268,7 @@
                     continue
                 print '   ', line
         print
+        cfgpb = ConfigurationProblem()
         try:
             cubesdir = pathsep.join(cwcfg.cubes_search_path())
             namesize = max(len(x) for x in cwcfg.available_cubes())
@@ -200,6 +284,7 @@
                 try:
                     tinfo = cwcfg.cube_pkginfo(cube)
                     tversion = tinfo.version
+                    cfgpb.add_cube(cube, tinfo)
                 except ConfigurationError:
                     tinfo = None
                     tversion = '[missing cube information]'
@@ -235,7 +320,21 @@
         else:
             print 'No instance available in %s' % regdir
         print
-
+        # configuration management problem solving
+        cfgpb.solve()
+        if cfgpb.warnings:
+            print 'Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings)
+        if cfgpb.errors:
+            print 'Errors:'
+            for op, cube, version in cfgpb.errors:
+                if op == 'add':
+                    print '* cube', cube,
+                    if version:
+                        print ' version', version,
+                    print 'is not installed, but required by %s' % ' '.join(cfgpb.reverse_constraints[cube])
+                else:
+                    print '* cube %s version %s is installed, but version %s is required by (%s)' % (
+                        cube, cfgpb.cubes[cube].version, version, ', '.join(cfgpb.reverse_constraints[cube]))
 
 class CreateInstanceCommand(Command):
     """Create an instance from a cube. This is an unified
@@ -887,7 +986,12 @@
 def run(args):
     """command line tool"""
     cwcfg.load_cwctl_plugins()
-    main_run(args, __doc__)
+    main_run(args, """%%prog %s [options] %s
+
+The CubicWeb swiss-knife.
+
+%s"""
+)
 
 if __name__ == '__main__':
     run(sys.argv[1:])
--- a/cwvreg.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/cwvreg.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.common.decorators import cached, clear_cache, monkeypatch
+from logilab.common.decorators import cached, clear_cache
 from logilab.common.deprecation import  deprecated
 from logilab.common.modutils import cleanup_sys_modules
 
--- a/dbapi.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/dbapi.py	Fri Feb 26 17:39:33 2010 +0100
@@ -27,15 +27,10 @@
 
 def _fake_property_value(self, name):
     try:
-        return super(dbapi.DBAPIRequest, self).property_value(name)
+        return super(DBAPIRequest, self).property_value(name)
     except KeyError:
         return ''
 
-def _fix_cls_attrs(reg, appobject):
-    appobject.vreg = reg.vreg
-    appobject.schema = reg.schema
-    appobject.config = reg.config
-
 def multiple_connections_fix():
     """some monkey patching necessary when an application has to deal with
     several connections to different repositories. It tries to hide buggy class
@@ -43,21 +38,6 @@
     registries.
     """
     defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
-    orig_select_best = defaultcls.orig_select_best = defaultcls._select_best
-    @monkeypatch(defaultcls)
-    def _select_best(self, appobjects, *args, **kwargs):
-        """return an instance of the most specific object according
-        to parameters
-
-        raise NoSelectableObject if no object apply
-        """
-        for appobjectcls in appobjects:
-            _fix_cls_attrs(self, appobjectcls)
-        selected = orig_select_best(self, appobjects, *args, **kwargs)
-        # redo the same thing on the instance so it won't use equivalent class
-        # attributes (which may change)
-        _fix_cls_attrs(self, selected)
-        return selected
 
     etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes']
     orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class
@@ -74,8 +54,6 @@
         return usercls
 
 def multiple_connections_unfix():
-    defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
-    defaultcls.select_best = defaultcls.orig_select_best
     etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes']
     etypescls.etype_class = etypescls.orig_etype_class
 
@@ -222,7 +200,7 @@
         except KeyError:
             # this occurs usually during test execution
             self._ = self.__ = unicode
-            self.pgettext = lambda x,y: y
+            self.pgettext = lambda x, y: y
         self.debug('request default language: %s', self.lang)
 
     def decorate_rset(self, rset):
--- a/debian/changelog	Thu Feb 18 09:22:04 2010 +0100
+++ b/debian/changelog	Fri Feb 26 17:39:33 2010 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.6.1-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 26 Feb 2010 14:14:01 +0100
+
 cubicweb (3.6.0-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Thu Feb 18 09:22:04 2010 +0100
+++ b/debian/control	Fri Feb 26 17:39:33 2010 +0100
@@ -77,7 +77,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.48.1), python-yams (>= 0.27.0), python-rql (>= 0.24.0), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.48.1), python-yams (>= 0.28.0), python-rql (>= 0.24.0), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/dataimport.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/dataimport.py	Fri Feb 26 17:39:33 2010 +0100
@@ -152,7 +152,7 @@
 
 def confirm(question):
     """A confirm function that asks for yes/no/abort and exits on abort."""
-    answer = shellutils.ASK.ask(question, ('Y','n','abort'), 'Y')
+    answer = shellutils.ASK.ask(question, ('Y', 'n', 'abort'), 'Y')
     if answer == 'abort':
         sys.exit(1)
     return answer == 'Y'
@@ -275,11 +275,12 @@
 
 def check_doubles(buckets):
     """Extract the keys that have more than one item in their bucket."""
-    return [(key, len(value)) for key,value in buckets.items() if len(value) > 1]
+    return [(k, len(v)) for k, v in buckets.items() if len(v) > 1]
 
 def check_doubles_not_none(buckets):
     """Extract the keys that have more than one item in their bucket."""
-    return [(key, len(value)) for key,value in buckets.items() if key is not None and len(value) > 1]
+    return [(k, len(v)) for k, v in buckets.items()
+            if k is not None and len(v) > 1]
 
 
 # object stores #################################################################
@@ -430,12 +431,14 @@
         return entity
 
     def _put(self, type, item):
-        query = ('INSERT %s X: ' % type) + ', '.join(['X %s %%(%s)s' % (key,key) for key in item])
+        query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
+                                                     for k in item)
         return self.rql(query, item)[0][0]
 
     def relate(self, eid_from, rtype, eid_to):
         # if reverse relation is found, eids are exchanged
-        eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(eid_from, rtype, eid_to)
+        eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
+            eid_from, rtype, eid_to)
         self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
                   {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
 
@@ -513,7 +516,7 @@
                   % (len(self.store.eids), len(self.store.types),
                      len(self.store.relations), nberrors))
         if self.errors:
-            if self.askerror==2 or (self.askerror and confirm('Display errors ?')):
+            if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
                 from pprint import pformat
                 for errkey, error in self.errors.items():
                     self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
--- a/devtools/devctl.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/devctl.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,32 +8,23 @@
 """
 __docformat__ = "restructuredtext en"
 
+# *ctl module should limit the number of import to be imported as quickly as
+# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
+# completion). So import locally in command helpers.
 import sys
 from datetime import datetime
-from os import mkdir, chdir, getcwd
+from os import mkdir, chdir
 from os.path import join, exists, abspath, basename, normpath, split, isdir
-from copy import deepcopy
 from warnings import warn
-from tempfile import NamedTemporaryFile
-from subprocess import Popen
 
 from logilab.common import STD_BLACKLIST
-from logilab.common.modutils import get_module_files
-from logilab.common.textutils import splitstrip
-from logilab.common.shellutils import ASK
 from logilab.common.clcommands import register_commands, pop_arg
 
-from yams import schema2dot
-
 from cubicweb.__pkginfo__ import version as cubicwebversion
 from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
 from cubicweb.toolsutils import Command, copy_skeleton, underline_title
-from cubicweb.schema import CONSTRAINTS
 from cubicweb.web.webconfig import WebConfiguration
 from cubicweb.server.serverconfig import ServerConfiguration
-from yams import BASE_TYPES
-from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
-                             WORKFLOW_TYPES, INTERNAL_TYPES)
 
 
 class DevConfiguration(ServerConfiguration, WebConfiguration):
@@ -110,13 +101,14 @@
     # set_schema triggers objects registrations
     vreg.set_schema(schema)
     w(DEFAULT_POT_HEAD)
-    _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube)
+    _generate_schema_pot(w, vreg, schema, libconfig=libconfig)
 
 
-def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
+def _generate_schema_pot(w, vreg, schema, libconfig=None):
+    from copy import deepcopy
     from cubicweb.i18n import add_msg
     from cubicweb.web import uicfg
-    from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES
+    from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES, CONSTRAINTS
     no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
     w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
     w('# \n')
@@ -221,7 +213,7 @@
         add_msg(w, objid)
 
 
-def _iter_vreg_objids(vreg, done, prefix=None):
+def _iter_vreg_objids(vreg, done):
     for reg, objdict in vreg.items():
         for objects in objdict.values():
             for obj in objects:
@@ -238,21 +230,6 @@
                     break
 
 
-def defined_in_library(etype, rtype, tetype, role):
-    """return true if the given relation definition exists in cubicweb's library
-    """
-    if libschema is None:
-        return False
-    if role == 'subject':
-        subjtype, objtype = etype, tetype
-    else:
-        subjtype, objtype = tetype, etype
-    try:
-        return libschema.rschema(rtype).has_rdef(subjtype, objtype)
-    except KeyError:
-        return False
-
-
 LANGS = ('en', 'fr', 'es')
 I18NDIR = join(BASEDIR, 'i18n')
 DEFAULT_POT_HEAD = r'''msgid ""
@@ -287,6 +264,7 @@
         import yams
         from logilab.common.fileutils import ensure_fs_mode
         from logilab.common.shellutils import globfind, find, rm
+        from logilab.common.modutils import get_module_files
         from cubicweb.i18n import extract_from_tal, execute
         tempdir = tempfile.mkdtemp()
         potfiles = [join(I18NDIR, 'static-messages.pot')]
@@ -501,6 +479,7 @@
 
 
     def run(self, args):
+        from logilab.common.shellutils import ASK
         if len(args) != 1:
             raise BadCommandUsage("exactly one argument (cube name) is expected")
         cubename, = args
@@ -558,6 +537,8 @@
         copy_skeleton(skeldir, cubedir, context)
 
     def _ask_for_dependancies(self):
+        from logilab.common.shellutils import ASK
+        from logilab.common.textutils import splitstrip
         includes = []
         for stdtype in ServerConfiguration.available_cubes():
             answer = ASK.ask("Depends on cube %s? " % stdtype,
@@ -575,17 +556,16 @@
 class ExamineLogCommand(Command):
     """Examine a rql log file.
 
-    usage: python exlog.py < rql.log
-
     will print out the following table
 
       total execution time || number of occurences || rql query
 
     sorted by descending total execution time
 
-    chances are the lines at the top are the ones that will bring
-    the higher benefit after optimisation. Start there.
+    chances are the lines at the top are the ones that will bring the higher
+    benefit after optimisation. Start there.
     """
+    arguments = '< rql.log'
     name = 'exlog'
     options = (
         )
@@ -662,7 +642,12 @@
               ]
 
     def run(self, args):
+        from subprocess import Popen
+        from tempfile import NamedTemporaryFile
         from logilab.common.textutils import splitstrip
+        from yams import schema2dot, BASE_TYPES
+        from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
+                                     WORKFLOW_TYPES, INTERNAL_TYPES)
         cubes = splitstrip(pop_arg(args, 1))
         dev_conf = DevConfiguration(*cubes)
         schema = dev_conf.load_schema()
--- a/devtools/fill.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/fill.py	Fri Feb 26 17:39:33 2010 +0100
@@ -142,7 +142,7 @@
 
     def generate_date(self, entity, attrname, index):
         """generates a random date (format is 'yyyy-mm-dd')"""
-        base = date(randint(2000, 2004), randint(1, 12), randint(1, 28))
+        base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365))
         return self._constrained_generate(entity, attrname, base, timedelta(days=1), index)
 
     def generate_time(self, entity, attrname, index):
--- a/devtools/realdbtest.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/realdbtest.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,4 +1,3 @@
-import logging
 from cubicweb import toolsutils
 from cubicweb.devtools import DEFAULT_SOURCES, BaseApptestConfiguration
 
--- a/devtools/repotest.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/repotest.py	Fri Feb 26 17:39:33 2010 +0100
@@ -191,15 +191,14 @@
         rqlst.solutions = remove_unused_solutions(rqlst, rqlst.solutions, {}, self.repo.schema)[0]
         return rqlst
 
-    def _user_session(self, groups=('guests',), ueid=None):
+    def user_groups_session(self, *groups):
+        """lightweight session using the current user with hi-jacked groups"""
         # use self.session.user.eid to get correct owned_by relation, unless explicit eid
-        if ueid is None:
-            ueid = self.session.user.eid
-        u = self.repo._build_user(self.session, ueid)
+        u = self.repo._build_user(self.session, self.session.user.eid)
         u._groups = set(groups)
         s = Session(u, self.repo)
         s._threaddata.pool = self.pool
-        return u, s
+        return s
 
     def execute(self, rql, args=None, eid_key=None, build_descr=True):
         return self.o.execute(self.session, rql, args, eid_key, build_descr)
--- a/devtools/test/unittest_dbfill.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/test/unittest_dbfill.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,6 +9,7 @@
 
 import os.path as osp
 import re
+import datetime
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -41,7 +42,6 @@
     def _available_Person_firstname(self, etype, attrname):
         return [f.strip() for f in file(osp.join(DATADIR, 'firstnames.txt'))]
 
-
     def setUp(self):
         config = ApptestConfiguration('data')
         config.bootstrap_cubes()
@@ -52,17 +52,6 @@
         self.bug_valgen = MyValueGenerator(e_schema)
         self.config = config
 
-    def _check_date(self, date):
-        """checks that 'date' is well-formed"""
-        year = date.year
-        month = date.month
-        day = date.day
-        self.failUnless(day in range(1, 29), '%s not in [0;28]' % day)
-        self.failUnless(month in range(1, 13), '%s not in [1;12]' % month)
-        self.failUnless(year in range(2000, 2005),
-                        '%s not in [2000;2004]' % year)
-
-
     def test_string(self):
         """test string generation"""
         surname = self.person_valgen.generate_attribute_value({}, 'surname', 12)
@@ -92,15 +81,14 @@
     def test_date(self):
         """test date generation"""
         # Test for random index
-        for index in range(5):
+        for index in range(10):
             date_value = self.person_valgen.generate_attribute_value({}, 'birthday', index)
-            self._check_date(date_value)
+            self.failUnless(isinstance(date_value, datetime.date))
 
     def test_phone(self):
         """tests make_tel utility"""
         self.assertEquals(make_tel(22030405), '22 03 04 05')
 
-
     def test_customized_generation(self):
         self.assertEquals(self.bug_valgen.generate_attribute_value({}, 'severity', 12),
                           u'dangerous')
@@ -110,7 +98,6 @@
                           u'yo')
 
 
-
 class ConstraintInsertionTC(TestCase):
 
     def test_writeme(self):
--- a/devtools/testlib.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/devtools/testlib.py	Fri Feb 26 17:39:33 2010 +0100
@@ -66,24 +66,7 @@
     return set(schema.entities()) - protected_entities
 
 
-def get_versions(self, checkversions=False):
-    """return the a dictionary containing cubes used by this instance
-    as key with their version as value, including cubicweb version. This is a
-    public method, not requiring a session id.
-
-    replace Repository.get_versions by this method if you don't want versions
-    checking
-    """
-    vcconf = {'cubicweb': self.config.cubicweb_version()}
-    self.config.bootstrap_cubes()
-    for pk in self.config.cubes():
-        version = self.config.cube_version(pk)
-        vcconf[pk] = version
-    self.config._cubes = None
-    return vcconf
-
-
-def refresh_repo(repo):
+def refresh_repo(repo, resetschema=False, resetvreg=False):
     devtools.reset_test_database(repo.config)
     for pool in repo.pools:
         pool.reconnect()
@@ -92,6 +75,8 @@
     repo.querier._rql_cache = {}
     for source in repo.sources:
         source.reset_caches()
+    if resetschema:
+        repo.set_schema(repo.config.load_schema(), resetvreg=resetvreg)
 
 
 # email handling, to test emails sent by an application ########################
@@ -160,6 +145,7 @@
     """
     appid = 'data'
     configcls = devtools.ApptestConfiguration
+    reset_schema = reset_vreg = False # reset schema / vreg between tests
 
     @classproperty
     def config(cls):
@@ -230,7 +216,7 @@
 
     @classmethod
     def _refresh_repo(cls):
-        refresh_repo(cls.repo)
+        refresh_repo(cls.repo, cls.reset_schema, cls.reset_vreg)
 
     # global resources accessors ###############################################
 
@@ -488,7 +474,7 @@
         Redirect exception
         """
         try:
-            res = callback(req)
+            callback(req)
         except Redirect, ex:
             try:
                 path, params = ex.location.split('?', 1)
--- a/embedded/README	Thu Feb 18 09:22:04 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-This directory contains extra libraries which are needed
-to make cubicweb work.
-
-The mx.DateTime python implementation is directly taken from
-the mx.DateTime distribution. The only modification is the
-strptime function which has been mocked using the standard
-``datetime`` module. (as provided by the python2.5's stdlib)
--- a/entities/test/unittest_wfobjs.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/entities/test/unittest_wfobjs.py	Fri Feb 26 17:39:33 2010 +0100
@@ -98,7 +98,7 @@
         trs = list(user.possible_transitions())
         self.assertEquals(len(trs), 1)
         self.assertEquals(trs[0].name, u'deactivate')
-        self.assertEquals(trs[0].destination().name, u'deactivated')
+        self.assertEquals(trs[0].destination(None).name, u'deactivated')
         # test a std user get no possible transition
         cnx = self.login('member')
         # fetch the entity using the new session
@@ -141,6 +141,27 @@
         trinfo = self._test_manager_deactivate(user)
         self.assertEquals(trinfo.transition.name, 'deactivate')
 
+    def test_goback_transition(self):
+        wf = self.session.user.current_workflow
+        asleep = wf.add_state('asleep')
+        wf.add_transition('rest', (wf.state_by_name('activated'), wf.state_by_name('deactivated')),
+                               asleep)
+        wf.add_transition('wake up', asleep)
+        user = self.create_user('stduser')
+        user.fire_transition('rest')
+        self.commit()
+        user.fire_transition('wake up')
+        self.commit()
+        self.assertEquals(user.state, 'activated')
+        user.fire_transition('deactivate')
+        self.commit()
+        user.fire_transition('rest')
+        self.commit()
+        user.fire_transition('wake up')
+        self.commit()
+        user.clear_all_caches()
+        self.assertEquals(user.state, 'deactivated')
+
     # XXX test managers can change state without matching transition
 
     def _test_stduser_deactivate(self):
@@ -207,7 +228,7 @@
         state3 = mwf.add_state(u'state3')
         swftr1 = mwf.add_wftransition(u'swftr1', swf, state1,
                                       [(swfstate2, state2), (swfstate3, state3)])
-        self.assertEquals(swftr1.destination().eid, swfstate1.eid)
+        self.assertEquals(swftr1.destination(None).eid, swfstate1.eid)
         # workflows built, begin test
         self.group = self.request().create_entity('CWGroup', name=u'grp1')
         self.commit()
--- a/entities/wfobjs.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/entities/wfobjs.py	Fri Feb 26 17:39:33 2010 +0100
@@ -123,7 +123,7 @@
             self._cw.execute('SET S allowed_transition T '
                              'WHERE S eid %(s)s, T eid %(t)s',
                              {'s': state, 't': tr.eid}, ('s', 't'))
-        tr.set_transition_permissions(requiredgroups, conditions, reset=False)
+        tr.set_permissions(requiredgroups, conditions, reset=False)
         return tr
 
     def add_transition(self, name, fromstates, tostate=None,
@@ -161,9 +161,9 @@
         execute = self._cw.unsafe_execute
         execute('SET X in_state S WHERE S eid %(s)s', {'s': todelstate.eid}, 's')
         execute('SET X from_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s',
-                {'os': todelstate.eid, 'ns': newstate.eid}, 's')
+                {'os': todelstate.eid, 'ns': replacement.eid}, 's')
         execute('SET X to_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s',
-                {'os': todelstate.eid, 'ns': newstate.eid}, 's')
+                {'os': todelstate.eid, 'ns': replacement.eid}, 's')
         todelstate.delete()
 
 
@@ -219,10 +219,9 @@
         """
         if self.transition_of:
             return self.transition_of[0].rest_path(), {}
-        return super(Transition, self).after_deletion_path()
+        return super(BaseTransition, self).after_deletion_path()
 
-    def set_transition_permissions(self, requiredgroups=(), conditions=(),
-                                   reset=True):
+    def set_permissions(self, requiredgroups=(), conditions=(), reset=True):
         """set or add (if `reset` is False) groups and conditions for this
         transition
         """
@@ -251,13 +250,30 @@
                              'T condition X WHERE T eid %(x)s',kwargs, 'x')
         # XXX clear caches?
 
+    @deprecated('[3.6.1] use set_permission')
+    def set_transition_permissions(self, requiredgroups=(), conditions=(),
+                                   reset=True):
+        return self.set_permissions(requiredgroups, conditions, reset)
+
 
 class Transition(BaseTransition):
     """customized class for Transition entities"""
     __regid__ = 'Transition'
 
-    def destination(self):
-        return self.destination_state[0]
+    def destination(self, entity):
+        try:
+            return self.destination_state[0]
+        except IndexError:
+            return entity.latest_trinfo().previous_state
+
+    def potential_destinations(self):
+        try:
+            yield self.destination_state[0]
+        except IndexError:
+            for incomingstate in self.reverse_allowed_transition:
+                for tr in incomingstate.reverse_destination_state:
+                    for previousstate in tr.reverse_allowed_transition:
+                        yield previousstate
 
     def parent(self):
         return self.workflow
@@ -271,9 +287,12 @@
     def subwf(self):
         return self.subworkflow[0]
 
-    def destination(self):
+    def destination(self, entity):
         return self.subwf.initial
 
+    def potential_destinations(self):
+        yield self.subwf.initial
+
     def add_exit_point(self, fromstate, tostate):
         if hasattr(fromstate, 'eid'):
             fromstate = fromstate.eid
@@ -311,7 +330,7 @@
         return result
 
     def clear_all_caches(self):
-        super(WorkflowableMixIn, self).clear_all_caches()
+        super(WorkflowTransition, self).clear_all_caches()
         clear_cache(self, 'exit_points')
 
 
--- a/entity.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/entity.py	Fri Feb 26 17:39:33 2010 +0100
@@ -12,10 +12,8 @@
 from logilab.common import interface
 from logilab.common.compat import all
 from logilab.common.decorators import cached
-from logilab.common.deprecation import deprecated
 from logilab.mtconverter import TransformData, TransformError, xml_escape
 
-from rql import parse
 from rql.utils import rqlvar_maker
 
 from cubicweb import Unauthorized, typed_eid
@@ -638,8 +636,17 @@
                 rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
                                        rql.split(' WHERE ', 1)[1])
         elif not ' ORDERBY ' in rql:
-            args = tuple(rql.split(' WHERE ', 1))
-            rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
+            args = rql.split(' WHERE ', 1)
+            # if modification_date already retreived, we should use it instead
+            # of adding another variable for sort. This should be be problematic
+            # but it's actually with sqlserver, see ticket #694445
+            if 'X modification_date ' in args[1]:
+                var = args[1].split('X modification_date ', 1)[1].split(',', 1)[0]
+                args.insert(1, var.strip())
+                rql = '%s ORDERBY %s DESC WHERE %s' % tuple(args)
+            else:
+                rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % \
+                      tuple(args)
         return rql
 
     # generic vocabulary methods ##############################################
--- a/etwist/service.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/etwist/service.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,6 +1,4 @@
 import os
-import os.path as osp
-import sys
 
 import win32serviceutil
 import win32service
--- a/etwist/twconfig.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/etwist/twconfig.py	Fri Feb 26 17:39:33 2010 +0100
@@ -16,7 +16,9 @@
 
 from os.path import join
 
-from cubicweb.web.webconfig import WebConfiguration, merge_options, Method
+from logilab.common.configuration import Method
+
+from cubicweb.web.webconfig import WebConfiguration, merge_options
 
 class TwistedConfiguration(WebConfiguration):
     """web instance (in a twisted web server) client of a RQL server"""
--- a/etwist/twctl.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/etwist/twctl.py	Fri Feb 26 17:39:33 2010 +0100
@@ -6,8 +6,6 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
-import sys
-
 from cubicweb.toolsutils import CommandHandler
 from cubicweb.web.webctl import WebCreateHandler
 
--- a/ext/rest.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/ext/rest.py	Fri Feb 26 17:39:33 2010 +0100
@@ -147,7 +147,7 @@
 
 try:
     from pygments import highlight
-    from pygments.lexers import get_lexer_by_name, LEXERS
+    from pygments.lexers import get_lexer_by_name
     from pygments.formatters import HtmlFormatter
 except ImportError:
     pygments_directive = None
--- a/ext/xhtml2fo.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/ext/xhtml2fo.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,4 +1,4 @@
-from xml.etree.ElementTree import QName, fromstring
+from xml.etree.ElementTree import QName
 from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer
 from pysixt.utils.xslfo.standard import cm
 from pysixt.utils.xslfo import SimplePageMaster
@@ -64,7 +64,7 @@
                              u'bottom': self.page_bmargin*cm,
                              u'left'  : self.page_lmargin*cm,
                              u'right' : self.page_rmargin*cm })
-        pm.add_peripheral_region(u"end",self.hf_height)
+        pm.add_peripheral_region(u"end", self.hf_height)
         dims = {}
         dims[u"bottom"] = self.hf_height + 0.25
         pm.set_main_region_margins(dims)
@@ -82,41 +82,39 @@
         props = { u"force-page-count": u"no-force",
                   u"initial-page-number": u"1",
                   u"format": u"1", }
-        self._output_properties(ps,props)
+        self._output_properties(ps, props)
 
         sc = self.create_staticcontent(ps, u"end")
         sc_bl = self.create_block(sc)
         attrs = { u"hyphenate": u"false", }
-        attrs[u"font-size"] = u"%.1fpt" %(self.font_size*0.7)
+        attrs[u"font-size"] = u"%.1fpt" % (self.font_size * 0.7)
         attrs[u"language"] = self.lang
         attrs[u"text-align"] = u"center"
-        self._output_properties(sc_bl,attrs)
+        self._output_properties(sc_bl, attrs)
         sc_bl.text = u"Page" + u" " # ### Should be localised!
         pn = self.create_pagenumber(sc_bl)
         pn.tail = u"/"
-        lpn = self.create_pagenumbercitation( sc_bl,
-                                              u"last-block-of-report-%d" % params[u"context_pos"]
-                                              )
+        self.create_pagenumbercitation(
+            sc_bl, u"last-block-of-report-%d" % params[u"context_pos"])
 
-
-        fl = self.create_flow(ps,u"body")
+        fl = self.create_flow(ps, u"body")
         bl = self.create_block(fl)
 
         # Sets on the highest block element the properties of the XHTML body
         # element. These properties (at the least the inheritable ones) will
         # be inherited by all the future FO elements.
-        bodies = list(self.in_tree.getiterator(QName(XHTML_NS,u"body")))
+        bodies = list(self.in_tree.getiterator(QName(XHTML_NS, u"body")))
         if len(bodies) > 0:
             attrs = self._extract_properties([bodies[0]])
         else:
             attrs = default_styles[u"body"].copy()
-        attrs[u"font-size"] = u"%.1fpt" %self.font_size
+        attrs[u"font-size"] = u"%.1fpt" % self.font_size
         attrs[u"language"] = self.lang
         self._output_properties(bl,attrs)
 
         # Processes the report content
-        self._copy_text(in_elt,bl)
-        self._process_nodes(in_elt.getchildren(),bl)
+        self._copy_text(in_elt, bl)
+        self._process_nodes(in_elt.getchildren(), bl)
 
         # Inserts an empty block at the end of the report in order to be able
         # to compute the last page number of this report.
@@ -130,7 +128,7 @@
         """
         Visit function called when starting the process of the input tree.
         """
-        content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS,u"div"))
+        content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS, u"div"))
                     if d.get(u"id") == self.section ]
         # Asks the process of the report elements with a specific visit
         # function
--- a/goa/goactl.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/goa/goactl.py	Fri Feb 26 17:39:33 2010 +0100
@@ -31,7 +31,6 @@
     (docutils.__path__[0], 'docutils'),
     (roman.__file__.replace('.pyc', '.py'), 'roman.py'),
 
-    (join(CW_SOFTWARE_ROOT, 'embedded', 'mx'), 'mx'),
     ('/usr/share/fckeditor/', 'fckeditor'),
 
     (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')),
--- a/goa/test/pytestconf.py	Thu Feb 18 09:22:04 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-"""this pytestconf automatically adds the mx's python version in the PYTHONPATH
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-import sys
-import os.path as osp
-
-import cubicweb
-# remove 'mx' modules imported by cubicweb
-for modname in sys.modules.keys():
-    if modname.startswith('mx'):
-        sys.modules.pop(modname)
-
-# this is where mx should get imported from
-mxpath = osp.abspath(osp.join(osp.dirname(cubicweb.__file__), 'embedded'))
-sys.path.insert(1, mxpath)
-
-# make sure the correct mx is imported
-import mx
-assert osp.dirname(mx.__file__) == osp.join(mxpath, 'mx'), '%s != %s' % (osp.dirname(mx.__file__), mxpath)
--- a/hooks/email.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/email.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,7 +8,6 @@
 __docformat__ = "restructuredtext en"
 
 from cubicweb.server import hook
-from cubicweb.server.repository import ensure_card_respected
 
 from logilab.common.compat import any
 
@@ -27,11 +26,6 @@
 
     def precommit_event(self):
         if self.condition():
-            # we've to handle cardinaly by ourselves since we're using unsafe_execute
-            # but use session.execute and not session.unsafe_execute to check we
-            # can change the relation
-            ensure_card_respected(self.session.execute, self.session,
-                                  self.entity.eid, self.rtype, self.email.eid)
             self.session.unsafe_execute(
                 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
                 {'x': self.entity.eid, 'y': self.email.eid}, 'x')
--- a/hooks/notification.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/notification.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,6 @@
 
 from logilab.common.textutils import normalize_text
 
-from cubicweb import RegistryException
 from cubicweb.selectors import implements
 from cubicweb.server import hook
 from cubicweb.sobjects.supervising import SupervisionMailOp
@@ -30,8 +29,8 @@
     category = 'notification'
 
     def select_view(self, vid, rset, row=0, col=0):
-        return self._cw.vreg['views'].select_or_none(vid, self._cw,
-                                                     rset=rset, row=0, col=0)
+        return self._cw.vreg['views'].select_or_none(vid, self._cw, rset=rset,
+                                                     row=row, col=col)
 
 
 class StatusChangeHook(NotificationHook):
--- a/hooks/storages.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/storages.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,7 +1,5 @@
 """hooks to handle attributes mapped to a custom storage
 """
-from os import unlink
-
 from cubicweb.server.hook import Hook
 from cubicweb.server.sources.storages import ETYPE_ATTR_STORAGE
 
@@ -16,30 +14,28 @@
     """"""
     __regid__ = 'bfss_add_entity'
     events = ('before_add_entity', )
-    #__select__ = Hook.__select__ & implements('Repository')
 
     def __call__(self):
-        for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
-            fpath = ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_added(self.entity, attr)
-            if fpath is not None:
-                AddFileOp(filepath=fpath)
+        etype = self.entity.__regid__
+        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
+            ETYPE_ATTR_STORAGE[etype][attr].entity_added(self.entity, attr)
 
 class PreUpdateEntityHook(BFSSHook):
     """"""
     __regid__ = 'bfss_update_entity'
     events = ('before_update_entity', )
-    #__select__ = Hook.__select__ & implements('Repository')
 
     def __call__(self):
-        for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
-            ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_updated(self.entity, attr)
+        etype = self.entity.__regid__
+        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
+            ETYPE_ATTR_STORAGE[etype][attr].entity_updated(self.entity, attr)
 
 class PreDeleteEntityHook(BFSSHook):
     """"""
     __regid__ = 'bfss_delete_entity'
     events = ('before_delete_entity', )
-    #__select__ = Hook.__select__ & implements('Repository')
 
     def __call__(self):
-        for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
-            ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_deleted(self.entity, attr)
+        etype = self.entity.__regid__
+        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
+            ETYPE_ATTR_STORAGE[etype][attr].entity_deleted(self.entity, attr)
--- a/hooks/syncschema.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/syncschema.py	Fri Feb 26 17:39:33 2010 +0100
@@ -18,7 +18,7 @@
 
 from logilab.common.decorators import clear_cache
 
-from cubicweb import ValidationError, RepositoryError
+from cubicweb import ValidationError
 from cubicweb.selectors import implements
 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS, display_name
 from cubicweb.server import hook, schemaserial as ss
@@ -85,6 +85,7 @@
     session.transaction_data.setdefault('createdattrs', []).append(
         '%s.%s' % (etype, rtype))
 
+
 def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
     errors = {}
     # don't use getattr(entity, attr), we would get the modified value if any
@@ -418,7 +419,7 @@
         rdef = self.init_rdef(composite=entity.composite)
         schema = session.vreg.schema
         rtype = rdef.name
-        rschema = session.vreg.schema.rschema(rtype)
+        rschema = schema.rschema(rtype)
         # this have to be done before permissions setting
         if rschema.inlined:
             # need to add a column if the relation is inlined and if this is the
@@ -440,15 +441,15 @@
             if not (rschema.subjects() or
                     rtype in session.transaction_data.get('createdtables', ())):
                 try:
-                    rschema = session.vreg.schema.rschema(rtype)
+                    rschema = schema.rschema(rtype)
                     tablesql = rschema2sql(rschema)
                 except KeyError:
                     # fake we add it to the schema now to get a correctly
                     # initialized schema but remove it before doing anything
                     # more dangerous...
-                    rschema = session.vreg.schema.add_relation_type(rdef)
+                    rschema = schema.add_relation_type(rdef)
                     tablesql = rschema2sql(rschema)
-                    session.vreg.schema.del_relation_type(rtype)
+                    schema.del_relation_type(rtype)
                 # create the necessary table
                 for sql in tablesql.split(';'):
                     if sql.strip():
@@ -485,6 +486,10 @@
             sql = adbh.sql_set_null_allowed(table, column, coltype,
                                             self.values['cardinality'][0] != '1')
             self.session.system_sql(sql)
+        if 'fulltextindexed' in self.values:
+            UpdateFTIndexOp(self.session)
+            self.session.transaction_data.setdefault('fti_update_etypes',
+                                                     set()).add(etype)
 
 
 class SourceDbCWConstraintAdd(hook.Operation):
@@ -690,7 +695,7 @@
             erschema = self.session.vreg.schema.schema_by_eid(self.eid)
         except KeyError:
             # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.eid)
+            self.warning('no schema for %s', self.eid)
             return
         perms = list(erschema.action_permissions(self.action))
         if hasattr(self, 'group_eid'):
@@ -717,7 +722,7 @@
             erschema = self.session.vreg.schema.schema_by_eid(self.eid)
         except KeyError:
             # duh, schema not found, log error and skip operation
-            self.error('no schema for %s', self.eid)
+            self.warning('no schema for %s', self.eid)
             return
         if isinstance(erschema, RelationSchema): # XXX 3.6 migration
             return
@@ -935,18 +940,6 @@
             SourceDbCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues,
                                   entity=entity)
 
-def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
-    errors = {}
-    # don't use getattr(entity, attr), we would get the modified value if any
-    for attr in ro_attrs:
-        if attr in entity.edited_attributes:
-            origval, newval = hook.entity_oldnewvalue(entity, attr)
-            if newval != origval:
-                errors[attr] = session._("can't change the %s attribute") % \
-                               display_name(session, attr)
-    if errors:
-        raise ValidationError(entity.eid, errors)
-
 
 class AfterDelRelationTypeHook(SyncSchemaHook):
     """before deleting a CWAttribute or CWRelation entity:
@@ -1135,6 +1128,41 @@
                                    expr=expr)
 
 
+
+class UpdateFTIndexOp(hook.SingleLastOperation):
+    """operation to update full text indexation of entity whose schema change
+
+    We wait after the commit to as the schema in memory is only updated after the commit.
+    """
+
+    def postcommit_event(self):
+        session = self.session
+        source = session.repo.system_source
+        to_reindex = session.transaction_data.get('fti_update_etypes', ())
+        self.info('%i etypes need full text indexed reindexation',
+                  len(to_reindex))
+        schema = self.session.repo.vreg.schema
+        for etype in to_reindex:
+            rset = session.execute('Any X WHERE X is %s' % etype)
+            self.info('Reindexing full text index for %i entity of type %s',
+                      len(rset), etype)
+            still_fti = list(schema[etype].indexable_attributes())
+            for entity in rset.entities():
+                try:
+                    source.fti_unindex_entity(session, entity.eid)
+                    for container in entity.fti_containers():
+                        if still_fti or container is not entity:
+                            session.repo.index_entity(session, container)
+                except Exception:
+                    self.critical('Error while updating Full Text Index for'
+                                  ' entity %s', entity.eid, exc_info=True)
+        if len(to_reindex):
+            # Transaction have already been committed
+            session.pool.commit()
+
+
+
+
 # specializes synchronization hooks ############################################
 
 
--- a/hooks/test/unittest_hooks.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/test/unittest_hooks.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,22 +9,8 @@
 
 from datetime import datetime
 
-from cubicweb import (ConnectionError, ValidationError, AuthenticationError,
-                      BadConnectionId)
-from cubicweb.devtools.testlib import CubicWebTC, get_versions
-
-from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.repository import Repository
-
-orig_get_versions = Repository.get_versions
-
-def setup_module(*args):
-    Repository.get_versions = get_versions
-
-def teardown_module(*args):
-    Repository.get_versions = orig_get_versions
-
-
+from cubicweb import ValidationError, AuthenticationError, BadConnectionId
+from cubicweb.devtools.testlib import CubicWebTC
 
 class CoreHooksTC(CubicWebTC):
 
@@ -270,238 +256,5 @@
             self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
 
 
-class SchemaModificationHooksTC(CubicWebTC):
-
-    @classmethod
-    def init_config(cls, config):
-        super(SchemaModificationHooksTC, cls).init_config(config)
-        config._cubes = None
-        cls.repo.fill_schema()
-
-    def index_exists(self, etype, attr, unique=False):
-        self.session.set_pool()
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
-
-    def _set_perms(self, eid):
-        self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup',
-                     {'x': eid}, 'x')
-        self.execute('SET X add_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"',
-                     {'x': eid}, 'x')
-        self.execute('SET X delete_permission G WHERE X eid %(x)s, G is CWGroup, G name "owners"',
-                     {'x': eid}, 'x')
-
-    def test_base(self):
-        schema = self.repo.schema
-        self.session.set_pool()
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-        # schema should be update on insertion (after commit)
-        eeid = self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')[0][0]
-        self._set_perms(eeid)
-        self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symmetric FALSE')
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-        # have to commit before adding definition relations
-        self.commit()
-        self.failUnless(schema.has_entity('Societe2'))
-        self.failUnless(schema.has_relation('concerne2'))
-        attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", '
-                               '   X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
-                               'WHERE RT name "name", E name "Societe2", F name "String"')[0][0]
-        self._set_perms(attreid)
-        concerne2_rdef_eid = self.execute(
-            'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
-            'WHERE RT name "concerne2", E name "Societe2"')[0][0]
-        self._set_perms(concerne2_rdef_eid)
-        self.failIf('name' in schema['Societe2'].subject_relations())
-        self.failIf('concerne2' in schema['Societe2'].subject_relations())
-        self.failIf(self.index_exists('Societe2', 'name'))
-        self.commit()
-        self.failUnless('name' in schema['Societe2'].subject_relations())
-        self.failUnless('concerne2' in schema['Societe2'].subject_relations())
-        self.failUnless(self.index_exists('Societe2', 'name'))
-        # now we should be able to insert and query Societe2
-        s2eid = self.execute('INSERT Societe2 X: X name "logilab"')[0][0]
-        self.execute('Societe2 X WHERE X name "logilab"')
-        self.execute('SET X concerne2 X WHERE X name "logilab"')
-        rset = self.execute('Any X WHERE X concerne2 Y')
-        self.assertEquals(rset.rows, [[s2eid]])
-        # check that when a relation definition is deleted, existing relations are deleted
-        rdefeid = self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, '
-                               '   X from_entity E, X to_entity E '
-                               'WHERE RT name "concerne2", E name "CWUser"')[0][0]
-        self._set_perms(rdefeid)
-        self.commit()
-        self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
-        self.commit()
-        self.failUnless('concerne2' in schema['CWUser'].subject_relations())
-        self.failIf('concerne2' in schema['Societe2'].subject_relations())
-        self.failIf(self.execute('Any X WHERE X concerne2 Y'))
-        # schema should be cleaned on delete (after commit)
-        self.execute('DELETE CWEType X WHERE X name "Societe2"')
-        self.execute('DELETE CWRType X WHERE X name "concerne2"')
-        self.failUnless(self.index_exists('Societe2', 'name'))
-        self.failUnless(schema.has_entity('Societe2'))
-        self.failUnless(schema.has_relation('concerne2'))
-        self.commit()
-        self.failIf(self.index_exists('Societe2', 'name'))
-        self.failIf(schema.has_entity('Societe2'))
-        self.failIf(schema.has_entity('concerne2'))
-        self.failIf('concerne2' in schema['CWUser'].subject_relations())
-
-    def test_is_instance_of_insertions(self):
-        seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0]
-        is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
-        self.assertEquals(is_etypes, ['Transition'])
-        instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
-        self.assertEquals(sorted(instanceof_etypes), ['BaseTransition', 'Transition'])
-        snames = [name for name, in self.execute('Any N WHERE S is BaseTransition, S name N')]
-        self.failIf('subdiv' in snames)
-        snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')]
-        self.failUnless('subdiv' in snames)
-
-
-    def test_perms_synchronization_1(self):
-        schema = self.repo.schema
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
-        self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
-        self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
-        self.commit()
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
-        self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
-        self.commit()
-        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
-
-    def test_perms_synchronization_2(self):
-        schema = self.repo.schema['in_group'].rdefs[('CWUser', 'CWGroup')]
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-        self.execute('DELETE X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-        self.commit()
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
-        self.execute('SET X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
-        self.commit()
-        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-
-    def test_nonregr_user_edit_itself(self):
-        ueid = self.session.user.eid
-        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
-        self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
-        self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
-        self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
-        self.commit()
-        eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
-        self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
-        self.execute('SET X final FALSE WHERE X eid %s' % eeid)
-        self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
-                     % (eeid, groupeids[0], groupeids[1]))
-        self.commit()
-        self.execute('Any X WHERE X is CWEType, X name "CWEType"')
-
-    # schema modification hooks tests #########################################
-
-    def test_uninline_relation(self):
-        self.session.set_pool()
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        self.failUnless(self.schema['state_of'].inlined)
-        try:
-            self.execute('SET X inlined FALSE WHERE X name "state_of"')
-            self.failUnless(self.schema['state_of'].inlined)
-            self.commit()
-            self.failIf(self.schema['state_of'].inlined)
-            self.failIf(self.index_exists('State', 'state_of'))
-            rset = self.execute('Any X, Y WHERE X state_of Y')
-            self.assertEquals(len(rset), 2) # user states
-        finally:
-            self.execute('SET X inlined TRUE WHERE X name "state_of"')
-            self.failIf(self.schema['state_of'].inlined)
-            self.commit()
-            self.failUnless(self.schema['state_of'].inlined)
-            self.failUnless(self.index_exists('State', 'state_of'))
-            rset = self.execute('Any X, Y WHERE X state_of Y')
-            self.assertEquals(len(rset), 2)
-
-    def test_indexed_change(self):
-        self.session.set_pool()
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        try:
-            self.execute('SET X indexed FALSE WHERE X relation_type R, R name "name"')
-            self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
-            self.failUnless(self.index_exists('Workflow', 'name'))
-            self.commit()
-            self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
-            self.failIf(self.index_exists('Workflow', 'name'))
-        finally:
-            self.execute('SET X indexed TRUE WHERE X relation_type R, R name "name"')
-            self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
-            self.failIf(self.index_exists('Workflow', 'name'))
-            self.commit()
-            self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
-            self.failUnless(self.index_exists('Workflow', 'name'))
-
-    def test_unique_change(self):
-        self.session.set_pool()
-        dbhelper = self.session.pool.source('system').dbhelper
-        sqlcursor = self.session.pool['system']
-        try:
-            self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
-                         'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
-                         'RT name "name", E name "Workflow"')
-            self.failIf(self.schema['Workflow'].has_unique_values('name'))
-            self.failIf(self.index_exists('Workflow', 'name', unique=True))
-            self.commit()
-            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
-            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
-        finally:
-            self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
-                         'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
-                         'RT name "name", E name "Workflow"')
-            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
-            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
-            self.commit()
-            self.failIf(self.schema['Workflow'].has_unique_values('name'))
-            self.failIf(self.index_exists('Workflow', 'name', unique=True))
-
-    def test_required_change_1(self):
-        self.execute('SET DEF cardinality "?1" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "title", E name "Bookmark"')
-        self.commit()
-        # should now be able to add bookmark without title
-        self.execute('INSERT Bookmark X: X path "/view"')
-        self.commit()
-
-    def test_required_change_2(self):
-        self.execute('SET DEF cardinality "11" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "surname", E name "CWUser"')
-        self.commit()
-        # should not be able anymore to add cwuser without surname
-        self.assertRaises(ValidationError, self.create_user, "toto")
-        self.execute('SET DEF cardinality "?1" '
-                     'WHERE DEF relation_type RT, DEF from_entity E,'
-                     'RT name "surname", E name "CWUser"')
-        self.commit()
-
-
-    def test_add_attribute_to_base_class(self):
-        attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
-                               'WHERE RT name "messageid", E name "BaseTransition", F name "String"')[0][0]
-        assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
-                     {'x': attreid}, 'x')
-        self.commit()
-        self.schema.rebuild_infered_relations()
-        self.failUnless('Transition' in self.schema['messageid'].subjects())
-        self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects())
-        self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"')
-
 if __name__ == '__main__':
     unittest_main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/unittest_syncschema.py	Fri Feb 26 17:39:33 2010 +0100
@@ -0,0 +1,290 @@
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb import ValidationError
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.server.sqlutils import SQL_PREFIX
+
+
+SCHEMA_EIDS = {}
+class SchemaModificationHooksTC(CubicWebTC):
+    reset_schema = True
+
+    @classmethod
+    def init_config(cls, config):
+        super(SchemaModificationHooksTC, cls).init_config(config)
+        # we have to read schema from the database to get eid for schema entities
+        config._cubes = None
+        cls.repo.fill_schema()
+        # remember them so we can reread it from the fs instead of the db (too
+        # costly) between tests
+        for x in cls.repo.schema.entities():
+            SCHEMA_EIDS[x] = x.eid
+        for x in cls.repo.schema.relations():
+            SCHEMA_EIDS[x] = x.eid
+            for rdef in x.rdefs.itervalues():
+                SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+
+    @classmethod
+    def _refresh_repo(cls):
+        super(SchemaModificationHooksTC, cls)._refresh_repo()
+        # rebuild schema eid index
+        schema = cls.repo.schema
+        for x in schema.entities():
+            x.eid = SCHEMA_EIDS[x]
+            schema._eid_index[x.eid] = x
+        for x in cls.repo.schema.relations():
+            x.eid = SCHEMA_EIDS[x]
+            schema._eid_index[x.eid] = x
+            for rdef in x.rdefs.itervalues():
+                rdef.eid = SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)]
+                schema._eid_index[rdef.eid] = rdef
+
+    def index_exists(self, etype, attr, unique=False):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
+
+    def _set_perms(self, eid):
+        self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup',
+                     {'x': eid}, 'x')
+        self.execute('SET X add_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"',
+                     {'x': eid}, 'x')
+        self.execute('SET X delete_permission G WHERE X eid %(x)s, G is CWGroup, G name "owners"',
+                     {'x': eid}, 'x')
+
+    def _set_attr_perms(self, eid):
+        self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup',
+                     {'x': eid}, 'x')
+        self.execute('SET X update_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"',
+                     {'x': eid}, 'x')
+
+    def test_base(self):
+        schema = self.repo.schema
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        # schema should be update on insertion (after commit)
+        eeid = self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')[0][0]
+        self._set_perms(eeid)
+        self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symmetric FALSE')
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        # have to commit before adding definition relations
+        self.commit()
+        self.failUnless(schema.has_entity('Societe2'))
+        self.failUnless(schema.has_relation('concerne2'))
+        attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", '
+                               '   X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+                               'WHERE RT name "name", E name "Societe2", F name "String"')[0][0]
+        self._set_attr_perms(attreid)
+        concerne2_rdef_eid = self.execute(
+            'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+            'WHERE RT name "concerne2", E name "Societe2"')[0][0]
+        self._set_perms(concerne2_rdef_eid)
+        self.failIf('name' in schema['Societe2'].subject_relations())
+        self.failIf('concerne2' in schema['Societe2'].subject_relations())
+        self.failIf(self.index_exists('Societe2', 'name'))
+        self.commit()
+        self.failUnless('name' in schema['Societe2'].subject_relations())
+        self.failUnless('concerne2' in schema['Societe2'].subject_relations())
+        self.failUnless(self.index_exists('Societe2', 'name'))
+        # now we should be able to insert and query Societe2
+        s2eid = self.execute('INSERT Societe2 X: X name "logilab"')[0][0]
+        self.execute('Societe2 X WHERE X name "logilab"')
+        self.execute('SET X concerne2 X WHERE X name "logilab"')
+        rset = self.execute('Any X WHERE X concerne2 Y')
+        self.assertEquals(rset.rows, [[s2eid]])
+        # check that when a relation definition is deleted, existing relations are deleted
+        rdefeid = self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, '
+                               '   X from_entity E, X to_entity E '
+                               'WHERE RT name "concerne2", E name "CWUser"')[0][0]
+        self._set_perms(rdefeid)
+        self.commit()
+        self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
+        self.commit()
+        self.failUnless('concerne2' in schema['CWUser'].subject_relations())
+        self.failIf('concerne2' in schema['Societe2'].subject_relations())
+        self.failIf(self.execute('Any X WHERE X concerne2 Y'))
+        # schema should be cleaned on delete (after commit)
+        self.execute('DELETE CWEType X WHERE X name "Societe2"')
+        self.execute('DELETE CWRType X WHERE X name "concerne2"')
+        self.failUnless(self.index_exists('Societe2', 'name'))
+        self.failUnless(schema.has_entity('Societe2'))
+        self.failUnless(schema.has_relation('concerne2'))
+        self.commit()
+        self.failIf(self.index_exists('Societe2', 'name'))
+        self.failIf(schema.has_entity('Societe2'))
+        self.failIf(schema.has_entity('concerne2'))
+        self.failIf('concerne2' in schema['CWUser'].subject_relations())
+
+    def test_is_instance_of_insertions(self):
+        seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0]
+        is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
+        self.assertEquals(is_etypes, ['Transition'])
+        instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
+        self.assertEquals(sorted(instanceof_etypes), ['BaseTransition', 'Transition'])
+        snames = [name for name, in self.execute('Any N WHERE S is BaseTransition, S name N')]
+        self.failIf('subdiv' in snames)
+        snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')]
+        self.failUnless('subdiv' in snames)
+
+
+    def test_perms_synchronization_1(self):
+        schema = self.repo.schema
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
+        self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
+        self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
+        self.commit()
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers',)))
+        self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+        self.commit()
+        self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
+
+    def test_perms_synchronization_2(self):
+        schema = self.repo.schema['in_group'].rdefs[('CWUser', 'CWGroup')]
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+        self.execute('DELETE X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+        self.commit()
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+        self.execute('SET X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+        self.commit()
+        self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+
+    def test_nonregr_user_edit_itself(self):
+        ueid = self.session.user.eid
+        groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
+        self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
+        self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
+        self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
+        self.commit()
+        eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
+        self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
+        self.execute('SET X final FALSE WHERE X eid %s' % eeid)
+        self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
+                     % (eeid, groupeids[0], groupeids[1]))
+        self.commit()
+        self.execute('Any X WHERE X is CWEType, X name "CWEType"')
+
+    # schema modification hooks tests #########################################
+
+    def test_uninline_relation(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        self.failUnless(self.schema['state_of'].inlined)
+        try:
+            self.execute('SET X inlined FALSE WHERE X name "state_of"')
+            self.failUnless(self.schema['state_of'].inlined)
+            self.commit()
+            self.failIf(self.schema['state_of'].inlined)
+            self.failIf(self.index_exists('State', 'state_of'))
+            rset = self.execute('Any X, Y WHERE X state_of Y')
+            self.assertEquals(len(rset), 2) # user states
+        finally:
+            self.execute('SET X inlined TRUE WHERE X name "state_of"')
+            self.failIf(self.schema['state_of'].inlined)
+            self.commit()
+            self.failUnless(self.schema['state_of'].inlined)
+            self.failUnless(self.index_exists('State', 'state_of'))
+            rset = self.execute('Any X, Y WHERE X state_of Y')
+            self.assertEquals(len(rset), 2)
+
+    def test_indexed_change(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        try:
+            self.execute('SET X indexed FALSE WHERE X relation_type R, R name "name"')
+            self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
+            self.failUnless(self.index_exists('Workflow', 'name'))
+            self.commit()
+            self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
+            self.failIf(self.index_exists('Workflow', 'name'))
+        finally:
+            self.execute('SET X indexed TRUE WHERE X relation_type R, R name "name"')
+            self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
+            self.failIf(self.index_exists('Workflow', 'name'))
+            self.commit()
+            self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
+            self.failUnless(self.index_exists('Workflow', 'name'))
+
+    def test_unique_change(self):
+        self.session.set_pool()
+        dbhelper = self.session.pool.source('system').dbhelper
+        sqlcursor = self.session.pool['system']
+        try:
+            self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
+                         'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+                         'RT name "name", E name "Workflow"')
+            self.failIf(self.schema['Workflow'].has_unique_values('name'))
+            self.failIf(self.index_exists('Workflow', 'name', unique=True))
+            self.commit()
+            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+        finally:
+            self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
+                         'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+                         'RT name "name", E name "Workflow"')
+            self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+            self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+            self.commit()
+            self.failIf(self.schema['Workflow'].has_unique_values('name'))
+            self.failIf(self.index_exists('Workflow', 'name', unique=True))
+
+    def test_required_change_1(self):
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "title", E name "Bookmark"')
+        self.commit()
+        # should now be able to add bookmark without title
+        self.execute('INSERT Bookmark X: X path "/view"')
+        self.commit()
+
+    def test_required_change_2(self):
+        self.execute('SET DEF cardinality "11" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "surname", E name "CWUser"')
+        self.commit()
+        # should not be able anymore to add cwuser without surname
+        self.assertRaises(ValidationError, self.create_user, "toto")
+        self.execute('SET DEF cardinality "?1" '
+                     'WHERE DEF relation_type RT, DEF from_entity E,'
+                     'RT name "surname", E name "CWUser"')
+        self.commit()
+
+
+    def test_add_attribute_to_base_class(self):
+        attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+                               'WHERE RT name "messageid", E name "BaseTransition", F name "String"')[0][0]
+        assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
+                     {'x': attreid}, 'x')
+        self.commit()
+        self.schema.rebuild_infered_relations()
+        self.failUnless('Transition' in self.schema['messageid'].subjects())
+        self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects())
+        self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"')
+
+    def test_change_fulltextindexed(self):
+        target = self.request().create_entity(u'EmailAddress', address=u'rick.roll@dance.com')
+        self.commit()
+        rset = self.execute('Any X Where X has_text "rick.roll"')
+        self.assertIn(target.eid, [item[0] for item in rset])
+
+        assert self.execute('''SET A fulltextindexed False
+                        WHERE E is CWEType,
+                              E name "EmailAddress",
+                              A is CWAttribute,
+                              A from_entity E,
+                              A relation_type R,
+                              R name "address"
+                    ''')
+        self.commit()
+        rset = self.execute('Any X Where X has_text "rick.roll"')
+        self.assertNotIn(target.eid, [item[0] for item in rset])
+
--- a/hooks/workflow.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/hooks/workflow.py	Fri Feb 26 17:39:33 2010 +0100
@@ -13,7 +13,6 @@
 from cubicweb.interfaces import IWorkflowable
 from cubicweb.selectors import implements
 from cubicweb.server import hook
-from cubicweb.entities.wfobjs import WorkflowTransition
 
 
 def _change_state(session, x, oldstate, newstate):
@@ -52,7 +51,6 @@
     """try to fire auto transition after state changes"""
 
     def precommit_event(self):
-        session = self.session
         entity = self.entity
         autotrs = list(entity.possible_transitions('auto'))
         if autotrs:
@@ -232,7 +230,7 @@
                     raise ValidationError(entity.eid, {'by_transition': msg})
             if entity.get('to_state'):
                 deststateeid = entity['to_state']
-                if not cowpowers and deststateeid != tr.destination().eid:
+                if not cowpowers and deststateeid != tr.destination(forentity).eid:
                     msg = session._("transition isn't allowed")
                     raise ValidationError(entity.eid, {'by_transition': msg})
                 if swtr is None:
@@ -241,7 +239,7 @@
                         msg = session._("state doesn't belong to entity's workflow")
                         raise ValidationError(entity.eid, {'to_state': msg})
             else:
-                deststateeid = tr.destination().eid
+                deststateeid = tr.destination(forentity).eid
         # everything is ok, add missing information on the trinfo entity
         entity['from_state'] = fromstate.eid
         entity['to_state'] = deststateeid
--- a/i18n.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/i18n.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,6 @@
 
 import re
 import os
-import sys
 from os.path import join, basename, splitext, exists
 from glob import glob
 
--- a/i18n/en.po	Thu Feb 18 09:22:04 2010 +0100
+++ b/i18n/en.po	Fri Feb 26 17:39:33 2010 +0100
@@ -125,6 +125,9 @@
 msgid "(UNEXISTANT EID)"
 msgstr ""
 
+msgid "(loading ...)"
+msgstr ""
+
 msgid "**"
 msgstr "0..n 0..n"
 
@@ -191,9 +194,6 @@
 msgid "Any"
 msgstr ""
 
-msgid "Application"
-msgstr ""
-
 msgid "Attributes"
 msgstr ""
 
@@ -348,9 +348,6 @@
 msgid "Entities"
 msgstr ""
 
-msgid "Environment"
-msgstr ""
-
 msgid "ExternalUri"
 msgstr "External Uri"
 
@@ -375,6 +372,9 @@
 msgid "Help"
 msgstr ""
 
+msgid "Instance"
+msgstr ""
+
 msgid "Int"
 msgstr "Integer"
 
@@ -501,7 +501,7 @@
 msgid "Relations"
 msgstr ""
 
-msgid "Request"
+msgid "Repository"
 msgstr ""
 
 #, python-format
@@ -514,9 +514,6 @@
 msgid "Search for"
 msgstr ""
 
-msgid "Server"
-msgstr ""
-
 msgid "SizeConstraint"
 msgstr "size constraint"
 
@@ -657,6 +654,9 @@
 msgid "Used by:"
 msgstr ""
 
+msgid "Web server"
+msgstr ""
+
 msgid "What's new?"
 msgstr ""
 
@@ -917,32 +917,29 @@
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "bookmark"
 
-msgid "add CWAttribute add_permission RQLExpression subject"
-msgstr "add rql expression"
-
 msgid "add CWAttribute constrained_by CWConstraint subject"
 msgstr "constraint"
 
-msgid "add CWAttribute delete_permission RQLExpression subject"
-msgstr "delete rql expression"
-
 msgid "add CWAttribute read_permission RQLExpression subject"
 msgstr "read rql expression"
 
 msgid "add CWAttribute relation_type CWRType object"
 msgstr "attribute definition"
 
+msgid "add CWAttribute update_permission RQLExpression subject"
+msgstr "rql expression for update permission"
+
 msgid "add CWEType add_permission RQLExpression subject"
-msgstr "rql expression for the add permission"
+msgstr "rql expression for add permission"
 
 msgid "add CWEType delete_permission RQLExpression subject"
-msgstr "rql expression for the delete permission"
+msgstr "rql expression for delete permission"
 
 msgid "add CWEType read_permission RQLExpression subject"
-msgstr "rql expression for the read permission"
+msgstr "rql expression for read permission"
 
 msgid "add CWEType update_permission RQLExpression subject"
-msgstr "rql expression for the update permission"
+msgstr "rql expression for update permission"
 
 msgid "add CWProperty for_user CWUser object"
 msgstr "property"
@@ -1034,10 +1031,6 @@
 msgid "add_permission"
 msgstr "add permission"
 
-msgctxt "CWAttribute"
-msgid "add_permission"
-msgstr "add permission"
-
 msgctxt "CWRelation"
 msgid "add_permission"
 msgstr "add permission"
@@ -1083,9 +1076,6 @@
 msgid "allow to set a specific workflow for an entity"
 msgstr ""
 
-msgid "allowed transition from this state"
-msgstr ""
-
 msgid "allowed transitions from this state"
 msgstr ""
 
@@ -1489,6 +1479,12 @@
 msgid "condition_object"
 msgstr "condition of"
 
+msgid "config mode"
+msgstr ""
+
+msgid "config type"
+msgstr ""
+
 msgid "confirm password"
 msgstr ""
 
@@ -1577,24 +1573,6 @@
 msgid "copy"
 msgstr ""
 
-msgid ""
-"core relation giving to a group the permission to add an entity or relation "
-"type"
-msgstr ""
-
-msgid ""
-"core relation giving to a group the permission to delete an entity or "
-"relation type"
-msgstr ""
-
-msgid ""
-"core relation giving to a group the permission to read an entity or relation "
-"type"
-msgstr ""
-
-msgid "core relation giving to a group the permission to update an entity type"
-msgstr ""
-
 msgid "core relation indicating a user's groups"
 msgstr ""
 
@@ -1661,17 +1639,13 @@
 msgstr "creating email address for user %(linkto)s"
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
-msgstr "RQL expression granting add permission on %(linkto)s"
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr "RQL expression granting read permission on %(linkto)s"
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"creating RQLExpression (CWAttribute %(linkto)s update_permission "
 "RQLExpression)"
-msgstr "RQL expression granting delete permission on %(linkto)s"
-
-msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
-msgstr "RQL expression granting read permission on %(linkto)s"
+msgstr "RQL expression granting update permission on %(linkto)s"
 
 msgid ""
 "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
@@ -1905,10 +1879,6 @@
 msgid "delete_permission"
 msgstr "delete permission"
 
-msgctxt "CWAttribute"
-msgid "delete_permission"
-msgstr "delete permission"
-
 msgctxt "CWRelation"
 msgid "delete_permission"
 msgstr "delete_permission"
@@ -2271,6 +2241,9 @@
 msgid "follow this link for more information on this %s"
 msgstr ""
 
+msgid "follow this link if javascript is deactivated"
+msgstr ""
+
 msgid "for_user"
 msgstr "for user"
 
@@ -2385,18 +2358,6 @@
 msgid "groups"
 msgstr ""
 
-msgid "groups allowed to add entities/relations of this type"
-msgstr ""
-
-msgid "groups allowed to delete entities/relations of this type"
-msgstr ""
-
-msgid "groups allowed to read entities/relations of this type"
-msgstr ""
-
-msgid "groups allowed to update entities of this type"
-msgstr ""
-
 msgid "groups grant permissions to the user"
 msgstr ""
 
@@ -2421,9 +2382,6 @@
 msgid "hide filter form"
 msgstr ""
 
-msgid "home"
-msgstr ""
-
 msgid ""
 "how to format date and time in the ui (\"man strftime\" for format "
 "description)"
@@ -2567,6 +2525,9 @@
 msgid "inlined"
 msgstr "inlined"
 
+msgid "instance home"
+msgstr ""
+
 msgid "instance schema"
 msgstr ""
 
@@ -2634,6 +2595,9 @@
 msgid "last connection date"
 msgstr ""
 
+msgid "last usage"
+msgstr ""
+
 msgid "last_login_time"
 msgstr "last login time"
 
@@ -2681,15 +2645,9 @@
 msgid "link a workflow to one or more entity type"
 msgstr ""
 
-msgid "link to each item in"
-msgstr ""
-
 msgid "list"
 msgstr ""
 
-msgid "loading"
-msgstr ""
-
 msgid "log in"
 msgstr ""
 
@@ -2888,6 +2846,9 @@
 msgid "no related project"
 msgstr ""
 
+msgid "no repository sessions found"
+msgstr ""
+
 msgid "no selected entities"
 msgstr ""
 
@@ -2898,6 +2859,9 @@
 msgid "no version information"
 msgstr ""
 
+msgid "no web sessions found"
+msgstr ""
+
 msgid "normal"
 msgstr ""
 
@@ -2934,6 +2898,12 @@
 msgid "open all"
 msgstr ""
 
+msgid "opened sessions"
+msgstr ""
+
+msgid "opened web sessions"
+msgstr ""
+
 msgid "order"
 msgstr ""
 
@@ -3182,6 +3152,9 @@
 msgid "required field"
 msgstr ""
 
+msgid "resources usage"
+msgstr ""
+
 msgid ""
 "restriction part of a rql query. For entity rql expression, X and U are "
 "predefined respectivly to the current object and to the request user. For "
@@ -3195,18 +3168,6 @@
 msgid "right"
 msgstr ""
 
-msgid "rql expression allowing to add entities/relations of this type"
-msgstr ""
-
-msgid "rql expression allowing to delete entities/relations of this type"
-msgstr ""
-
-msgid "rql expression allowing to read entities/relations of this type"
-msgstr ""
-
-msgid "rql expression allowing to update entities of this type"
-msgstr ""
-
 msgid "rql expressions"
 msgstr ""
 
@@ -3300,9 +3261,6 @@
 msgid "september"
 msgstr ""
 
-msgid "server debug information"
-msgstr ""
-
 msgid "server information"
 msgstr ""
 
@@ -3744,6 +3702,10 @@
 msgid "update_permission"
 msgstr "can be updated by"
 
+msgctxt "CWAttribute"
+msgid "update_permission"
+msgstr "can be updated by"
+
 msgctxt "CWGroup"
 msgid "update_permission_object"
 msgstr "has permission to update"
@@ -3774,7 +3736,8 @@
 
 msgid ""
 "use to define a transition from one or multiple states to a destination "
-"states in workflow's definitions."
+"states in workflow's definitions. Transition without destination state will "
+"go back to the state from which we arrived to the current state."
 msgstr ""
 
 msgid "use_email"
@@ -3847,6 +3810,9 @@
 msgid "vcard"
 msgstr ""
 
+msgid "versions configuration"
+msgstr ""
+
 msgid "view"
 msgstr ""
 
--- a/i18n/es.po	Thu Feb 18 09:22:04 2010 +0100
+++ b/i18n/es.po	Fri Feb 26 17:39:33 2010 +0100
@@ -130,6 +130,9 @@
 msgid "(UNEXISTANT EID)"
 msgstr ""
 
+msgid "(loading ...)"
+msgstr "(Cargando ...)"
+
 msgid "**"
 msgstr "0..n 0..n"
 
@@ -199,9 +202,6 @@
 msgid "Any"
 msgstr "Cualquiera"
 
-msgid "Application"
-msgstr "Aplicación"
-
 msgid "Attributes"
 msgstr "Atributos"
 
@@ -356,9 +356,6 @@
 msgid "Entities"
 msgstr "Entidades"
 
-msgid "Environment"
-msgstr "Ambiente"
-
 msgid "ExternalUri"
 msgstr ""
 
@@ -383,6 +380,9 @@
 msgid "Help"
 msgstr ""
 
+msgid "Instance"
+msgstr ""
+
 msgid "Int"
 msgstr "Número entero"
 
@@ -509,8 +509,8 @@
 msgid "Relations"
 msgstr "Relaciones"
 
-msgid "Request"
-msgstr "Petición"
+msgid "Repository"
+msgstr ""
 
 #, python-format
 msgid "Schema %s"
@@ -522,9 +522,6 @@
 msgid "Search for"
 msgstr "Buscar"
 
-msgid "Server"
-msgstr "Servidor"
-
 msgid "SizeConstraint"
 msgstr ""
 
@@ -665,6 +662,9 @@
 msgid "Used by:"
 msgstr "Utilizado por :"
 
+msgid "Web server"
+msgstr ""
+
 msgid "What's new?"
 msgstr "Lo último en el sitio"
 
@@ -940,21 +940,18 @@
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "Agregar a los favoritos "
 
-msgid "add CWAttribute add_permission RQLExpression subject"
-msgstr ""
-
 msgid "add CWAttribute constrained_by CWConstraint subject"
 msgstr "Restricción"
 
-msgid "add CWAttribute delete_permission RQLExpression subject"
-msgstr ""
-
 msgid "add CWAttribute read_permission RQLExpression subject"
 msgstr ""
 
 msgid "add CWAttribute relation_type CWRType object"
 msgstr "Definición de atributo"
 
+msgid "add CWAttribute update_permission RQLExpression subject"
+msgstr ""
+
 msgid "add CWEType add_permission RQLExpression subject"
 msgstr "Expresión RQL de agregación"
 
@@ -1057,10 +1054,6 @@
 msgid "add_permission"
 msgstr ""
 
-msgctxt "CWAttribute"
-msgid "add_permission"
-msgstr ""
-
 msgctxt "CWRelation"
 msgid "add_permission"
 msgstr ""
@@ -1106,9 +1099,6 @@
 msgid "allow to set a specific workflow for an entity"
 msgstr ""
 
-msgid "allowed transition from this state"
-msgstr "transición autorizada desde este estado"
-
 msgid "allowed transitions from this state"
 msgstr "transiciones autorizadas desde este estado"
 
@@ -1520,6 +1510,12 @@
 msgid "condition_object"
 msgstr "condición de"
 
+msgid "config mode"
+msgstr ""
+
+msgid "config type"
+msgstr ""
+
 msgid "confirm password"
 msgstr "Confirmar contraseña"
 
@@ -1610,32 +1606,6 @@
 msgid "copy"
 msgstr "Copiar"
 
-msgid ""
-"core relation giving to a group the permission to add an entity or relation "
-"type"
-msgstr ""
-"Relación sistema que otorga a un grupo la autorización de agregar una "
-"entidad o una relación"
-
-msgid ""
-"core relation giving to a group the permission to delete an entity or "
-"relation type"
-msgstr ""
-"Relación sistema que otorga a un grupo la autorización de eliminar una "
-"entidad o relación"
-
-msgid ""
-"core relation giving to a group the permission to read an entity or relation "
-"type"
-msgstr ""
-"Relación sistema que otorga a un grupo la autorización de leer una entidad o "
-"una relación "
-
-msgid "core relation giving to a group the permission to update an entity type"
-msgstr ""
-"Relación sistema que otorga a un grupo la autorización de actualizar una "
-"entidad"
-
 msgid "core relation indicating a user's groups"
 msgstr ""
 "Relación sistema que indica los grupos a los cuales pertenece un usuario"
@@ -1708,19 +1678,15 @@
 msgstr "Creación de una dirección electrónica para el usuario %(linkto)s"
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
 msgstr ""
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"creating RQLExpression (CWAttribute %(linkto)s update_permission "
 "RQLExpression)"
 msgstr ""
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
-msgstr ""
-
-msgid ""
 "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
 msgstr ""
 "Creación de una expresión RQL para la autorización de agregar %(linkto)s"
@@ -1956,10 +1922,6 @@
 msgid "delete_permission"
 msgstr ""
 
-msgctxt "CWAttribute"
-msgid "delete_permission"
-msgstr ""
-
 msgctxt "CWRelation"
 msgid "delete_permission"
 msgstr ""
@@ -2329,6 +2291,9 @@
 msgid "follow this link for more information on this %s"
 msgstr ""
 
+msgid "follow this link if javascript is deactivated"
+msgstr ""
+
 msgid "for_user"
 msgstr "Para el usuario"
 
@@ -2443,18 +2408,6 @@
 msgid "groups"
 msgstr "Grupos"
 
-msgid "groups allowed to add entities/relations of this type"
-msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo"
-
-msgid "groups allowed to delete entities/relations of this type"
-msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo"
-
-msgid "groups allowed to read entities/relations of this type"
-msgstr "Grupos autorizados a leer entidades/relaciones de este tipo"
-
-msgid "groups allowed to update entities of this type"
-msgstr "Grupos autorizados a actualizar entidades de este tipo"
-
 msgid "groups grant permissions to the user"
 msgstr "Los grupos otorgan las autorizaciones al usuario"
 
@@ -2479,9 +2432,6 @@
 msgid "hide filter form"
 msgstr "Esconder el filtro"
 
-msgid "home"
-msgstr "Inicio"
-
 msgid ""
 "how to format date and time in the ui (\"man strftime\" for format "
 "description)"
@@ -2634,6 +2584,9 @@
 msgid "inlined"
 msgstr ""
 
+msgid "instance home"
+msgstr ""
+
 msgid "instance schema"
 msgstr ""
 
@@ -2705,6 +2658,9 @@
 msgid "last connection date"
 msgstr "Ultima fecha de conexión"
 
+msgid "last usage"
+msgstr ""
+
 msgid "last_login_time"
 msgstr "Ultima fecha de conexión"
 
@@ -2757,15 +2713,9 @@
 msgid "link a workflow to one or more entity type"
 msgstr ""
 
-msgid "link to each item in"
-msgstr "ligar hacia cada elemento en"
-
 msgid "list"
 msgstr "Lista"
 
-msgid "loading"
-msgstr "Cargando"
-
 msgid "log in"
 msgstr "Identificarse"
 
@@ -2970,6 +2920,9 @@
 msgid "no related project"
 msgstr "no hay proyecto relacionado"
 
+msgid "no repository sessions found"
+msgstr ""
+
 msgid "no selected entities"
 msgstr "no hay entidades seleccionadas"
 
@@ -2980,6 +2933,9 @@
 msgid "no version information"
 msgstr "no información de version"
 
+msgid "no web sessions found"
+msgstr ""
+
 msgid "normal"
 msgstr ""
 
@@ -3016,6 +2972,12 @@
 msgid "open all"
 msgstr "abrir todos"
 
+msgid "opened sessions"
+msgstr ""
+
+msgid "opened web sessions"
+msgstr ""
+
 msgid "order"
 msgstr "orden"
 
@@ -3263,6 +3225,9 @@
 msgid "required field"
 msgstr "Campo requerido"
 
+msgid "resources usage"
+msgstr ""
+
 msgid ""
 "restriction part of a rql query. For entity rql expression, X and U are "
 "predefined respectivly to the current object and to the request user. For "
@@ -3280,18 +3245,6 @@
 msgid "right"
 msgstr "Derecha"
 
-msgid "rql expression allowing to add entities/relations of this type"
-msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo"
-
-msgid "rql expression allowing to delete entities/relations of this type"
-msgstr "expresion RQL permitiendo eliminar entidades/relaciones de este tipo"
-
-msgid "rql expression allowing to read entities/relations of this type"
-msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo"
-
-msgid "rql expression allowing to update entities of this type"
-msgstr "expresion RQL permitiendo actualizar entidades de este tipo"
-
 msgid "rql expressions"
 msgstr "expresiones rql"
 
@@ -3385,9 +3338,6 @@
 msgid "september"
 msgstr "septiembre"
 
-msgid "server debug information"
-msgstr "server debug information"
-
 msgid "server information"
 msgstr "server information"
 
@@ -3833,6 +3783,10 @@
 msgid "update_permission"
 msgstr ""
 
+msgctxt "CWAttribute"
+msgid "update_permission"
+msgstr ""
+
 msgctxt "CWGroup"
 msgid "update_permission_object"
 msgstr ""
@@ -3863,10 +3817,9 @@
 
 msgid ""
 "use to define a transition from one or multiple states to a destination "
-"states in workflow's definitions."
-msgstr ""
-"utilizado para definir una transición desde uno o multiples estados hacia "
-"uno o varios estados destino en las definiciones del workflow"
+"states in workflow's definitions. Transition without destination state will "
+"go back to the state from which we arrived to the current state."
+msgstr ""
 
 msgid "use_email"
 msgstr "correo electrónico"
@@ -3944,6 +3897,9 @@
 msgid "vcard"
 msgstr "vcard"
 
+msgid "versions configuration"
+msgstr ""
+
 msgid "view"
 msgstr "ver"
 
@@ -4063,12 +4019,24 @@
 #~ msgid "%s results matching query"
 #~ msgstr "%s resultados de la demanda"
 
+#~ msgid "Application"
+#~ msgstr "Aplicación"
+
 #~ msgid "Debug level set to %s"
 #~ msgstr "Nivel de debug puesto a %s"
 
+#~ msgid "Environment"
+#~ msgstr "Ambiente"
+
 #~ msgid "No query has been executed"
 #~ msgstr "Ninguna búsqueda ha sido ejecutada"
 
+#~ msgid "Request"
+#~ msgstr "Petición"
+
+#~ msgid "Server"
+#~ msgstr "Servidor"
+
 #~ msgid "There is no workflow defined for this entity."
 #~ msgstr "No hay workflow para este entidad"
 
@@ -4164,6 +4132,9 @@
 #~ "Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)"
 #~ "s #%(toeid)s"
 
+#~ msgid "allowed transition from this state"
+#~ msgstr "transición autorizada desde este estado"
+
 #~ msgid "button_reset"
 #~ msgstr "Cancelar los cambios"
 
@@ -4177,6 +4148,33 @@
 #~ msgstr "Edición de una copia"
 
 #~ msgid ""
+#~ "core relation giving to a group the permission to add an entity or "
+#~ "relation type"
+#~ msgstr ""
+#~ "Relación sistema que otorga a un grupo la autorización de agregar una "
+#~ "entidad o una relación"
+
+#~ msgid ""
+#~ "core relation giving to a group the permission to delete an entity or "
+#~ "relation type"
+#~ msgstr ""
+#~ "Relación sistema que otorga a un grupo la autorización de eliminar una "
+#~ "entidad o relación"
+
+#~ msgid ""
+#~ "core relation giving to a group the permission to read an entity or "
+#~ "relation type"
+#~ msgstr ""
+#~ "Relación sistema que otorga a un grupo la autorización de leer una "
+#~ "entidad o una relación "
+
+#~ msgid ""
+#~ "core relation giving to a group the permission to update an entity type"
+#~ msgstr ""
+#~ "Relación sistema que otorga a un grupo la autorización de actualizar una "
+#~ "entidad"
+
+#~ msgid ""
 #~ "creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
 #~ msgstr ""
 #~ "Creación de una expresión RQL para la autorización de agregar relaciones %"
@@ -4229,6 +4227,21 @@
 #~ msgid "entity types which may use this transition"
 #~ msgstr "Entidades que pueden utilizar esta transición"
 
+#~ msgid "groups allowed to add entities/relations of this type"
+#~ msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo"
+
+#~ msgid "groups allowed to delete entities/relations of this type"
+#~ msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo"
+
+#~ msgid "groups allowed to read entities/relations of this type"
+#~ msgstr "Grupos autorizados a leer entidades/relaciones de este tipo"
+
+#~ msgid "groups allowed to update entities of this type"
+#~ msgstr "Grupos autorizados a actualizar entidades de este tipo"
+
+#~ msgid "home"
+#~ msgstr "Inicio"
+
 #~ msgid "initial state for entities of this type"
 #~ msgstr "Estado inicial para las entidades de este tipo"
 
@@ -4241,6 +4254,12 @@
 #~ msgid "link a transition to one or more entity type"
 #~ msgstr "liga una transición a una o mas tipos de entidad"
 
+#~ msgid "link to each item in"
+#~ msgstr "ligar hacia cada elemento en"
+
+#~ msgid "loading"
+#~ msgstr "Cargando"
+
 #~ msgid "nothing to edit"
 #~ msgstr "nada que editar"
 
@@ -4295,6 +4314,29 @@
 #~ msgid "remove this Transition"
 #~ msgstr "Eliminar esta transición"
 
+#~ msgid "rql expression allowing to add entities/relations of this type"
+#~ msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo"
+
+#~ msgid "rql expression allowing to delete entities/relations of this type"
+#~ msgstr ""
+#~ "expresion RQL permitiendo eliminar entidades/relaciones de este tipo"
+
+#~ msgid "rql expression allowing to read entities/relations of this type"
+#~ msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo"
+
+#~ msgid "rql expression allowing to update entities of this type"
+#~ msgstr "expresion RQL permitiendo actualizar entidades de este tipo"
+
+#~ msgid "server debug information"
+#~ msgstr "server debug information"
+
+#~ msgid ""
+#~ "use to define a transition from one or multiple states to a destination "
+#~ "states in workflow's definitions."
+#~ msgstr ""
+#~ "utilizado para definir una transición desde uno o multiples estados hacia "
+#~ "uno o varios estados destino en las definiciones del workflow"
+
 #~ msgid ""
 #~ "user for which this property is applying. If this relation is not set, "
 #~ "the property is considered as a global property"
--- a/i18n/fr.po	Thu Feb 18 09:22:04 2010 +0100
+++ b/i18n/fr.po	Fri Feb 26 17:39:33 2010 +0100
@@ -130,6 +130,9 @@
 msgid "(UNEXISTANT EID)"
 msgstr "(EID INTROUVABLE)"
 
+msgid "(loading ...)"
+msgstr "(chargement ...)"
+
 msgid "**"
 msgstr "0..n 0..n"
 
@@ -198,9 +201,6 @@
 msgid "Any"
 msgstr "N'importe"
 
-msgid "Application"
-msgstr "Application"
-
 msgid "Attributes"
 msgstr "Attributs"
 
@@ -355,9 +355,6 @@
 msgid "Entities"
 msgstr "entités"
 
-msgid "Environment"
-msgstr "Environement"
-
 msgid "ExternalUri"
 msgstr "Uri externe"
 
@@ -382,6 +379,9 @@
 msgid "Help"
 msgstr "Aide"
 
+msgid "Instance"
+msgstr "Instance"
+
 msgid "Int"
 msgstr "Nombre entier"
 
@@ -508,8 +508,8 @@
 msgid "Relations"
 msgstr "Relations"
 
-msgid "Request"
-msgstr "Requête"
+msgid "Repository"
+msgstr "Entrepôt de données"
 
 #, python-format
 msgid "Schema %s"
@@ -521,9 +521,6 @@
 msgid "Search for"
 msgstr "Rechercher"
 
-msgid "Server"
-msgstr "Serveur"
-
 msgid "SizeConstraint"
 msgstr "contrainte de taille"
 
@@ -664,6 +661,9 @@
 msgid "Used by:"
 msgstr "Utilisé par :"
 
+msgid "Web server"
+msgstr "Serveur web"
+
 msgid "What's new?"
 msgstr "Nouveautés"
 
@@ -945,21 +945,18 @@
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "signet"
 
-msgid "add CWAttribute add_permission RQLExpression subject"
-msgstr "expression rql d'ajout"
-
 msgid "add CWAttribute constrained_by CWConstraint subject"
 msgstr "contrainte"
 
-msgid "add CWAttribute delete_permission RQLExpression subject"
-msgstr "expression rql de suppression"
-
 msgid "add CWAttribute read_permission RQLExpression subject"
 msgstr "expression rql de lecture"
 
 msgid "add CWAttribute relation_type CWRType object"
 msgstr "définition d'attribut"
 
+msgid "add CWAttribute update_permission RQLExpression subject"
+msgstr "permission de mise à jour"
+
 msgid "add CWEType add_permission RQLExpression subject"
 msgstr "définir une expression RQL d'ajout"
 
@@ -1062,10 +1059,6 @@
 msgid "add_permission"
 msgstr "permission d'ajout"
 
-msgctxt "CWAttribute"
-msgid "add_permission"
-msgstr "permission d'ajout"
-
 msgctxt "CWRelation"
 msgid "add_permission"
 msgstr "permission d'ajout"
@@ -1113,9 +1106,6 @@
 msgid "allow to set a specific workflow for an entity"
 msgstr "permet de spécifier un workflow donné pour une entité"
 
-msgid "allowed transition from this state"
-msgstr "transition autorisée depuis cet état"
-
 msgid "allowed transitions from this state"
 msgstr "transitions autorisées depuis cet état"
 
@@ -1528,6 +1518,12 @@
 msgid "condition_object"
 msgstr "condition de"
 
+msgid "config mode"
+msgstr "mode de configuration"
+
+msgid "config type"
+msgstr "type de configuration"
+
 msgid "confirm password"
 msgstr "confirmer le mot de passe"
 
@@ -1619,32 +1615,6 @@
 msgid "copy"
 msgstr "copier"
 
-msgid ""
-"core relation giving to a group the permission to add an entity or relation "
-"type"
-msgstr ""
-"relation système donnant à un groupe la permission d'ajouter une entité ou "
-"une relation"
-
-msgid ""
-"core relation giving to a group the permission to delete an entity or "
-"relation type"
-msgstr ""
-"relation système donnant à un group la permission de supprimer une entité ou "
-"une relation"
-
-msgid ""
-"core relation giving to a group the permission to read an entity or relation "
-"type"
-msgstr ""
-"relation système donnant à un group la permission de lire une entité ou une "
-"relation"
-
-msgid "core relation giving to a group the permission to update an entity type"
-msgstr ""
-"relation système donnant à un groupe la permission de mettre à jour une "
-"entityé"
-
 msgid "core relation indicating a user's groups"
 msgstr ""
 "relation système indiquant les groupes auxquels appartient l'utilisateur"
@@ -1717,18 +1687,14 @@
 msgstr "création d'une adresse électronique pour l'utilisateur %(linkto)s"
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
-msgstr "création d'une expression rql pour le droit d'ajout de %(linkto)s"
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s"
 
 msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"creating RQLExpression (CWAttribute %(linkto)s update_permission "
 "RQLExpression)"
 msgstr ""
-"création d'une expression rql pour le droit de suppression de %(linkto)s"
-
-msgid ""
-"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
-msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s"
+"création d'une expression rql pour le droit de mise à jour de %(linkto)s"
 
 msgid ""
 "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
@@ -1928,8 +1894,8 @@
 msgid "define a relation type, used to build the instance schema"
 msgstr "définit un type de relation"
 
-msgid "define a rql expression used to define vpermissions"
-msgstr ""
+msgid "define a rql expression used to define permissions"
+msgstr "définit une expression rql donnant une permission"
 
 msgid "define a schema constraint"
 msgstr "définit une contrainte de schema"
@@ -1972,10 +1938,6 @@
 msgid "delete_permission"
 msgstr "permission de supprimer"
 
-msgctxt "CWAttribute"
-msgid "delete_permission"
-msgstr "permission de supprimer"
-
 msgctxt "CWRelation"
 msgid "delete_permission"
 msgstr "permission de supprimer"
@@ -2351,6 +2313,9 @@
 msgid "follow this link for more information on this %s"
 msgstr "suivez ce lien pour plus d'information sur ce %s"
 
+msgid "follow this link if javascript is deactivated"
+msgstr ""
+
 msgid "for_user"
 msgstr "pour l'utilisateur"
 
@@ -2468,18 +2433,6 @@
 msgid "groups"
 msgstr "groupes"
 
-msgid "groups allowed to add entities/relations of this type"
-msgstr "groupes autorisés à ajouter des entités/relations de ce type"
-
-msgid "groups allowed to delete entities/relations of this type"
-msgstr "groupes autorisés à supprimer des entités/relations de ce type"
-
-msgid "groups allowed to read entities/relations of this type"
-msgstr "groupes autorisés à lire des entités/relations de ce type"
-
-msgid "groups allowed to update entities of this type"
-msgstr "groupes autorisés à mettre à jour les entités de ce type"
-
 msgid "groups grant permissions to the user"
 msgstr "les groupes donnent des permissions à l'utilisateur"
 
@@ -2504,9 +2457,6 @@
 msgid "hide filter form"
 msgstr "cacher le filtre"
 
-msgid "home"
-msgstr "maison"
-
 msgid ""
 "how to format date and time in the ui (\"man strftime\" for format "
 "description)"
@@ -2659,6 +2609,9 @@
 msgid "inlined"
 msgstr "mise en ligne"
 
+msgid "instance home"
+msgstr "répertoire de l'instance"
+
 msgid "instance schema"
 msgstr "schéma de l'instance"
 
@@ -2731,6 +2684,9 @@
 msgid "last connection date"
 msgstr "dernière date de connexion"
 
+msgid "last usage"
+msgstr "dernier usage"
+
 msgid "last_login_time"
 msgstr "dernière date de connexion"
 
@@ -2783,15 +2739,9 @@
 msgid "link a workflow to one or more entity type"
 msgstr "lie un workflow à un ou plusieurs types d'entité"
 
-msgid "link to each item in"
-msgstr "lier vers chaque élément dans"
-
 msgid "list"
 msgstr "liste"
 
-msgid "loading"
-msgstr "chargement"
-
 msgid "log in"
 msgstr "s'identifier"
 
@@ -2992,6 +2942,9 @@
 msgid "no related project"
 msgstr "pas de projet rattaché"
 
+msgid "no repository sessions found"
+msgstr "aucune session trouvée"
+
 msgid "no selected entities"
 msgstr "pas d'entité sélectionnée"
 
@@ -3002,6 +2955,9 @@
 msgid "no version information"
 msgstr "pas d'information de version"
 
+msgid "no web sessions found"
+msgstr "aucune session trouvée"
+
 msgid "normal"
 msgstr "normal"
 
@@ -3038,6 +2994,12 @@
 msgid "open all"
 msgstr "tout ouvrir"
 
+msgid "opened sessions"
+msgstr "sessions ouvertes"
+
+msgid "opened web sessions"
+msgstr "sessions web ouvertes"
+
 msgid "order"
 msgstr "ordre"
 
@@ -3287,6 +3249,9 @@
 msgid "required field"
 msgstr "champ requis"
 
+msgid "resources usage"
+msgstr "resources utilisées"
+
 msgid ""
 "restriction part of a rql query. For entity rql expression, X and U are "
 "predefined respectivly to the current object and to the request user. For "
@@ -3305,22 +3270,6 @@
 msgid "right"
 msgstr "droite"
 
-msgid "rql expression allowing to add entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit d'ajouter des entités/relations de ce type"
-
-msgid "rql expression allowing to delete entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit de supprimer des entités/relations de ce type"
-
-msgid "rql expression allowing to read entities/relations of this type"
-msgstr ""
-"expression RQL donnant le droit de lire des entités/relations de ce type"
-
-msgid "rql expression allowing to update entities of this type"
-msgstr ""
-"expression RQL donnant le droit de modifier des entités/relations de ce type"
-
 msgid "rql expressions"
 msgstr "conditions rql"
 
@@ -3414,9 +3363,6 @@
 msgid "september"
 msgstr "septembre"
 
-msgid "server debug information"
-msgstr "informations de déboguage serveur"
-
 msgid "server information"
 msgstr "informations serveur"
 
@@ -3866,6 +3812,10 @@
 msgid "update_permission"
 msgstr "permission de modifier"
 
+msgctxt "CWAttribute"
+msgid "update_permission"
+msgstr "permission de modifier"
+
 msgctxt "CWGroup"
 msgid "update_permission_object"
 msgstr "peut modifier"
@@ -3896,10 +3846,12 @@
 
 msgid ""
 "use to define a transition from one or multiple states to a destination "
-"states in workflow's definitions."
+"states in workflow's definitions. Transition without destination state will "
+"go back to the state from which we arrived to the current state."
 msgstr ""
-"utiliser dans une définition de processus pour ajouter une transition depuis "
-"un ou plusieurs états vers un état de destination."
+"utilisé dans une définition de processus pour ajouter une transition depuis "
+"un ou plusieurs états vers un état de destination. Une transition sans état "
+"de destination retournera à l'état précédent l'état courant."
 
 msgid "use_email"
 msgstr "adresse électronique"
@@ -3975,6 +3927,9 @@
 msgid "vcard"
 msgstr "vcard"
 
+msgid "versions configuration"
+msgstr "configuration de version"
+
 msgid "view"
 msgstr "voir"
 
@@ -4072,7 +4027,7 @@
 
 #, python-format
 msgid "wrong query parameter line %s"
-msgstr ""
+msgstr "mauvais paramètre de requête ligne %s"
 
 msgid "xbel"
 msgstr "xbel"
--- a/mail.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/mail.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,7 +8,6 @@
 __docformat__ = "restructuredtext en"
 
 from base64 import b64encode, b64decode
-from itertools import repeat
 from time import time
 from email.MIMEMultipart import MIMEMultipart
 from email.MIMEText import MIMEText
--- a/migration.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/migration.py	Fri Feb 26 17:39:33 2010 +0100
@@ -193,7 +193,7 @@
 
         if `retry` is true the r[etry] answer may return 2
         """
-        possibleanswers = ['y','n']
+        possibleanswers = ['y', 'n']
         if abort:
             possibleanswers.append('abort')
         if shell:
--- a/misc/migration/3.6.1_Any.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/misc/migration/3.6.1_Any.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,1 +1,2 @@
 sync_schema_props_perms(syncprops=False)
+sync_schema_props_perms('destination_state', syncperms=False)
--- a/mixins.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/mixins.py	Fri Feb 26 17:39:33 2010 +0100
@@ -10,10 +10,8 @@
 
 from itertools import chain
 
-from logilab.common.deprecation import deprecated
 from logilab.common.decorators import cached
 
-from cubicweb import typed_eid
 from cubicweb.selectors import implements
 from cubicweb.interfaces import IEmailable, ITree
 
--- a/rqlrewrite.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/rqlrewrite.py	Fri Feb 26 17:39:33 2010 +0100
@@ -12,7 +12,6 @@
 
 from rql import nodes as n, stmts, TypeResolverException
 
-from logilab.common.compat import any
 from logilab.common.graph import has_path
 
 from cubicweb import Unauthorized, typed_eid
@@ -110,7 +109,10 @@
     return newsolutions
 
 
-class Unsupported(Exception): pass
+class Unsupported(Exception):
+    """raised when an rql expression can't be inserted in some rql query
+    because it create an unresolvable query (eg no solutions found)
+    """
 
 
 class RQLRewriter(object):
@@ -291,7 +293,7 @@
     def snippet_subquery(self, varmap, transformedsnippet):
         """introduce the given snippet in a subquery"""
         subselect = stmts.Select()
-        selectvar, snippetvar = varmap
+        selectvar = varmap[0]
         subselect.append_selected(n.VariableRef(
             subselect.get_variable(selectvar)))
         aliases = [selectvar]
@@ -408,7 +410,7 @@
                 cardindex = 1
                 ttypes_func = rschema.subjects
                 rdef = lambda x, y: rschema.rdef(y, x)
-        except KeyError, ex:
+        except KeyError:
             # may be raised by self.varinfo['xhs_rels'][sniprel.r_type]
             return None
         # can't share neged relation or relations with different outer join
@@ -441,9 +443,9 @@
                 while argname in self.kwargs:
                     argname = select.allocate_varname()
                 # insert "U eid %(u)s"
-                var = select.get_variable(self.u_varname)
-                select.add_constant_restriction(select.get_variable(self.u_varname),
-                                                'eid', unicode(argname), 'Substitute')
+                select.add_constant_restriction(
+                    select.get_variable(self.u_varname),
+                    'eid', unicode(argname), 'Substitute')
                 self.kwargs[argname] = self.session.user.eid
             return self.u_varname
         key = (self.current_expr, self.varmap, vname)
--- a/schema.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/schema.py	Fri Feb 26 17:39:33 2010 +0100
@@ -15,15 +15,14 @@
 
 from logilab.common.decorators import cached, clear_cache, monkeypatch
 from logilab.common.logging_ext import set_log_methods
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import deprecated, class_moved
 from logilab.common.graph import get_cycles
 from logilab.common.compat import any
 
 from yams import BadSchemaDefinition, buildobjs as ybo
 from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
      RelationDefinitionSchema, PermissionMixIn
-from yams.constraints import (BaseConstraint, StaticVocabularyConstraint,
-                              FormatConstraint)
+from yams.constraints import BaseConstraint, FormatConstraint
 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
                          obsolete as yobsolete, cleanup_sys_modules)
 
@@ -462,7 +461,6 @@
     """set of entities and relations schema defining the possible data sets
     used in an application
 
-
     :type name: str
     :ivar name: name of the schema, usually the instance identifier
 
@@ -767,7 +765,7 @@
                     rqlst.add_selected(objvar)
                 else:
                     colindex = selected.index(objvar.name)
-                found.append((action, objvar, colindex))
+                found.append((action, colindex))
                 # remove U eid %(u)s if U is not used in any other relation
                 uvrefs = rqlst.defined_vars['U'].references()
                 if len(uvrefs) == 1:
@@ -835,7 +833,7 @@
             # check every special has_*_permission relation is satisfied
             get_eschema = session.vreg.schema.eschema
             try:
-                for eaction, var, col in has_perm_defs:
+                for eaction, col in has_perm_defs:
                     for i in xrange(len(rset)):
                         eschema = get_eschema(rset.description[i][col])
                         eschema.check_perm(session, eaction, eid=rset[i][col])
@@ -1092,6 +1090,12 @@
 # XXX deprecated
 
 from yams.buildobjs import RichString
+from yams.constraints import StaticVocabularyConstraint
+
+RichString = class_moved(RichString)
+
+StaticVocabularyConstraint = class_moved(StaticVocabularyConstraint)
+FormatConstraint = class_moved(FormatConstraint)
 
 PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
 PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
--- a/schemas/base.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/schemas/base.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,7 @@
 _ = unicode
 
 from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
-                            String, Boolean, Datetime, Password)
+                            String, Datetime, Password)
 from cubicweb.schema import (RQLConstraint, WorkflowableEntityType,
                              ERQLExpression, RRQLExpression)
 from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
--- a/schemas/workflow.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/schemas/workflow.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,7 @@
 _ = unicode
 
 from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
-                            ObjectRelation, RichString, String)
+                            RichString, String)
 from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
 from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS,
                               HOOKS_RTYPE_PERMS)
@@ -97,12 +97,13 @@
 
 class Transition(BaseTransition):
     """use to define a transition from one or multiple states to a destination
-    states in workflow's definitions.
+    states in workflow's definitions. Transition without destination state will
+    go back to the state from which we arrived to the current state.
     """
     __specializes_schema__ = True
 
     destination_state = SubjectRelation(
-        'State', cardinality='1*',
+        'State', cardinality='?*',
         constraints=[RQLConstraint('S transition_of WF, O state_of WF',
                                    msg=_('state and transition don\'t belong the the same workflow'))],
         description=_('destination state for this transition'))
--- a/selectors.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/selectors.py	Fri Feb 26 17:39:33 2010 +0100
@@ -43,7 +43,7 @@
 __docformat__ = "restructuredtext en"
 
 import logging
-from warnings import warn, filterwarnings
+from warnings import warn
 
 from logilab.common.deprecation import class_renamed
 from logilab.common.compat import all, any
@@ -51,8 +51,7 @@
 
 from yams import BASE_TYPES
 
-from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
-                      role, typed_eid)
+from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
 # even if not used, let yes here so it's importable through this module
 from cubicweb.appobject import Selector, objectify_selector, yes
 from cubicweb.vregistry import class_regid
@@ -163,7 +162,6 @@
     def score_interfaces(self, req, cls_or_inst, cls):
         score = 0
         etypesreg = req.vreg['etypes']
-        eschema = cls_or_inst.e_schema
         for iface in self.expected_ifaces:
             if isinstance(iface, basestring):
                 # entity type
@@ -528,6 +526,9 @@
             return 0
         return 1
 
+    def score_class(self, eclass, req):
+        return 1 # necessarily true if we're there
+
 
 class implements(ImplementsMixIn, EClassSelector):
     """Return non-zero score for entity that are of the given type(s) or
--- a/server/__init__.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/__init__.py	Fri Feb 26 17:39:33 2010 +0100
@@ -96,10 +96,11 @@
 def create_user(session, login, pwd, *groups):
     # monkey patch this method if you want to customize admin/anon creation
     # (that maybe necessary if you change CWUser's schema)
-    session.create_entity('CWUser', login=login, upassword=pwd)
+    user = session.create_entity('CWUser', login=login, upassword=pwd)
     for group in groups:
-        session.execute('SET U in_group G WHERE G name %(group)s',
-                        {'group': group})
+        session.execute('SET U in_group G WHERE U eid %(u)s, G name %(group)s',
+                        {'u': user.eid, 'group': group})
+    return user
 
 def init_repository(config, interactive=True, drop=False, vreg=None):
     """initialise a repository database by creating tables add filling them
--- a/server/checkintegrity.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/checkintegrity.py	Fri Feb 26 17:39:33 2010 +0100
@@ -64,7 +64,7 @@
     else:
         yield eschema
 
-def reindex_entities(schema, session):
+def reindex_entities(schema, session, withpb=True):
     """reindex all entities in the repository"""
     # deactivate modification_date hook since we don't want them
     # to be updated due to the reindexation
@@ -92,22 +92,27 @@
             etypes.add(container)
     print 'Reindexing entities of type %s' % \
           ', '.join(sorted(str(e) for e in etypes))
-    pb = ProgressBar(len(etypes) + 1)
+    if withpb:
+        pb = ProgressBar(len(etypes) + 1)
     # first monkey patch Entity.check to disable validation
     from cubicweb.entity import Entity
     _check = Entity.check
     Entity.check = lambda self, creation=False: True
     # clear fti table first
     session.system_sql('DELETE FROM %s' % session.repo.system_source.dbhelper.fti_table)
-    pb.update()
+    if withpb:
+        pb.update()
     # reindex entities by generating rql queries which set all indexable
     # attribute to their current value
     for eschema in etypes:
         for entity in session.execute('Any X WHERE X is %s' % eschema).entities():
             FTIndexEntityOp(session, entity=entity)
-        pb.update()
+        if withpb:
+            pb.update()
     # restore Entity.check
     Entity.check = _check
+    repo.config.disabled_hooks_categories.remove('metadata')
+    repo.config.disabled_hooks_categories.remove('integrity')
 
 
 def check_schema(schema, session, eids, fix=1):
@@ -276,7 +281,7 @@
                 print >> sys.stderr
 
 
-def check(repo, cnx, checks, reindex, fix):
+def check(repo, cnx, checks, reindex, fix, withpb=True):
     """check integrity of instance's repository,
     using given user and password to locally connect to the repository
     (no running cubicweb server needed)
@@ -297,5 +302,5 @@
     if reindex:
         cnx.rollback()
         session.set_pool()
-        reindex_entities(repo.schema, session)
+        reindex_entities(repo.schema, session, withpb=withpb)
         cnx.commit()
--- a/server/hook.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/hook.py	Fri Feb 26 17:39:33 2010 +0100
@@ -44,7 +44,7 @@
 from logilab.common.logging_ext import set_log_methods
 
 from cubicweb.cwvreg import CWRegistry, VRegistry
-from cubicweb.selectors import (objectify_selector, lltrace, match_search_state,
+from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
                                 implements)
 from cubicweb.appobject import AppObject
 
@@ -134,7 +134,7 @@
         return iter(chain(*self.iterators))
 
 
-class match_rtype(match_search_state):
+class match_rtype(ExpectedValueSelector):
     """accept if parameters specified as initializer arguments are specified
     in named arguments given to the selector
 
@@ -159,7 +159,7 @@
         return 1
 
 
-class match_rtype_sets(match_search_state):
+class match_rtype_sets(ExpectedValueSelector):
     """accept if parameters specified as initializer arguments are specified
     in named arguments given to the selector
     """
--- a/server/hookhelper.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/hookhelper.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,6 @@
 
 from logilab.common.deprecation import deprecated, class_moved
 
-from cubicweb import RepositoryError
 from cubicweb.server import hook
 
 @deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook')
--- a/server/migractions.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/migractions.py	Fri Feb 26 17:39:33 2010 +0100
@@ -29,12 +29,11 @@
 
 from logilab.common.deprecation import deprecated
 from logilab.common.decorators import cached, clear_cache
-from logilab.common.adbh import get_adv_func_helper
 
 from yams.constraints import SizeConstraint
 from yams.schema2sql import eschema2sql, rschema2sql
 
-from cubicweb import AuthenticationError, ETYPE_NAME_MAP
+from cubicweb import AuthenticationError
 from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES,
                              CubicWebRelationSchema, order_eschemas)
 from cubicweb.dbapi import get_repository, repo_connect
@@ -156,7 +155,7 @@
             else:
                 bkup = tarfile.open(backupfile, 'w|gz')
                 for filename in os.listdir(tmpdir):
-                    bkup.add(osp.join(tmpdir,filename), filename)
+                    bkup.add(osp.join(tmpdir, filename), filename)
                 bkup.close()
                 # call hooks
                 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
@@ -170,7 +169,6 @@
         # check
         if not osp.exists(backupfile):
             raise Exception("Backup file %s doesn't exist" % backupfile)
-            return
         if askconfirm and not self.confirm('Restore %s database from %s ?'
                                            % (self.config.appid, backupfile)):
             return
@@ -293,9 +291,6 @@
             apc = osp.join(self.config.migration_scripts_dir(), '%s.py' % event)
         if osp.exists(apc):
             if self.config.free_wheel:
-                from cubicweb.server.hooks import setowner_after_add_entity
-                self.repo.hm.unregister_hook(setowner_after_add_entity,
-                                             'after_add_entity', '')
                 self.cmd_deactivate_verification_hooks()
             self.info('executing %s', apc)
             confirm = self.confirm
@@ -308,8 +303,6 @@
                 self.confirm = confirm
                 self.execscript_confirm = execscript_confirm
                 if self.config.free_wheel:
-                    self.repo.hm.register_hook(setowner_after_add_entity,
-                                               'after_add_entity', '')
                     self.cmd_reactivate_verification_hooks()
 
     def install_custom_sql_scripts(self, directory, driver):
@@ -352,7 +345,7 @@
                                             'T eid %%(x)s' % perm, {'x': teid}, 'x',
                                             ask_confirm=False):
                 if not gname in newgroups:
-                    if not confirm or self.confirm('remove %s permission of %s to %s?'
+                    if not confirm or self.confirm('Remove %s permission of %s to %s?'
                                                    % (action, erschema, gname)):
                         self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
                                      % (perm, teid),
@@ -360,7 +353,7 @@
                 else:
                     newgroups.remove(gname)
             for gname in newgroups:
-                if not confirm or self.confirm('grant %s permission of %s to %s?'
+                if not confirm or self.confirm('Grant %s permission of %s to %s?'
                                                % (action, erschema, gname)):
                     self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s'
                                  % (perm, teid),
@@ -371,7 +364,7 @@
                                                     'T eid %s' % (perm, teid),
                                                     ask_confirm=False):
                 if not expression in newexprs:
-                    if not confirm or self.confirm('remove %s expression for %s permission of %s?'
+                    if not confirm or self.confirm('Remove %s expression for %s permission of %s?'
                                                    % (expression, action, erschema)):
                         # deleting the relation will delete the expression entity
                         self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
@@ -381,7 +374,7 @@
                     newexprs.pop(expression)
             for expression in newexprs.values():
                 expr = expression.expression
-                if not confirm or self.confirm('add %s expression for %s permission of %s?'
+                if not confirm or self.confirm('Add %s expression for %s permission of %s?'
                                                % (expr, action, erschema)):
                     self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, '
                                  'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
@@ -528,7 +521,7 @@
 
     def checkpoint(self, ask_confirm=True):
         """checkpoint action"""
-        if not ask_confirm or self.confirm('commit now ?', shell=False):
+        if not ask_confirm or self.confirm('Commit now ?', shell=False):
             self.commit()
 
     def cmd_add_cube(self, cube, update_database=True):
@@ -664,7 +657,7 @@
         self.cmd_add_attribute(etype, newname, attrtype, commit=True)
         # skipp NULL values if the attribute is required
         rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname)
-        card = eschema.rproperty(newname, 'cardinality')[0]
+        card = eschema.rdef(newname).cardinality[0]
         if card == '1':
             rql += ', NOT X %s NULL' % oldname
         self.rqlexec(rql, ask_confirm=self.verbosity>=2)
@@ -735,10 +728,10 @@
                         elif role == 'object':
                             subjschema = tschema
                             objschema = spschema
-                        if (rschema.rproperty(subjschema, objschema, 'infered')
+                        if (rschema.rdef(subjschema, objschema).infered
                             or (instschema.has_relation(rschema) and
-                                instschema[rschema].has_rdef(subjschema, objschema))):
-                                continue
+                                (subjschema, objschema) in instschema[rschema].rdefs)):
+                            continue
                         self.cmd_add_relation_definition(
                             subjschema.type, rschema.type, objschema.type)
         if auto:
@@ -936,7 +929,7 @@
                 if syncprops:
                     self._synchronize_eschema(etype, syncperms=syncperms)
                 else:
-                    self._synchronize_permissions(self.fs_schema[etype], erschema.eid)
+                    self._synchronize_permissions(self.fs_schema[etype], etype.eid)
         if commit:
             self.commit()
 
@@ -973,7 +966,7 @@
         you usually want to use sync_schema_props_perms instead.
         """
         oldvalue = None
-        for constr in self.repo.schema.eschema(etype).constraints(rtype):
+        for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints:
             if isinstance(constr, SizeConstraint):
                 oldvalue = constr.max
         if oldvalue == size:
@@ -1146,13 +1139,13 @@
         should only be used for low level stuff undoable with existing higher
         level actions
         """
-        if not ask_confirm or self.confirm('execute sql: %s ?' % sql):
+        if not ask_confirm or self.confirm('Execute sql: %s ?' % sql):
             self.session.set_pool() # ensure pool is set
             try:
                 cu = self.session.system_sql(sql, args)
             except:
                 ex = sys.exc_info()[1]
-                if self.confirm('error: %s\nabort?' % ex):
+                if self.confirm('Error: %s\nabort?' % ex):
                     raise
                 return
             try:
@@ -1175,11 +1168,11 @@
                 msg = '%s (%s)' % (rql, kwargs)
             else:
                 msg = rql
-            if not ask_confirm or self.confirm('execute rql: %s ?' % msg):
+            if not ask_confirm or self.confirm('Execute rql: %s ?' % msg):
                 try:
                     res = execute(rql, kwargs, cachekey)
                 except Exception, ex:
-                    if self.confirm('error: %s\nabort?' % ex):
+                    if self.confirm('Error: %s\nabort?' % ex):
                         raise
         return res
 
@@ -1264,12 +1257,12 @@
         else:
             msg = rql
         if self.ask_confirm:
-            if not self._h.confirm('execute rql: %s ?' % msg):
+            if not self._h.confirm('Execute rql: %s ?' % msg):
                 raise StopIteration
         try:
             rset = self._h._cw.execute(rql, kwargs)
         except Exception, ex:
-            if self._h.confirm('error: %s\nabort?' % ex):
+            if self._h.confirm('Error: %s\nabort?' % ex):
                 raise
             else:
                 raise StopIteration
--- a/server/msplanner.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/msplanner.py	Fri Feb 26 17:39:33 2010 +0100
@@ -90,7 +90,6 @@
 from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep,
                                        add_types_restriction)
 from cubicweb.server.mssteps import *
-from cubicweb.server.sources import AbstractSource
 
 Variable._ms_table_key = lambda x: x.name
 Relation._ms_table_key = lambda x: x.r_type
@@ -272,7 +271,7 @@
         for source in self._sourcesterms:
             print '-', source
             for term, sols in self._sourcesterms[source].items():
-                print '  -', term, id(term), ':' ,sols
+                print '  -', term, id(term), ':', sols
 
     def copy_solutions(self, solindices):
         return [self._solutions[solidx].copy() for solidx in solindices]
@@ -465,7 +464,7 @@
                     try:
                         lhsv = self._extern_term(lhs, termssources, inserted)
                         rhsv = self._extern_term(rhs, termssources, inserted)
-                    except KeyError, ex:
+                    except KeyError:
                         continue
                     self._remove_term_sources(lhsv, rel, rhsv, termssources)
                     self._remove_term_sources(rhsv, rel, lhsv, termssources)
@@ -684,11 +683,11 @@
                         # go to the next iteration directly!
                         continue
                     if not sourceterms:
-                         try:
-                             del self._sourcesterms[source]
-                         except KeyError:
-                             # XXX already cleaned
-                             pass
+                        try:
+                            del self._sourcesterms[source]
+                        except KeyError:
+                            # XXX already cleaned
+                            pass
                 # set of terms which should be additionaly selected when
                 # possible
                 needsel = set()
@@ -857,7 +856,6 @@
         terms = [term]
         sources = sorted(sources)
         sourcesterms = self._sourcesterms
-        nbunlinked = 1
         linkedterms = self._linkedterms
         # term has to belong to the same scope if there is more
         # than the system source remaining
@@ -873,7 +871,7 @@
         for source in sources:
             cross_rels.update(self._crossrelations.get(source, {}))
         exclude = {}
-        for rel, crossvars in cross_rels.iteritems():
+        for crossvars in cross_rels.itervalues():
             vars = [t for t in crossvars if isinstance(t, Variable)]
             try:
                 exclude[vars[0]] = vars[1]
@@ -897,7 +895,6 @@
             modified = False
             for term in candidates[:]:
                 if isinstance(term, Constant):
-                    relation = term.relation()
                     if sorted(set(x[0] for x in self._term_sources(term))) != sources:
                         continue
                     terms.append(term)
@@ -1023,12 +1020,6 @@
         if server.DEBUG & server.DBG_MS:
             print '-'*80
             print 'PLANNING', rqlst
-        for select in rqlst.children:
-            if len(select.solutions) > 1:
-                hasmultiplesols = True
-                break
-        else:
-            hasmultiplesols = False
         # preprocess deals with security insertion and returns a new syntax tree
         # which have to be executed to fulfill the query: according
         # to permissions for variable's type, different rql queries may have to
@@ -1036,7 +1027,7 @@
         plan.preprocess(rqlst)
         ppis = [PartPlanInformation(plan, select, self.rqlhelper)
                 for select in rqlst.children]
-        steps = self._union_plan(plan, rqlst, ppis)
+        steps = self._union_plan(plan, ppis)
         if server.DEBUG & server.DBG_MS:
             from pprint import pprint
             for step in plan.steps:
@@ -1054,7 +1045,7 @@
             for sppi in sppis:
                 if sppi.needsplit or sppi.part_sources != ppi.part_sources:
                     temptable = 'T%s' % make_uid(id(subquery))
-                    sstep = self._union_plan(plan, subquery.query, sppis, temptable)[0]
+                    sstep = self._union_plan(plan, sppis, temptable)[0]
                     break
             else:
                 sstep = None
@@ -1065,7 +1056,7 @@
                 ppi.plan.add_step(sstep)
         return inputmap
 
-    def _union_plan(self, plan, union, ppis, temptable=None):
+    def _union_plan(self, plan, ppis, temptable=None):
         tosplit, cango, allsources = [], {}, set()
         for planinfo in ppis:
             if planinfo.needsplit:
@@ -1191,7 +1182,7 @@
                                             for step in steps
                                             for select in step.union.children):
                 if temptable:
-                    step = IntersectFetchStep(plan)
+                    step = IntersectFetchStep(plan) # XXX not implemented
                 else:
                     step = IntersectStep(plan)
             else:
--- a/server/pool.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/pool.py	Fri Feb 26 17:39:33 2010 +0100
@@ -87,7 +87,7 @@
         # implementation details of flying insert requires the system source
         # first
         yield self.source_cnxs['system'][0]
-        for uri, (source, cursor) in self.source_cnxs.items():
+        for uri, (source, cnx) in self.source_cnxs.items():
             if uri == 'system':
                 continue
             yield source
@@ -109,6 +109,11 @@
         else:
             sources = (source,)
         for source in sources:
+            try:
+                # properly close existing connection if any
+                self.source_cnxs[source.uri][1].close()
+            except:
+                pass
             source.info('trying to reconnect')
             self.source_cnxs[source.uri] = (source, source.get_connection())
             self._cursors.pop(source.uri, None)
--- a/server/querier.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/querier.py	Fri Feb 26 17:39:33 2010 +0100
@@ -12,7 +12,7 @@
 
 from logilab.common.cache import Cache
 from logilab.common.compat import any
-from rql import RQLHelper, RQLSyntaxError
+from rql import RQLSyntaxError
 from rql.stmts import Union, Select
 from rql.nodes import Relation, VariableRef, Constant, SubQuery
 
@@ -26,7 +26,7 @@
 
 READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
 
-def empty_rset(session, rql, args, rqlst=None):
+def empty_rset(rql, args, rqlst=None):
     """build an empty result set object"""
     return ResultSet([], rql, args, rqlst=rqlst)
 
@@ -210,7 +210,6 @@
             self.cache_key = None
 
     def _insert_security(self, union, noinvariant):
-        rh = self.rqlhelper
         for select in union.children[:]:
             for subquery in select.with_:
                 self._insert_security(subquery.query, noinvariant)
@@ -572,7 +571,7 @@
         """execute a rql query, return resulting rows and their description in
         a `ResultSet` object
 
-        * `rql` should be an unicode string or a plain ascii string
+        * `rql` should be an Unicode string or a plain ASCII string
         * `args` the optional parameters dictionary associated to the query
         * `build_descr` is a boolean flag indicating if the description should
           be built on select queries (if false, the description will be en empty
@@ -580,17 +579,17 @@
         * `eid_key` must be both a key in args and a substitution in the rql
           query. It should be used to enhance cacheability of rql queries.
           It may be a tuple for keys in args.
-          eid_key must be providen in case where a eid substitution is providen
-          and resolve some ambiguity in the possible solutions infered for each
+          `eid_key` must be provided in cases where a eid substitution is provided
+          and resolves ambiguities in the possible solutions inferred for each
           variable in the query.
 
-        on INSERT queries, there will be on row with the eid of each inserted
+        on INSERT queries, there will be one row with the eid of each inserted
         entity
 
         result for DELETE and SET queries is undefined yet
 
         to maximize the rql parsing/analyzing cache performance, you should
-        always use substitute arguments in queries (eg avoid query such as
+        always use substitute arguments in queries (i.e. avoid query such as
         'Any X WHERE X eid 123'!)
         """
         if server.DEBUG & (server.DBG_RQL | server.DBG_SQL):
@@ -613,7 +612,7 @@
                 except UnknownEid:
                     # we want queries such as "Any X WHERE X eid 9999"
                     # return an empty result instead of raising UnknownEid
-                    return empty_rset(session, rql, args)
+                    return empty_rset(rql, args)
                 cachekey.append(etype)
                 # ensure eid is correctly typed in args
                 args[key] = typed_eid(args[key])
@@ -631,7 +630,7 @@
             except UnknownEid:
                 # we want queries such as "Any X WHERE X eid 9999"
                 # return an empty result instead of raising UnknownEid
-                return empty_rset(session, rql, args, rqlst)
+                return empty_rset(rql, args, rqlst)
             self._rql_cache[cachekey] = rqlst
         orig_rqlst = rqlst
         if not rqlst.TYPE == 'select':
--- a/server/repository.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/repository.py	Fri Feb 26 17:39:33 2010 +0100
@@ -19,7 +19,7 @@
 
 import sys
 import Queue
-from os.path import join, exists
+from os.path import join
 from datetime import datetime
 from time import time, localtime, strftime
 
@@ -29,7 +29,7 @@
 from yams import BadSchemaDefinition
 from rql import RQLSyntaxError
 
-from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, CW_EVENT_MANAGER,
+from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
                       UnknownEid, AuthenticationError, ExecutionError,
                       ETypeNotSupportedBySources, MultiSourcesError,
                       BadConnectionId, Unauthorized, ValidationError,
@@ -101,14 +101,12 @@
     this kind of behaviour has to be done in the repository so we don't have
     hooks order hazardness
     """
-    # skip delete queries (only?) if session is an internal session. This is
-    # hooks responsability to ensure they do not violate relation's cardinality
-    if session.is_super_session:
+    # XXX now that rql in migraction default to unsafe_execute we don't want to
+    #     skip that for super session (though we can still skip it for internal
+    #     sessions). Also we should imo rely on the orm to first fetch existing
+    #     entity if any then delete it.
+    if session.is_internal_session:
         return
-    ensure_card_respected(session.unsafe_execute, session, eidfrom, rtype, eidto)
-
-
-def ensure_card_respected(execute, session, eidfrom, rtype, eidto):
     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
     # relation even with '1?'  cardinality if thoses relations are added in the
@@ -116,14 +114,28 @@
     # the web interface but may occurs during test or dbapi connection (though
     # not expected for this).  So: don't do it, we pretend to ensure repository
     # consistency.
+    #
+    # XXX we don't want read permissions to be applied but we want delete
+    # permission to be checked
+    rschema = session.repo.schema.rschema(rtype)
     if card[0] in '1?':
-        rschema = session.repo.schema.rschema(rtype)
-        if not rschema.inlined:
-            execute('DELETE X %s Y WHERE X eid %%(x)s,NOT Y eid %%(y)s' % rtype,
-                    {'x': eidfrom, 'y': eidto}, 'x')
+        if not rschema.inlined: # inlined relations will be implicitly deleted
+            rset = session.unsafe_execute('Any X,Y WHERE X %s Y, X eid %%(x)s, '
+                                          'NOT Y eid %%(y)s' % rtype,
+                                          {'x': eidfrom, 'y': eidto}, 'x')
+            if rset:
+                safe_delete_relation(session, rschema, *rset[0])
     if card[1] in '1?':
-        execute('DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype,
-                {'x': eidfrom, 'y': eidto}, 'y')
+        rset = session.unsafe_execute('Any X,Y WHERE X %s Y, Y eid %%(y)s, '
+                                      'NOT X eid %%(x)s' % rtype,
+                                      {'x': eidfrom, 'y': eidto}, 'y')
+        if rset:
+            safe_delete_relation(session, rschema, *rset[0])
+
+def safe_delete_relation(session, rschema, subject, object):
+    if not rschema.has_perm(session, 'delete', fromeid=subject, toeid=object):
+        raise Unauthorized()
+    session.repo.glob_delete_relation(session, subject, rschema.type, object)
 
 
 class Repository(object):
@@ -316,8 +328,8 @@
             return self._available_pools.get(True, timeout=5)
         except Queue.Empty:
             raise Exception('no pool available after 5 secs, probably either a '
-                            'bug in code (to many uncommited/rollbacked '
-                            'connections) or to much load on the server (in '
+                            'bug in code (too many uncommited/rollbacked '
+                            'connections) or too much load on the server (in '
                             'which case you can try to set a bigger '
                             'connections pools size)')
 
@@ -371,6 +383,23 @@
         except ZeroDivisionError:
             pass
 
+    def stats(self): # XXX restrict to managers session?
+        import threading
+        results = {}
+        for hits, misses, title in (
+            (self.querier.cache_hit, self.querier.cache_miss, 'rqlt_st'),
+            (self.system_source.cache_hit, self.system_source.cache_miss, 'sql'),
+            ):
+            results['%s_cache_hit' % title] =  hits
+            results['%s_cache_miss' % title] = misses
+            results['%s_cache_hit_percent' % title] = (hits * 100) / (hits + misses)
+        results['sql_no_cache'] = self.system_source.no_cache
+        results['nb_open_sessions'] = len(self._sessions)
+        results['nb_active_threads'] = threading.activeCount()
+        results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks)
+        results['available_pools'] = self._available_pools.qsize()
+        return results
+
     def _login_from_email(self, login):
         session = self.internal_session()
         try:
@@ -435,7 +464,8 @@
         public method, not requiring a session id.
         """
         versions = self.get_versions(not (self.config.creating
-                                          or self.config.repairing))
+                                          or self.config.repairing
+                                          or self.config.mode == 'test'))
         cubes = list(versions)
         cubes.remove('cubicweb')
         return cubes
--- a/server/rqlannotation.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/rqlannotation.py	Fri Feb 26 17:39:33 2010 +0100
@@ -30,7 +30,7 @@
                     need_distinct = True
                     # XXX could mark as not invariant
                     break
-    for name, var in rqlst.defined_vars.items():
+    for var in rqlst.defined_vars.itervalues():
         stinfo = var.stinfo
         if stinfo.get('ftirels'):
             has_text_query = True
@@ -130,7 +130,8 @@
 
 
 
-class CantSelectPrincipal(Exception): pass
+class CantSelectPrincipal(Exception):
+    """raised when no 'principal' variable can be found"""
 
 def _select_principal(sqlscope, relations, _sort=lambda x:x):
     """given a list of rqlst relations, select one which will be used to
@@ -139,13 +140,11 @@
     """
     # _sort argument is there for test
     diffscope_rels = {}
-    has_same_scope_rel = False
     ored_rels = set()
     diffscope_rels = set()
     for rel in _sort(relations):
         # note: only eid and has_text among all final relations may be there
         if rel.r_type in ('eid', 'identity'):
-            has_same_scope_rel = rel.sqlscope is sqlscope
             continue
         if rel.ored(traverse_scope=True):
             ored_rels.add(rel)
--- a/server/schemaserial.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/schemaserial.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,8 +8,6 @@
 __docformat__ = "restructuredtext en"
 
 import os
-import sys
-import os
 from itertools import chain
 
 from logilab.common.shellutils import ProgressBar
@@ -284,23 +282,19 @@
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-# XXX 2.47 migration
-HAS_FULLTEXT_CONTAINER = True
-
 def rschema_relations_values(rschema):
     values = _ervalues(rschema)
     values['final'] = rschema.final
     values['symmetric'] = rschema.symmetric
     values['inlined'] = rschema.inlined
-    if HAS_FULLTEXT_CONTAINER:
-        if isinstance(rschema.fulltext_container, str):
-            values['fulltext_container'] = unicode(rschema.fulltext_container)
-        else:
-            values['fulltext_container'] = rschema.fulltext_container
+    if isinstance(rschema.fulltext_container, str):
+        values['fulltext_container'] = unicode(rschema.fulltext_container)
+    else:
+        values['fulltext_container'] = rschema.fulltext_container
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-def _rdef_values(rschema, objtype, props):
+def _rdef_values(objtype, props):
     amap = {'order': 'ordernum'}
     values = {}
     for prop, default in schemamod.RelationDefinitionSchema.rproperty_defs(objtype).iteritems():
@@ -316,13 +310,13 @@
         values[amap.get(prop, prop)] = value
     return values
 
-def nfrdef_relations_values(rschema, objtype, props):
-    values = _rdef_values(rschema, objtype, props)
+def nfrdef_relations_values(objtype, props):
+    values = _rdef_values(objtype, props)
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-def frdef_relations_values(rschema, objtype, props):
-    values = _rdef_values(rschema, objtype, props)
+def frdef_relations_values(objtype, props):
+    values = _rdef_values(objtype, props)
     default = values['default']
     del values['default']
     if default is not None:
@@ -435,7 +429,7 @@
 _LOCATE_RDEF_RQL1 = 'SE name %(se)s,ER name %(rt)s,OE name %(oe)s'
 
 def frdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(rschema, objtype, props)
+    relations, values = frdef_relations_values(objtype, props)
     relations.append(_LOCATE_RDEF_RQL0)
     values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
     yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
@@ -443,7 +437,7 @@
         yield rql + ', EDEF is CWAttribute', values
 
 def nfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(rschema, objtype, props)
+    relations, values = nfrdef_relations_values(objtype, props)
     relations.append(_LOCATE_RDEF_RQL0)
     values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
     yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
@@ -472,7 +466,12 @@
     and CWGroup entities
     """
     for action in erschema.ACTIONS:
-        for group_or_rqlexpr in erschema.action_permissions(action):
+        try:
+            grantedto = erschema.action_permissions(action)
+        except KeyError:
+            # may occurs when modifying persistent schema
+            continue
+        for group_or_rqlexpr in grantedto:
             if isinstance(group_or_rqlexpr, basestring):
                 # group
                 try:
@@ -505,14 +504,14 @@
     return __rdef2rql(genmap, rschema, subjtype, objtype, props)
 
 def updatefrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(rschema, objtype, props)
+    relations, values = frdef_relations_values(objtype, props)
     values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
     yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations),
                                                      _LOCATE_RDEF_RQL0,
                                                      _LOCATE_RDEF_RQL1), values
 
 def updatenfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(rschema, objtype, props)
+    relations, values = nfrdef_relations_values(objtype, props)
     values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
     yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations),
                                                     _LOCATE_RDEF_RQL0,
--- a/server/serverconfig.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/serverconfig.py	Fri Feb 26 17:39:33 2010 +0100
@@ -7,14 +7,12 @@
 """
 __docformat__ = "restructuredtext en"
 
-import os
 from os.path import join, exists
 
 from logilab.common.configuration import REQUIRED, Method, Configuration, \
      ini_format_section
 from logilab.common.decorators import wproperty, cached, clear_cache
 
-from cubicweb import CW_SOFTWARE_ROOT, RegistryNotFound
 from cubicweb.toolsutils import read_config, restrict_perms_to_user
 from cubicweb.cwconfig import CubicWebConfiguration, merge_options
 from cubicweb.server import SOURCE_TYPES
--- a/server/serverctl.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/serverctl.py	Fri Feb 26 17:39:33 2010 +0100
@@ -7,6 +7,9 @@
 """
 __docformat__ = 'restructuredtext en'
 
+# *ctl module should limit the number of import to be imported as quickly as
+# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
+# completion). So import locally in command helpers.
 import sys
 import os
 
@@ -17,7 +20,6 @@
 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
 from cubicweb.toolsutils import Command, CommandHandler, underline_title
 from cubicweb.server import SOURCE_TYPES
-from cubicweb.server.utils import ask_source_config
 from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
                                           SourceConfiguration)
 
@@ -132,6 +134,7 @@
         """create an instance by copying files from the given cube and by
         asking information necessary to build required configuration files
         """
+        from cubicweb.server.utils import ask_source_config
         config = self.config
         print underline_title('Configuring the repository')
         config.input_config('email', inputlevel)
@@ -443,7 +446,6 @@
 
     def run(self, args):
         """run the command with its specific arguments"""
-        from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
         from cubicweb.server.utils import crypt_password, manager_userpasswd
         appid = pop_arg(args, 1, msg='No instance specified !')
         config = ServerConfiguration.config_for(appid)
@@ -455,13 +457,21 @@
             sys.exit(1)
         cnx = source_cnx(sourcescfg['system'])
         cursor = cnx.cursor()
+        # check admin exists
+        cursor.execute("SELECT * FROM cw_CWUser WHERE cw_login=%(l)s",
+                       {'l': adminlogin})
+        if not cursor.fetchall():
+            print ("-> error: admin user %r specified in sources doesn't exist "
+                   "in the database" % adminlogin)
+            print "   fix your sources file before running this command"
+            cnx.close()
+            sys.exit(1)
+        # ask for a new password
         _, passwd = manager_userpasswd(adminlogin, confirm=True,
                                        passwdmsg='new password for %s' % adminlogin)
         try:
-            sqlexec("UPDATE %(sp)sCWUser SET %(sp)supassword='%(p)s' WHERE %(sp)slogin='%(l)s'"
-                    % {'sp': SQL_PREFIX,
-                       'p': crypt_password(passwd), 'l': adminlogin},
-                    cursor, withpb=False)
+            cursor.execute("UPDATE cw_CWUser SET cw_upassword=%(p)s WHERE cw_login=%(l)s",
+                           {'p': crypt_password(passwd), 'l': adminlogin})
             sconfig = Configuration(options=USER_OPTIONS)
             sconfig['login'] = adminlogin
             sconfig['password'] = passwd
@@ -475,6 +485,7 @@
         else:
             cnx.commit()
             print '-> password reset, sources file regenerated.'
+        cnx.close()
 
 
 class StartRepositoryCommand(Command):
--- a/server/session.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/session.py	Fri Feb 26 17:39:33 2010 +0100
@@ -71,9 +71,9 @@
         self._threads_in_transaction = set()
         self._closed = False
 
-    def __str__(self):
-        return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
-                                             self.id, id(self))
+    def __unicode__(self):
+        return '<%ssession %s (%s 0x%x)>' % (
+            self.cnxtype, unicode(self.user.login), self.id, id(self))
 
     def hijack_user(self, user):
         """return a fake request/session using specified user"""
@@ -225,7 +225,7 @@
                 self.pgettext = pgettext
             except KeyError:
                 self._ = self.__ = unicode
-                self.pgettext = lambda x,y: y
+                self.pgettext = lambda x, y: y
         self.lang = language
 
     def change_property(self, prop, value):
@@ -314,7 +314,7 @@
             except KeyError:
                 pass
             pool.pool_reset()
-            self._threaddata.pool = None
+            del self._threaddata.pool
             # free pool once everything is done to avoid race-condition
             self.repo._free_pool(pool)
 
@@ -410,13 +410,13 @@
     @property
     def super_session(self):
         try:
-            csession = self._threaddata.childsession
+            csession = self.childsession
         except AttributeError:
             if isinstance(self, (ChildSession, InternalSession)):
                 csession = self
             else:
                 csession = ChildSession(self)
-            self._threaddata.childsession = csession
+            self.childsession = csession
         # need shared pool set
         self.set_pool(checkclosed=False)
         return csession
@@ -443,11 +443,24 @@
         rset = self._execute(self, rql, kwargs, eid_key, build_descr)
         return self.decorate_rset(rset, propagate)
 
+    def _clear_thread_data(self):
+        """remove everything from the thread local storage, except pool
+        which is explicitly removed by reset_pool, and mode which is set anyway
+        by _touch
+        """
+        store = self._threaddata
+        for name in ('commit_state', 'transaction_data', 'pending_operations',
+                     '_rewriter'):
+            try:
+                delattr(store, name)
+            except AttributeError:
+                pass
+
     def commit(self, reset_pool=True):
         """commit the current session's transaction"""
         if self.pool is None:
             assert not self.pending_operations
-            self.transaction_data.clear()
+            self._clear_thread_data()
             self._touch()
             self.debug('commit session %s done (no db activity)', self.id)
             return
@@ -501,10 +514,8 @@
                                   exc_info=sys.exc_info())
             self.info('%s session %s done', trstate, self.id)
         finally:
+            self._clear_thread_data()
             self._touch()
-            self.commit_state = None
-            self.pending_operations[:] = []
-            self.transaction_data.clear()
             if reset_pool:
                 self.reset_pool(ignoremode=True)
 
@@ -512,7 +523,7 @@
         """rollback the current session's transaction"""
         if self.pool is None:
             assert not self.pending_operations
-            self.transaction_data.clear()
+            self._clear_thread_data()
             self._touch()
             self.debug('rollback session %s done (no db activity)', self.id)
             return
@@ -527,9 +538,8 @@
             self.pool.rollback()
             self.debug('rollback for session %s done', self.id)
         finally:
+            self._clear_thread_data()
             self._touch()
-            self.pending_operations[:] = []
-            self.transaction_data.clear()
             if reset_pool:
                 self.reset_pool(ignoremode=True)
 
@@ -584,6 +594,7 @@
 
     @property
     def rql_rewriter(self):
+        # in thread local storage since the rewriter isn't thread safe
         try:
             return self._threaddata._rewriter
         except AttributeError:
--- a/server/sources/ldapuser.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/sources/ldapuser.py	Fri Feb 26 17:39:33 2010 +0100
@@ -210,11 +210,11 @@
                 if res:
                     ldapemailaddr = res[0].get(ldap_emailattr)
                     if ldapemailaddr:
-                        rset = session.execute('EmailAddress X,A WHERE '
+                        rset = session.execute('EmailAddress A WHERE '
                                                'U use_email X, U eid %(u)s',
                                                {'u': eid})
                         ldapemailaddr = unicode(ldapemailaddr)
-                        for emaileid, emailaddr in rset:
+                        for emailaddr, in rset:
                             if emailaddr == ldapemailaddr:
                                 break
                         else:
@@ -448,12 +448,12 @@
     def _auth_cram_md5(self, conn, user, userpwd):
         from ldap import sasl
         auth_token = sasl.cram_md5(user['dn'], userpwd)
-        conn.sasl_interactive_bind_s('', auth_tokens)
+        conn.sasl_interactive_bind_s('', auth_token)
 
     def _auth_digest_md5(self, conn, user, userpwd):
         from ldap import sasl
         auth_token = sasl.digest_md5(user['dn'], userpwd)
-        conn.sasl_interactive_bind_s('', auth_tokens)
+        conn.sasl_interactive_bind_s('', auth_token)
 
     def _auth_gssapi(self, conn, user, userpwd):
         # print XXX not proper sasl/gssapi
--- a/server/sources/native.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/sources/native.py	Fri Feb 26 17:39:33 2010 +0100
@@ -421,12 +421,16 @@
             # str(query) to avoid error if it's an unicode string
             cursor.execute(str(query), args)
         except Exception, ex:
-            self.critical("sql: %r\n args: %s\ndbms message: %r",
-                          query, args, ex.args[0])
+            if self.repo.config.mode != 'test':
+                # during test we get those message when trying to alter sqlite
+                # db schema
+                self.critical("sql: %r\n args: %s\ndbms message: %r",
+                              query, args, ex.args[0])
             if rollback:
                 try:
                     session.pool.connection(self.uri).rollback()
-                    self.critical('transaction has been rollbacked')
+                    if self.repo.config.mode != 'test':
+                        self.critical('transaction has been rollbacked')
                 except:
                     pass
             raise
@@ -443,11 +447,15 @@
             # str(query) to avoid error if it's an unicode string
             cursor.executemany(str(query), args)
         except Exception, ex:
-            self.critical("sql many: %r\n args: %s\ndbms message: %r",
-                          query, args, ex.args[0])
+            if self.repo.config.mode != 'test':
+                # during test we get those message when trying to alter sqlite
+                # db schema
+                self.critical("sql many: %r\n args: %s\ndbms message: %r",
+                              query, args, ex.args[0])
             try:
                 session.pool.connection(self.uri).rollback()
-                self.critical('transaction has been rollbacked')
+                if self.repo.config.mode != 'test':
+                    self.critical('transaction has been rollbacked')
             except:
                 pass
             raise
--- a/server/sources/rql2sql.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/sources/rql2sql.py	Fri Feb 26 17:39:33 2010 +0100
@@ -38,7 +38,6 @@
 from rql.nodes import (SortTerm, VariableRef, Constant, Function, Not,
                        Variable, ColumnAlias, Relation, SubQuery, Exists)
 
-from cubicweb import server
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import cleanup_solutions
 
--- a/server/sources/storages.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/sources/storages.py	Fri Feb 26 17:39:33 2010 +0100
@@ -62,7 +62,7 @@
             else:
                 fpath = self.new_fs_path(entity, attr)
                 # bytes storage used to store file's path
-                entity[attr]= Binary(fpath)
+                entity[attr] = Binary(fpath)
                 file(fpath, 'w').write(value.getvalue())
                 AddFileOp(entity._cw, filepath=fpath)
         # else entity[attr] is expected to be an already existant file path
--- a/server/sqlutils.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/sqlutils.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,9 +9,7 @@
 
 import os
 import subprocess
-from os.path import exists
-from warnings import warn
-from datetime import datetime, date, timedelta
+from datetime import datetime, date
 
 import logilab.common as lgc
 from logilab.common import db
@@ -24,7 +22,6 @@
 
 from cubicweb import Binary, ConfigurationError
 from cubicweb.uilib import remove_html_tags
-from cubicweb.toolsutils import restrict_perms_to_user
 from cubicweb.schema import PURE_VIRTUAL_RTYPES
 from cubicweb.server import SQL_CONNECT_HOOKS
 from cubicweb.server.utils import crypt_password
--- a/server/test/data/schema.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/data/schema.py	Fri Feb 26 17:39:33 2010 +0100
@@ -137,7 +137,7 @@
 class para(RelationType):
     __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
-        'update':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
+        'update': ('managers', ERQLExpression('X in_state S, S name "todo"')),
         }
 
 class test(RelationType):
--- a/server/test/unittest_checkintegrity.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_checkintegrity.py	Fri Feb 26 17:39:33 2010 +0100
@@ -20,7 +20,7 @@
         sys.stderr = sys.stdout = StringIO()
         try:
             check(repo, cnx, ('entities', 'relations', 'text_index', 'metadata'),
-                  True, True)
+                  reindex=True, fix=True, withpb=False)
         finally:
             sys.stderr = sys.__stderr__
             sys.stdout = sys.__stdout__
--- a/server/test/unittest_migractions.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_migractions.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,26 +9,18 @@
 from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb import ConfigurationError
-from cubicweb.devtools.testlib import CubicWebTC, get_versions
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.schema import CubicWebSchemaLoader
 from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.repository import Repository
 from cubicweb.server.migractions import *
 
-orig_get_versions = Repository.get_versions
-
-def setup_module(*args):
-    Repository.get_versions = get_versions
-
-def teardown_module(*args):
-    Repository.get_versions = orig_get_versions
-
 
 class MigrationCommandsTC(CubicWebTC):
 
     @classmethod
     def init_config(cls, config):
         super(MigrationCommandsTC, cls).init_config(config)
+        # we have to read schema from the database to get eid for schema entities
         config._cubes = None
         cls.repo.fill_schema()
         cls.origschema = deepcopy(cls.repo.schema)
@@ -42,7 +34,7 @@
     @classmethod
     def _refresh_repo(cls):
         super(MigrationCommandsTC, cls)._refresh_repo()
-        cls.repo.schema = cls.vreg.schema = deepcopy(cls.origschema)
+        cls.repo.set_schema(deepcopy(cls.origschema), resetvreg=False)
 
     def setUp(self):
         CubicWebTC.setUp(self)
@@ -157,7 +149,7 @@
                           sorted(str(e) for e in self.schema.entities() if not e.final))
         self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
         eschema = self.schema.eschema('Folder2')
-        for cstr in eschema.constraints('name'):
+        for cstr in eschema.rdef('name').constraints:
             self.failUnless(hasattr(cstr, 'eid'))
 
     def test_add_drop_entity_type(self):
@@ -281,7 +273,7 @@
     def test_sync_schema_props_perms(self):
         cursor = self.mh.session
         cursor.set_pool()
-        nbrqlexpr_start = len(cursor.execute('RQLExpression X'))
+        nbrqlexpr_start = cursor.execute('Any COUNT(X) WHERE X is RQLExpression')[0][0]
         migrschema['titre'].rdefs[('Personne', 'String')].order = 7
         migrschema['adel'].rdefs[('Personne', 'String')].order = 6
         migrschema['ass'].rdefs[('Personne', 'String')].order = 5
@@ -345,19 +337,22 @@
         self.assertEquals(len(self._erqlexpr_rset('delete', 'Affaire')), 1)
         self.assertEquals(len(self._erqlexpr_rset('add', 'Affaire')), 1)
         # no change for rqlexpr to add and delete concerne relation
-        for rdef in self.schema['concerne'].rdefs.values():
-            print rdef, rdef.permissions
         self.assertEquals(len(self._rrqlexpr_rset('delete', 'concerne')), len(delete_concerne_rqlexpr))
         self.assertEquals(len(self._rrqlexpr_rset('add', 'concerne')), len(add_concerne_rqlexpr))
         # * migrschema involve:
-        #   * 8 deletion (2 in Affaire read + Societe + travaille + para rqlexprs)
+        #   * 7 rqlexprs deletion (2 in (Affaire read + Societe + travaille) + 1
+        #     in para attribute)
         #   * 1 update (Affaire update)
         #   * 2 new (Note add, ecrit_par add)
+        #   * 2 implicit new for attributes update_permission (Note.para, Personne.test)
         # remaining orphan rql expr which should be deleted at commit (composite relation)
-        self.assertEquals(len(cursor.execute('RQLExpression X WHERE NOT ET1 read_permission X, NOT ET2 add_permission X, '
-                                             'NOT ET3 delete_permission X, NOT ET4 update_permission X')), 8+1)
+        self.assertEquals(cursor.execute('Any COUNT(X) WHERE X is RQLExpression, '
+                                         'NOT ET1 read_permission X, NOT ET2 add_permission X, '
+                                         'NOT ET3 delete_permission X, NOT ET4 update_permission X')[0][0],
+                          7+1)
         # finally
-        self.assertEquals(len(cursor.execute('RQLExpression X')), nbrqlexpr_start + 1 + 2)
+        self.assertEquals(cursor.execute('Any COUNT(X) WHERE X is RQLExpression')[0][0],
+                          nbrqlexpr_start + 1 + 2 + 2)
 
         self.mh.rollback()
 
--- a/server/test/unittest_msplanner.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_msplanner.py	Fri Feb 26 17:39:33 2010 +0100
@@ -5,7 +5,6 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from logilab.common.decorators import clear_cache
 from cubicweb.devtools import init_test_database
 from cubicweb.devtools.repotest import BasePlannerTC, test_plan
 
@@ -78,12 +77,12 @@
         self.prevrqlexpr_affaire = affreadperms[-1]
         # add access to type attribute so S can't be invariant
         affreadperms[-1] = ERQLExpression('X concerne S?, S owned_by U, S type "X"')
-        self.schema['Affaire'].permissions['read'] = tuple(affreadperms)
+        self.schema['Affaire'].set_action_permissions('read', affreadperms)
         # hijack CWUser security
         userreadperms = list(self.schema['CWUser'].permissions['read'])
         self.prevrqlexpr_user = userreadperms[-1]
         userreadperms[-1] = ERQLExpression('X owned_by U')
-        self.schema['CWUser'].permissions['read'] = tuple(userreadperms)
+        self.schema['CWUser'].set_action_permissions('read', userreadperms)
         self.add_source(FakeUserROSource, 'ldap')
         self.add_source(FakeCardSource, 'cards')
 
@@ -96,9 +95,7 @@
     def restore_orig_affaire_security(self):
         affreadperms = list(self.schema['Affaire'].permissions['read'])
         affreadperms[-1] = self.prevrqlexpr_affaire
-        self.schema['Affaire'].permissions['read'] = tuple(affreadperms)
-        clear_cache(self.schema['Affaire'], 'get_rqlexprs')
-        #clear_cache(self.schema['Affaire'], 'get_groups')
+        self.schema['Affaire'].set_action_permissions('read', affreadperms)
 
     def restore_orig_cwuser_security(self):
         if hasattr(self, '_orig_cwuser_security_restored'):
@@ -106,9 +103,7 @@
         self._orig_cwuser_security_restored = True
         userreadperms = list(self.schema['CWUser'].permissions['read'])
         userreadperms[-1] = self.prevrqlexpr_user
-        self.schema['CWUser'].permissions['read'] = tuple(userreadperms)
-        clear_cache(self.schema['CWUser'], 'get_rqlexprs')
-        #clear_cache(self.schema['CWUser'], 'get_groups')
+        self.schema['CWUser'].set_action_permissions('read', userreadperms)
 
 
 class PartPlanInformationTC(BaseMSPlannerTC):
@@ -762,7 +757,7 @@
 
     def test_security_has_text(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X WHERE X has_text "bla"',
                    [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
                      [self.cards, self.system], None, {'E': 'table0.C0'}, []),
@@ -789,7 +784,7 @@
 
     def test_security_has_text_limit_offset(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         # note: same as the above query but because of the subquery usage, the display differs (not printing solutions for each union)
         self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla"',
                    [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
@@ -827,7 +822,7 @@
     def test_security_user(self):
         """a guest user trying to see another user: EXISTS(X owned_by U) is automatically inserted"""
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X WHERE X login "bla"',
                    [('FetchStep',
                      [('Any X WHERE X login "bla", X is CWUser', [{'X': 'CWUser'}])],
@@ -838,7 +833,7 @@
 
     def test_security_complex_has_text(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X WHERE X has_text "bla", X firstname "bla"',
                    [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table0.C0'}, []),
@@ -852,7 +847,7 @@
 
     def test_security_complex_has_text_limit_offset(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla", X firstname "bla"',
                    [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table1.C0'}, []),
@@ -869,7 +864,7 @@
 
     def test_security_complex_aggregat(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any MAX(X)',
                    [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
                      [self.cards, self.system],  None, {'E': 'table1.C0'}, []),
@@ -914,7 +909,7 @@
 
     def test_security_complex_aggregat2(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         X_ET_ALL_SOLS = []
         for s in X_ALL_SOLS:
             ets = {'ET': 'CWEType'}
@@ -978,7 +973,7 @@
 
     def test_security_3sources(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
                    [('FetchStep',
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -996,8 +991,7 @@
     def test_security_3sources_identity(self):
         self.restore_orig_cwuser_security()
         # use a guest user
-        self.session = self._user_session()[1]
-        print self.session
+        self.session = self.user_groups_session('guests')
         self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
                    [('FetchStep',
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -1011,7 +1005,7 @@
     def test_security_3sources_identity_optional_var(self):
         self.restore_orig_cwuser_security()
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L',
                    [('FetchStep',
                      [('Any U,L WHERE U identity 5, U login L, U is CWUser',
@@ -1032,7 +1026,7 @@
 
     def test_security_3sources_limit_offset(self):
         # use a guest user
-        self.session = self._user_session()[1]
+        self.session = self.user_groups_session('guests')
         self._test('Any X, XT LIMIT 10 OFFSET 10 WHERE X is Card, X owned_by U, X title XT, U login "syt"',
                    [('FetchStep',
                      [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -1668,7 +1662,7 @@
                     ])
 
     def test_update3(self):
-        anoneid = self._user_session()[1].user.eid
+        anoneid = self.user_groups_session('guests').user.eid
         # since we are adding a in_state relation for an entity in the system
         # source, states should only be searched in the system source as well
         self._test('SET X in_state S WHERE X eid %(x)s, S name "deactivated"',
--- a/server/test/unittest_multisources.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_multisources.py	Fri Feb 26 17:39:33 2010 +0100
@@ -30,13 +30,25 @@
 repo2, cnx2 = init_test_database(config=ExternalSource1Configuration('data'))
 repo3, cnx3 = init_test_database(config=ExternalSource2Configuration('data'))
 
-# XXX, access existing connection, no pyro connection
+# hi-jacking
 from cubicweb.server.sources.pyrorql import PyroRQLSource
-PyroRQLSource.get_connection = lambda x: x.uri == 'extern-multi' and cnx3 or cnx2
-# necessary since the repository is closing its initial connections pool though
-# we want to keep cnx2 valid
 from cubicweb.dbapi import Connection
-Connection.close = lambda x: None
+
+PyroRQLSource_get_connection = PyroRQLSource.get_connection
+Connection_close = Connection.close
+
+def setup_module(*args):
+    # hi-jack PyroRQLSource.get_connection to access existing connection (no
+    # pyro connection)
+    PyroRQLSource.get_connection = lambda x: x.uri == 'extern-multi' and cnx3 or cnx2
+    # also necessary since the repository is closing its initial connections
+    # pool though we want to keep cnx2 valid
+    Connection.close = lambda x: None
+
+def teardown_module(*args):
+    PyroRQLSource.get_connection = PyroRQLSource_get_connection
+    Connection.close = Connection_close
+
 
 class TwoSourcesTC(CubicWebTC):
     config = TwoSourcesConfiguration('data')
--- a/server/test/unittest_querier.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_querier.py	Fri Feb 26 17:39:33 2010 +0100
@@ -78,7 +78,7 @@
     def test_preprocess_security(self):
         plan = self._prepare_plan('Any ETN,COUNT(X) GROUPBY ETN '
                                   'WHERE X is ET, ET name ETN')
-        plan.session = self._user_session(('users',))[1]
+        plan.session = self.user_groups_session('users')
         union = plan.rqlst
         plan.preprocess(union)
         self.assertEquals(len(union.children), 1)
@@ -158,7 +158,7 @@
 
     def test_preprocess_security_aggregat(self):
         plan = self._prepare_plan('Any MAX(X)')
-        plan.session = self._user_session(('users',))[1]
+        plan.session = self.user_groups_session('users')
         union = plan.rqlst
         plan.preprocess(union)
         self.assertEquals(len(union.children), 1)
@@ -257,7 +257,7 @@
         result, descr = rset.rows, rset.description
         self.assertEquals(descr[0][0], 'String')
         self.assertEquals(descr[0][1], 'Int')
-        self.assertEquals(result[0][0], 'CWRelation')
+        self.assertEquals(result[0][0], 'RQLExpression') # XXX may change as schema evolve
 
     def test_select_groupby_orderby(self):
         rset = self.execute('Any N GROUPBY N ORDERBY N WHERE X is CWGroup, X name N')
@@ -928,7 +928,7 @@
         self.assertEqual(len(rset.rows), 0, rset.rows)
 
     def test_delete_3(self):
-        u, s = self._user_session(('users',))
+        s = self.user_groups_session('users')
         peid, = self.o.execute(s, "INSERT Personne P: P nom 'toto'")[0]
         seid, = self.o.execute(s, "INSERT Societe S: S nom 'logilab'")[0]
         self.o.execute(s, "SET P travaille S")
@@ -959,7 +959,7 @@
         (using cachekey on sql generation returned always the same query for an eid,
         whatever the relation)
         """
-        u, s = self._user_session(('users',))
+        s = self.user_groups_session('users')
         aeid, = self.o.execute(s, 'INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')[0]
         # XXX would be nice if the rql below was enough...
         #'INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y'
--- a/server/test/unittest_repository.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_repository.py	Fri Feb 26 17:39:33 2010 +0100
@@ -215,7 +215,7 @@
                                if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
                                                  'creation_date', 'modification_date', 'cwuri',
                                                  'owned_by', 'created_by',
-                                                 'add_permission', 'delete_permission', 'read_permission')],
+                                                 'update_permission', 'read_permission')],
                               ['relation_type',
                                'from_entity', 'to_entity',
                                'in_basket', 'constrained_by', 
--- a/server/test/unittest_schemaserial.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_schemaserial.py	Fri Feb 26 17:39:33 2010 +0100
@@ -68,22 +68,17 @@
     def test_rschema2rql2(self):
         self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
                               [
-            ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
-
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWAttribute'}),
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWAttribute'}),
+            ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
 
             ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWEType'}),
+             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWEType'}),
             ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWEType'}),
+             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWEType'}),
 
             ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWRelation'}),
+             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWRelation'}),
             ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWRelation'}),
+             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWRelation'}),
             ])
 
     def test_rschema2rql3(self):
@@ -133,8 +128,8 @@
         expected = [
             ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
              {'rt': 'add_permission', 'symmetric': False,
-              'description': u'core relation giving to a group the permission to add an entity or relation type',
-              'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'})
+              'description': u'', 'final': False, 'fulltext_container': None,
+              'inlined': False, 'name': u'add_permission'})
             ]
         for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'))):
             yield self.assertEquals, (rql, args), expected[i]
@@ -172,10 +167,12 @@
                               [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
                                ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
                                ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
+                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                               ('INSERT RQLExpression E: '
+                                'E expression %(e)s, E exprtype %(t)s, E mainvars %(v)s, '
+                                'X update_permission E '
+                                'WHERE ', # completed by the outer function
+                                {'e': u'U has_update_permission X', 't': u'ERQLExpression', 'v': u'X'}),
                                ])
 
     #def test_perms2rql(self):
--- a/server/test/unittest_security.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/test/unittest_security.py	Fri Feb 26 17:39:33 2010 +0100
@@ -30,7 +30,7 @@
         rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
         origgroups = self.schema['Personne'].get_groups('read')
         self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
-        self.repo.vreg.rqlhelper.compute_solutions(rqlst)
+        self.repo.vreg.solutions(self.session, rqlst, None)
         solution = rqlst.solutions[0]
         check_read_access(self.schema, self.session.user, rqlst, solution)
         cnx = self.login('anon')
@@ -494,12 +494,13 @@
         # needed to avoid check_perm error
         session.set_pool()
         # needed to remove rql expr granting update perm to the user
+        affaire_perms = self.schema['Affaire'].permissions.copy()
         self.schema['Affaire'].set_action_permissions('update', self.schema['Affaire'].get_groups('update'))
-        self.assertRaises(Unauthorized,
-                          self.schema['Affaire'].check_perm, session, 'update', eid=eid)
-        cu = cnx.cursor()
-        self.schema['Affaire'].set_action_permissions('read', ('users',))
         try:
+            self.assertRaises(Unauthorized,
+                              self.schema['Affaire'].check_perm, session, 'update', eid=eid)
+            cu = cnx.cursor()
+            self.schema['Affaire'].set_action_permissions('read', ('users',))
             aff = cu.execute('Any X WHERE X ref "ARCT01"').get_entity(0, 0)
             aff.fire_transition('abort')
             cnx.commit()
@@ -510,7 +511,9 @@
             # from the current state but Unauthorized if it exists but user can't pass it
             self.assertRaises(ValidationError, user.fire_transition, 'deactivate')
         finally:
-            self.schema['Affaire'].set_action_permissions('read', ('managers',))
+            # restore orig perms
+            for action, perms in affaire_perms.iteritems():
+                self.schema['Affaire'].set_action_permissions(action, perms)
 
     def test_trinfo_security(self):
         aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
--- a/server/utils.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/server/utils.py	Fri Feb 26 17:39:33 2010 +0100
@@ -107,6 +107,9 @@
         self.func = auto_restart_func
         self.name = func.__name__
 
+    def __str__(self):
+        return '%s (%s seconds)' % (self.name, self.interval)
+
     def start(self):
         self._t = Timer(self.interval, self.func)
         self._t.start()
--- a/setup.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/setup.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-"""
+"""Generic Setup script, takes package info from __pkginfo__.py file
 
 :organization: Logilab
 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -8,9 +8,6 @@
 """
 # pylint: disable-msg=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
 # This program is free software; you can redistribute it and/or modify it under
 # the terms of the GNU General Public License as published by the Free Software
 # Foundation; either version 2 of the License, or (at your option) any later
@@ -23,7 +20,6 @@
 # You should have received a copy of the GNU General Public License along with
 # this program; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-""" Generic Setup script, takes package info from __pkginfo__.py file """
 
 import os
 import sys
--- a/skeleton/MANIFEST.in	Thu Feb 18 09:22:04 2010 +0100
+++ b/skeleton/MANIFEST.in	Fri Feb 26 17:39:33 2010 +0100
@@ -1,5 +1,4 @@
 include *.py
-
+include */*.py
 recursive-include data external_resources *.gif *.png *.css *.ico *.js
 recursive-include i18n *.pot *.po
-recursive-include migration *.py
--- a/sobjects/notification.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/sobjects/notification.py	Fri Feb 26 17:39:33 2010 +0100
@@ -11,7 +11,7 @@
 from itertools import repeat
 
 from logilab.common.textutils import normalize_text
-from logilab.common.deprecation import class_renamed, deprecated
+from logilab.common.deprecation import class_renamed, class_moved, deprecated
 
 from cubicweb.selectors import yes
 from cubicweb.view import Component
@@ -183,7 +183,6 @@
                                   entity.eid, self.user_data['login'])
 
 
-from logilab.common.deprecation import class_renamed, class_moved, deprecated
 from cubicweb.hooks.notification import RenderAndSendNotificationView
 from cubicweb.mail import parse_message_id
 
--- a/sobjects/test/unittest_email.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/sobjects/test/unittest_email.py	Fri Feb 26 17:39:33 2010 +0100
@@ -46,10 +46,10 @@
         self.commit()
         cnx = self.login('toto')
         cu = cnx.cursor()
-        cu.execute('SET U primary_email E WHERE E eid %(e)s, U login "toto"',
-                   {'e': email1})
-        self.assertRaises(Unauthorized, cnx.commit)
-
+        self.assertRaises(Unauthorized,
+                          cu.execute, 'SET U primary_email E WHERE E eid %(e)s, U login "toto"',
+                          {'e': email1})
+        cnx.close()
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/test/unittest_cwconfig.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/test/unittest_cwconfig.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,6 +9,7 @@
 import os
 from os.path import dirname, join, abspath
 
+from logilab.common.modutils import cleanup_sys_modules
 from logilab.common.testlib import TestCase, unittest_main
 from logilab.common.changelog import Version
 
@@ -21,8 +22,12 @@
             return '/'.join(parts[i+1:])
     raise Exception('duh? %s' % path)
 
+CUSTOM_CUBES_DIR = abspath(join(dirname(__file__), 'data', 'cubes'))
+
+
 class CubicWebConfigurationTC(TestCase):
     def setUp(self):
+        cleanup_sys_modules([CUSTOM_CUBES_DIR, ApptestConfiguration.CUBES_DIR])
         self.config = ApptestConfiguration('data')
         self.config._cubes = ('email', 'file')
 
@@ -86,16 +91,14 @@
         # make sure we don't import the email cube, but the stdlib email package
         import email
         self.assertNotEquals(dirname(email.__file__), self.config.CUBES_DIR)
-        os.environ['CW_CUBES_PATH'] = join(dirname(__file__), 'data', 'cubes')
+        os.environ['CW_CUBES_PATH'] = CUSTOM_CUBES_DIR
         self.assertEquals(self.config.cubes_search_path(),
-                          [abspath(join(dirname(__file__), 'data', 'cubes')),
-                           self.config.CUBES_DIR])
-        os.environ['CW_CUBES_PATH'] = '%s%s%s%s%s' % (join(dirname(__file__), 'data', 'cubes'),
-                                                      os.pathsep, self.config.CUBES_DIR,
-                                                      os.pathsep, 'unexistant')
+                          [CUSTOM_CUBES_DIR, self.config.CUBES_DIR])
+        os.environ['CW_CUBES_PATH'] = os.pathsep.join([
+            CUSTOM_CUBES_DIR, self.config.CUBES_DIR, 'unexistant'])
         # filter out unexistant and duplicates
         self.assertEquals(self.config.cubes_search_path(),
-                          [abspath(join(dirname(__file__), 'data', 'cubes')),
+                          [CUSTOM_CUBES_DIR,
                            self.config.CUBES_DIR])
         self.failUnless('mycube' in self.config.available_cubes())
         # test cubes python path
@@ -104,12 +107,12 @@
         self.assertEquals(cubes.__path__, self.config.cubes_search_path())
         # this import should succeed once path is adjusted
         from cubes import mycube
-        self.assertEquals(mycube.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'mycube'))])
+        self.assertEquals(mycube.__path__, [join(CUSTOM_CUBES_DIR, 'mycube')])
         # file cube should be overriden by the one found in data/cubes
         sys.modules.pop('cubes.file', None)
         del cubes.file
         from cubes import file
-        self.assertEquals(file.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'file'))])
+        self.assertEquals(file.__path__, [join(CUSTOM_CUBES_DIR, 'file')])
 
 
 if __name__ == '__main__':
--- a/test/unittest_entity.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/test/unittest_entity.py	Fri Feb 26 17:39:33 2010 +0100
@@ -175,12 +175,14 @@
             Personne.fetch_attrs = pfetch_attrs
             Societe.fetch_attrs = sfetch_attrs
 
-    def test_related_rql(self):
+    def test_related_rql_base(self):
         Personne = self.vreg['etypes'].etype_class('Personne')
         Note = self.vreg['etypes'].etype_class('Note')
+        SubNote = self.vreg['etypes'].etype_class('SubNote')
         self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note))
         Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type'))
         Note.fetch_attrs, Note.fetch_order = fetch_config(('type',))
+        SubNote.fetch_attrs, SubNote.fetch_order = fetch_config(('type',))
         p = self.request().create_entity('Personne', nom=u'pouet')
         self.assertEquals(p.related_rql('evaluee'),
                           'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, '
@@ -188,20 +190,26 @@
         Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', ))
         # XXX
         self.assertEquals(p.related_rql('evaluee'),
-                          'Any X,AA ORDERBY Z DESC '
-                          'WHERE X modification_date Z, E eid %(x)s, E evaluee X, '
-                          'X modification_date AA')
+                          'Any X,AA ORDERBY AA DESC '
+                          'WHERE E eid %(x)s, E evaluee X, X modification_date AA')
 
         tag = self.vreg['etypes'].etype_class('Tag')(self.request())
         self.assertEquals(tag.related_rql('tags', 'subject'),
-                          'Any X,AA ORDERBY Z DESC '
-                          'WHERE X modification_date Z, E eid %(x)s, E tags X, '
-                          'X modification_date AA')
+                          'Any X,AA ORDERBY AA DESC '
+                          'WHERE E eid %(x)s, E tags X, X modification_date AA')
         self.assertEquals(tag.related_rql('tags', 'subject', ('Personne',)),
                           'Any X,AA,AB ORDERBY AA ASC '
                           'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, '
                           'X modification_date AB')
 
+    def test_related_rql_ambigous_cant_use_fetch_order(self):
+        tag = self.vreg['etypes'].etype_class('Tag')(self.request())
+        for ttype in self.schema['tags'].objects():
+            self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',)
+        self.assertEquals(tag.related_rql('tags', 'subject'),
+                          'Any X,AA ORDERBY AA DESC '
+                          'WHERE E eid %(x)s, E tags X, X modification_date AA')
+
     def test_unrelated_rql_security_1(self):
         user = self.request().user
         rql = user.unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
--- a/test/unittest_utils.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/test/unittest_utils.py	Fri Feb 26 17:39:33 2010 +0100
@@ -6,14 +6,18 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
-from logilab.common.testlib import TestCase, unittest_main
-
-import simplejson
+import re
 import decimal
 import datetime
 
-from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList, CubicWebJsonEncoder
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList
 
+try:
+    import simplejson
+    from cubicweb.utils import CubicWebJsonEncoder
+except ImportError:
+    simplejson = None
 
 class MakeUidTC(TestCase):
     def test_1(self):
@@ -26,6 +30,9 @@
             uid = make_uid('xyz')
             if uid in d:
                 self.fail(len(d))
+            if re.match('\d', uid):
+                self.fail('make_uid must not return something begining with '
+                          'some numeric character, got %s' % uid)
             d.add(uid)
 
 
@@ -53,6 +60,9 @@
             yield self.assertEquals, l, expected
 
 class JSONEncoerTests(TestCase):
+    def setUp(self):
+        if simplejson is None:
+            self.skip('simplejson not available')
 
     def encode(self, value):
         return simplejson.dumps(value, cls=CubicWebJsonEncoder)
--- a/toolsutils.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/toolsutils.py	Fri Feb 26 17:39:33 2010 +0100
@@ -82,7 +82,7 @@
         if askconfirm:
             print
             print diffs
-            action = ASK.ask('Replace ?', ('N','y','q'), 'N')
+            action = ASK.ask('Replace ?', ('N', 'y', 'q'), 'N')
         else:
             action = 'y'
         if action == 'y':
--- a/utils.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/utils.py	Fri Feb 26 17:39:33 2010 +0100
@@ -7,27 +7,35 @@
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.mtconverter import xml_escape
-
-import locale
 import sys
 import decimal
 import datetime
-from md5 import md5
-from time import time
-from random import randint, seed
+import random
+
+from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import deprecated
 
 # initialize random seed from current time
-seed()
+random.seed()
 
 if sys.version_info[:2] < (2, 5):
+
+    from time import time
+    from md5 import md5
+    from random import randint
+
     def make_uid(key):
         """forge a unique identifier
-        not that unique on win32"""
-        msg = str(key) + "%.10f" % time() + str(randint(0, 1000000))
-        return md5(msg).hexdigest()
+        XXX not that unique on win32
+        """
+        key = str(key)
+        msg = key + "%.10f" % time() + str(randint(0, 1000000))
+        return key + md5(msg).hexdigest()
+
 else:
+
     from uuid import uuid4
+
     def make_uid(key):
         # remove dash, generated uid are used as identifier sometimes (sql table
         # names at least)
@@ -328,3 +336,12 @@
                 # we never ever want to fail because of an unknown type,
                 # just return None in those cases.
                 return None
+
+from logilab.common import date
+_THIS_MOD_NS = globals()
+for funcname in ('date_range', 'todate', 'todatetime', 'datetime2ticks',
+                 'days_in_month', 'days_in_year', 'previous_month',
+                 'next_month', 'first_day', 'last_day', 'ustrftime',
+                 'strptime'):
+    msg = '[3.6] %s has been moved to logilab.common.date' % funcname
+    _THIS_MOD_NS[funcname] = deprecated(msg)(getattr(date, funcname))
--- a/view.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/view.py	Fri Feb 26 17:39:33 2010 +0100
@@ -91,9 +91,9 @@
     * the `category` attribute may be used in the interface to regroup related
       objects together
 
-    At instantiation time, the standard `req`, `rset`, and `cursor`
-    attributes are added and the `w` attribute will be set at rendering
-    time to a write function to use.
+    At instantiation time, the standard `_cw`, and `cw_rset` attributes are
+    added and the `w` attribute will be set at rendering time to a write
+    function to use.
     """
     __registry__ = 'views'
 
--- a/vregistry.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/vregistry.py	Fri Feb 26 17:39:33 2010 +0100
@@ -23,7 +23,7 @@
 
 import sys
 from os import listdir, stat
-from os.path import dirname, join, realpath, split, isdir, exists
+from os.path import dirname, join, realpath, isdir, exists
 from logging import getLogger
 from warnings import warn
 
--- a/web/action.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/action.py	Fri Feb 26 17:39:33 2010 +0100
@@ -10,7 +10,7 @@
 
 from cubicweb import target
 from cubicweb.selectors import (partial_relation_possible, match_search_state,
-                                one_line_rset, yes)
+                                one_line_rset)
 from cubicweb.appobject import AppObject
 
 
--- a/web/application.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/application.py	Fri Feb 26 17:39:33 2010 +0100
@@ -205,6 +205,8 @@
         #      reopening. Is it actually a problem?
         self._update_last_login_time(req)
         args = req.form
+        for forminternal_key in ('__form_id', '__domid', '__errorurl'):
+            args.pop(forminternal_key, None)
         args['__message'] = req._('welcome %s !') % req.user.login
         if 'vid' in req.form:
             args['vid'] = req.form['vid']
@@ -379,7 +381,11 @@
                         'eidmap': req.data.get('eidmap', {})
                         }
             req.set_session_data(req.form['__errorurl'], forminfo)
-            raise Redirect(req.form['__errorurl'])
+            # XXX form session key / __error_url should be differentiated:
+            # session key is 'url + #<form dom id', though we usually don't want
+            # the browser to move to the form since it hides the global
+            # messages.
+            raise Redirect(req.form['__errorurl'].rsplit('#', 1)[0])
         self.error_handler(req, ex, tb=False)
 
     def error_handler(self, req, ex, tb=False):
@@ -389,6 +395,8 @@
         req.remove_header('Etag')
         req.message = None
         req.reset_headers()
+        if req.json_request:
+            raise RemoteCallFailed(unicode(ex))
         try:
             req.data['ex'] = ex
             if tb:
--- a/web/captcha.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/captcha.py	Fri Feb 26 17:39:33 2010 +0100
@@ -38,7 +38,7 @@
     draw = ImageDraw.Draw(img)
     # draw 100 random colored boxes on the background
     x, y = img.size
-    for num in range(100):
+    for num in xrange(100):
         draw.rectangle((randint(0, x), randint(0, y),
                         randint(0, x), randint(0, y)),
                        fill=randint(0, 0xffffff))
--- a/web/controller.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/controller.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,4 +1,4 @@
-"""abstract controler classe for CubicWeb web client
+"""abstract controller classe for CubicWeb web client
 
 
 :organization: Logilab
@@ -8,9 +8,6 @@
 """
 __docformat__ = "restructuredtext en"
 
-import datetime
-
-from cubicweb import typed_eid
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
 from cubicweb.web import LOGGER, Redirect, RequestError
@@ -124,6 +121,9 @@
         else:
             self._cw.set_message(self._cw._('entity deleted'))
 
+    def validate_cache(self, view):
+        view.set_http_cache_headers()
+        self._cw.validate_cache()
 
     def reset(self):
         """reset form parameters and redirect to a view determinated by given
--- a/web/data/cubicweb.css	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/data/cubicweb.css	Fri Feb 26 17:39:33 2010 +0100
@@ -392,7 +392,11 @@
   color: #111100;
 }
 
-
+ul.boxListing a.boxMenu:hover {
+                                background: #eeedd9 url(puce_down.png) no-repeat scroll 98% 6px;
+                                cursor:pointer;
+                                border-top:medium none;
+                                }
 a.boxMenu {
   background: transparent url("puce_down.png") 98% 6px no-repeat;
   display: block;
--- a/web/data/cubicweb.edition.js	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/data/cubicweb.edition.js	Fri Feb 26 17:39:33 2010 +0100
@@ -240,12 +240,13 @@
 /*
  * makes an AJAX request to get an inline-creation view's content
  * @param peid : the parent entity eid
+ * @param petype : the parent entity type
  * @param ttype : the target (inlined) entity type
  * @param rtype : the relation type between both entities
  */
-function addInlineCreationForm(peid, ttype, rtype, role, i18nctx, insertBefore) {
+function addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore) {
     insertBefore = insertBefore || getNode('add' + rtype + ':' + peid + 'link').parentNode;
-    var d = asyncRemoteExec('inline_creation_form', peid, ttype, rtype, role, i18nctx);
+    var d = asyncRemoteExec('inline_creation_form', peid, petype, ttype, rtype, role, i18nctx);
     d.addCallback(function (response) {
         var dom = getDomFromResponse(response);
         preprocessAjaxLoad(null, dom);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.iprogress.js	Fri Feb 26 17:39:33 2010 +0100
@@ -0,0 +1,65 @@
+function ProgressBar() {
+    this.budget = 100;
+    this.todo = 100;
+    this.done = 100;
+    this.color_done = "green";
+    this.color_budget = "blue";
+    this.color_todo = "#cccccc"; //  grey
+    this.height = 16;
+    this.middle = this.height/2;
+    this.radius = 4;
+}
+
+ProgressBar.prototype.draw_one_rect = function(ctx, pos, color, fill) {
+    ctx.beginPath();
+    ctx.lineWidth = 1;
+    ctx.strokeStyle = color;
+    if (fill) {
+	ctx.fillStyle = color;
+	ctx.fillRect(0,0,pos,this.middle*2);
+    } else {
+	ctx.lineWidth = 2;
+	ctx.strokeStyle = "black";
+	ctx.moveTo(pos,0);
+	ctx.lineTo(pos,this.middle*2);
+	ctx.stroke();
+    }
+};
+
+ProgressBar.prototype.draw_one_circ = function(ctx, pos, color) {
+    ctx.beginPath();
+    ctx.lineWidth = 2;
+    ctx.strokeStyle = color;
+    ctx.moveTo(0,this.middle);
+    ctx.lineTo(pos,this.middle);
+    ctx.arc(pos,this.middle,this.radius,0,Math.PI*2,true);
+    ctx.stroke();
+};
+
+
+ProgressBar.prototype.draw_circ = function(ctx) {
+    this.draw_one_circ(ctx,this.budget,this.color_budget);
+    this.draw_one_circ(ctx,this.todo,this.color_todo);
+    this.draw_one_circ(ctx,this.done,this.color_done);
+};
+
+
+ProgressBar.prototype.draw_rect = function(ctx) {
+    this.draw_one_rect(ctx,this.todo,this.color_todo,true);
+    this.draw_one_rect(ctx,this.done,this.color_done,true);
+    this.draw_one_rect(ctx,this.budget,this.color_budget,false);
+};
+
+
+function draw_progressbar(cid, done, todo, budget, color) {
+    var canvas = document.getElementById(cid);
+    if (canvas.getContext) {
+        var ctx = canvas.getContext("2d");
+	var bar = new ProgressBar();
+	bar.budget = budget;
+	bar.todo = todo;
+	bar.done = done;
+        bar.color_done = color;
+	bar.draw_rect(ctx);
+    }
+}
--- a/web/facet.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/facet.py	Fri Feb 26 17:39:33 2010 +0100
@@ -15,7 +15,7 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.graph import has_path
 from logilab.common.decorators import cached
-from logilab.common.date import datetime2ticks, ustrftime
+from logilab.common.date import datetime2ticks
 from logilab.common.compat import all
 
 from rql import parse, nodes
@@ -267,6 +267,7 @@
     context = ''
     needs_update = False
     start_unfolded = True
+    cw_rset = None # ensure facets have a cw_rset attribute
 
     def __init__(self, req, rqlst=None, filtered_variable=None,
                  **kwargs):
--- a/web/form.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/form.py	Fri Feb 26 17:39:33 2010 +0100
@@ -14,8 +14,7 @@
 
 from cubicweb.appobject import AppObject
 from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb import tags
-from cubicweb.web import stdmsgs, httpcache, formfields
+from cubicweb.web import httpcache, formfields, controller
 
 
 class FormViewMixIn(object):
@@ -69,12 +68,43 @@
     __metaclass__ = metafieldsform
     __registry__ = 'forms'
 
+    internal_fields = ('__errorurl',) + controller.NAV_FORM_PARAMETERS
+
     parent_form = None
     force_session_key = None
+    domid = 'form'
+    copy_nav_params = False
 
-    def __init__(self, req, rset, **kwargs):
-        super(Form, self).__init__(req, rset=rset, **kwargs)
-        self.restore_previous_post(self.session_key())
+    def __init__(self, req, rset=None, row=None, col=None,
+                 submitmsg=None, mainform=True, **kwargs):
+        super(Form, self).__init__(req, rset=rset, row=row, col=col)
+        self.fields = list(self.__class__._fields_)
+        if mainform:
+            self.add_hidden(u'__form_id', kwargs.pop('formvid', self.__regid__))
+        for key, val in kwargs.iteritems():
+            if key in controller.NAV_FORM_PARAMETERS:
+                self.add_hidden(key, val)
+            elif key == 'redirect_path':
+                self.add_hidden(u'__redirectpath', val)
+            elif hasattr(self.__class__, key) and not key[0] == '_':
+                setattr(self, key, val)
+            else:
+                self.cw_extra_kwargs[key] = val
+            # skip other parameters, usually given for selection
+            # (else write a custom class to handle them)
+        if mainform:
+            self.add_hidden(u'__errorurl', self.session_key())
+            self.add_hidden(u'__domid', self.domid)
+            self.restore_previous_post(self.session_key())
+        # XXX why do we need two different variables (mainform and copy_nav_params ?)
+        if self.copy_nav_params:
+            for param in controller.NAV_FORM_PARAMETERS:
+                if not param in kwargs:
+                    value = req.form.get(param)
+                    if value:
+                        self.add_hidden(param, value)
+        if submitmsg is not None:
+            self.add_hidden(u'__message', submitmsg)
 
     @property
     def root_form(self):
--- a/web/formfields.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/formfields.py	Fri Feb 26 17:39:33 2010 +0100
@@ -91,7 +91,10 @@
        optional fieldset to which this field belongs to
     :order:
        key used by automatic forms to sort fields
-
+    :ignore_req_params:
+       when true, this field won't consider value potentialy specified using
+       request's form parameters (eg you won't be able to specify a value using for
+       instance url like http://mywebsite.com/form?field=value)
     """
     # default widget associated to this class of fields. May be overriden per
     # instance
@@ -113,6 +116,7 @@
     order = None
     value = _MARKER
     fallback_on_none_attribute = False
+    ignore_req_params = False
 
     def __init__(self, name=None, label=_MARKER, widget=None, **kwargs):
         for key, val in kwargs.items():
@@ -348,9 +352,9 @@
     def process_form_value(self, form):
         """process posted form and return correctly typed value"""
         try:
-            return form.formvalues[self]
+            return form.formvalues[(self, form)]
         except KeyError:
-            value = form.formvalues[self] = self._process_form_value(form)
+            value = form.formvalues[(self, form)] = self._process_form_value(form)
             return value
 
     def _process_form_value(self, form):
@@ -413,12 +417,17 @@
 
 class PasswordField(StringField):
     widget = fw.PasswordInput
+    def form_init(self, form):
+        if self.eidparam and form.edited_entity.has_eid():
+            # see below: value is probably set but we can't retreive it. Ensure
+            # the field isn't show as a required field on modification
+            self.required = False
 
     def typed_value(self, form, load_bytes=False):
         if self.eidparam:
             # no way to fetch actual password value with cw
             if form.edited_entity.has_eid():
-                return INTERNAL_FIELD_VALUE
+                return ''
             return self.initial_typed_value(form, load_bytes)
         return super(PasswordField, self).typed_value(form, load_bytes)
 
@@ -591,7 +600,7 @@
             if data:
                 encoding = self.encoding(form)
                 try:
-                    form.formvalues[self] = unicode(data.getvalue(), encoding)
+                    form.formvalues[(self, form)] = unicode(data.getvalue(), encoding)
                 except UnicodeError:
                     pass
                 else:
@@ -811,7 +820,7 @@
             for field in form.root_form.fields_by_name('__linkto'):
                 if field.value in searchedvalues:
                     form.root_form.remove_field(field)
-            form.formvalues[self] = value
+            form.formvalues[(self, form)] = value
 
     def format_single_value(self, req, value):
         return value
@@ -819,13 +828,13 @@
     def process_form_value(self, form):
         """process posted form and return correctly typed value"""
         try:
-            return form.formvalues[self]
+            return form.formvalues[(self, form)]
         except KeyError:
             value = self._process_form_value(form)
             # if value is None, there are some remaining pending fields, we'll
             # have to recompute this later -> don't cache in formvalues
             if value is not None:
-                form.formvalues[self] = value
+                form.formvalues[(self, form)] = value
             return value
 
     def _process_form_value(self, form):
--- a/web/formwidgets.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/formwidgets.py	Fri Feb 26 17:39:33 2010 +0100
@@ -59,21 +59,6 @@
     def _render(self, form, field, renderer):
         raise NotImplementedError()
 
-    def typed_value(self, form, field):
-        """return field's *typed* value specified in:
-        3. extra form values given to render()
-        4. field's typed value
-        """
-        qname = field.input_name(form)
-        for key in (field, qname):
-            try:
-                return form.formvalues[key]
-            except KeyError:
-                continue
-        if field.name != qname and field.name in form.formvalues:
-            return form.formvalues[field.name]
-        return field.typed_value(form)
-
     def format_value(self, form, field, value):
         return field.format_value(form._cw, value)
 
@@ -97,15 +82,20 @@
         return self.values(form, field), attrs
 
     def values(self, form, field):
-        qname = field.input_name(form, self.suffix)
-        if qname in form.form_previous_values:
-            values = form.form_previous_values[qname]
-        elif qname in form._cw.form:
-            values = form._cw.form[qname]
-        elif field.name != qname and field.name in form._cw.form:
-            # compat: accept attr=value in req.form to specify value of attr-subject
-            values = form._cw.form[field.name]
-        else:
+        values = None
+        if not field.ignore_req_params:
+            qname = field.input_name(form, self.suffix)
+            # value from a previous post that has raised a validation error
+            if qname in form.form_previous_values:
+                values = form.form_previous_values[qname]
+            # value specified using form parameters
+            elif qname in form._cw.form:
+                values = form._cw.form[qname]
+            elif field.name != qname and field.name in form._cw.form:
+                # XXX compat: accept attr=value in req.form to specify value of
+                # attr-subject
+                values = form._cw.form[field.name]
+        if values is None:
             values = self.typed_value(form, field)
             if values != INTERNAL_FIELD_VALUE:
                 values = self.format_value(form, field, values)
@@ -113,6 +103,21 @@
             values = (values,)
         return values
 
+    def typed_value(self, form, field):
+        """return field's *typed* value specified in:
+        3. extra form values given to render()
+        4. field's typed value
+        """
+        qname = field.input_name(form)
+        for key in ((field, form), qname):
+            try:
+                return form.formvalues[key]
+            except KeyError:
+                continue
+        if field.name != qname and field.name in form.formvalues:
+            return form.formvalues[field.name]
+        return field.typed_value(form)
+
     def process_field_data(self, form, field):
         posted = form._cw.form
         val = posted.get(field.input_name(form, self.suffix))
@@ -164,9 +169,9 @@
         assert self.suffix is None, 'suffix not supported'
         values, attrs = self.values_and_attributes(form, field)
         assert len(values) == 1
-        id = attrs.pop('id')
+        domid = attrs.pop('id')
         inputs = [tags.input(name=field.input_name(form),
-                             value=values[0], type=self.type, id=id, **attrs),
+                             value=values[0], type=self.type, id=domid, **attrs),
                   '<br/>',
                   tags.input(name=field.input_name(form, '-confirm'),
                              value=values[0], type=self.type, **attrs),
--- a/web/htmlwidgets.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/htmlwidgets.py	Fri Feb 26 17:39:33 2010 +0100
@@ -15,7 +15,7 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import UStringIO
-from cubicweb.uilib import toggle_action, limitsize, htmlescape
+from cubicweb.uilib import toggle_action, htmlescape
 from cubicweb.web import jsonize
 
 # XXX HTMLWidgets should have access to req (for datadir / static urls,
@@ -280,7 +280,7 @@
         if value is None:
             return u''
         elif isinstance(value, int):
-            return u'%09d'%value
+            return u'%09d' % value
         else:
             return unicode(value)
 
@@ -316,7 +316,7 @@
             self.w(u'<th %s>%s</th>' % (' '.join(attrs), column.name))
         self.w(u'</tr>')
         self.w(u'</thead><tbody>')
-        for rowindex, row in enumerate(self.model.get_rows()):
+        for rowindex in xrange(len(self.model.get_rows())):
             klass = (rowindex%2==1) and 'odd' or 'even'
             self.w(u'<tr class="%s" %s>' % (klass, self.highlight))
             for column, sortvalue in self.itercols(rowindex):
@@ -373,14 +373,10 @@
         budget = self.budget
         if budget == 0:
             pourcent = 100
-            todo_pourcent = 0
         else:
             pourcent = done*100./budget
-            todo_pourcent = min(todo*100./budget, 100-pourcent)
-        bar_pourcent = pourcent
         if pourcent > 100.1:
             color = 'red'
-            bar_pourcent = 100
         elif todo+done > self.red_threshold*budget:
             color = 'red'
         elif todo+done > self.orange_threshold*budget:
--- a/web/request.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/request.py	Fri Feb 26 17:39:33 2010 +0100
@@ -135,7 +135,7 @@
                                             self.user.properties['ui.language'])
                     self.set_language(lang)
                     return
-                except KeyError, ex:
+                except KeyError:
                     pass
             if vreg.config['language-negociation']:
                 # 2. http negociated language
@@ -671,7 +671,7 @@
                     try:
                         scorekey, scoreval = score.split('=')
                         if scorekey == 'q': # XXX 'level'
-                            score = float(score[2:]) # remove 'q='
+                            score = float(scoreval)
                     except ValueError:
                         continue
             values.append((score, value))
--- a/web/test/unittest_form.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/test/unittest_form.py	Fri Feb 26 17:39:33 2010 +0100
@@ -207,9 +207,9 @@
             upassword = PasswordField(eidparam=True, role='subject')
         form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity)
         self.assertTextEquals(self._render_entity_field('upassword', form),
-                              '''<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
+                              '''<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" tabindex="1" type="password" value="" />
 <br/>
-<input name="upassword-subject-confirm:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
+<input name="upassword-subject-confirm:%(eid)s" tabindex="1" type="password" value="" />
 &#160;
 <span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
 
--- a/web/test/unittest_pdf.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/test/unittest_pdf.py	Fri Feb 26 17:39:33 2010 +0100
@@ -27,6 +27,8 @@
         fopproc = sub(['/usr/bin/fop', foptmp.name, pdftmp.name])
         fopproc.wait()
         del foptmp
+        if fopproc.returncode:
+            self.skip('fop returned status %s' % fopproc.returncode)
         pdftmp.seek(0) # a bit superstitious
         reference = open(osp.join(DATADIR, 'sample1.pdf'), 'r').read()
         output = pdftmp.read()
--- a/web/test/unittest_urlrewrite.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/test/unittest_urlrewrite.py	Fri Feb 26 17:39:33 2010 +0100
@@ -39,6 +39,7 @@
             ('/notfound', dict(vid='404')),
             ('/error', dict(vid='error')),
             ('/sparql', dict(vid='sparql')),
+            ('/processinfo', dict(vid='processinfo')),
             ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'primary'}),
             ('/add/([^/]+?)/?$' , dict(vid='creation', etype=r'\1')),
             ('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
--- a/web/test/unittest_views_editforms.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/test/unittest_views_editforms.py	Fri Feb 26 17:39:33 2010 +0100
@@ -35,9 +35,6 @@
 
 
     def test_cwuser_relations_by_category(self):
-        #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
-        #    if rtype == 'tags':
-        #        print rtype, role, stype, otype, ':', tag
         e = self.vreg['etypes'].etype_class('CWUser')(self.request())
         # see custom configuration in views.cwuser
         self.assertEquals(rbc(e, 'main', 'attributes'),
@@ -63,7 +60,11 @@
                                ('owned_by', 'subject'),
                                ('bookmarked_by', 'object'),
                                ])
-        self.assertListEquals(rbc(e, 'main', 'relations'),
+        # XXX skip 'tags' relation here and in the hidden category because
+        # of some test interdependancy when pytest is launched on whole cw
+        # (appears here while expected in hidden
+        self.assertListEquals([x for x in rbc(e, 'main', 'relations')
+                               if x != ('tags', 'object')],
                               [('primary_email', 'subject'),
                                ('custom_workflow', 'subject'),
                                ('connait', 'subject'),
@@ -73,10 +74,10 @@
                               [('use_email', 'subject'),
                                ])
         # owned_by is defined both as subject and object relations on CWUser
-        self.assertListEquals(sorted(rbc(e, 'main', 'hidden')),
+        self.assertListEquals(sorted(x for x in rbc(e, 'main', 'hidden')
+                                     if x != ('tags', 'object')),
                               sorted([('has_text', 'subject'),
                                       ('identity', 'subject'),
-                                      ('tags', 'object'),
                                       ('for_user', 'object'),
                                       ('created_by', 'object'),
                                       ('wf_info_for', 'object'),
@@ -166,14 +167,14 @@
         geid = self.execute('CWGroup X LIMIT 1')[0][0]
         rset = self.execute('CWUser X LIMIT 1')
         self.view('inline-edition', rset, row=0, col=0, rtype='in_group',
-                  peid=geid, role='object', template=None, i18nctx='',
-                  pform=MOCKPFORM).source
+                  peid=geid, role='object', i18nctx='', pform=MOCKPFORM,
+                  template=None).source
 
     def test_automatic_inline_creation_formview(self):
         geid = self.execute('CWGroup X LIMIT 1')[0][0]
         self.view('inline-creation', None, etype='CWUser', rtype='in_group',
-                  peid=geid, template=None, i18nctx='', role='object',
-                  pform=MOCKPFORM).source
+                  peid=geid, petype='CWGroup', i18nctx='', role='object', pform=MOCKPFORM,
+                  template=None)
 
 MOCKPFORM = mock_object(form_previous_values={}, form_valerror=None)
 
--- a/web/test/unittest_viewselector.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/test/unittest_viewselector.py	Fri Feb 26 17:39:33 2010 +0100
@@ -67,9 +67,8 @@
         req = self.request()
         self.assertListEqual(self.pviews(req, None),
                              [('changelog', wdoc.ChangeLogView),
-                              ('debug', debug.DebugView),
                               ('index', startup.IndexView),
-                              ('info', management.ProcessInformationView),
+                              ('info', debug.ProcessInformationView),
                               ('manage', startup.ManageView),
                               ('owl', owl.OWLView),
                               ('propertiesform', cwproperties.CWPropertiesForm),
--- a/web/uicfg.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/uicfg.py	Fri Feb 26 17:39:33 2010 +0100
@@ -112,7 +112,6 @@
 from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
                             RelationTagsDict, register_rtag, _ensure_str_key)
 from cubicweb.schema import META_RTYPES
-from cubicweb.web import formwidgets
 
 
 # primary view configuration ##################################################
@@ -251,7 +250,7 @@
     _allowed_form_types = ('main', 'inlined', 'muledit')
     _allowed_values = {'main': ('attributes', 'inlined', 'relations',
                                 'metadata', 'hidden'),
-                       'inlined': ('attributes', 'hidden'),
+                       'inlined': ('attributes', 'inlined', 'hidden'),
                        'muledit': ('attributes', 'hidden'),
                        }
 
--- a/web/views/autoform.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/autoform.py	Fri Feb 26 17:39:33 2010 +0100
@@ -169,13 +169,15 @@
     :attr etype: the entity type being created in the inline form
     """
     __regid__ = 'inline-creation'
-    __select__ = (match_kwargs('peid', 'rtype')
+    __select__ = (match_kwargs('peid', 'petype', 'rtype')
                   & specified_etype_implements('Any'))
+    _select_attrs = InlineEntityEditionFormView._select_attrs + ('petype',)
 
     @property
     def removejs(self):
         entity = self._entity()
-        card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
+        rdef = entity.e_schema.rdef(self.rtype, neg_role(self.role), self.petype)
+        card= rdef.role_cardinality(self.role)
         # when one is adding an inline entity for a relation of a single card,
         # the 'add a new xxx' link disappears. If the user then cancel the addition,
         # we have to make this link appears back. This is done by giving add new link
@@ -207,7 +209,7 @@
     :attr card: the cardinality of the relation according to role of `peid`
     """
     __regid__ = 'inline-addnew-link'
-    __select__ = (match_kwargs('peid', 'rtype')
+    __select__ = (match_kwargs('peid', 'petype', 'rtype')
                   & specified_etype_implements('Any'))
 
     _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
@@ -218,8 +220,8 @@
         divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
         self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
           % divid)
-        js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
-            self.peid, self.etype, self.rtype, self.role, i18nctx)
+        js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s', '%s')" % (
+            self.peid, self.petype, self.etype, self.rtype, self.role, i18nctx)
         if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
             js = "toggleVisibility('%s'); %s" % (divid, js)
         __ = self._cw.pgettext
@@ -566,18 +568,18 @@
                 except f.FieldNotFound:
                     # meta attribute such as <attr>_format
                     continue
+        if self.fieldsets_in_order:
+            fsio = list(self.fieldsets_in_order)
+        else:
+            fsio = [None]
+        self.fieldsets_in_order = fsio
+        # add fields for relation whose target should have an inline form
+        for formview in self.inlined_form_views():
+            field = self._inlined_form_view_field(formview)
+            self.fields.append(field)
+            if not field.fieldset in fsio:
+                fsio.append(field.fieldset)
         if self.formtype == 'main':
-            if self.fieldsets_in_order:
-                fsio = list(self.fieldsets_in_order)
-            else:
-                fsio = [None]
-            self.fieldsets_in_order = fsio
-            # add fields for relation whose target should have an inline form
-            for formview in self.inlined_form_views():
-                field = self._inlined_form_view_field(formview)
-                self.fields.append(field)
-                if not field.fieldset in fsio:
-                    fsio.append(field.fieldset)
             # add the generic relation field if necessary
             if entity.has_eid() and (
                 self.display_fields is None or
@@ -655,6 +657,8 @@
         """return a list of (relation schema, role) to edit for the entity"""
         if self.display_fields is not None:
             return self.display_fields
+        if self.edited_entity.has_eid() and not self.edited_entity.has_perm('update'):
+            return []
         # XXX we should simply put eid in the generated section, no?
         return [(rtype, role) for rtype, _, role in self._relations_by_section(
             'attributes', 'update', strict) if rtype != 'eid']
@@ -706,8 +710,9 @@
                 if self.should_display_add_new_relation_link(rschema, formviews, card):
                     addnewlink = self._cw.vreg['views'].select(
                         'inline-addnew-link', self._cw,
-                        etype=ttype, rtype=rschema, role=role,
-                        peid=self.edited_entity.eid, pform=self, card=card)
+                        etype=ttype, rtype=rschema, role=role, card=card,
+                        peid=self.edited_entity.eid,
+                        petype=self.edited_entity.e_schema, pform=self)
                     formviews.append(addnewlink)
                 allformviews += formviews
         return allformviews
@@ -767,7 +772,9 @@
         """
         yield self._cw.vreg['views'].select('inline-creation', self._cw,
                                             etype=ttype, rtype=rschema, role=role,
-                                            peid=self.edited_entity.eid, pform=self)
+                                            peid=self.edited_entity.eid,
+                                            petype=self.edited_entity.e_schema,
+                                            pform=self)
 
 
 ## default form ui configuration ##############################################
--- a/web/views/basecontrollers.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/basecontrollers.py	Fri Feb 26 17:39:33 2010 +0100
@@ -172,7 +172,9 @@
 
 def _validation_error(req, ex):
     req.cnx.rollback()
-    forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True)
+    # XXX necessary to remove existant validation error?
+    # imo (syt), it's not necessary
+    req.get_session_data(req.form.get('__errorurl'), pop=True)
     foreid = ex.entity
     eidmap = req.data.get('eidmap', {})
     for var, eid in eidmap.items():
@@ -351,6 +353,7 @@
         except NoSelectableObject:
             vid = req.form.get('fallbackvid', 'noresult')
             view = self._cw.vreg['views'].select(vid, req, rset=rset)
+        self.validate_cache(view)
         return self._call_view(view)
 
     @xhtmlize
@@ -383,10 +386,10 @@
 
     @check_pageid
     @xhtmlize
-    def js_inline_creation_form(self, peid, ttype, rtype, role, i18nctx):
+    def js_inline_creation_form(self, peid, petype, ttype, rtype, role, i18nctx):
         view = self._cw.vreg['views'].select('inline-creation', self._cw,
-                                         etype=ttype, peid=peid, rtype=rtype,
-                                         role=role)
+                                             etype=ttype, rtype=rtype, role=role,
+                                             peid=peid, petype=petype)
         return self._call_view(view, i18nctx=i18nctx)
 
     @jsonize
@@ -399,7 +402,7 @@
 
     @xhtmlize
     def js_reledit_form(self):
-        args = dict((x,self._cw.form[x])
+        args = dict((x, self._cw.form[x])
                     for x in frozenset(('rtype', 'role', 'reload', 'landing_zone')))
         entity = self._cw.entity_from_eid(int(self._cw.form['eid']))
         # note: default is reserved in js land
--- a/web/views/boxes.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/boxes.py	Fri Feb 26 17:39:33 2010 +0100
@@ -24,7 +24,6 @@
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web import uicfg
 from cubicweb.web.box import BoxTemplate
 
 
--- a/web/views/cwuser.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/cwuser.py	Fri Feb 26 17:39:33 2010 +0100
@@ -12,17 +12,16 @@
 from cubicweb.selectors import one_line_rset, implements, match_user_groups
 from cubicweb.view import EntityView
 from cubicweb.web import action, uicfg
-from cubicweb.web.views import primary
 
-uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden')
-
-uicfg.primaryview_section.tag_attribute(('CWGroup', 'name'), 'hidden')
-uicfg.primaryview_section.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations')
-uicfg.primaryview_section.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations')
-uicfg.primaryview_section.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations')
-uicfg.primaryview_section.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations')
-uicfg.primaryview_section.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations')
-uicfg.primaryview_section.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
+_pvs = uicfg.primaryview_section
+_pvs.tag_attribute(('CWUser', 'login'), 'hidden')
+_pvs.tag_attribute(('CWGroup', 'name'), 'hidden')
+_pvs.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations')
+_pvs.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations')
+_pvs.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations')
+_pvs.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations')
+_pvs.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations')
+_pvs.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
 
 class UserPreferencesEntityAction(action.Action):
     __regid__ = 'prefs'
--- a/web/views/debug.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/debug.py	Fri Feb 26 17:39:33 2010 +0100
@@ -25,41 +25,89 @@
         w(u'</ul>')
 
 
-class DebugView(StartupView):
-    __regid__ = 'debug'
+
+class ProcessInformationView(StartupView):
+    __regid__ = 'info'
     __select__ = none_rset() & match_user_groups('managers')
-    title = _('server debug information')
+
+    title = _('server information')
 
     def call(self, **kwargs):
         """display server information"""
+        req = self._cw
+        dtformat = req.property_value('ui.datetime-format')
+        _ = req._
         w = self.w
-        w(u'<h1>server sessions</h1>')
-        sessions = self._cw.cnx._repo._sessions.items()
+        # generic instance information
+        w(u'<h1>%s</h1>' % _('Instance'))
+        w(u'<table>')
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            _('config type'), self._cw.vreg.config.name))
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            _('config mode'), self._cw.vreg.config.mode))
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            _('instance home'), self._cw.vreg.config.apphome))
+        w(u'</table>')
+        vcconf = req.vreg.config.vc_config()
+        w(u'<h3>%s</h3>' % _('versions configuration'))
+        w(u'<table>')
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            'CubicWeb', vcconf.get('cubicweb', _('no version information'))))
+        for cube in sorted(self._cw.vreg.config.cubes()):
+            cubeversion = vcconf.get(cube, _('no version information'))
+            w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+                cube, cubeversion))
+        w(u'</table>')
+        # repository information
+        repo = req.vreg.config.repository(None)
+        w(u'<h1>%s</h1>' % _('Repository'))
+        w(u'<h3>%s</h3>' % _('resources usage'))
+        w(u'<table>')
+        stats = repo.stats()
+        for element in sorted(stats):
+            w(u'<tr><th align="left">%s</th><td>%s %s</td></tr>'
+                   % (element, xml_escape(unicode(stats[element])),
+                      element.endswith('percent') and '%' or '' ))
+        w(u'</table>')
+        if req.cnx._cnxtype == 'inmemory':
+            w(u'<h3>%s</h3>' % _('opened sessions'))
+            sessions = repo._sessions.values()
+            if sessions:
+                w(u'<ul>')
+                for session in sessions:
+                    w(u'<li>%s (%s: %s)<br/>' % (
+                        xml_escape(unicode(session)),
+                        _('last usage'),
+                        strftime(dtformat, localtime(session.timestamp))))
+                    dict_to_html(w, session.data)
+                    w(u'</li>')
+                w(u'</ul>')
+            else:
+                w(u'<p>%s</p>' % _('no repository sessions found'))
+        # web server information
+        w(u'<h1>%s</h1>' % _('Web server'))
+        w(u'<table>')
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            _('base url'), req.base_url()))
+        w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
+            _('data directory url'), req.datadir_url))
+        w(u'</table>')
+        from cubicweb.web.application import SESSION_MANAGER
+        sessions = SESSION_MANAGER.current_sessions()
+        w(u'<h3>%s</h3>' % _('opened web sessions'))
         if sessions:
             w(u'<ul>')
-            for sid, session in sessions:
-                w(u'<li>%s  (last usage: %s)<br/>' % (xml_escape(str(session)),
-                                                      strftime('%Y-%m-%d %H:%M:%S',
-                                                               localtime(session.timestamp))))
+            for session in sessions:
+                w(u'<li>%s (%s: %s)<br/>' % (
+                    session.sessionid,
+                    _('last usage'),
+                    strftime(dtformat, localtime(session.last_usage_time))))
                 dict_to_html(w, session.data)
                 w(u'</li>')
             w(u'</ul>')
         else:
-            w(u'<p>no server sessions found</p>')
-        from cubicweb.web.application import SESSION_MANAGER
-        w(u'<h1>web sessions</h1>')
-        sessions = SESSION_MANAGER.current_sessions()
-        if sessions:
-            w(u'<ul>')
-            for session in sessions:
-                w(u'<li>%s (last usage: %s)<br/>' % (session.sessionid,
-                                                     strftime('%Y-%m-%d %H:%M:%S',
-                                                              localtime(session.last_usage_time))))
-                dict_to_html(w, session.data)
-                w(u'</li>')
-            w(u'</ul>')
-        else:
-            w(u'<p>no web sessions found</p>')
+            w(u'<p>%s</p>' % _('no web sessions found'))
+
 
 
 class RegistryView(StartupView):
@@ -74,7 +122,7 @@
         self.w(u'<p>%s</p>\n' % ' - '.join('<a href="/_registry#%s">%s</a>'
                                            % (key, key) for key in keys))
         for key in keys:
-            self.w(u'<h2><a name="%s">%s</a></h2>' % (key,key))
+            self.w(u'<h2><a name="%s">%s</a></h2>' % (key, key))
             items = self._cw.vreg[key].items()
             if items:
                 self.w(u'<table><tbody>')
--- a/web/views/editcontroller.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/editcontroller.py	Fri Feb 26 17:39:33 2010 +0100
@@ -159,8 +159,9 @@
                 field = form.field_by_name(name, role, eschema=entity.e_schema)
             else:
                 field = form.field_by_name(name, role)
-            if field.has_been_modified(form):
-                self.handle_formfield(form, field, rqlquery)
+            for field in field.actual_fields(form):
+                if field.has_been_modified(form):
+                    self.handle_formfield(form, field, rqlquery)
         if self.errors:
             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
             raise ValidationError(entity.eid, errors)
--- a/web/views/editforms.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/editforms.py	Fri Feb 26 17:39:33 2010 +0100
@@ -17,7 +17,7 @@
 from logilab.common.decorators import cached
 
 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
-                                specified_etype_implements, yes)
+                                specified_etype_implements, implements, yes)
 from cubicweb.view import EntityView
 from cubicweb import tags
 from cubicweb.web import uicfg, stdmsgs, eid_param, \
@@ -30,15 +30,13 @@
 
 class DeleteConfForm(forms.CompositeForm):
     __regid__ = 'deleteconf'
-    __select__ = non_final_entity()
+    # XXX non_final_entity does not implement eclass_selector
+    __select__ = implements('Any')
 
     domid = 'deleteconf'
     copy_nav_params = True
     form_buttons = [fw.Button(stdmsgs.BUTTON_DELETE, cwaction='delete'),
                     fw.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
-    @property
-    def action(self):
-        return self._cw.build_url('edit')
 
     def __init__(self, *args, **kwargs):
         super(DeleteConfForm, self).__init__(*args, **kwargs)
@@ -98,12 +96,10 @@
     def render_form(self, entity):
         """fetch and render the form"""
         self.form_title(entity)
-        form = self._cw.vreg['forms'].select('edition', self._cw, rset=entity.cw_rset,
-                                             row=entity.cw_row, col=entity.cw_col,
-                                             entity=entity,
+        form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity,
                                              submitmsg=self.submited_message())
         self.init_form(form, entity)
-        self.w(form.render(formvid=u'edition'))
+        self.w(form.render())
 
     def init_form(self, form, entity):
         """customize your form before rendering here"""
@@ -236,18 +232,18 @@
         """a view to edit multiple entities of the same type the first column
         should be the eid
         """
-        #self.form_title(entity)
-        form = self._cw.vreg['forms'].select(self.__regid__, self._cw,
-                                             rset=self.cw_rset,
-                                             copy_nav_params=True)
         # XXX overriding formvid (eg __form_id) necessary to make work edition:
         # the edit controller try to select the form with no rset but
         # entity=entity, and use this form to edit the entity. So we want
         # edition form there but specifying formvid may have other undesired
-        # side effect. Maybe we should provide another variable optinally
+        # side effect. Maybe we should provide another variable optionally
         # telling which form the edit controller should select (eg difffers
         # between html generation / post handling form)
-        self.w(form.render(formvid='edition'))
+        form = self._cw.vreg['forms'].select(self.__regid__, self._cw,
+                                             rset=self.cw_rset,
+                                             copy_nav_params=True,
+                                             formvid='edition')
+        self.w(form.render())
 
 
 # click and edit handling ('reledit') ##########################################
@@ -307,7 +303,7 @@
         # compute value, checking perms, build form
         if rschema.final:
             form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
-            if not self.should_edit_attribute(entity, rschema, role, form):
+            if not self.should_edit_attribute(entity, rschema, form):
                 self.w(entity.printable_value(rtype))
                 return
             value = entity.printable_value(rtype) or default
@@ -330,14 +326,17 @@
         self.relation_form(lzone, value, form,
                            self._build_renderer(entity, rtype, role))
 
-    def should_edit_attribute(self, entity, rschema, role, form):
+    def should_edit_attribute(self, entity, rschema, form):
         rtype = str(rschema)
-        ttype = rschema.targets(entity.__regid__, role)[0]
-        afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
+        rdef = entity.e_schema.rdef(rtype)
+        afs = uicfg.autoform_section.etype_get(
+            entity.__regid__, rtype, 'subject', rdef.object)
         if 'main_hidden' in afs or not entity.has_perm('update'):
             return False
+        if not rdef.has_perm(self._cw, 'update', eid=entity.eid):
+            return False
         try:
-            form.field_by_name(rtype, role, entity.e_schema)
+            form.field_by_name(rtype, 'subject', entity.e_schema)
         except FieldNotFound:
             return False
         return True
--- a/web/views/formrenderers.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/formrenderers.py	Fri Feb 26 17:39:33 2010 +0100
@@ -85,10 +85,6 @@
         if self.display_progress_div:
             w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
         w(u'<fieldset>')
-        w(tags.input(type=u'hidden', name=u'__form_id',
-                     value=values.get('formvid', form.__regid__)))
-        if form.redirect_path:
-            w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
         self.render_fields(w, form, values)
         self.render_buttons(w, form)
         w(u'</fieldset>')
@@ -380,10 +376,6 @@
         attrs_fs_label += '<div class="formBody">'
         return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
 
-    def _render_fields(self, fields, w, form):
-        if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
-            super(EntityFormRenderer, self)._render_fields(fields, w, form)
-
     def render_buttons(self, w, form):
         if len(form.form_buttons) == 3:
             w("""<table width="100%%">
--- a/web/views/forms.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/forms.py	Fri Feb 26 17:39:33 2010 +0100
@@ -15,9 +15,7 @@
 
 from cubicweb import typed_eid
 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
 from cubicweb.web import uicfg, form, formwidgets as fwdgs
-from cubicweb.web.controller import NAV_FORM_PARAMETERS
 from cubicweb.web.formfields import StringField, relvoc_unrelated, guess_field
 
 
@@ -58,54 +56,20 @@
     """
     __regid__ = 'base'
 
-    internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
 
     # attributes overrideable by subclasses or through __init__
     needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',)
     needs_css = ('cubicweb.form.css',)
-    domid = 'form'
     action = None
     onsubmit = "return freezeFormButtons('%(domid)s');"
     cssclass = None
     cssstyle = None
     cwtarget = None
     redirect_path = None
-    copy_nav_params = False
     form_buttons = None
     form_renderer_id = 'default'
     fieldsets_in_order = None
 
-    def __init__(self, req, rset=None, row=None, col=None,
-                 submitmsg=None, mainform=True,
-                 **kwargs):
-        super(FieldsForm, self).__init__(req, rset=rset, row=row, col=col)
-        self.fields = list(self.__class__._fields_)
-        for key, val in kwargs.items():
-            if key in NAV_FORM_PARAMETERS:
-                self.add_hidden(key, val)
-            elif hasattr(self.__class__, key) and not key[0] == '_':
-                setattr(self, key, val)
-            else:
-                self.cw_extra_kwargs[key] = val
-            # skip other parameters, usually given for selection
-            # (else write a custom class to handle them)
-        if mainform:
-            self.add_hidden('__errorurl', self.session_key())
-            self.add_hidden('__domid', self.domid)
-            self.restore_previous_post(self.session_key())
-
-        # XXX why do we need two different variables (mainform and copy_nav_params ?)
-        if self.copy_nav_params:
-            for param in NAV_FORM_PARAMETERS:
-                if not param in kwargs:
-                    value = req.form.get(param)
-                    if value:
-                        self.add_hidden(param, value)
-        if submitmsg is not None:
-            self.add_hidden('__message', submitmsg)
-        if 'domid' in kwargs:# session key changed
-            self.restore_previous_post(self.session_key())
-
     @property
     def needs_multipart(self):
         """true if the form needs enctype=multipart/form-data"""
@@ -113,6 +77,7 @@
 
     def add_hidden(self, name, value=None, **kwargs):
         """add an hidden field to the form"""
+        kwargs.setdefault('ignore_req_params', True)
         kwargs.setdefault('widget', fwdgs.HiddenInput)
         field = StringField(name=name, value=value, **kwargs)
         if 'id' in kwargs:
@@ -145,7 +110,7 @@
     def default_renderer(self):
         return self._cw.vreg['formrenderers'].select(
             self.form_renderer_id, self._cw,
-            rset=self.cw_rset, row=self.cw_row, col=self.cw_col)
+            rset=self.cw_rset, row=self.cw_row, col=self.cw_col or 0)
 
     formvalues = None
     def build_context(self, formvalues=None):
@@ -178,6 +143,7 @@
             renderer = self.default_renderer()
         return renderer.render(self, values)
 
+
 _AFF = uicfg.autoform_field
 _AFF_KWARGS = uicfg.autoform_field_kwargs
 
@@ -248,6 +214,8 @@
         # entity primary view
         if self._cw.json_request and self.edited_entity.has_eid():
             return '%s#%s' % (self.edited_entity.absolute_url(), self.domid)
+        # XXX we should not consider some url parameters that may lead to
+        # different url after a validation error
         return '%s#%s' % (self._cw.url(), self.domid)
 
     def build_context(self, formvalues=None):
@@ -258,8 +226,7 @@
         for field in self.fields:
             if field.eidparam:
                 edited.add(field.role_name())
-        self.add_hidden('_cw_edited_fields', u','.join(edited),
-                        eidparam=True)
+        self.add_hidden('_cw_edited_fields', u','.join(edited), eidparam=True)
 
     def default_renderer(self):
         return self._cw.vreg['formrenderers'].select(
--- a/web/views/management.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/management.py	Fri Feb 26 17:39:33 2010 +0100
@@ -12,7 +12,7 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
-from cubicweb.view import AnyRsetView, StartupView, EntityView
+from cubicweb.view import AnyRsetView, StartupView, EntityView, View
 from cubicweb.uilib import html_traceback, rest_traceback
 from cubicweb.web import formwidgets as wdgs
 from cubicweb.web.formfields import guess_field
@@ -273,63 +273,17 @@
     return binfo
 
 
-class ProcessInformationView(StartupView):
-    __regid__ = 'info'
+class CwStats(View):
+    """A textual stats output for monitoring tools such as munin """
+
+    __regid__ = 'processinfo'
+    content_type = 'text/txt'
+    templatable = False
     __select__ = none_rset() & match_user_groups('users', 'managers')
 
-    title = _('server information')
-
-    def call(self, **kwargs):
-        """display server information"""
-        vcconf = self._cw.vreg.config.vc_config()
-        req = self._cw
-        _ = req._
-        # display main information
-        self.w(u'<h3>%s</h3>' % _('Application'))
-        self.w(u'<table border="1">')
-        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-            'CubicWeb', vcconf.get('cubicweb', _('no version information'))))
-        for pkg in self._cw.vreg.config.cubes():
-            pkgversion = vcconf.get(pkg, _('no version information'))
-            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-                pkg, pkgversion))
-        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-            _('home'), self._cw.vreg.config.apphome))
-        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-            _('base url'), req.base_url()))
-        self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
-            _('data directory url'), req.datadir_url))
-        self.w(u'</table>')
-        self.w(u'<br/>')
-        # environment and request and server information
-        try:
-            # need to remove our adapter and then modpython-apache wrapper...
-            env = req._areq._req.subprocess_env
-        except AttributeError:
-            return
-        self.w(u'<h3>%s</h3>' % _('Environment'))
-        self.w(u'<table border="1">')
-        for attr in env.keys():
-            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, xml_escape(env[attr])))
-        self.w(u'</table>')
-        self.w(u'<h3>%s</h3>' % _('Request'))
-        self.w(u'<table border="1">')
-        for attr in ('filename', 'form', 'hostname', 'main', 'method',
-                     'path_info', 'protocol',
-                     'search_state', 'the_request', 'unparsed_uri', 'uri'):
-            val = getattr(req, attr)
-            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, xml_escape(val)))
-        self.w(u'</table>')
-        server = req.server
-        self.w(u'<h3>%s</h3>' % _('Server'))
-        self.w(u'<table border="1">')
-        for attr in dir(server):
-            val = getattr(server, attr)
-            if attr.startswith('_') or callable(val):
-                continue
-            self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
-                   % (attr, xml_escape(val)))
-        self.w(u'</table>')
-
+    def call(self):
+        stats = self._cw.vreg.config.repository(None).stats()
+        results = []
+        for element in stats:
+            results.append(u'%s %s' % (element, stats[element]))
+        self.w(u'\n'.join(results))
--- a/web/views/navigation.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/navigation.py	Fri Feb 26 17:39:33 2010 +0100
@@ -59,7 +59,6 @@
     nb_chars = 5
 
     def display_func(self, rset, col, attrname):
-        req = self._cw
         if attrname is not None:
             def index_display(row):
                 if not rset[row][col]: # outer join
@@ -159,7 +158,7 @@
     context = 'navbottom'
     order = 10
     def call(self, view=None):
-        entity = self.cw_rset.get_entity(0,0)
+        entity = self.cw_rset.get_entity(0, 0)
         previous = entity.previous_entity()
         next = entity.next_entity()
         if previous or next:
--- a/web/views/old_calendar.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/old_calendar.py	Fri Feb 26 17:39:33 2010 +0100
@@ -40,7 +40,6 @@
 
     def nav_header(self, date, smallshift=3, bigshift=9):
         """prints shortcut links to go to previous/next steps (month|week)"""
-        prev1 = next1 = prev2 = nex2 = date
         prev1 = previous_month(date, smallshift)
         next1 = next_month(date, smallshift)
         prev2 = previous_month(date, bigshift)
@@ -110,7 +109,7 @@
         self._cw.add_css('cubicweb.calendar.css')
         schedule = {}
         for row in xrange(len(self.cw_rset.rows)):
-            entity = self.cw_rset.get_entity(row,0)
+            entity = self.cw_rset.get_entity(row, 0)
             infos = u'<div class="event">'
             infos += self._cw.view(itemvid, self.cw_rset, row=row)
             infos += u'</div>'
@@ -137,11 +136,11 @@
         end = last_day(next_month(day, shift))
         return begin, end
 
-    def _build_ampm_cells(self, daynum, events):
+    def _build_ampm_cells(self, events):
         """create a view without any hourly details.
 
-        :param daynum: day of the built cell
-        :param events: dictionnary with all events classified by hours"""
+        :param events: dictionnary with all events classified by hours
+        """
         # split events according am/pm
         am_events = [event for e_time, e_list in events.iteritems()
                      if 0 <= e_time.hour < 12
@@ -318,7 +317,7 @@
             day = first_day + timedelta(daynum)
             events = schedule.get(day)
             if events:
-                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
+                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(events))
             else:
                 current_row.append((AMPM_DAY % (daynum+1),
                                     AMPM_EMPTY % ("amCell", "am"),
@@ -387,7 +386,7 @@
 
     def format_day_events(self, day, events):
         if events:
-            self.w(u'\n'.join(self._build_ampm_cells(day, events)))
+            self.w(u'\n'.join(self._build_ampm_cells(events)))
         else:
             self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"),
                               AMPM_EMPTY % ("pmCell", "pm")))
@@ -409,7 +408,7 @@
             day = first_day + timedelta(daynum)
             events = schedule.get(day)
             if events:
-                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
+                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(events))
             else:
                 current_row.append((AMPM_DAY % (daynum+1),
                                     AMPM_EMPTY % ("amCell", "am"),
--- a/web/views/plots.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/plots.py	Fri Feb 26 17:39:33 2010 +0100
@@ -7,16 +7,12 @@
 """
 __docformat__ = "restructuredtext en"
 
-import os
-import time
-
 from simplejson import dumps
 
-from logilab.common import flatten
 from logilab.common.date import datetime2ticks
 from logilab.mtconverter import xml_escape
 
-from cubicweb.utils import make_uid, UStringIO
+from cubicweb.utils import UStringIO
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import multi_columns_rset
 from cubicweb.web.views import baseviews
@@ -98,7 +94,7 @@
         #     datetime labels on tooltips is to insert an additional column
         #     cf. function onPlotHover in cubicweb.flot.js
         if self.timemode:
-            plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x,y in plot]
+            plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x, y in plot]
         return dumps(plot)
 
     def _render(self, req, width=500, height=400):
--- a/web/views/sparql.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/sparql.py	Fri Feb 26 17:39:33 2010 +0100
@@ -1,11 +1,11 @@
 """SPARQL integration
 
 :organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-v__docformat__ = "restructuredtext en"
+__docformat__ = "restructuredtext en"
 
 import rql
 from yams import xy
@@ -15,9 +15,9 @@
 
 from cubicweb.view import StartupView, AnyRsetView
 from cubicweb.web import Redirect, form, formfields, formwidgets as fwdgs
-from cubicweb.web.views import forms, urlrewrite
+from cubicweb.web.views import forms
 try:
-    from cubicweb.spa2rql import Sparql2rqlTranslator
+    from cubicweb.spa2rql import Sparql2rqlTranslator, UnsupportedQuery
 except ImportError:
     # fyzz not available (only a recommends)
     Sparql2rqlTranslator = None
@@ -45,7 +45,7 @@
         if sparql:
             try:
                 qinfo = Sparql2rqlTranslator(self._cw.vreg.schema).translate(sparql)
-            except rql.TypeResolverException, ex:
+            except rql.TypeResolverException:
                 self.w(self._cw._('can not resolve entity types:') + u' ' + unicode('ex'))
             except UnsupportedQuery:
                 self.w(self._cw._('we are not yet ready to handle this query'))
--- a/web/views/tabs.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/tabs.py	Fri Feb 26 17:39:33 2010 +0100
@@ -48,7 +48,10 @@
             tabid or vid, xml_escape(self._cw.build_url('json', **urlparams))))
         if show_spinbox:
             w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
-              % (tabid or vid, self._cw._('loading')))
+              % (tabid or vid, self._cw._('(loading ...)')))
+        w(u'<noscript><p><a class="style: hidden" id="seo-%s" href="%s">%s</a></p></noscript>'
+          % (tabid or vid, xml_escape(self._cw.build_url(**urlparams)), xml_escape('%s (%s)') %
+             (tabid or vid, self._cw._('follow this link if javascript is deactivated'))))
         w(u'</div>')
         self._prepare_bindings(tabid or vid, reloadable)
 
--- a/web/views/treeview.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/treeview.py	Fri Feb 26 17:39:33 2010 +0100
@@ -9,7 +9,6 @@
 
 import simplejson as json
 
-from logilab.common.decorators import monkeypatch
 from logilab.mtconverter import xml_escape
 from cubicweb.utils import make_uid
 from cubicweb.interfaces import ITree
@@ -115,7 +114,6 @@
 
     def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs):
         assert treeid is not None
-        entity = self.cw_rset.get_entity(row, col)
         itemview = self._cw.view(vid, self.cw_rset, row=row, col=col)
         last_class = morekwargs['is_last'] and ' class="last"' or ''
         self.w(u'<li%s>%s</li>' % (last_class, itemview))
@@ -177,8 +175,8 @@
             if treeid.startswith('throw_away'):
                 divtail = ''
             else:
-                divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ %\
-                    (treeid, entity.eid)
+                divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ % (
+                    treeid, entity.eid)
             w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
 
             # add empty <ul> because jquery's treeview plugin checks for
--- a/web/views/urlrewrite.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/urlrewrite.py	Fri Feb 26 17:39:33 2010 +0100
@@ -83,6 +83,7 @@
         ('/notfound', dict(vid='404')),
         ('/error', dict(vid='error')),
         ('/sparql', dict(vid='sparql')),
+        ('/processinfo', dict(vid='processinfo')),
         # XXX should be case insensitive as 'create', but I would like to find another way than
         # relying on the etype_selector
         (rgx('/schema/([^/]+?)/?'),  dict(vid='primary', rql=r'Any X WHERE X is CWEType, X name "\1"')),
--- a/web/views/workflow.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/workflow.py	Fri Feb 26 17:39:33 2010 +0100
@@ -69,7 +69,7 @@
                                       entity.view('oneline')))
         msg = self.req._('status will change from %(st1)s to %(st2)s') % {
             'st1': entity.printable_state,
-            'st2': self._cw._(transition.destination().name)}
+            'st2': self._cw._(transition.destination(entity).name)}
         self.w(u'<p>%s</p>\n' % msg)
         self.w(form.render())
 
@@ -318,7 +318,8 @@
         for transition in self.entity.reverse_transition_of:
             for incomingstate in transition.reverse_allowed_transition:
                 yield incomingstate.eid, transition.eid, transition
-            yield transition.eid, transition.destination().eid, transition
+            for outgoingstate in transition.potential_destinations():
+                yield transition.eid, outgoingstate.eid, transition
 
 
 class WorkflowImageView(TmpFileViewMixin, view.EntityView):
--- a/web/views/xbel.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/views/xbel.py	Fri Feb 26 17:39:33 2010 +0100
@@ -26,8 +26,6 @@
 
     def call(self):
         """display a list of entities by calling their <item_vid> view"""
-        title = self.page_title()
-        url = self._cw.build_url(rql=self._cw.form.get('rql', ''))
         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
         self.w(u'<!DOCTYPE xbel PUBLIC "+//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML" "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd">')
         self.w(u'<xbel version="1.0">')
--- a/web/webconfig.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/web/webconfig.py	Fri Feb 26 17:39:33 2010 +0100
@@ -11,7 +11,6 @@
 import os
 from os.path import join, exists, split
 
-from logilab.common.configuration import Method
 from logilab.common.decorators import cached
 
 from cubicweb.toolsutils import read_config
--- a/wsgi/handler.py	Thu Feb 18 09:22:04 2010 +0100
+++ b/wsgi/handler.py	Fri Feb 26 17:39:33 2010 +0100
@@ -8,9 +8,9 @@
 
 __docformat__ = "restructuredtext en"
 
-from cubicweb import ObjectNotFound, AuthenticationError
+from cubicweb import AuthenticationError
 from cubicweb.web import (NotFound, Redirect, DirectResponse, StatusResponse,
-                       ExplicitLogin)
+                          ExplicitLogin)
 from cubicweb.web.application import CubicWebPublisher
 from cubicweb.wsgi.request import CubicWebWsgiRequest