backport stable into default
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 03 Dec 2009 17:17:43 +0100
changeset 3998 94cc7cad3d2d
parent 3895 92ead039d3d0 (current diff)
parent 3995 9b52725d8c53 (diff)
child 4003 b9436fe77c9e
child 4008 fce83937a885
backport stable into default
__pkginfo__.py
common/mail.py
common/migration.py
common/mixins.py
cwvreg.py
debian/rules
devtools/__init__.py
devtools/devctl.py
devtools/testlib.py
entities/test/unittest_wfobjs.py
entities/wfobjs.py
entity.py
hooks/integrity.py
rqlrewrite.py
rset.py
schema.py
schemas/_regproc.sql.mysql
schemas/_regproc.sql.postgres
schemas/workflow.py
server/__init__.py
server/hook.py
server/migractions.py
server/pool.py
server/schemaserial.py
server/serverconfig.py
server/serverctl.py
server/session.py
server/sources/native.py
server/test/data/schema.py
server/test/unittest_multisources.py
server/test/unittest_rql2sql.py
test/unittest_schema.py
utils.py
view.py
web/form.py
web/formwidgets.py
web/test/unittest_form.py
web/test/unittest_views_editforms.py
web/test/unittest_viewselector.py
web/views/boxes.py
web/views/cwproperties.py
web/views/editcontroller.py
web/views/editforms.py
web/views/forms.py
web/views/igeocodable.py
web/views/management.py
web/views/massmailing.py
web/views/primary.py
web/views/sparql.py
web/views/tabs.py
web/views/timetable.py
web/views/treeview.py
web/views/workflow.py
--- a/.hgignore	Mon Nov 23 14:13:53 2009 +0100
+++ b/.hgignore	Thu Dec 03 17:17:43 2009 +0100
@@ -8,3 +8,4 @@
 \~$
 \#.*?\#$
 \.swp$
+^doc/book/en/apidoc$
--- a/.hgtags	Mon Nov 23 14:13:53 2009 +0100
+++ b/.hgtags	Thu Dec 03 17:17:43 2009 +0100
@@ -82,3 +82,13 @@
 37d025b2aa7735dae4a861059014c560b45b19e6 cubicweb-debian-version-3.5.4-1
 1eca47d59fd932fe23f643ca239cf2408e5b1856 cubicweb-version-3.5.5
 aad818d9d9b6fdb2ffea56c0a9af718c0b69899d cubicweb-debian-version-3.5.5-1
+b79f361839a7251b35eb8378fbc0773de7c8a815 cubicweb-version-3.5.6
+e6225e8e36c6506c774e0a76acc301d8ae1c1028 cubicweb-debian-version-3.5.6-1
+b79f361839a7251b35eb8378fbc0773de7c8a815 cubicweb-version-3.5.6
+4e619e97b3fd70769a0f454963193c10cb87f9d4 cubicweb-version-3.5.6
+e6225e8e36c6506c774e0a76acc301d8ae1c1028 cubicweb-debian-version-3.5.6-1
+5f7c939301a1b915e17eec61c05e8e9ab8bdc182 cubicweb-debian-version-3.5.6-1
+0fc300eb4746e01f2755b9eefd986d58d8366ccf cubicweb-version-3.5.7
+7a96c0544c138a0c5f452e5b2428ce6e2b7cb378 cubicweb-debian-version-3.5.7-1
+1677312fd8a3e8c0a5ae083e3104ca62b7c9a5bb cubicweb-version-3.5.9
+d7f2d32340fb59753548ef29cbc1958ef3a55fc6 cubicweb-debian-version-3.5.9-1
--- a/__pkginfo__.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/__pkginfo__.py	Thu Dec 03 17:17:43 2009 +0100
@@ -7,7 +7,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 5, 5)
+numversion = (3, 5, 10)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
--- a/common/mail.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/common/mail.py	Thu Dec 03 17:17:43 2009 +0100
@@ -198,6 +198,11 @@
                 subject = self.subject()
             except SkipEmail:
                 continue
+            except Exception, ex:
+                # shouldn't make the whole transaction fail because of rendering
+                # error (unauthorized or such)
+                self.exception(str(ex))
+                continue
             msg = format_mail(self.user_data, [emailaddr], content, subject,
                               config=self._cw.vreg.config, msgid=msgid, references=refs)
             yield [emailaddr], msg
--- a/common/migration.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/common/migration.py	Thu Dec 03 17:17:43 2009 +0100
@@ -268,6 +268,7 @@
         in interactive mode,  display the migration script path, ask for
         confirmation and execute it if confirmed
         """
+        migrscript = os.path.normpath(migrscript)
         if migrscript.endswith('.py'):
             script_mode = 'python'
         elif migrscript.endswith('.txt') or migrscript.endswith('.rst'):
@@ -295,7 +296,7 @@
                 return func(*args, **kwargs)
         else: # script_mode == 'doctest'
             import doctest
-            doctest.testfile(os.path.abspath(migrscript), module_relative=False,
+            doctest.testfile(migrscript, module_relative=False,
                              optionflags=doctest.ELLIPSIS, globs=scriptlocals)
 
     def cmd_option_renamed(self, oldname, newname):
--- a/common/mixins.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/common/mixins.py	Thu Dec 03 17:17:43 2009 +0100
@@ -194,7 +194,12 @@
         return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
 
 
+"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
+classes which have the relation described by the dict's key.
 
+NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
+(eg without plugged classes). This includes bases Entity and AnyEntity classes.
+"""
 MI_REL_TRIGGERS = {
     ('primary_email',   'subject'): EmailableMixIn,
     ('use_email',   'subject'): EmailableMixIn,
--- a/cwconfig.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/cwconfig.py	Thu Dec 03 17:17:43 2009 +0100
@@ -216,9 +216,12 @@
 
     if os.environ.get('APYCOT_ROOT'):
         mode = 'test'
-        CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ
-        # create __init__ file
-        file(join(CUBES_DIR, '__init__.py'), 'w').close()
+        if CWDEV:
+            CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ
+            # create __init__ file
+            file(join(CUBES_DIR, '__init__.py'), 'w').close()
+        else:
+            CUBES_DIR = '/usr/share/cubicweb/cubes/'
     elif (CWDEV and _forced_mode != 'system'):
         mode = 'user'
         CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes')))
@@ -612,7 +615,10 @@
         root = os.environ['APYCOT_ROOT']
         REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
         RUNTIME_DIR = tempfile.gettempdir()
-        MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
+        if CWDEV:
+            MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
+        else:
+            MIGRATION_DIR = '/usr/share/cubicweb/migration/'
         if not exists(REGISTRY_DIR):
             os.makedirs(REGISTRY_DIR)
     else:
--- a/cwvreg.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/cwvreg.py	Thu Dec 03 17:17:43 2009 +0100
@@ -499,7 +499,7 @@
         for key, val in propvalues:
             try:
                 values[key] = self.typed_value(key, val)
-            except ValueError:
+            except ValueError, ex:
                 self.warning('%s (you should probably delete that property '
                              'from the database)', ex)
             except UnknownProperty, ex:
--- a/debian/changelog	Mon Nov 23 14:13:53 2009 +0100
+++ b/debian/changelog	Thu Dec 03 17:17:43 2009 +0100
@@ -1,3 +1,33 @@
+cubicweb (3.5.10-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Thu, 03 Dec 2009 15:48:38 +0100
+
+cubicweb (3.5.9-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Sun, 29 Nov 2009 23:28:47 +0100
+
+cubicweb (3.5.8-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Sun, 29 Nov 2009 22:43:11 +0100
+
+cubicweb (3.5.7-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr>  Sat, 28 Nov 2009 11:50:08 +0100
+
+cubicweb (3.5.6-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Adrien Di Mascio <Adrien.DiMascio@logilab.fr>  Mon, 23 Nov 2009 18:55:01 +0100
+
 cubicweb (3.5.5-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Mon Nov 23 14:13:53 2009 +0100
+++ b/debian/control	Thu Dec 03 17:17:43 2009 +0100
@@ -4,6 +4,7 @@
 Maintainer: Logilab S.A. <contact@logilab.fr>
 Uploaders: Sylvain Thenault <sylvain.thenault@logilab.fr>,
            Julien Jehannet <julien.jehannet@logilab.fr>,
+           Adrien Di Mascio <Adrien.DiMascio@logilab.fr>,
            Aurélien Campéas <aurelien.campeas@logilab.fr>,
            Nicolas Chauvat <nicolas.chauvat@logilab.fr>
 Build-Depends: debhelper (>= 5), python-dev (>=2.4), python-central (>= 0.5)
@@ -76,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.44.0), python-yams (>= 0.25.0), python-rql (>= 0.22.3), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.45.2), python-yams (>= 0.25.0), python-rql (>= 0.22.3), python-lxml
 Recommends: python-simpletal (>= 4.0)
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/debian/rules	Mon Nov 23 14:13:53 2009 +0100
+++ b/debian/rules	Thu Dec 03 17:17:43 2009 +0100
@@ -8,7 +8,7 @@
 PY_VERSION:=$(shell pyversions -d)
 
 build: build-stamp
-build-stamp: 
+build-stamp:
 	dh_testdir
 	# XXX doesn't work if logilab-doctools, logilab-xml are not in build depends
 	# and I can't get pbuilder find them in its chroot :(
@@ -17,7 +17,7 @@
 	python setup.py build
 	touch build-stamp
 
-clean: 
+clean:
 	dh_testdir
 	dh_testroot
 	rm -f build-stamp configure-stamp
@@ -82,6 +82,6 @@
 
 binary-arch:
 
-binary: binary-indep 
+binary: binary-indep
 .PHONY: build clean binary binary-indep binary-arch
 
--- a/devtools/devctl.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/devtools/devctl.py	Thu Dec 03 17:17:43 2009 +0100
@@ -21,9 +21,10 @@
 from logilab.common.shellutils import ASK
 from logilab.common.clcommands import register_commands
 
+from cubicweb.__pkginfo__ import version as cubicwebversion
 from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
-from cubicweb.__pkginfo__ import version as cubicwebversion
 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
 
@@ -138,6 +139,8 @@
         libschema = {}
         afs = uicfg.autoform_section
         appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+        for cstrtype in CONSTRAINTS:
+            add_msg(w, cstrtype)
     done = set()
     for eschema in sorted(schema.entities()):
         etype = eschema.type
--- a/devtools/testlib.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/devtools/testlib.py	Thu Dec 03 17:17:43 2009 +0100
@@ -24,7 +24,7 @@
 from logilab.common.decorators import cached, classproperty, clear_cache
 from logilab.common.deprecation import deprecated
 
-from cubicweb import NoSelectableObject, AuthenticationError
+from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
 from cubicweb import cwconfig, devtools, web, server
 from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
 from cubicweb.sobjects import notification
@@ -673,10 +673,13 @@
         try:
             validatorclass = self.vid_validators[view.__regid__]
         except KeyError:
-            if template is None:
-                default_validator = htmlparser.HTMLValidator
+            if view.content_type in ('text/html', 'application/xhtml+xml'):
+                if template is None:
+                    default_validator = htmlparser.HTMLValidator
+                else:
+                    default_validator = htmlparser.DTDValidator
             else:
-                default_validator = htmlparser.DTDValidator
+                default_validator = None
             validatorclass = self.content_type_validators.get(view.content_type,
                                                               default_validator)
         if validatorclass is None:
@@ -779,7 +782,7 @@
             rset = cu.execute('%s X' % etype)
             edict[str(etype)] = set(row[0] for row in rset.rows)
         existingrels = {}
-        ignored_relations = SYSTEM_RELATIONS | self.ignored_relations
+        ignored_relations = SYSTEM_RELATIONS + self.ignored_relations
         for rschema in self.schema.relations():
             if rschema.final or rschema in ignored_relations:
                 continue
@@ -788,7 +791,11 @@
         q = make_relations_queries(self.schema, edict, cu, ignored_relations,
                                    existingrels=existingrels)
         for rql, args in q:
-            cu.execute(rql, args)
+            try:
+                cu.execute(rql, args)
+            except ValidationError, ex:
+                # failed to satisfy some constraint
+                print 'error in automatic db population', ex
         self.post_populate(cu)
         self.commit()
 
--- a/doc/book/en/development/devweb/facets.rst	Mon Nov 23 14:13:53 2009 +0100
+++ b/doc/book/en/development/devweb/facets.rst	Thu Dec 03 17:17:43 2009 +0100
@@ -1,3 +1,129 @@
 The facets system
 -----------------
-XXX feed me
\ No newline at end of file
+XXX feed me more (below is the extracted of adim blog)
+
+
+Recently, for internal purposes, we've made a little cubicweb application to
+help us
+organizing visits to find new office locations. Here's an *excerpt* of the
+schema:
+
+.. sourcecode:: python
+
+  class Office(WorkflowableEntityType):
+      price = Int(description='euros / m2 / HC / HT')
+      surface = Int(description='m2')
+      description = RichString(fulltextindexed=True)
+      has_address = SubjectRelation('PostalAddress', cardinality='1?', composite='subject')
+      proposed_by = SubjectRelation('Agency')
+      comments = ObjectRelation('Comment', cardinality='1*', composite='object')
+      screenshots = SubjectRelation(('File', 'Image'), cardinality='*1',
+                                    composite='subject')
+
+The two other entity types defined in the schema are `Visit` and `Agency` but we
+can also guess from the above that this application uses the two cubes
+`comment`_ and
+`addressbook`_ (remember, cubicweb is only a game where you assemble cubes !). 
+
+While we know that just defining the schema in enough to have a full, usable,
+(testable !) application, we also know that every application needs to be 
+customized to fulfill the needs it was built for. So in this case, what we
+needed most was some custom filters that would let us restrict searches
+according
+to surfaces, prices or zipcodes. Fortunately for us, Cubicweb provides the
+**facets** (image_) mechanism and a few base classes that make the task quite
+easy:
+
+.. sourcecode:: python
+
+  class PostalCodeFacet(RelationFacet): 
+      id = 'postalcode-facet'             # every registered class must have an id
+      __select__ = implements('Office')   # this facet should only be selected when 
+                                          # visualizing offices
+      rtype = 'has_address'               # this facet is a filter on the entity linked to
+                                          # the office thrhough the relation
+                                          # has_address
+      target_attr = 'postalcode'          # the filter's key is the attribute "postal_code"
+                                          # of the target PostalAddress entity
+
+This is a typical `RelationFacet`: we want to be able to filter offices
+according
+to the attribute `postalcode` of their associated `PostalAdress`. Each line in
+the class is explained by the comment on its right.
+
+Now, here is the code to define a filter based on the `surface` attribute of the
+`Office`:
+
+.. sourcecode:: python
+
+  class SurfaceFacet(AttributeFacet):
+      id = 'surface-facet'              # every registered class must have an id
+      __select__ = implements('Office') # this facet should only be selected when 
+                                        # visualizing offices
+      rtype = 'surface'                 # the filter's key is the attribute "surface" 
+      comparator = '>='                 # override the default value of operator since 
+                                        # we want to filter according to a
+                                        # minimal 
+                                        # value, not an exact one
+
+      def rset_vocabulary(self, ___):
+          """override the default vocabulary method since we want to hard-code
+          our threshold values. 
+          Not overriding would generate a filter box with all existing surfaces
+          defined in the database.
+          """
+          return [('> 200', '200'), ('> 250', '250'),
+                  ('> 275', '275'), ('> 300', '300')]
+
+
+And that's it: we have two filter boxes automatically displayed on each page
+presenting more than one office. The `price` facet is basically the same as the
+`surface` one but with a different vocabulary and with ``rtype = 'price'``.
+
+(The cube also benefits from the builtin google map views defined by
+cubicweb but that's for another blog).
+
+.. _image: http://www.cubicweb.org/image/197646?vid=download
+.. _comment: http://www.cubicweb.org/project/cubicweb-comment
+.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
+
+CubicWeb has this really nice builtin `facet`_ system to
+define restrictions `filters`_ really as easily as possible.
+
+We've just added two new kind of facets in CubicWeb :
+
+- The **RangeFacet** which displays a slider using `jquery`_
+  to choose a lower bound and an upper bound. The **RangeWidget** 
+  works with either numerical values or date values
+
+- The **HasRelationFacet** which displays a simple checkbox and
+  lets you refine your selection in order to get only entities
+  that actually use this relation.
+
+.. image :: http://www.cubicweb.org/Image/343498?vid=download
+
+
+Here's an example of code that defines a facet to filter 
+musical works according to their composition date:
+
+.. sourcecode:: python
+
+    class CompositionDateFacet(DateRangeFacet):
+        # 1. make sure this facet is displayed only on Track selection
+        __select__ = DateRangeFacet.__select__ & implements('Track')
+        # 2. give the facet an id (required by CubicWeb)
+        id = 'compdate-facet'
+        # 3. specify the attribute name that actually stores the date in the DB
+        rtype = 'composition_date'
+
+And that's it, on each page displaying tracks, you'll be able to filter them
+according to their composition date with a jquery slider.
+
+All this, brought by CubicWeb (in the next 3.3 version)
+
+.. _facet: http://en.wikipedia.org/wiki/Faceted_browser
+.. _filters: http://www.cubicweb.org/blogentry/154152
+.. _jquery: http://www.jqueryui.com/
+
+To use **HasRelationFacet** on a reverse relation add ``role = 'object'`` in
+it's definitions.
--- a/doc/book/en/development/devweb/internationalization.rst	Mon Nov 23 14:13:53 2009 +0100
+++ b/doc/book/en/development/devweb/internationalization.rst	Thu Dec 03 17:17:43 2009 +0100
@@ -20,6 +20,9 @@
 String internationalization
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+User defined string
+```````````````````
+
 In the Python code and cubicweb-tal templates translatable strings can be
 marked in one of the following ways :
 
@@ -63,15 +66,40 @@
 Translations in cubicweb-tal template can also be done with TAL tags
 `i18n:content` and `i18n:replace`.
 
-.. note::
-
-   We dont need to mark the translation strings of entities/relations
-   used by a particular instance's schema as they are generated
-   automatically.
 
 If you need to add messages on top of those that can be found in the source,
 you can create a file named `i18n/static-messages.pot`.
 
+Generated string
+````````````````
+
+We do not need to mark the translation strings of entities/relations used by a
+particular instance's schema as they are generated automatically. String for
+various actions are also generated.
+
+For exemple the following schema ::
+
+  Class EntityA(EntityType):
+      relationa2b = SubjectRelation('EntityB')
+
+  class EntityB(EntityType):
+      pass
+
+May generate the following message ::
+
+  creating EntityB (EntityA %(linkto)s relation_a2b EntityB)
+
+This message will be used in views of ``EntityA`` for creation of a new
+``EntityB`` with a preset relation ``relation_a2b`` between the current
+``EntityA`` and the new ``EntityB``. The opposite message ::
+
+  creating EntityA (EntityA relation_a2b %(linkto)s EntityA)
+
+Is used for similar creation of an ``EntityA`` from a view of ``EntityB``.
+
+In the translated string you can use ``%(linkto)s`` for reference to the source
+``entity``.
+
 Handle the translation catalog
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
--- a/entities/test/unittest_wfobjs.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/entities/test/unittest_wfobjs.py	Thu Dec 03 17:17:43 2009 +0100
@@ -37,12 +37,18 @@
         self.commit()
         wf.add_state(u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        # XXX enhance message
-        self.assertEquals(ex.errors, {'state_of': 'unique constraint S name N, Y state_of O, Y name N failed'})
+        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf2.add_state(u'foo', initial=True)
         self.commit()
+        # gnark gnark
+        bar = wf.add_state(u'bar')
+        self.commit()
+        print '*'*80
+        bar.set_attributes(name=u'foo')
+        ex = self.assertRaises(ValidationError, self.commit)
+        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
 
     def test_duplicated_transition(self):
         wf = add_wf(self, 'Company')
@@ -51,8 +57,19 @@
         wf.add_transition(u'baz', (foo,), bar, ('managers',))
         wf.add_transition(u'baz', (bar,), foo)
         ex = self.assertRaises(ValidationError, self.commit)
-        # XXX enhance message
-        self.assertEquals(ex.errors, {'transition_of': 'unique constraint S name N, Y transition_of O, Y name N failed'})
+        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+        # no pb if not in the same workflow
+        wf2 = add_wf(self, 'Company')
+        foo = wf.add_state(u'foo', initial=True)
+        bar = wf.add_state(u'bar')
+        wf.add_transition(u'baz', (foo,), bar, ('managers',))
+        self.commit()
+        # gnark gnark
+        biz = wf.add_transition(u'biz', (bar,), foo)
+        self.commit()
+        biz.set_attributes(name=u'baz')
+        ex = self.assertRaises(ValidationError, self.commit)
+        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
 
 
 class WorkflowTC(CubicWebTC):
@@ -375,7 +392,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid})
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow': 'constraint S is ET, O workflow_of ET failed'})
+        self.assertEquals(ex.errors, {'custom_workflow': 'workflow isn\'t a workflow for this type'})
 
     def test_del_custom_wf(self):
         """member in some state shared by the new workflow, nothing has to be
--- a/entities/wfobjs.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/entities/wfobjs.py	Thu Dec 03 17:17:43 2009 +0100
@@ -34,6 +34,7 @@
         return any(et for et in self.reverse_default_workflow
                    if et.name == etype)
 
+    # XXX define parent() instead? what if workflow of multiple types?
     def after_deletion_path(self):
         """return (path, parameters) which should be used as redirect
         information when this entity is being deleted
@@ -101,7 +102,7 @@
         self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
                          {'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
         if initial:
-            assert not self.initial
+            assert not self.initial, "Initial state already defined as %s" % self.initial
             self._cw.execute('SET WF initial_state S '
                              'WHERE S eid %(s)s, WF eid %(wf)s',
                              {'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
@@ -245,6 +246,9 @@
     def destination(self):
         return self.destination_state[0]
 
+    def parent(self):
+        return self.workflow
+
 
 class WorkflowTransition(BaseTransition):
     """customized class for WorkflowTransition entities"""
@@ -310,6 +314,9 @@
     def destination(self):
         return self.destination_state and self.destination_state[0] or None
 
+    def parent(self):
+        return self.reverse_subworkflow_exit[0]
+
 
 class State(AnyEntity):
     """customized class for State entities"""
@@ -322,13 +329,8 @@
         # take care, may be missing in multi-sources configuration
         return self.state_of and self.state_of[0]
 
-    def after_deletion_path(self):
-        """return (path, parameters) which should be used as redirect
-        information when this entity is being deleted
-        """
-        if self.state_of:
-            return self.state_of[0].rest_path(), {}
-        return super(State, self).after_deletion_path()
+    def parent(self):
+        return self.workflow
 
 
 class TrInfo(AnyEntity):
@@ -353,13 +355,8 @@
     def transition(self):
         return self.by_transition and self.by_transition[0] or None
 
-    def after_deletion_path(self):
-        """return (path, parameters) which should be used as redirect
-        information when this entity is being deleted
-        """
-        if self.for_entity:
-            return self.for_entity.rest_path(), {}
-        return 'view', {}
+    def parent(self):
+        return self.for_entity
 
 
 class WorkflowableMixIn(object):
@@ -431,6 +428,7 @@
     def possible_transitions(self, type='normal'):
         """generates transition that MAY be fired for the given entity,
         expected to be in this state
+        used only by the UI
         """
         if self.current_state is None or self.current_workflow is None:
             return
--- a/entity.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/entity.py	Thu Dec 03 17:17:43 2009 +0100
@@ -100,8 +100,18 @@
                 attr = 'reverse_%s' % rschema.type
                 setattr(cls, attr, ObjectRelation(rschema))
         if mixins:
-            cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
-            cls.debug('plugged %s mixins on %s', mixins, etype)
+            # see etype class instantation in cwvreg.ETypeRegistry.etype_class method:
+            # due to class dumping, cls is the generated top level class with actual
+            # user class as (only) parent. Since we want to be able to override mixins
+            # method from this user class, we have to take care to insert mixins after that
+            # class
+            #
+            # note that we don't plug mixins as user class parent since it causes pb
+            # with some cases of entity classes inheritance.
+            mixins.insert(0, cls.__bases__[0])
+            mixins += cls.__bases__[1:]
+            cls.__bases__ = tuple(mixins)
+            cls.info('plugged %s mixins on %s', mixins, cls)
 
     @classmethod
     def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
@@ -657,6 +667,7 @@
         rdef = rtype.role_rdef(self.e_schema, targettype, role)
         insertsecurity = (rdef.has_local_role('add') and not
                           rdef.has_perm(self._cw, 'add', **securitycheck_args))
+        # XXX consider constraint.mainvars to check if constraint apply
         if vocabconstraints:
             # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
             # will be included as well
@@ -777,15 +788,26 @@
                              kwargs, 'x')
 
     def set_relations(self, _cw_unsafe=False, **kwargs):
+        """add relations to the given object. To set a relation where this entity
+        is the object of the relation, use 'reverse_'<relation> as argument name.
+
+        Values may be an entity, a list of entity, or None (meaning that all
+        relations of the given type from or to this object should be deleted).
+        """
         if _cw_unsafe:
             execute = self.req.unsafe_execute
         else:
             execute = self.req.execute
+        # XXX update cache
         for attr, values in kwargs.iteritems():
             if attr.startswith('reverse_'):
                 restr = 'Y %s X' % attr[len('reverse_'):]
             else:
                 restr = 'X %s Y' % attr
+            if values is None:
+                execute('DELETE %s WHERE X eid %%(x)s' % restr,
+                        {'x': self.eid}, 'x')
+                continue
             if not isinstance(values, (tuple, list, set, frozenset)):
                 values = (values,)
             execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
--- a/hooks/integrity.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/hooks/integrity.py	Thu Dec 03 17:17:43 2009 +0100
@@ -9,7 +9,7 @@
 __docformat__ = "restructuredtext en"
 
 from cubicweb import ValidationError
-from cubicweb.schema import RQLVocabularyConstraint
+from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
 from cubicweb.selectors import entity_implements
 from cubicweb.common.uilib import soup2xhtml
 from cubicweb.server import hook
@@ -146,6 +146,7 @@
     events = ('after_add_relation',)
 
     def __call__(self):
+        # XXX get only RQL[Unique]Constraints?
         constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
                                                 'constraints')
         if constraints:
@@ -167,7 +168,7 @@
         for attr in entity.edited_attributes:
             if schema.rschema(attr).final:
                 constraints = [c for c in entity.rdef(attr).constraints
-                               if isinstance(c, RQLVocabularyConstraint)]
+                               if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
                 if constraints:
                     _CheckConstraintsOp(self._cw, constraints=constraints,
                                         rdef=(entity.eid, attr, None))
--- a/i18n/en.po	Mon Nov 23 14:13:53 2009 +0100
+++ b/i18n/en.po	Thu Dec 03 17:17:43 2009 +0100
@@ -226,6 +226,9 @@
 msgid "Boolean_plural"
 msgstr "Booleans"
 
+msgid "BoundConstraint"
+msgstr "bound constraint"
+
 msgid "Browse by category"
 msgstr ""
 
@@ -329,6 +332,9 @@
 msgid "Do you want to delete the following element(s) ?"
 msgstr ""
 
+msgid "Download page as pdf"
+msgstr ""
+
 msgid "EmailAddress"
 msgstr "Email address"
 
@@ -353,6 +359,12 @@
 msgid "Float_plural"
 msgstr "Floats"
 
+# schema pot file, generated on 2009-12-03 09:22:35
+#
+# singular and plural forms for each entity type
+msgid "FormatConstraint"
+msgstr "format constraint"
+
 msgid "From:"
 msgstr ""
 
@@ -368,6 +380,9 @@
 msgid "Interval"
 msgstr "Interval"
 
+msgid "IntervalBoundConstraint"
+msgstr "interval constraint"
+
 msgid "Interval_plural"
 msgstr "Intervals"
 
@@ -455,18 +470,30 @@
 msgid "Please note that this is only a shallow copy"
 msgstr ""
 
+msgid "RQLConstraint"
+msgstr "RQL constraint"
+
 msgid "RQLExpression"
 msgstr "RQL expression"
 
 msgid "RQLExpression_plural"
 msgstr "RQL expressions"
 
+msgid "RQLUniqueConstraint"
+msgstr "RQL unique constraint"
+
+msgid "RQLVocabularyConstraint"
+msgstr "RQL vocabulary constraint"
+
 msgid "Read permissions"
 msgstr ""
 
 msgid "Recipients:"
 msgstr ""
 
+msgid "RegexpConstraint"
+msgstr "regular expression constrainte"
+
 msgid "Registry's content"
 msgstr ""
 
@@ -489,6 +516,9 @@
 msgid "Server"
 msgstr ""
 
+msgid "SizeConstraint"
+msgstr "size constraint"
+
 msgid "Startup views"
 msgstr ""
 
@@ -498,6 +528,9 @@
 msgid "State_plural"
 msgstr "States"
 
+msgid "StaticVocabularyConstraint"
+msgstr "vocabulary constraint"
+
 msgid "String"
 msgstr "String"
 
@@ -634,6 +667,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr ""
 
+msgid "UniqueConstraint"
+msgstr "unique constraint"
+
 msgid "Update permissions"
 msgstr ""
 
@@ -895,9 +931,6 @@
 msgid "add"
 msgstr ""
 
-msgid "add BaseTransition transition_of Workflow object"
-msgstr ""
-
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "bookmark"
 
@@ -1388,12 +1421,6 @@
 msgid "components_etypenavigation_description"
 msgstr "permit to filter search results by entity type"
 
-msgid "components_help"
-msgstr "help button"
-
-msgid "components_help_description"
-msgstr "the help button on the top right-hand corner"
-
 msgid "components_loggeduserlink"
 msgstr "user link"
 
@@ -1415,12 +1442,6 @@
 msgid "components_navigation_description"
 msgstr "pagination component for large resultsets"
 
-msgid "components_pdfview"
-msgstr ""
-
-msgid "components_pdfview_description"
-msgstr ""
-
 msgid "components_rqlinput"
 msgstr "rql input box"
 
@@ -1498,6 +1519,12 @@
 msgid "contentnavigation_breadcrumbs_description"
 msgstr "breadcrumbs bar that display a path locating the page in the site"
 
+msgid "contentnavigation_metadata"
+msgstr "entity's metadata"
+
+msgid "contentnavigation_metadata_description"
+msgstr ""
+
 msgid "contentnavigation_prevnext"
 msgstr "previous / next entity"
 
@@ -1514,6 +1541,12 @@
 "section containing entities related by the \"see also\" relation on entities "
 "supporting it."
 
+msgid "contentnavigation_view_page_as_pdf"
+msgstr "icon to display page as pdf"
+
+msgid "contentnavigation_view_page_as_pdf_description"
+msgstr ""
+
 msgid "contentnavigation_wfhistory"
 msgstr "workflow history"
 
@@ -1595,10 +1628,6 @@
 msgid "created_by_object"
 msgstr "has created"
 
-msgid ""
-"creating BaseTransition (BaseTransition transition_of Workflow %(linkto)s)"
-msgstr ""
-
 msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
 msgstr "creating bookmark for %(linkto)s"
 
@@ -1723,6 +1752,9 @@
 msgid "csv export"
 msgstr ""
 
+msgid "ctxtoolbar"
+msgstr "toolbar"
+
 #, python-format
 msgid "currently attached file: %s"
 msgstr ""
@@ -1966,6 +1998,9 @@
 msgid "destination state for this transition"
 msgstr ""
 
+msgid "destination state must be in the same workflow as our parent transition"
+msgstr ""
+
 msgid "destination state of a transition"
 msgstr ""
 
@@ -2017,9 +2052,6 @@
 msgid "display the component or not"
 msgstr ""
 
-msgid "display the pdf icon or not"
-msgstr ""
-
 msgid ""
 "distinct label to distinguate between other permission entity of the same "
 "name"
@@ -2035,9 +2067,6 @@
 msgid "download icon"
 msgstr ""
 
-msgid "download page as pdf"
-msgstr ""
-
 msgid "download schema as owl"
 msgstr ""
 
@@ -2129,6 +2158,9 @@
 msgid "eta_date"
 msgstr ""
 
+msgid "exit state must a subworkflow state"
+msgstr ""
+
 msgid "exit_point"
 msgstr ""
 
@@ -2228,6 +2260,10 @@
 msgid "follow"
 msgstr ""
 
+#, python-format
+msgid "follow this link for more information on this %s"
+msgstr ""
+
 msgid "for_user"
 msgstr "for user"
 
@@ -3338,6 +3374,12 @@
 msgid "state"
 msgstr ""
 
+msgid "state and transition don't belong the the same workflow"
+msgstr ""
+
+msgid "state doesn't apply to this entity's type"
+msgstr ""
+
 msgid "state doesn't belong to entity's current workflow"
 msgstr ""
 
@@ -3349,6 +3391,9 @@
 "workflow for this entity first."
 msgstr ""
 
+msgid "state doesn't belong to this workflow"
+msgstr ""
+
 msgid "state_of"
 msgstr "state of"
 
@@ -3389,6 +3434,10 @@
 msgid "subworkflow"
 msgstr ""
 
+msgid ""
+"subworkflow isn't a workflow for the same types as the transition's workflow"
+msgstr ""
+
 msgid "subworkflow state"
 msgstr ""
 
@@ -3567,6 +3616,10 @@
 msgid "toggle check boxes"
 msgstr ""
 
+#, python-format
+msgid "transition %s isn't allowed from %s"
+msgstr ""
+
 msgid "transition doesn't belong to entity's workflow"
 msgstr ""
 
@@ -3856,6 +3909,12 @@
 msgid "workflow"
 msgstr ""
 
+msgid "workflow already have a state of that name"
+msgstr ""
+
+msgid "workflow already have a transition of that name"
+msgstr ""
+
 #, python-format
 msgid "workflow changed to \"%s\""
 msgstr ""
@@ -3866,6 +3925,12 @@
 msgid "workflow history item"
 msgstr ""
 
+msgid "workflow isn't a workflow for this type"
+msgstr ""
+
+msgid "workflow isn't a workflow of this type"
+msgstr ""
+
 msgid "workflow to which this state belongs"
 msgstr ""
 
@@ -3904,6 +3969,12 @@
 msgid "you should probably delete that property"
 msgstr ""
 
+#~ msgid "components_help"
+#~ msgstr "help button"
+
+#~ msgid "components_help_description"
+#~ msgstr "the help button on the top right-hand corner"
+
 #~ msgctxt "inlined:CWRelation:from_entity:subject"
 #~ msgid "remove this CWEType"
 #~ msgstr "remove this entity type"
--- a/i18n/es.po	Mon Nov 23 14:13:53 2009 +0100
+++ b/i18n/es.po	Thu Dec 03 17:17:43 2009 +0100
@@ -234,6 +234,9 @@
 msgid "Boolean_plural"
 msgstr "Booleanos"
 
+msgid "BoundConstraint"
+msgstr ""
+
 msgid "Browse by category"
 msgstr "Busca por categoría"
 
@@ -337,6 +340,9 @@
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Desea suprimir el(los) elemento(s) siguiente(s)"
 
+msgid "Download page as pdf"
+msgstr ""
+
 msgid "EmailAddress"
 msgstr "Correo Electrónico"
 
@@ -361,6 +367,12 @@
 msgid "Float_plural"
 msgstr "Números flotantes"
 
+# schema pot file, generated on 2009-12-03 09:22:35
+#
+# singular and plural forms for each entity type
+msgid "FormatConstraint"
+msgstr ""
+
 msgid "From:"
 msgstr "De: "
 
@@ -376,6 +388,9 @@
 msgid "Interval"
 msgstr "Duración"
 
+msgid "IntervalBoundConstraint"
+msgstr ""
+
 msgid "Interval_plural"
 msgstr "Duraciones"
 
@@ -463,18 +478,30 @@
 msgid "Please note that this is only a shallow copy"
 msgstr "Recuerde que no es más que una copia superficial"
 
+msgid "RQLConstraint"
+msgstr ""
+
 msgid "RQLExpression"
 msgstr "Expresión RQL"
 
 msgid "RQLExpression_plural"
 msgstr "Expresiones RQL"
 
+msgid "RQLUniqueConstraint"
+msgstr ""
+
+msgid "RQLVocabularyConstraint"
+msgstr ""
+
 msgid "Read permissions"
 msgstr "Autorización de leer"
 
 msgid "Recipients:"
 msgstr "Destinatarios"
 
+msgid "RegexpConstraint"
+msgstr ""
+
 msgid "Registry's content"
 msgstr ""
 
@@ -497,6 +524,9 @@
 msgid "Server"
 msgstr "Servidor"
 
+msgid "SizeConstraint"
+msgstr ""
+
 msgid "Startup views"
 msgstr "Vistas de Inicio"
 
@@ -506,6 +536,9 @@
 msgid "State_plural"
 msgstr "Estados"
 
+msgid "StaticVocabularyConstraint"
+msgstr ""
+
 msgid "String"
 msgstr "Cadena de caracteres"
 
@@ -642,6 +675,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr "No encontramos el nombre \"%s\" en el esquema"
 
+msgid "UniqueConstraint"
+msgstr ""
+
 msgid "Update permissions"
 msgstr "Autorización de modificar"
 
@@ -918,9 +954,6 @@
 msgid "add"
 msgstr "Agregar"
 
-msgid "add BaseTransition transition_of Workflow object"
-msgstr ""
-
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "Agregar a los favoritos "
 
@@ -1420,12 +1453,6 @@
 msgid "components_etypenavigation_description"
 msgstr "Permite filtrar por tipo de entidad los resultados de búsqueda"
 
-msgid "components_help"
-msgstr "Botón de ayuda"
-
-msgid "components_help_description"
-msgstr "El botón de ayuda, en el encabezado de página"
-
 msgid "components_loggeduserlink"
 msgstr "Liga usuario"
 
@@ -1448,12 +1475,6 @@
 "Componente que permite distribuir sobre varias páginas las búsquedas que "
 "arrojan mayores resultados que un número previamente elegido"
 
-msgid "components_pdfview"
-msgstr ""
-
-msgid "components_pdfview_description"
-msgstr ""
-
 msgid "components_rqlinput"
 msgstr "Barra rql"
 
@@ -1531,6 +1552,12 @@
 msgid "contentnavigation_breadcrumbs_description"
 msgstr "Muestra un camino que permite localizar la página actual en el sitio"
 
+msgid "contentnavigation_metadata"
+msgstr ""
+
+msgid "contentnavigation_metadata_description"
+msgstr ""
+
 msgid "contentnavigation_prevnext"
 msgstr "Elemento anterior / siguiente"
 
@@ -1547,6 +1574,12 @@
 "sección que muestra las entidades ligadas por la relación \"vea también\" , "
 "si la entidad soporta esta relación."
 
+msgid "contentnavigation_view_page_as_pdf"
+msgstr ""
+
+msgid "contentnavigation_view_page_as_pdf_description"
+msgstr ""
+
 msgid "contentnavigation_wfhistory"
 msgstr "Histórico del workflow."
 
@@ -1644,10 +1677,6 @@
 msgid "created_by_object"
 msgstr "ha creado"
 
-msgid ""
-"creating BaseTransition (BaseTransition transition_of Workflow %(linkto)s)"
-msgstr ""
-
 msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
 msgstr "Creando Favorito"
 
@@ -1780,6 +1809,9 @@
 msgid "csv export"
 msgstr "Exportar CSV"
 
+msgid "ctxtoolbar"
+msgstr ""
+
 #, python-format
 msgid "currently attached file: %s"
 msgstr "archivo adjunto: %s"
@@ -2027,6 +2059,9 @@
 msgid "destination state for this transition"
 msgstr "Estado destino para esta transición"
 
+msgid "destination state must be in the same workflow as our parent transition"
+msgstr ""
+
 msgid "destination state of a transition"
 msgstr "Estado destino de una transición"
 
@@ -2078,9 +2113,6 @@
 msgid "display the component or not"
 msgstr "Mostrar el componente o no"
 
-msgid "display the pdf icon or not"
-msgstr ""
-
 msgid ""
 "distinct label to distinguate between other permission entity of the same "
 "name"
@@ -2098,9 +2130,6 @@
 msgid "download icon"
 msgstr "ícono de descarga"
 
-msgid "download page as pdf"
-msgstr ""
-
 msgid "download schema as owl"
 msgstr "Descargar esquema en OWL"
 
@@ -2197,6 +2226,9 @@
 msgid "eta_date"
 msgstr "fecha de fin"
 
+msgid "exit state must a subworkflow state"
+msgstr ""
+
 msgid "exit_point"
 msgstr ""
 
@@ -2296,6 +2328,10 @@
 msgid "follow"
 msgstr "Seguir la liga"
 
+#, python-format
+msgid "follow this link for more information on this %s"
+msgstr ""
+
 msgid "for_user"
 msgstr "Para el usuario"
 
@@ -3437,6 +3473,12 @@
 msgid "state"
 msgstr "estado"
 
+msgid "state and transition don't belong the the same workflow"
+msgstr ""
+
+msgid "state doesn't apply to this entity's type"
+msgstr ""
+
 msgid "state doesn't belong to entity's current workflow"
 msgstr ""
 
@@ -3448,6 +3490,9 @@
 "workflow for this entity first."
 msgstr ""
 
+msgid "state doesn't belong to this workflow"
+msgstr ""
+
 msgid "state_of"
 msgstr "estado_de"
 
@@ -3488,6 +3533,10 @@
 msgid "subworkflow"
 msgstr ""
 
+msgid ""
+"subworkflow isn't a workflow for the same types as the transition's workflow"
+msgstr ""
+
 msgid "subworkflow state"
 msgstr ""
 
@@ -3666,6 +3715,10 @@
 msgid "toggle check boxes"
 msgstr "cambiar valor"
 
+#, python-format
+msgid "transition %s isn't allowed from %s"
+msgstr ""
+
 msgid "transition doesn't belong to entity's workflow"
 msgstr ""
 
@@ -3965,6 +4018,12 @@
 msgid "workflow"
 msgstr ""
 
+msgid "workflow already have a state of that name"
+msgstr ""
+
+msgid "workflow already have a transition of that name"
+msgstr ""
+
 #, python-format
 msgid "workflow changed to \"%s\""
 msgstr ""
@@ -3975,6 +4034,12 @@
 msgid "workflow history item"
 msgstr ""
 
+msgid "workflow isn't a workflow for this type"
+msgstr ""
+
+msgid "workflow isn't a workflow of this type"
+msgstr ""
+
 msgid "workflow to which this state belongs"
 msgstr ""
 
@@ -4089,6 +4154,12 @@
 #~ msgid "comment:"
 #~ msgstr "Comentario:"
 
+#~ msgid "components_help"
+#~ msgstr "Botón de ayuda"
+
+#~ msgid "components_help_description"
+#~ msgstr "El botón de ayuda, en el encabezado de página"
+
 #~ msgid "creating State (State state_of CWEType %(linkto)s)"
 #~ msgstr "Creación de un estado por el tipo %(linkto)s"
 
--- a/i18n/fr.po	Mon Nov 23 14:13:53 2009 +0100
+++ b/i18n/fr.po	Thu Dec 03 17:17:43 2009 +0100
@@ -233,6 +233,9 @@
 msgid "Boolean_plural"
 msgstr "Booléen"
 
+msgid "BoundConstraint"
+msgstr "contrainte de bornes"
+
 msgid "Browse by category"
 msgstr "Naviguer par catégorie"
 
@@ -336,6 +339,9 @@
 msgid "Do you want to delete the following element(s) ?"
 msgstr "Voulez vous supprimer le(s) élément(s) suivant(s)"
 
+msgid "Download page as pdf"
+msgstr "télécharger la page au format PDF"
+
 msgid "EmailAddress"
 msgstr "Adresse électronique"
 
@@ -360,6 +366,12 @@
 msgid "Float_plural"
 msgstr "Nombres flottants"
 
+# schema pot file, generated on 2009-12-03 09:22:35
+#
+# singular and plural forms for each entity type
+msgid "FormatConstraint"
+msgstr "contrainte de format"
+
 msgid "From:"
 msgstr "De :"
 
@@ -375,6 +387,9 @@
 msgid "Interval"
 msgstr "Durée"
 
+msgid "IntervalBoundConstraint"
+msgstr "contrainte d'interval"
+
 msgid "Interval_plural"
 msgstr "Durées"
 
@@ -462,18 +477,30 @@
 msgid "Please note that this is only a shallow copy"
 msgstr "Attention, cela n'effectue qu'une copie de surface"
 
+msgid "RQLConstraint"
+msgstr "contrainte rql"
+
 msgid "RQLExpression"
 msgstr "Expression RQL"
 
 msgid "RQLExpression_plural"
 msgstr "Expressions RQL"
 
+msgid "RQLUniqueConstraint"
+msgstr "contrainte rql d'unicité"
+
+msgid "RQLVocabularyConstraint"
+msgstr "contrainte rql de vocabulaire"
+
 msgid "Read permissions"
 msgstr "Permissions de lire"
 
 msgid "Recipients:"
 msgstr "Destinataires :"
 
+msgid "RegexpConstraint"
+msgstr "contrainte expression régulière"
+
 msgid "Registry's content"
 msgstr "Contenu du registre"
 
@@ -496,6 +523,9 @@
 msgid "Server"
 msgstr "Serveur"
 
+msgid "SizeConstraint"
+msgstr "contrainte de taille"
+
 msgid "Startup views"
 msgstr "Vues de départ"
 
@@ -505,6 +535,9 @@
 msgid "State_plural"
 msgstr "États"
 
+msgid "StaticVocabularyConstraint"
+msgstr "contrainte de vocabulaire"
+
 msgid "String"
 msgstr "Chaîne de caractères"
 
@@ -641,6 +674,9 @@
 msgid "Unable to find anything named \"%s\" in the schema !"
 msgstr "Rien de nommé \"%s\" dans le schéma"
 
+msgid "UniqueConstraint"
+msgstr "contrainte d'unicité"
+
 msgid "Update permissions"
 msgstr "Permissions de modifier"
 
@@ -753,7 +789,7 @@
 msgstr "actions"
 
 msgid "actions_about"
-msgstr ""
+msgstr "à propos"
 
 msgid "actions_about_description"
 msgstr ""
@@ -777,7 +813,7 @@
 msgstr ""
 
 msgid "actions_changelog"
-msgstr ""
+msgstr "changements récents"
 
 msgid "actions_changelog_description"
 msgstr ""
@@ -819,7 +855,7 @@
 msgstr ""
 
 msgid "actions_help"
-msgstr ""
+msgstr "aide"
 
 msgid "actions_help_description"
 msgstr ""
@@ -861,7 +897,7 @@
 msgstr ""
 
 msgid "actions_poweredby"
-msgstr ""
+msgstr "powered by"
 
 msgid "actions_poweredby_description"
 msgstr ""
@@ -923,9 +959,6 @@
 msgid "add"
 msgstr "ajouter"
 
-msgid "add BaseTransition transition_of Workflow object"
-msgstr ""
-
 msgid "add Bookmark bookmarked_by CWUser object"
 msgstr "signet"
 
@@ -1425,12 +1458,6 @@
 msgid "components_etypenavigation_description"
 msgstr "permet de filtrer par type d'entité les résultats d'une recherche"
 
-msgid "components_help"
-msgstr "bouton aide"
-
-msgid "components_help_description"
-msgstr "le bouton d'aide, dans l'en-tête de page"
-
 msgid "components_loggeduserlink"
 msgstr "lien utilisateur"
 
@@ -1454,12 +1481,6 @@
 "composant permettant de présenter sur plusieurs pages les requêtes renvoyant "
 "plus d'un certain nombre de résultat"
 
-msgid "components_pdfview"
-msgstr "icône pdf"
-
-msgid "components_pdfview_description"
-msgstr "l'icône pdf pour obtenir la page courant au format PDF"
-
 msgid "components_rqlinput"
 msgstr "barre rql"
 
@@ -1538,6 +1559,12 @@
 msgstr ""
 "affiche un chemin permettant de localiser la page courante dans le site"
 
+msgid "contentnavigation_metadata"
+msgstr "méta-données de l'entité"
+
+msgid "contentnavigation_metadata_description"
+msgstr ""
+
 msgid "contentnavigation_prevnext"
 msgstr "élément précedent / suivant"
 
@@ -1554,6 +1581,12 @@
 "section affichant les entités liées par la relation \"voir aussi\" si "
 "l'entité supporte cette relation."
 
+msgid "contentnavigation_view_page_as_pdf"
+msgstr "icône pdf"
+
+msgid "contentnavigation_view_page_as_pdf_description"
+msgstr "l'icône pdf pour obtenir la page courant au format PDF"
+
 msgid "contentnavigation_wfhistory"
 msgstr "historique du workflow."
 
@@ -1651,10 +1684,6 @@
 msgid "created_by_object"
 msgstr "a créé"
 
-msgid ""
-"creating BaseTransition (BaseTransition transition_of Workflow %(linkto)s)"
-msgstr ""
-
 msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
 msgstr "création d'un signet pour %(linkto)s"
 
@@ -1787,6 +1816,9 @@
 msgid "csv export"
 msgstr "export CSV"
 
+msgid "ctxtoolbar"
+msgstr "barre d'outils"
+
 #, python-format
 msgid "currently attached file: %s"
 msgstr "fichie actuellement attaché %s"
@@ -2039,6 +2071,11 @@
 msgid "destination state for this transition"
 msgstr "états accessibles par cette transition"
 
+msgid "destination state must be in the same workflow as our parent transition"
+msgstr ""
+"l'état de destination doit être dans le même workflow que la transition "
+"parente"
+
 msgid "destination state of a transition"
 msgstr "état d'arrivée d'une transition"
 
@@ -2093,9 +2130,6 @@
 msgid "display the component or not"
 msgstr "afficher le composant ou non"
 
-msgid "display the pdf icon or not"
-msgstr "afficher l'icône pdf ou non"
-
 msgid ""
 "distinct label to distinguate between other permission entity of the same "
 "name"
@@ -2113,9 +2147,6 @@
 msgid "download icon"
 msgstr "icône de téléchargement"
 
-msgid "download page as pdf"
-msgstr "télécharger la page au format PDF"
-
 msgid "download schema as owl"
 msgstr "télécharger le schéma OWL"
 
@@ -2211,6 +2242,9 @@
 msgid "eta_date"
 msgstr "date de fin"
 
+msgid "exit state must a subworkflow state"
+msgstr "l'état de sortie doit être un état du sous-workflow"
+
 msgid "exit_point"
 msgstr "état de sortie"
 
@@ -2310,6 +2344,10 @@
 msgid "follow"
 msgstr "suivre le lien"
 
+#, python-format
+msgid "follow this link for more information on this %s"
+msgstr "suivez ce lien pour plus d'information sur ce %s"
+
 msgid "for_user"
 msgstr "pour l'utilisateur"
 
@@ -3455,6 +3493,12 @@
 msgid "state"
 msgstr "état"
 
+msgid "state and transition don't belong the the same workflow"
+msgstr "l'état et la transition n'appartiennent pas au même workflow"
+
+msgid "state doesn't apply to this entity's type"
+msgstr "cet état ne s'applique pas à ce type d'entité"
+
 msgid "state doesn't belong to entity's current workflow"
 msgstr "l'état n'appartient pas au workflow courant de l'entité"
 
@@ -3468,6 +3512,9 @@
 "l'état n'appartient pas au workflow courant de l'entité. Vous désirez peut-"
 "être spécifier que cette entité doit utiliser ce workflow."
 
+msgid "state doesn't belong to this workflow"
+msgstr "l'état n'appartient pas à ce workflow"
+
 msgid "state_of"
 msgstr "état de"
 
@@ -3508,6 +3555,12 @@
 msgid "subworkflow"
 msgstr "sous-workflow"
 
+msgid ""
+"subworkflow isn't a workflow for the same types as the transition's workflow"
+msgstr ""
+"le sous-workflow ne s'applique pas aux mêmes types que le workflow de cette "
+"transition"
+
 msgid "subworkflow state"
 msgstr "état de sous-workflow"
 
@@ -3687,6 +3740,10 @@
 msgid "toggle check boxes"
 msgstr "inverser les cases à cocher"
 
+#, python-format
+msgid "transition %s isn't allowed from %s"
+msgstr "la transition %s n'est pas autorisée depuis l'état %s"
+
 msgid "transition doesn't belong to entity's workflow"
 msgstr "la transition n'appartient pas au workflow de l'entité"
 
@@ -3828,7 +3885,7 @@
 msgstr "a la permission de modifier"
 
 msgid "updated"
-msgstr ""
+msgstr "mis à jour"
 
 #, python-format
 msgid "updated %(etype)s #%(eid)s (%(title)s)"
@@ -3988,6 +4045,12 @@
 msgid "workflow"
 msgstr "workflow"
 
+msgid "workflow already have a state of that name"
+msgstr "le workflow a déja un état du même nom"
+
+msgid "workflow already have a transition of that name"
+msgstr "le workflow a déja une transition du même nom"
+
 #, python-format
 msgid "workflow changed to \"%s\""
 msgstr "workflow changé à \"%s\""
@@ -3998,6 +4061,12 @@
 msgid "workflow history item"
 msgstr "entrée de l'historique de workflow"
 
+msgid "workflow isn't a workflow for this type"
+msgstr "le workflow ne s'applique pas à ce type d'entité"
+
+msgid "workflow isn't a workflow of this type"
+msgstr ""
+
 msgid "workflow to which this state belongs"
 msgstr "workflow auquel cet état appartient"
 
@@ -4036,9 +4105,18 @@
 msgid "you should probably delete that property"
 msgstr "vous devriez probablement supprimer cette propriété"
 
+#~ msgid "components_help"
+#~ msgstr "bouton aide"
+
+#~ msgid "components_help_description"
+#~ msgstr "le bouton d'aide, dans l'en-tête de page"
+
 #~ msgid "destination state"
 #~ msgstr "état de destination"
 
+#~ msgid "display the pdf icon or not"
+#~ msgstr "afficher l'icône pdf ou non"
+
 #~ msgctxt "inlined:CWRelation:from_entity:subject"
 #~ msgid "remove this CWEType"
 #~ msgstr "supprimer ce type d'entité"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.5.10_Any.py	Thu Dec 03 17:17:43 2009 +0100
@@ -0,0 +1,5 @@
+sync_schema_props_perms('state_of')
+sync_schema_props_perms('transition_of')
+for etype in ('State', 'BaseTransition', 'Transition', 'WorkflowTransition'):
+    sync_schema_props_perms((etype, 'name', 'String'))
+
--- a/rqlrewrite.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/rqlrewrite.py	Thu Dec 03 17:17:43 2009 +0100
@@ -285,7 +285,7 @@
             if not eschema.has_perm(self.session, action):
                 rqlexprs = eschema.get_rqlexprs(action)
                 if not rqlexprs:
-                    raise Unauthorised()
+                    raise Unauthorized()
                 self.insert_snippets([((varname, 'X'), rqlexprs)])
 
     def snippet_subquery(self, varmap, transformedsnippet):
--- a/rset.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/rset.py	Thu Dec 03 17:17:43 2009 +0100
@@ -501,7 +501,6 @@
     @cached
     def column_types(self, col):
         """return the list of different types in the column with the given col
-        index default to 0 (ie the first column)
 
         :type col: int
         :param col: the index of the desired column
--- a/schema.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/schema.py	Thu Dec 03 17:17:43 2009 +0100
@@ -120,6 +120,37 @@
 
 __builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name)
 
+
+# rql expression utilities function ############################################
+
+def guess_rrqlexpr_mainvars(expression):
+    defined = set(split_expression(expression))
+    mainvars = []
+    if 'S' in defined:
+        mainvars.append('S')
+    if 'O' in defined:
+        mainvars.append('O')
+    if 'U' in defined:
+        mainvars.append('U')
+    if not mainvars:
+        raise Exception('unable to guess selection variables')
+    return ','.join(mainvars)
+
+def split_expression(rqlstring):
+    for expr in rqlstring.split(','):
+        for word in expr.split():
+            yield word
+
+def normalize_expression(rqlstring):
+    """normalize an rql expression to ease schema synchronization (avoid
+    suppressing and reinserting an expression if only a space has been added/removed
+    for instance)
+    """
+    return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
+
+
+# Schema objects definition ###################################################
+
 def ERSchema_display_name(self, req, form='', context=None):
     """return a internationalized string for the entity/relation type name in
     a given form
@@ -271,16 +302,6 @@
 RelationDefinitionSchema.check_permission_definitions = check_permission_definitions
 
 
-def system_etypes(schema):
-    """return system entity types only: skip final, schema and application entities
-    """
-    for eschema in schema.entities():
-        if eschema.final or eschema.schema_entity():
-            continue
-        yield eschema.type
-
-# Schema objects definition ###################################################
-
 class CubicWebEntitySchema(EntitySchema):
     """a entity has a type, a set of subject and or object relations
     the entity schema defines the possible relations for a given type and some
@@ -536,25 +557,35 @@
 
 # Possible constraints ########################################################
 
-class RQLVocabularyConstraint(BaseConstraint):
-    """the rql vocabulary constraint :
-
-    limit the proposed values to a set of entities returned by a rql query,
-    but this is not enforced at the repository level
-
-     restriction is additional rql restriction that will be added to
-     a predefined query, where the S and O variables respectivly represent
-     the subject and the object of the relation
+class BaseRQLConstraint(BaseConstraint):
+    """base class for rql constraints
     """
 
-    def __init__(self, restriction):
-        self.restriction = restriction
+    def __init__(self, restriction, mainvars=None):
+        self.restriction = normalize_expression(restriction)
+        if mainvars is None:
+            mainvars = guess_rrqlexpr_mainvars(restriction)
+        else:
+            normmainvars = []
+            for mainvar in mainvars.split(','):
+                mainvar = mainvar.strip()
+                if not mainvar.isalpha():
+                    raise Exception('bad mainvars %s' % mainvars)
+                normmainvars.append(mainvar)
+            assert mainvars, 'bad mainvars %s' % mainvars
+            mainvars = ','.join(sorted(normmainvars))
+        self.mainvars = mainvars
 
     def serialize(self):
-        return self.restriction
+        # start with a comma for bw compat, see below
+        return ';' + self.mainvars + ';' + self.restriction
 
     def deserialize(cls, value):
-        return cls(value)
+        # XXX < 3.5.10 bw compat
+        if not value.startswith(';'):
+            return cls(value)
+        _, mainvars, restriction = value.split(';', 2)
+        return cls(restriction, mainvars)
     deserialize = classmethod(deserialize)
 
     def check(self, entity, rtype, value):
@@ -568,60 +599,104 @@
         pass # this is a vocabulary constraint, not enforce XXX why?
 
     def __str__(self):
-        return self.restriction
+        return '%s(Any %s WHERE %s)' % (self.__class__.__name__, self.mainvars,
+                                        self.restriction)
 
     def __repr__(self):
-        return '<%s : %s>' % (self.__class__.__name__, repr(self.restriction))
+        return '<%s @%#x>' % (self.__str__(), id(self))
 
 
-class RQLConstraint(RQLVocabularyConstraint):
-    """the rql constraint is similar to the RQLVocabularyConstraint but
-    are also enforced at the repository level
+class RQLVocabularyConstraint(BaseRQLConstraint):
+    """the rql vocabulary constraint :
+
+    limit the proposed values to a set of entities returned by a rql query,
+    but this is not enforced at the repository level
+
+     restriction is additional rql restriction that will be added to
+     a predefined query, where the S and O variables respectivly represent
+     the subject and the object of the relation
+
+     mainvars is a string that should be used as selection variable (eg
+     `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
+     done to guess it according to variable used in the expression.
     """
-    def exec_query(self, session, eidfrom, eidto):
-        if eidto is None:
-            rql = 'Any S WHERE S eid %(s)s, ' + self.restriction
-            return session.unsafe_execute(rql, {'s': eidfrom}, 's',
-                                          build_descr=False)
-        rql = 'Any S,O WHERE S eid %(s)s, O eid %(o)s, ' + self.restriction
-        return session.unsafe_execute(rql, {'s': eidfrom, 'o': eidto},
-                                      ('s', 'o'), build_descr=False)
-    def error(self, eid, rtype, msg):
-        raise ValidationError(eid, {rtype: msg})
+
+
+class RepoEnforcedRQLConstraintMixIn(object):
+
+    def __init__(self, restriction, mainvars=None, msg=None):
+        super(RepoEnforcedRQLConstraintMixIn, self).__init__(restriction, mainvars)
+        self.msg = msg
+
+    def serialize(self):
+        # start with a semicolon for bw compat, see below
+        return ';%s;%s\n%s' % (self.mainvars, self.restriction,
+                               self.msg or '')
+
+    def deserialize(cls, value):
+        # XXX < 3.5.10 bw compat
+        if not value.startswith(';'):
+            return cls(value)
+        value, msg = value.split('\n', 1)
+        _, mainvars, restriction = value.split(';', 2)
+        return cls(restriction, mainvars, msg)
+    deserialize = classmethod(deserialize)
 
     def repo_check(self, session, eidfrom, rtype, eidto=None):
         """raise ValidationError if the relation doesn't satisfy the constraint
         """
-        if not self.exec_query(session, eidfrom, eidto):
-            # XXX at this point dunno if the validation error `occured` on
-            #     eidfrom or eidto (from user interface point of view)
-            self.error(eidfrom, rtype, 'constraint %s failed' % self)
+        if not self.match_condition(session, eidfrom, eidto):
+            # XXX at this point if both or neither of S and O are in mainvar we
+            # dunno if the validation error `occured` on eidfrom or eidto (from
+            # user interface point of view)
+            if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
+                maineid = eidfrom
+            else:
+                maineid = eidto
+            if self.msg:
+                msg = session._(self.msg)
+            else:
+                msg = '%(constraint)s %(restriction)s failed' % {
+                    'constraint':  session._(self.type()),
+                    'restriction': self.restriction}
+            raise ValidationError(maineid, {rtype: msg})
+
+    def exec_query(self, session, eidfrom, eidto):
+        if eidto is None:
+            # checking constraint for an attribute relation
+            restriction = 'S eid %(s)s, ' + self.restriction
+            args, ck = {'s': eidfrom}, 's'
+        else:
+            restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
+            args, ck = {'s': eidfrom, 'o': eidto}, ('s', 'o')
+        rql = 'Any %s WHERE %s' % (self.mainvars,  restriction)
+        if self.distinct_query:
+            rql = 'DISTINCT ' + rql
+        return session.unsafe_execute(rql, args, ck, build_descr=False)
 
 
-class RQLUniqueConstraint(RQLConstraint):
+class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
+    """the rql constraint is similar to the RQLVocabularyConstraint but
+    are also enforced at the repository level
+    """
+    distinct_query = False
+
+    def match_condition(self, session, eidfrom, eidto):
+        return self.exec_query(session, eidfrom, eidto)
+
+
+class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
     """the unique rql constraint check that the result of the query isn't
     greater than one
     """
-    def repo_check(self, session, eidfrom, rtype, eidto=None):
-        """raise ValidationError if the relation doesn't satisfy the constraint
-        """
-        if len(self.exec_query(session, eidfrom, eidto)) > 1:
-            # XXX at this point dunno if the validation error `occured` on
-            #     eidfrom or eidto (from user interface point of view)
-            self.error(eidfrom, rtype, 'unique constraint %s failed' % self)
-
+    distinct_query = True
 
-def split_expression(rqlstring):
-    for expr in rqlstring.split(','):
-        for word in expr.split():
-            yield word
+    # XXX turns mainvars into a required argument in __init__, since we've no
+    #     way to guess it correctly (eg if using S,O or U the constraint will
+    #     always be satisfied since we've to use a DISTINCT query)
 
-def normalize_expression(rqlstring):
-    """normalize an rql expression to ease schema synchronization (avoid
-    suppressing and reinserting an expression if only a space has been added/removed
-    for instance)
-    """
-    return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
+    def match_condition(self, session, eidfrom, eidto):
+        return len(self.exec_query(session, eidfrom, eidto)) <= 1
 
 
 class RQLExpression(object):
@@ -793,22 +868,11 @@
             return self._check(session, x=eid)
         return self._check(session)
 
-PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
 
 class RRQLExpression(RQLExpression):
     def __init__(self, expression, mainvars=None, eid=None):
         if mainvars is None:
-            defined = set(split_expression(expression))
-            mainvars = []
-            if 'S' in defined:
-                mainvars.append('S')
-            if 'O' in defined:
-                mainvars.append('O')
-            if 'U' in defined:
-                mainvars.append('U')
-            if not mainvars:
-                raise Exception('unable to guess selection variables')
-            mainvars = ','.join(mainvars)
+            mainvars = guess_rrqlexpr_mainvars(expression)
         RQLExpression.__init__(self, expression, mainvars, eid)
         # graph of links between variable, used by rql rewriter
         self.vargraph = {}
@@ -851,7 +915,6 @@
             kwargs['o'] = toeid
         return self._check(session, **kwargs)
 
-PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
 
 # workflow extensions #########################################################
 
@@ -888,13 +951,13 @@
     __metaclass__ = workflowable_definition
     __abstract__ = True
 
-PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
 
 # schema loading ##############################################################
 
 CONSTRAINTS['RQLConstraint'] = RQLConstraint
 CONSTRAINTS['RQLUniqueConstraint'] = RQLUniqueConstraint
 CONSTRAINTS['RQLVocabularyConstraint'] = RQLVocabularyConstraint
+CONSTRAINTS.pop('MultipleStaticVocabularyConstraint', None) # don't want this in cw yams schema
 PyFileReader.context.update(CONSTRAINTS)
 
 
@@ -1009,7 +1072,12 @@
 stmts.Select.set_statement_type = bw_set_statement_type
 
 # XXX deprecated
+
 from yams.constraints import format_constraint
 format_constraint = deprecated('[3.4] use RichString instead of format_constraint')(format_constraint)
 from yams.buildobjs import RichString
+
+PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
+PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
+PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
 PyFileReader.context['format_constraint'] = format_constraint
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/_regproc.mysql.sql	Thu Dec 03 17:17:43 2009 +0100
@@ -0,0 +1,22 @@
+/* -*- sql -*- 
+
+   mysql specific registered procedures, 
+
+*/
+
+/* XXX limit_size version dealing with format as postgres version does.
+   XXX mysql doesn't support overloading, each function should have a different name
+       
+   NOTE: fulltext renamed since it cause a mysql name conflict
+ */
+
+CREATE FUNCTION text_limit_size(vfulltext TEXT, maxsize INT)
+RETURNS TEXT
+NO SQL
+BEGIN
+    IF LENGTH(vfulltext) < maxsize THEN
+       RETURN vfulltext;
+    ELSE
+       RETURN SUBSTRING(vfulltext from 1 for maxsize) || '...';
+    END IF;
+END ;;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/_regproc.postgres.sql	Thu Dec 03 17:17:43 2009 +0100
@@ -0,0 +1,47 @@
+/* -*- sql -*- 
+
+   postgres specific registered procedures, 
+   require the plpgsql language installed 
+
+*/
+
+CREATE FUNCTION comma_join (anyarray) RETURNS text AS $$
+    SELECT array_to_string($1, ', ')
+$$ LANGUAGE SQL;;
+
+CREATE AGGREGATE group_concat (
+  basetype = anyelement,
+  sfunc = array_append,
+  stype = anyarray,
+  finalfunc = comma_join,
+  initcond = '{}'
+);;
+
+
+
+CREATE FUNCTION limit_size (fulltext text, format text, maxsize integer) RETURNS text AS $$
+DECLARE
+    plaintext text;
+BEGIN
+    IF char_length(fulltext) < maxsize THEN
+       RETURN fulltext;
+    END IF;
+    IF format = 'text/html' OR format = 'text/xhtml' OR format = 'text/xml' THEN
+       plaintext := regexp_replace(fulltext, '<[\\w/][^>]+>', '', 'g');
+    ELSE
+       plaintext := fulltext;
+    END IF;
+    IF char_length(plaintext) < maxsize THEN
+       RETURN plaintext;
+    ELSE
+       RETURN substring(plaintext from 1 for maxsize) || '...';
+    END IF;
+END
+$$ LANGUAGE plpgsql;;
+
+
+CREATE FUNCTION text_limit_size (fulltext text, maxsize integer) RETURNS text AS $$
+BEGIN
+    RETURN limit_size(fulltext, 'text/plain', maxsize);
+END
+$$ LANGUAGE plpgsql;;
--- a/schemas/_regproc.sql.mysql	Mon Nov 23 14:13:53 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-/* -*- sql -*- 
-
-   mysql specific registered procedures, 
-
-*/
-
-/* XXX limit_size version dealing with format as postgres version does.
-   XXX mysql doesn't support overloading, each function should have a different name
-       
-   NOTE: fulltext renamed since it cause a mysql name conflict
- */
-
-CREATE FUNCTION text_limit_size(vfulltext TEXT, maxsize INT)
-RETURNS TEXT
-NO SQL
-BEGIN
-    IF LENGTH(vfulltext) < maxsize THEN
-       RETURN vfulltext;
-    ELSE
-       RETURN SUBSTRING(vfulltext from 1 for maxsize) || '...';
-    END IF;
-END ;;
--- a/schemas/_regproc.sql.postgres	Mon Nov 23 14:13:53 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/* -*- sql -*- 
-
-   postgres specific registered procedures, 
-   require the plpgsql language installed 
-
-*/
-
-CREATE FUNCTION comma_join (anyarray) RETURNS text AS $$
-    SELECT array_to_string($1, ', ')
-$$ LANGUAGE SQL;;
-
-CREATE AGGREGATE group_concat (
-  basetype = anyelement,
-  sfunc = array_append,
-  stype = anyarray,
-  finalfunc = comma_join,
-  initcond = '{}'
-);;
-
-
-
-CREATE FUNCTION limit_size (fulltext text, format text, maxsize integer) RETURNS text AS $$
-DECLARE
-    plaintext text;
-BEGIN
-    IF char_length(fulltext) < maxsize THEN
-       RETURN fulltext;
-    END IF;
-    IF format = 'text/html' OR format = 'text/xhtml' OR format = 'text/xml' THEN
-       plaintext := regexp_replace(fulltext, '<[\\w/][^>]+>', '', 'g');
-    ELSE
-       plaintext := fulltext;
-    END IF;
-    IF char_length(plaintext) < maxsize THEN
-       RETURN plaintext;
-    ELSE
-       RETURN substring(plaintext from 1 for maxsize) || '...';
-    END IF;
-END
-$$ LANGUAGE plpgsql;;
-
-
-CREATE FUNCTION text_limit_size (fulltext text, maxsize integer) RETURNS text AS $$
-BEGIN
-    RETURN limit_size(fulltext, 'text/plain', maxsize);
-END
-$$ LANGUAGE plpgsql;;
--- a/schemas/workflow.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/schemas/workflow.py	Thu Dec 03 17:17:43 2009 +0100
@@ -27,7 +27,8 @@
                                   constraints=[RQLConstraint('O final FALSE')])
 
     initial_state = SubjectRelation('State', cardinality='?*',
-                                   constraints=[RQLConstraint('O state_of S')],
+                                   constraints=[RQLConstraint('O state_of S',
+                                                              msg=_('state doesn\'t belong to this workflow'))],
                                    description=_('initial state for this workflow'))
 
 
@@ -38,7 +39,8 @@
     subject = 'CWEType'
     object = 'Workflow'
     cardinality = '?*'
-    constraints = [RQLConstraint('S final FALSE, O workflow_of S')]
+    constraints = [RQLConstraint('S final FALSE, O workflow_of S',
+                                 msg=_('workflow isn\'t a workflow of this type'))]
 
 
 class State(EntityType):
@@ -48,18 +50,22 @@
     __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
-                  maxsize=256)
+                  maxsize=256,
+                  constraints=[RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N', 'Y',
+                                                   _('workflow already have a state of that name'))])
     description = RichString(fulltextindexed=True, default_format='text/rest',
                              description=_('semantic description of this state'))
 
     # XXX should be on BaseTransition w/ AND/OR selectors when we will
     # implements #345274
     allowed_transition = SubjectRelation('BaseTransition', cardinality='**',
-                                         constraints=[RQLConstraint('S state_of WF, O transition_of WF')],
+                                         constraints=[RQLConstraint('S state_of WF, O transition_of WF',
+                                                                    msg=_('state and transition don\'t belong the the same workflow'))],
                                          description=_('allowed transitions from this state'))
     state_of = SubjectRelation('Workflow', cardinality='1*', composite='object',
                                description=_('workflow to which this state belongs'),
-                               constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N')])
+                               constraints=[RQLUniqueConstraint('S name N, Y state_of O, Y name N', 'Y',
+                                                                _('workflow already have a state of that name'))])
 
 
 class BaseTransition(EntityType):
@@ -67,7 +73,9 @@
     __permissions__ = META_ETYPE_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True,
-                  maxsize=256)
+                  maxsize=256,
+                  constraints=[RQLUniqueConstraint('S name N, S transition_of WF, Y transition_of WF, Y name N', 'Y',
+                                                   _('workflow already have a transition of that name'))])
     type = String(vocabulary=(_('normal'), _('auto')), default='normal')
     description = RichString(fulltextindexed=True,
                          description=_('semantic description of this transition'))
@@ -83,7 +91,8 @@
                                                   'allowed to pass this transition'))
     transition_of = SubjectRelation('Workflow', cardinality='1*', composite='object',
                                     description=_('workflow to which this transition belongs'),
-                                    constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N')])
+                                    constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N', 'Y',
+                                                                     _('workflow already have a transition of that name'))])
 
 
 class Transition(BaseTransition):
@@ -94,7 +103,8 @@
 
     destination_state = SubjectRelation(
         'State', cardinality='1*',
-        constraints=[RQLConstraint('S transition_of WF, O state_of WF')],
+        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'))
 
 
@@ -103,7 +113,9 @@
     __specializes_schema__ = True
 
     subworkflow = SubjectRelation('Workflow', cardinality='1*',
-                                  constraints=[RQLConstraint('S transition_of WF, WF workflow_of ET, O workflow_of ET')])
+                                  constraints=[RQLConstraint('S transition_of WF, WF workflow_of ET, O workflow_of ET',
+                                                             msg=_('subworkflow isn\'t a workflow for the same types as the transition\'s workflow'))]
+                                  )
     # XXX use exit_of and inline it
     subworkflow_exit = SubjectRelation('SubWorkflowExitPoint', cardinality='*1',
                                        composite='subject')
@@ -113,11 +125,13 @@
     """define how we get out from a sub-workflow"""
     subworkflow_state = SubjectRelation(
         'State', cardinality='1*',
-        constraints=[RQLConstraint('T subworkflow_exit S, T subworkflow WF, O state_of WF')],
+        constraints=[RQLConstraint('T subworkflow_exit S, T subworkflow WF, O state_of WF',
+                                   msg=_('exit state must a subworkflow state'))],
         description=_('subworkflow state'))
     destination_state = SubjectRelation(
         'State', cardinality='?*',
-        constraints=[RQLConstraint('T subworkflow_exit S, T transition_of WF, O state_of WF')],
+        constraints=[RQLConstraint('T subworkflow_exit S, T transition_of WF, O state_of WF',
+                                   msg=_('destination state must be in the same workflow as our parent transition'))],
         description=_('destination state. No destination state means that transition '
                       'should go back to the state from which we\'ve entered the '
                       'subworkflow.'))
@@ -214,7 +228,8 @@
     __permissions__ = META_RTYPE_PERMS
 
     cardinality = '?*'
-    constraints = [RQLConstraint('S is ET, O workflow_of ET')]
+    constraints = [RQLConstraint('S is ET, O workflow_of ET',
+                                 msg=_('workflow isn\'t a workflow for this type'))]
     object = 'Workflow'
 
 
@@ -243,5 +258,6 @@
     inlined = False
 
     cardinality = '1*'
-    constraints = [RQLConstraint('S is ET, O state_of WF, WF workflow_of ET')]
+    constraints = [RQLConstraint('S is ET, O state_of WF, WF workflow_of ET',
+                                 msg=_('state doesn\'t apply to this entity\'s type'))]
     object = 'State'
--- a/server/__init__.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/__init__.py	Thu Dec 03 17:17:43 2009 +0100
@@ -143,14 +143,6 @@
         #skip_entities=[str(e) for e in schema.entities()
         #               if not repo.system_source.support_entity(str(e))])
     sqlexec(schemasql, execute, pbtitle=_title)
-    # install additional driver specific sql files
-    for fpath in glob(join(CW_SOFTWARE_ROOT, 'schemas', '*.sql.%s' % driver)):
-        print '-> installing', fpath
-        sqlexec(open(fpath).read(), execute, False, delimiter=';;')
-    for directory in reversed(config.cubes_path()):
-        for fpath in glob(join(directory, 'schema', '*.sql.%s' % driver)):
-            print '-> installing', fpath
-            sqlexec(open(fpath).read(), execute, False, delimiter=';;')
     sqlcursor.close()
     sqlcnx.commit()
     sqlcnx.close()
@@ -183,6 +175,10 @@
     assert len(repo.sources) == 1, repo.sources
     handler = config.migration_handler(schema, interactive=False,
                                        cnx=cnx, repo=repo)
+    # install additional driver specific sql files
+    handler.install_custom_sql_scripts(join(CW_SOFTWARE_ROOT, 'schemas'), driver)
+    for directory in reversed(config.cubes_path()):
+        handler.install_custom_sql_scripts(join(directory, 'schema'), driver)
     initialize_schema(config, schema, handler)
     # yoo !
     cnx.commit()
--- a/server/hook.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/hook.py	Thu Dec 03 17:17:43 2009 +0100
@@ -369,6 +369,9 @@
         operation list
         """
 
+    def postcommit_event(self):
+        """the observed connections pool has committed"""
+
     @property
     @deprecated('[3.6] use self.session.user')
     def user(self):
--- a/server/migractions.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/migractions.py	Thu Dec 03 17:17:43 2009 +0100
@@ -24,6 +24,8 @@
 import shutil
 import os.path as osp
 from datetime import datetime
+from glob import glob
+from warnings import warn
 
 from logilab.common.deprecation import deprecated
 from logilab.common.decorators import cached, clear_cache
@@ -104,12 +106,12 @@
             if migrscript.endswith('.sql'):
                 if self.execscript_confirm(migrscript):
                     sqlexec(open(migrscript).read(), self.session.system_sql)
-            elif migrscript.endswith('.py'):
+            elif migrscript.endswith('.py') or migrscript.endswith('.txt'):
                 return super(ServerMigrationHelper, self).cmd_process_script(
                     migrscript, funcname, *args, **kwargs)
             else:
                 print
-                print ('-> ignoring %s, only .py and .sql scripts are considered' %
+                print ('-> ignoring %s, only .py .sql and .txt scripts are considered' %
                        migrscript)
                 print
             self.commit()
@@ -308,6 +310,21 @@
                                                'after_add_entity', '')
                     self.cmd_reactivate_verification_hooks()
 
+    def install_custom_sql_scripts(self, directory, driver):
+        self.session.set_pool() # ensure pool is set
+        for fpath in glob(osp.join(directory, '*.sql.%s' % driver)):
+            newname = osp.basename(fpath).replace('.sql.%s' % driver,
+                                                  '.%s.sql' % driver)
+            warn('[3.5.6] rename %s into %s' % (fpath, newname),
+                 DeprecationWarning)
+            print '-> installing', fpath
+            sqlexec(open(fpath).read(), self.session.system_sql, False,
+                    delimiter=';;')
+        for fpath in glob(osp.join(directory, '*.%s.sql' % driver)):
+            print '-> installing', fpath
+            sqlexec(open(fpath).read(), self.session.system_sql, False,
+                    delimiter=';;')
+
     # schema synchronization internals ########################################
 
     def _synchronize_permissions(self, erschema, teid):
@@ -544,8 +561,11 @@
         self.fs_schema = self._create_context()['fsschema'] = newcubes_schema
         new = set()
         # execute pre-create files
+        driver = self.repo.system_source.dbdriver
         for pack in reversed(newcubes):
-            self.exec_event_script('precreate', self.config.cube_dir(pack))
+            cubedir = self.config.cube_dir(pack)
+            self.install_custom_sql_scripts(osp.join(cubedir, 'schema'), driver)
+            self.exec_event_script('precreate', cubedir)
         # add new entity and relation types
         for rschema in newcubes_schema.relations():
             if not rschema in self.repo.schema:
--- a/server/pool.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/pool.py	Thu Dec 03 17:17:43 2009 +0100
@@ -123,6 +123,7 @@
         self.source_cnxs[source.uri] = (source, cnx)
         self._cursors.pop(source.uri, None)
 
+
 from cubicweb.server.hook import (Operation, LateOperation, SingleOperation,
                                   SingleLastOperation)
 from logilab.common.deprecation import class_moved, class_renamed
--- a/server/serverconfig.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/serverconfig.py	Thu Dec 03 17:17:43 2009 +0100
@@ -10,7 +10,7 @@
 import os
 from os.path import join, exists
 
-from logilab.common.configuration import Method, Configuration, \
+from logilab.common.configuration import REQUIRED, Method, Configuration, \
      ini_format_section
 from logilab.common.decorators import wproperty, cached, clear_cache
 
@@ -28,12 +28,22 @@
                'inputlevel': 0,
                }),
     ('password', {'type' : 'password',
+                  'default': REQUIRED,
                   'help': "cubicweb manager account's password",
                   'inputlevel': 0,
                   }),
     )
 
-def generate_sources_file(sourcesfile, sourcescfg, keys=None):
+class SourceConfiguration(Configuration):
+    def __init__(self, appid, options):
+        self.appid = appid # has to be done before super call
+        super(SourceConfiguration, self).__init__(options=options)
+
+    # make Method('default_instance_id') usable in db option defs (in native.py)
+    def default_instance_id(self):
+        return self.appid
+
+def generate_sources_file(appid, sourcesfile, sourcescfg, keys=None):
     """serialize repository'sources configuration into a INI like file
 
     the `keys` parameter may be used to sort sections
@@ -53,7 +63,7 @@
                 options = USER_OPTIONS
             else:
                 options = SOURCE_TYPES[sconfig['adapter']].options
-            _sconfig = Configuration(options=options)
+            _sconfig = SourceConfiguration(appid, options=options)
             for attr, val in sconfig.items():
                 if attr == 'uri':
                     continue
@@ -236,7 +246,8 @@
         if exists(sourcesfile):
             import shutil
             shutil.copy(sourcesfile, sourcesfile + '.bak')
-        generate_sources_file(sourcesfile, sourcescfg, ['admin', 'system'])
+        generate_sources_file(self.appid, sourcesfile, sourcescfg,
+                              ['admin', 'system'])
         restrict_perms_to_user(sourcesfile)
 
     def pyro_enabled(self):
--- a/server/serverctl.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/serverctl.py	Thu Dec 03 17:17:43 2009 +0100
@@ -18,7 +18,8 @@
 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
+from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
+                                          SourceConfiguration)
 
 # utility functions ###########################################################
 
@@ -113,6 +114,7 @@
             config._cubes = None
         login, pwd = manager_userpasswd()
 
+
 # repository specific command handlers ########################################
 
 class RepositoryCreateHandler(CommandHandler):
@@ -135,8 +137,8 @@
         sourcesfile = config.sources_file()
         # XXX hack to make Method('default_instance_id') usable in db option
         # defs (in native.py)
-        Configuration.default_instance_id = staticmethod(lambda: config.appid)
-        sconfig = Configuration(options=SOURCE_TYPES['native'].options)
+        sconfig = SourceConfiguration(config.appid,
+                                      options=SOURCE_TYPES['native'].options)
         sconfig.adapter = 'native'
         sconfig.input_config(inputlevel=inputlevel)
         sourcescfg = {'system': sconfig}
@@ -238,6 +240,7 @@
 
 
 # repository specific commands ################################################
+
 class CreateInstanceDBCommand(Command):
     """Create the system database of an instance (run after 'create').
 
@@ -329,7 +332,7 @@
             cmd_run('db-init', config.appid)
         else:
             print ('-> nevermind, you can do it later with '
-                   '"cubicweb-ctl db-init %s".' % self.config.appid)
+                   '"cubicweb-ctl db-init %s".' % config.appid)
 
 
 class InitInstanceCommand(Command):
@@ -356,8 +359,20 @@
     def run(self, args):
         print '\n'+underline_title('Initializing the system database')
         from cubicweb.server import init_repository
+        from logilab.common.db import get_connection
         appid = pop_arg(args, msg='No instance specified !')
         config = ServerConfiguration.config_for(appid)
+        try:
+            system = config.sources()['system']
+            get_connection(
+                system['db-driver'], database=system['db-name'],
+                host=system.get('db-host'), port=system.get('db-port'),
+                user=system.get('db-user'), password=system.get('db-password'))
+        except Exception, ex:
+            raise ConfigurationError(
+                'You seem to have provided wrong connection information in '\
+                'the %s file. Resolve this first (error: %s).'
+                % (config.sources_file(), str(ex).strip()))
         init_repository(config, drop=self.config.drop)
 
 
@@ -402,6 +417,7 @@
             cnx.commit()
             print '-> rights granted to %s on instance %s.' % (appid, user)
 
+
 class ResetAdminPasswordCommand(Command):
     """Reset the administrator password.
 
--- a/server/session.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/session.py	Thu Dec 03 17:17:43 2009 +0100
@@ -476,6 +476,15 @@
                     self.rollback(reset_pool)
                     raise
             self.pool.commit()
+            self.commit_state = trstate = 'postcommit'
+            while self.pending_operations:
+                operation = self.pending_operations.pop(0)
+                operation.processed = trstate
+                try:
+                    operation.handle_event('%s_event' % trstate)
+                except:
+                    self.exception('error while %sing', trstate)
+            self.debug('%s session %s done', trstate, self.id)
         finally:
             self._touch()
             self.commit_state = None
--- a/server/sources/native.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/sources/native.py	Thu Dec 03 17:17:43 2009 +0100
@@ -337,7 +337,7 @@
     def manual_insert(self, results, table, session):
         """insert given result into a temporary table on the system source"""
         if server.DEBUG & server.DBG_RQL:
-            print '  manual insertion of', res, 'into', table
+            print '  manual insertion of', results, 'into', table
         if not results:
             return
         query_args = ['%%(%s)s' % i for i in xrange(len(results[0]))]
--- a/server/sources/rql2sql.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/sources/rql2sql.py	Thu Dec 03 17:17:43 2009 +0100
@@ -693,26 +693,11 @@
         lhsvar, _, rhsvar, rhsconst = relation_info(relation)
         # we are sure here to have a lhsvar
         assert lhsvar is not None
-        if isinstance(relation.parent, Not):
+        if isinstance(relation.parent, Not) \
+               and len(lhsvar.stinfo['relations']) > 1 \
+               and (rhsvar is None or rhsvar._q_invariant):
             self._state.done.add(relation.parent)
-            if rhsvar is not None and not rhsvar._q_invariant:
-                # if the lhs variable is only linked to this relation, this mean we
-                # only want the relation to NOT exists
-                self._state.push_scope()
-                lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
-                rhssql = rhsvar.accept(self)
-                restrictions, tables = self._state.pop_scope()
-                restrictions.append('%s=%s' % (lhssql, rhssql))
-                if not tables:
-                    sql = 'NOT EXISTS(SELECT 1 WHERE %s)' % (
-                        ' AND '.join(restrictions))
-                else:
-                    sql = 'NOT EXISTS(SELECT 1 FROM %s WHERE %s)' % (
-                        ', '.join(tables), ' AND '.join(restrictions))
-            else:
-                lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
-                sql = '%s IS NULL' % self._inlined_var_sql(lhsvar, relation.r_type)
-            return sql
+            return '%s IS NULL' % self._inlined_var_sql(lhsvar, relation.r_type)
         lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
         if rhsconst is not None:
             return '%s=%s' % (lhssql, rhsconst.accept(self))
--- a/server/test/data/schema.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/test/data/schema.py	Thu Dec 03 17:17:43 2009 +0100
@@ -172,7 +172,7 @@
     name = 'ecrit_par'
     subject = 'Note'
     object ='Personne'
-    constraints = [RQLConstraint('E concerns P, X version_of P')]
+    constraints = [RQLConstraint('E concerns P, S version_of P')]
     cardinality = '?*'
 
 class ecrit_par_2(RelationDefinition):
--- a/server/test/unittest_rql2sql.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/test/unittest_rql2sql.py	Thu Dec 03 17:17:43 2009 +0100
@@ -1044,7 +1044,12 @@
 UNION ALL
 SELECT _S.cw_in_state
 FROM cw_Note AS _S
-WHERE _S.cw_eid=0 AND _S.cw_in_state IS NOT NULL''')
+WHERE _S.cw_eid=0 AND _S.cw_in_state IS NOT NULL'''),
+
+    ('Any X WHERE NOT Y for_user X, X eid 123',
+     '''SELECT 123
+WHERE NOT EXISTS(SELECT 1 FROM cw_CWProperty AS _Y WHERE _Y.cw_for_user=123)
+'''),
 
     ]
 
@@ -1553,7 +1558,16 @@
         self.o = SQLGenerator(schema, dbms_helper)
 
     def _norm_sql(self, sql):
-        return sql.strip().replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0')
+        sql = sql.strip().replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0')
+        newsql = []
+        latest = None
+        for line in sql.splitlines(False):
+            firstword = line.split(None, 1)[0]
+            if firstword == 'WHERE' and latest == 'SELECT':
+                newsql.append('FROM (SELECT 1) AS _T')
+            newsql.append(line)
+            latest = firstword
+        return '\n'.join(newsql)
 
     def test_from_clause_needed(self):
         queries = [("Any 1 WHERE EXISTS(T is CWGroup, T name 'managers')",
--- a/server/test/unittest_schemaserial.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/server/test/unittest_schemaserial.py	Thu Dec 03 17:17:43 2009 +0100
@@ -55,13 +55,13 @@
              {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
               'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u'O final FALSE'}),
+             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u';O;O final FALSE\n'}),
 
             ('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': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
               'ordernum': 1, 'cardinality': u'1*', 'se': 'CWAttribute'}),
             ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u'O final TRUE'}),
+             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u';O;O final TRUE\n'}),
             ])
 
     def test_rschema2rql2(self):
@@ -143,35 +143,35 @@
 
     def test_eperms2rql1(self):
         self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 2',
-                               'SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
-                               'SET X read_permission Y WHERE X is CWEType, X name "CWEType", Y eid 1',
-                               'SET X add_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
-                               'SET X update_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
-                               'SET X update_permission Y WHERE X is CWEType, X name "CWEType", Y eid 3',
-                               'SET X delete_permission Y WHERE X is CWEType, X name "CWEType", Y eid 0',
+                              ['SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X add_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X update_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X update_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+                               'SET X delete_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
                                ])
 
     def test_rperms2rql2(self):
         self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('read_permission'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 2',
-                               'SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
-                               'SET X read_permission Y WHERE X is CWRType, X name "read_permission", Y eid 1',
-                               'SET X add_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
-                               'SET X delete_permission Y WHERE X is CWRType, X name "read_permission", Y eid 0',
+                              ['SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
                                ])
 
     def test_rperms2rql3(self):
         self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('name'), self.GROUP_MAPPING)],
-                              ['SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 2',
-                               'SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 0',
-                               'SET X read_permission Y WHERE X is CWRType, X name "name", Y eid 1',
-                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 2',
-                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 0',
-                               'SET X add_permission Y WHERE X is CWRType, X name "name", Y eid 1',
-                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 2',
-                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 0',
-                               'SET X delete_permission Y WHERE X is CWRType, X name "name", Y eid 1',
+                              ['SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+                               'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
                                ])
 
     #def test_perms2rql(self):
--- a/test/unittest_schema.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/test/unittest_schema.py	Thu Dec 03 17:17:43 2009 +0100
@@ -18,9 +18,11 @@
 from yams.buildobjs import RelationDefinition, EntityType, RelationType
 from yams.reader import PyFileReader
 
-from cubicweb.schema import CubicWebSchema, CubicWebEntitySchema, \
-     RQLConstraint, CubicWebSchemaLoader, RQLExpression, ERQLExpression, RRQLExpression, \
-     normalize_expression, order_eschemas
+from cubicweb.schema import (
+    CubicWebSchema, CubicWebEntitySchema, CubicWebSchemaLoader,
+    RQLConstraint, RQLUniqueConstraint, RQLVocabularyConstraint,
+    RQLExpression, ERQLExpression, RRQLExpression,
+    normalize_expression, order_eschemas)
 from cubicweb.devtools import TestServerConfiguration as TestConfiguration
 
 DATADIR = join(dirname(__file__), 'data')
@@ -83,6 +85,18 @@
 
 class CubicWebSchemaTC(TestCase):
 
+    def test_rql_constraints_inheritance(self):
+        # isinstance(cstr, RQLVocabularyConstraint)
+        # -> expected to return RQLVocabularyConstraint and RQLConstraint
+        #   instances but not RQLUniqueConstraint
+        #
+        # isinstance(cstr, RQLConstraint)
+        # -> expected to return RQLConstraint instances but not
+        #    RRQLVocabularyConstraint and QLUniqueConstraint
+        self.failIf(issubclass(RQLUniqueConstraint, RQLVocabularyConstraint))
+        self.failIf(issubclass(RQLUniqueConstraint, RQLConstraint))
+        self.failUnless(issubclass(RQLConstraint, RQLVocabularyConstraint))
+
     def test_normalize(self):
         """test that entities, relations and attributes name are normalized
         """
--- a/utils.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/utils.py	Thu Dec 03 17:17:43 2009 +0100
@@ -410,6 +410,8 @@
                 return obj.strftime('%Y/%m/%d')
             elif isinstance(obj, pydatetime.time):
                 return obj.strftime('%H:%M:%S')
+            elif isinstance(obj, pydatetime.timedelta):
+                return '%10d.%s' % (obj.days, obj.seconds)
             elif isinstance(obj, decimal.Decimal):
                 return float(obj)
             try:
--- a/view.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/view.py	Thu Dec 03 17:17:43 2009 +0100
@@ -179,6 +179,7 @@
         if rset is None:
             raise NotImplementedError, self
         wrap = self.templatable and len(rset) > 1 and self.add_div_section
+        # XXX propagate self.extra_kwars?
         for i in xrange(len(rset)):
             if wrap:
                 self.w(u'<div class="section">')
@@ -200,7 +201,7 @@
         return True
 
     def is_primary(self):
-        return self.__regid__ == 'primary'
+        return self.extra_kwargs.get('is_primary', self.__regid__ == 'primary')
 
     def url(self):
         """return the url associated with this view. Should not be
@@ -323,7 +324,10 @@
             else:
                 w(u'<span>%s</span> ' % label)
         if table:
-            w(u'<td>%s</td></tr>' % value)
+            if not (show_label and label):
+                w(u'<td colspan="2">%s</td></tr>' % value)
+            else:
+                w(u'<td>%s</td></tr>' % value)
         else:
             w(u'<span>%s</span></div>' % value)
 
--- a/web/data/cubicweb.edition.js	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/data/cubicweb.edition.js	Thu Dec 03 17:17:43 2009 +0100
@@ -270,11 +270,14 @@
 /*
  * removes the part of the form used to edit an inlined entity
  */
-function removeInlineForm(peid, rtype, eid) {
+function removeInlineForm(peid, rtype, eid, showaddnewlink) {
     jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() {
 	$(this).remove();
 	updateInlinedEntitiesCounters(rtype);
     });
+    if (showaddnewlink) {
+	toggleVisibility(showaddnewlink);
+    }
 }
 
 /*
--- a/web/form.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/form.py	Thu Dec 03 17:17:43 2009 +0100
@@ -36,6 +36,163 @@
         return False
 
 
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/web/form.py
+=======
+# XXX should disappear
+class FormMixIn(object):
+    """abstract form mix-in
+    XXX: you should inherit from this FIRST (obscure pb with super call)
+    """
+    force_session_key = None
+
+    def session_key(self):
+        """return the key that may be used to store / retreive data about a
+        previous post which failed because of a validation error
+        """
+        if self.force_session_key is None:
+            return '%s#%s' % (self.req.url(), self.domid)
+        return self.force_session_key
+
+    def restore_previous_post(self, sessionkey):
+        # get validation session data which may have been previously set.
+        # deleting validation errors here breaks form reloading (errors are
+        # no more available), they have to be deleted by application's publish
+        # method on successful commit
+        forminfo = self.req.get_session_data(sessionkey, pop=True)
+        if forminfo:
+            # XXX remove req.data assigment once cw.web.widget is killed
+            self.req.data['formvalues'] = self._form_previous_values = forminfo['values']
+            self.req.data['formerrors'] = self._form_valerror = forminfo['errors']
+            self.req.data['displayederrors'] = self.form_displayed_errors = set()
+            # if some validation error occured on entity creation, we have to
+            # get the original variable name from its attributed eid
+            foreid = self.form_valerror.entity
+            for var, eid in forminfo['eidmap'].items():
+                if foreid == eid:
+                    self.form_valerror.eid = var
+                    break
+            else:
+                self.form_valerror.eid = foreid
+        else:
+            self._form_previous_values = {}
+            self._form_valerror = None
+
+    @property
+    def form_previous_values(self):
+        if self.parent_form is None:
+            return self._form_previous_values
+        return self.parent_form.form_previous_values
+
+    @property
+    def form_valerror(self):
+        if self.parent_form is None:
+            return self._form_valerror
+        return self.parent_form.form_valerror
+
+    # XXX deprecated with new form system. Should disappear
+
+    domid = 'entityForm'
+    category = 'form'
+    controller = 'edit'
+    http_cache_manager = httpcache.NoHTTPCacheManager
+    add_to_breadcrumbs = False
+
+    def html_headers(self):
+        """return a list of html headers (eg something to be inserted between
+        <head> and </head> of the returned page
+
+        by default forms are neither indexed nor followed
+        """
+        return [NOINDEX, NOFOLLOW]
+
+    def linkable(self):
+        """override since forms are usually linked by an action,
+        so we don't want them to be listed by appli.possible_views
+        """
+        return False
+
+
+    def button(self, label, klass='validateButton', tabindex=None, **kwargs):
+        if tabindex is None:
+            tabindex = self.req.next_tabindex()
+        return tags.input(value=label, klass=klass, **kwargs)
+
+    def action_button(self, label, onclick=None, __action=None, **kwargs):
+        if onclick is None:
+            onclick = "postForm('__action_%s', \'%s\', \'%s\')" % (
+                __action, label, self.domid)
+        return self.button(label, onclick=onclick, **kwargs)
+
+    def button_ok(self, label=None, type='submit', name='defaultsubmit',
+                  **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
+        return self.button(label, name=name, type=type, **kwargs)
+
+    def button_apply(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
+        return self.action_button(label, __action='apply', type=type, **kwargs)
+
+    def button_delete(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
+        return self.action_button(label, __action='delete', type=type, **kwargs)
+
+    def button_cancel(self, label=None, type='button', **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
+        return self.action_button(label, __action='cancel', type=type, **kwargs)
+
+    def button_reset(self, label=None, type='reset', name='__action_cancel',
+                     **kwargs):
+        label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
+        return self.button(label, type=type, **kwargs)
+
+    def need_multipart(self, entity, categories=('primary', 'secondary')):
+        """return a boolean indicating if form's enctype should be multipart
+        """
+        for rschema, _, x in entity.relations_by_category(categories):
+            if entity.get_widget(rschema, x).need_multipart:
+                return True
+        # let's find if any of our inlined entities needs multipart
+        for rschema, targettypes, x in entity.relations_by_category('inlineview'):
+            assert len(targettypes) == 1, \
+                   "I'm not able to deal with several targets and inlineview"
+            ttype = targettypes[0]
+            inlined_entity = self.vreg.etype_class(ttype)(self.req, None, None)
+            for irschema, _, x in inlined_entity.relations_by_category(categories):
+                if inlined_entity.get_widget(irschema, x).need_multipart:
+                    return True
+        return False
+
+    def error_message(self):
+        """return formatted error message
+
+        This method should be called once inlined field errors has been consumed
+        """
+        errex = self.req.data.get('formerrors') or self.form_valerror
+        # get extra errors
+        if errex is not None:
+            errormsg = self.req._('please correct the following errors:')
+            displayed = self.req.data.get('displayederrors') or self.form_displayed_errors
+            errors = sorted((field, err) for field, err in errex.errors.items()
+                            if not field in displayed)
+            if errors:
+                if len(errors) > 1:
+                    templstr = '<li>%s</li>\n'
+                else:
+                    templstr = '&#160;%s\n'
+                for field, err in errors:
+                    if field is None:
+                        errormsg += templstr % err
+                    else:
+                        errormsg += templstr % '%s: %s' % (self.req._(field), err)
+                if len(errors) > 1:
+                    errormsg = '<ul>%s</ul>' % errormsg
+            return u'<div class="errorMessage">%s</div>' % errormsg
+        return u''
+
+
+###############################################################################
+
+>>>>>>> /tmp/form.py~other.xdns1y
 class metafieldsform(type):
     """metaclass for FieldsForm to retrieve fields defined as class attributes
     and put them into a single ordered list: '_fields_'.
@@ -65,6 +222,7 @@
     __registry__ = 'forms'
 
     parent_form = None
+    force_session_key = None
 
     def __init__(self, req, rset, **kwargs):
         super(Form, self).__init__(req, rset=rset, **kwargs)
@@ -77,6 +235,18 @@
             return self
         return self.parent_form.root_form
 
+    @property
+    def form_previous_values(self):
+        if self.parent_form is None:
+            return self._form_previous_values
+        return self.parent_form.form_previous_values
+
+    @property
+    def form_valerror(self):
+        if self.parent_form is None:
+            return self._form_valerror
+        return self.parent_form.form_valerror
+
     @iclassmethod
     def _fieldsattr(cls_or_self):
         if isinstance(cls_or_self, type):
@@ -127,7 +297,9 @@
         """return the key that may be used to store / retreive data about a
         previous post which failed because of a validation error
         """
-        return '%s#%s' % (self._cw.url(), self.domid)
+        if self.force_session_key is None:
+            return '%s#%s' % (self.req.url(), self.domid)
+        return self.force_session_key
 
     def restore_previous_post(self, sessionkey):
         # get validation session data which may have been previously set.
@@ -136,9 +308,9 @@
         # method on successful commit
         forminfo = self._cw.get_session_data(sessionkey, pop=True)
         if forminfo:
-            # XXX remove req.data assigment once cw.web.widget is killed
-            self._cw.data['formvalues'] = self.form_previous_values = forminfo['values']
-            self._cw.data['formerrors'] = self.form_valerror = forminfo['errors']
+            # XXX remove _cw.data assigment once cw.web.widget is killed
+            self._cw.data['formvalues'] = self._form_previous_values = forminfo['values']
+            self._cw.data['formerrors'] = self._form_valerror = forminfo['errors']
             self._cw.data['displayederrors'] = self.form_displayed_errors = set()
             # if some validation error occured on entity creation, we have to
             # get the original variable name from its attributed eid
@@ -150,5 +322,5 @@
             else:
                 self.form_valerror.eid = foreid
         else:
-            self.form_previous_values = {}
-            self.form_valerror = None
+            self._form_previous_values = {}
+            self._form_valerror = None
--- a/web/formwidgets.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/formwidgets.py	Thu Dec 03 17:17:43 2009 +0100
@@ -412,6 +412,8 @@
         init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
         # XXX entity form specific
         attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
+        if not values:
+            values = ('',)
         return name, values, attrs
 
     def _get_url(self, entity, field):
--- a/web/test/unittest_form.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/test/unittest_form.py	Thu Dec 03 17:17:43 2009 +0100
@@ -95,7 +95,7 @@
         self.req.form['__linkto'] = 'in_group:%s:subject' % geid
         form = self.vreg['forms'].select('edition', self.req, entity=e)
         form.content_type = 'text/html'
-        pageinfo = self._check_html(form.form_render(), form, template=None)
+        pageinfo = self._check_html(form.render(), form, template=None)
         inputs = pageinfo.find_tag('select', False)
         self.failUnless(any(attrs for t, attrs in inputs if attrs.get('name') == 'in_group:A'))
         inputs = pageinfo.find_tag('input', False)
@@ -126,14 +126,14 @@
             creation_date = DateTimeField(widget=DateTimePicker)
         form = CustomChangeStateForm(self.req, redirect_path='perdu.com',
                                      entity=self.entity)
-        form.form_render(state=123, trcomment=u'',
-                         trcomment_format=u'text/plain')
+        form.render(formvalues=dict(state=123, trcomment=u'',
+                                    trcomment_format=u'text/plain'))
 
     def test_change_state_form(self):
         form = ChangeStateForm(self.req, redirect_path='perdu.com',
                                entity=self.entity)
-        form.form_render(state=123, trcomment=u'',
-                         trcomment_format=u'text/plain')
+        form.render(formvalues=dict(state=123, trcomment=u'',
+                                    trcomment_format=u'text/plain'))
 
     # fields tests ############################################################
 
--- a/web/test/unittest_views_editforms.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/test/unittest_views_editforms.py	Thu Dec 03 17:17:43 2009 +0100
@@ -5,7 +5,7 @@
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from logilab.common.testlib import unittest_main
+from logilab.common.testlib import unittest_main, mock_object
 from logilab.common.compat import any
 
 from cubicweb.devtools.testlib import CubicWebTC
@@ -155,13 +155,16 @@
         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='').source
+                  peid=geid, role='object', template=None, i18nctx='',
+                  pform=MOCKPFORM).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').source
+                  peid=geid, template=None, i18nctx='', role='object',
+                  pform=MOCKPFORM).source
 
+MOCKPFORM = mock_object(form_previous_values={}, form_valerror=None)
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_viewselector.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/test/unittest_viewselector.py	Thu Dec 03 17:17:43 2009 +0100
@@ -25,9 +25,9 @@
 SITEACTIONS = [actions.SiteConfigurationAction,
                actions.ManageAction,
                schema.ViewSchemaAction,
-               actions.SiteInfoAction,
-               ]
-FOOTERACTIONS = [wdoc.ChangeLogAction,
+               actions.SiteInfoAction]
+FOOTERACTIONS = [wdoc.HelpAction,
+                 wdoc.ChangeLogAction,
                  wdoc.AboutAction,
                  actions.PoweredByAction]
 
@@ -218,7 +218,6 @@
                               ('text', baseviews.TextView),
                               ('treeview', treeview.TreeView),
                               ('vcard', vcard.VCardCWUserView),
-                              ('wfhistory', workflow.WFHistoryView),
                               ('xbel', xbel.XbelView),
                               ('xml', xmlrss.XMLView),
                               ])
--- a/web/views/boxes.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/boxes.py	Thu Dec 03 17:17:43 2009 +0100
@@ -69,7 +69,8 @@
                 else:
                     menu = defaultmenu
                 action.fill_menu(self, menu)
-        if box.is_empty() and not other_menu.is_empty():
+        # if we've nothing but actions in the other_menu, add them directly into the box
+        if box.is_empty() and len(self._menus_by_id) == 1 and not other_menu.is_empty():
             box.items = other_menu.items
             other_menu.items = []
         else: # ensure 'more actions' menu appears last
--- a/web/views/cwproperties.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/cwproperties.py	Thu Dec 03 17:17:43 2009 +0100
@@ -202,7 +202,7 @@
             self.form_row(form, key, splitlabel)
         renderer = self._cw.vreg['formrenderers'].select('cwproperties', self._cw,
                                                      display_progress_div=False)
-        return form.form_render(renderer=renderer)
+        return form.render(renderer=renderer)
 
     def form_row(self, form, key, splitlabel):
         entity = self.entity_for_key(key)
--- a/web/views/editcontroller.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/editcontroller.py	Thu Dec 03 17:17:43 2009 +0100
@@ -70,6 +70,9 @@
         req = self._cw
         self.errors = []
         self.relations_rql = []
+        # so we're able to know the main entity from the repository side
+        if '__maineid' in form:
+            req.set_shared_data('__maineid', form['__maineid'], querydata=True)
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
         self._pending_relations = []
--- a/web/views/editforms.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/editforms.py	Thu Dec 03 17:17:43 2009 +0100
@@ -28,6 +28,7 @@
 from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
 from cubicweb.web.views import forms
 
+_pvdc = uicfg.primaryview_display_ctrl
 
 def relation_id(eid, rtype, role, reid):
     """return an identifier for a relation between two entities"""
@@ -96,7 +97,7 @@
             w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
                                       href=entity.absolute_url()))
         w(u'</ul>\n')
-        w(form.form_render())
+        w(form.render())
 
 
 class ClickAndEditFormView(FormViewMixIn, EntityView):
@@ -149,8 +150,7 @@
             self.relation_form(lzone, value, form,
                                self._build_renderer(entity, rtype, role))
         else:
-            if rvid is None:
-                rvid = self._compute_best_vid(entity.e_schema, rschema, role)
+            rvid = self._compute_best_vid(entity.e_schema, rschema, role)
             rset = entity.related(rtype, role)
             if rset:
                 value = self._cw.view(rvid, rset)
@@ -202,7 +202,7 @@
           u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
           % (divid, divid, divid))
         w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
-        w(form.form_render(renderer=renderer))
+        w(form.render(renderer=renderer))
         w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
                 divid, xml_escape(self._onclick % form.event_args),
                 self.req._(self._landingzonemsg)))
@@ -211,6 +211,9 @@
         w(u'</div>')
 
     def _compute_best_vid(self, eschema, rschema, role):
+        dispctrl = _pvdc.etype_get(eschema, rschema, role)
+        if dispctrl.get('rvid'):
+            return dispctrl['rvid']
         if eschema.cardinality(rschema, role) in '+*':
             return self._many_rvid
         return self._one_rvid
@@ -254,6 +257,8 @@
     __slots__ = ('event_args',)
     def form_render(self, **_args):
         return u''
+    def render(self, **_args):
+        return u''
     def append_field(self, *args):
         pass
 
@@ -269,7 +274,7 @@
         eschema = entity.e_schema
         rtype = str(rschema)
         # XXX check autoform_section. what if 'generic'?
-        dispctrl = uicfg.primaryview_display_ctrl.etype_get(eschema, rtype, role)
+        dispctrl = _pvdc.etype_get(eschema, rtype, role)
         vid = dispctrl.get('vid', 'reledit')
         if vid != 'reledit': # reledit explicitly disabled
             return False
@@ -310,7 +315,7 @@
                                              entity=entity,
                                              submitmsg=self.submited_message())
         self.init_form(form, entity)
-        self.w(form.form_render(formvid=u'edition'))
+        self.w(form.render(rendervalues=dict(formvid=u'edition')))
 
     def init_form(self, form, entity):
         """customize your form before rendering here"""
@@ -447,7 +452,7 @@
         form = self._cw.vreg['forms'].select(self.__regid__, self._cw,
                                              rset=self.cw_rset,
                                              copy_nav_params=True)
-        self.w(form.form_render())
+        self.w(form.render())
 
 
 class InlineEntityEditionFormView(FormViewMixIn, EntityView):
@@ -479,9 +484,13 @@
         form = self.vreg['forms'].select('edition', self._cw,
                                          entity=entity,
                                          form_renderer_id='inline',
-                                         mainform=False, copy_nav_params=False,
+                                         copy_nav_params=False,
+                                         mainform=False,
+                                         parent_form=self.pform,
                                          **self.extra_kwargs)
-        form.parent_form = self.pform
+        if self.pform is None:
+            form.restore_previous_post(form.session_key())
+        #assert form.parent_form
         self.add_hiddens(form, entity)
         return form
 
@@ -500,16 +509,24 @@
         """fetch and render the form"""
         entity = self._entity()
         divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
-        title = self.req.pgettext(i18nctx, 'This %s' % entity.e_schema)
-        removejs = self.removejs % (self.peid, self.rtype, entity.eid)
+        title = self.form_title(entity, i18nctx)
+        removejs = self.removejs and self.removejs % (
+            self.peid, self.rtype, entity.eid)
         countkey = '%s_count' % self.rtype
         try:
             self._cw.data[countkey] += 1
-        except:
+        except KeyError:
             self._cw.data[countkey] = 1
-        self.w(self.form.form_render(
+        self.w(self.form.form.render(
             divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
             counter=self.req.data[countkey], **kwargs))
+        self.w(self.form.render(
+            rendervalues=dict(divid=divid, title=title, removejs=removejs,
+                              i18nctx=i18nctx, counter=self._cw.data[countkey]),
+            formvalues=kwargs))
+
+    def form_title(self, entity, i18nctx):
+        return self.req.pgettext(i18nctx, 'This %s' % entity.e_schema)
 
     def add_hiddens(self, form, entity):
         """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
@@ -540,7 +557,20 @@
     __select__ = (match_kwargs('peid', 'rtype')
                   & specified_etype_implements('Any'))
     _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',)
-    removejs = "removeInlineForm('%s', '%s', '%s')"
+
+    @property
+    def removejs(self):
+        entity = self._entity()
+        card = entity.e_schema.role_rproperty(neg_role(self.role), self.rtype, 'cardinality')
+        card = card[self.role == 'object']
+        # 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
+        # id to removeInlineForm.
+        if card not in '?1':
+            return "removeInlineForm('%s', '%s', '%s')"
+        divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
+        return "removeInlineForm('%%s', '%%s', '%%s', '%s')" % divid
 
     @cached
     def _entity(self):
--- a/web/views/forms.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/forms.py	Thu Dec 03 17:17:43 2009 +0100
@@ -10,6 +10,7 @@
 from warnings import warn
 
 from logilab.common.compat import any
+from logilab.common.deprecation import deprecated
 
 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
@@ -89,6 +90,8 @@
         if mainform:
             self.form_add_hidden('__errorurl', self.session_key())
             self.form_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:
@@ -125,15 +128,14 @@
         if self.needs_css:
             self._cw.add_css(self.needs_css)
 
-    def form_render(self, **values):
+    def render(self, formvalues=None, rendervalues=None, renderer=None):
         """render this form, using the renderer given in args or the default
         FormRenderer()
         """
-        self.build_context(values)
-        renderer = values.pop('renderer', None)
+        self.build_context(formvalues or {})
         if renderer is None:
             renderer = self.form_default_renderer()
-        return renderer.render(self, values)
+        return renderer.render(self, rendervalues or {})
 
     def form_default_renderer(self):
         return self._cw.vreg['formrenderers'].select(self.form_renderer_id,
@@ -146,8 +148,8 @@
         containing field 'name' (qualified), 'id', 'value' (for display, always
         a string).
 
-        rendervalues is an optional dictionary containing extra kwargs given to
-        form_render()
+        rendervalues is an optional dictionary containing extra form values
+        given to render()
         """
         if self.context is not None:
             return # already built
@@ -249,6 +251,17 @@
         """
         return self.form_valerror and field.name in self.form_valerror.errors
 
+    @deprecated('use .render(formvalues, rendervalues)')
+    def form_render(self, **values):
+        """render this form, using the renderer given in args or the default
+        FormRenderer()
+        """
+        self.build_context(values)
+        renderer = values.pop('renderer', None)
+        if renderer is None:
+            renderer = self.form_default_renderer()
+        return renderer.render(self, values)
+
 
 class EntityFieldsForm(FieldsForm):
     __regid__ = 'base'
@@ -279,6 +292,19 @@
         if msg:
             self.form_add_hidden('__message', msg)
 
+    def session_key(self):
+        """return the key that may be used to store / retreive data about a
+        previous post which failed because of a validation error
+        """
+        try:
+            return self.force_session_key
+        except AttributeError:
+            # XXX if this is a json request, suppose we should redirect to the
+            # entity primary view
+            if self.req.json_request:
+                return '%s#%s' % (self.edited_entity.absolute_url(), self.domid)
+            return '%s#%s' % (self.req.url(), self.domid)
+
     def _field_has_error(self, field):
         """return true if the field has some error in given validation exception
         """
@@ -401,6 +427,8 @@
             return eid_param(field.id, self.edited_entity.eid)
         return field.id
 
+    # XXX all this vocabulary handling should be on the field, no?
+
     def form_field_vocabulary(self, field, limit=None):
         """return vocabulary for the given field"""
         role, rtype = field.role, field.name
--- a/web/views/igeocodable.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/igeocodable.py	Thu Dec 03 17:17:43 2009 +0100
@@ -76,7 +76,7 @@
         self._cw.demote_to_html()
         # remove entities that don't define latitude and longitude
         self.cw_rset = self.cw_rset.filtered_rset(lambda e: e.latitude and e.longitude)
-        self._cw.add_js('http://maps.google.com/maps?sensor=false&file=api&amp;v=2&amp;key=%s' % gmap_key,
+        self._cw.add_js('http://maps.google.com/maps?sensor=false&file=api&v=2&key=%s' % gmap_key,
                         localfile=False)
         self._cw.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
         rql = self.cw_rset.printable_rql()
--- a/web/views/management.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/management.py	Thu Dec 03 17:17:43 2009 +0100
@@ -115,7 +115,7 @@
                                          __redirectpath=entity.rest_path())
         field = guess_field(entity.e_schema, self._cw.schema.rschema('owned_by'))
         form.append_field(field)
-        self.w(form.form_render(display_progress_div=False))
+        self.w(form.render(rendervalues=dict(display_progress_div=False)))
 
     def owned_by_information(self, entity):
         ownersrset = entity.related('owned_by')
@@ -185,7 +185,7 @@
         form.append_field(field)
         renderer = self._cw.vreg['formrenderers'].select(
             'htable', self._cw, rset=None, display_progress_div=False)
-        self.w(form.form_render(renderer=renderer))
+        self.w(form.render(renderer=renderer))
 
 
 class ErrorView(AnyRsetView):
@@ -248,7 +248,7 @@
             form.form_add_hidden('__bugreporting', '1')
             form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
             form.action = req.build_url('reportbug')
-            w(form.form_render())
+            w(form.render())
 
 
 def exc_message(ex, encoding):
--- a/web/views/massmailing.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/massmailing.py	Thu Dec 03 17:17:43 2009 +0100
@@ -127,4 +127,4 @@
         from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
         form = self._cw.vreg['forms'].select('massmailing', self._cw, rset=self.cw_rset,
                                 action='sendmail', domid='sendmail')
-        self.w(form.form_render(sender=from_addr))
+        self.w(form.render(formvalues=dict(sender=from_addr)))
--- a/web/views/primary.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/primary.py	Thu Dec 03 17:17:43 2009 +0100
@@ -13,6 +13,7 @@
 from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized
+from cubicweb.selectors import match_kwargs
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
 from cubicweb.web import uicfg
@@ -50,7 +51,10 @@
         #self.render_entity_toolbox(entity)
         # entity's attributes and relations, excluding meta data
         # if the entity isn't meta itself
-        boxes = self._prepare_side_boxes(entity)
+        if self.is_primary():
+            boxes = self._prepare_side_boxes(entity)
+        else:
+            boxes = None
         if boxes or hasattr(self, 'render_side_related'):
             self.w(u'<table width="100%"><tr><td style="width: 75%">')
         self.render_entity_summary(entity)
@@ -88,7 +92,12 @@
         """default implementation return dc_title"""
         title = xml_escape(entity.dc_title())
         if title:
-            self.w(u'<h1>%s</h1>' % title)
+            if self.is_primary():
+                self.w(u'<h1>%s</h1>' % title)
+            else:
+                atitle = self.req._('follow this link for more information on this %s') % entity.dc_type()
+                self.w(u'<h4><a href="%s" title="%s">%s</a></h4>'
+                       % (entity.absolute_url(), atitle, title))
 
     def render_entity_toolbox(self, entity):
         self.content_navigation_components('ctxtoolbar')
@@ -123,15 +132,26 @@
                     value = None
             if self.skip_none and (value is None or value == ''):
                 continue
-            self._render_attribute(rschema, value, role=role, table=True)
+            try:
+                self._render_attribute(dispctrl, rschema, value,
+                                       role=role, table=True)
+            except TypeError:
+                warn('[3.6] _render_attribute prototype has changed, '
+                     'please update %s' % self.__class___, DeprecationWarning)
+                self._render_attribute(rschema, value, role=role, table=True)
         self.w(u'</table>')
 
     def render_entity_relations(self, entity, siderelations=None):
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
             rset = self._relation_rset(entity, rschema, role, dispctrl)
             if rset:
-                self._render_relation(rset, dispctrl, 'autolimited',
-                                      self.show_rel_label)
+                try:
+                    self._render_relation(dispctrl, rset, 'autolimited')
+                except TypeError:
+                    warn('[3.6] _render_relation prototype has changed, '
+                         'please update %s' % self.__class__, DeprecationWarning)
+                    self._render_relation(rset, dispctrl, 'autolimited',
+                                          self.show_rel_label)
 
     def render_side_boxes(self, boxes):
         """display side related relations:
@@ -139,7 +159,13 @@
         """
         for box in boxes:
             if isinstance(box, tuple):
-                label, rset, vid  = box
+                try:
+                    label, rset, vid, dispctrl  = box
+                except ValueError:
+                    warn('box views should now be defined as a 4-uple (label, rset, vid, dispctrl), '
+                         'please update %s' % self.__class__.__name__,
+                         DeprecationWarning)
+                    label, rset, vid  = box
                 self.w(u'<div class="sideBox">')
                 self.wview(vid, rset, title=label)
                 self.w(u'</div>')
@@ -159,11 +185,19 @@
                 continue
             label = display_name(self._cw, rschema.type, role)
             vid = dispctrl.get('vid', 'sidebox')
-            sideboxes.append( (label, rset, vid) )
+            sideboxes.append( (label, rset, vid, dispctrl) )
         sideboxes += self._cw.vreg['boxes'].poss_visible_objects(
             self._cw, rset=self.cw_rset, row=self.cw_row, view=self,
             context='incontext')
-        return sideboxes
+        # XXX since we've two sorted list, it may be worth using bisect
+        def get_order(x):
+            if isinstance(x, tuple):
+                # x is a view box (label, rset, vid, dispctrl)
+                # default to 1000 so view boxes occurs after component boxes
+                return x[-1].get('order', 1000)
+            # x is a component box
+            return x.propval('order')
+        return sorted(sideboxes, key=get_order)
 
     def _section_def(self, entity, where):
         rdefs = []
@@ -193,20 +227,25 @@
             rset = dispctrl['filter'](rset)
         return rset
 
-    def _render_relation(self, rset, dispctrl, defaultvid, showlabel):
+    def _render_relation(self, dispctrl, rset, defaultvid):
         self.w(u'<div class="section">')
-        if showlabel:
-            self.w(u'<h4>%s</h4>' % self._cw._(dispctrl['label']),
+        if dispctrl.get('showlabel', self.show_rel_label):
+            self.w(u'<h4>%s</h4>' % self._cw._(dispctrl['label']))
+        self.wview(dispctrl.get('vid', defaultvid), rset,
                    initargs={'dispctrl': dispctrl})
         self.w(u'</div>')
 
-    def _render_attribute(self, rschema, value, role='subject', table=False):
+    def _render_attribute(self, dispctrl, rschema, value,
+                          role='subject', table=False):
         if rschema.final:
-            show_label = self.show_attr_label
+            showlabel = dispctrl.get('showlabel', self.show_attr_label)
         else:
-            show_label = self.show_rel_label
-        label = display_name(self._cw, rschema.type, role)
-        self.field(label, value, show_label=show_label, tr=False, table=table)
+            showlabel = dispctrl.get('showlabel', self.show_rel_label)
+        if dispctrl.get('label'):
+            label = self._cw._(dispctrl.get('label'))
+        else:
+            label = display_name(self.req, rschema.type, role)
+        self.field(label, value, show_label=showlabel, tr=False, table=table)
 
 
 class RelatedView(EntityView):
@@ -239,6 +278,21 @@
                                                self._cw._('see them all')))
             self.w(u'</div>')
 
+
+class URLAttributeView(EntityView):
+    """use this view for attributes whose value is an url and that you want
+    to display as clickable link
+    """
+    id = 'urlattr'
+    __select__ = EntityView.__select__ & match_kwargs('rtype')
+
+    def cell_call(self, row, col, rtype, **kwargs):
+        entity = self.rset.get_entity(row, col)
+        url = entity.printable_value(rtype)
+        if url:
+            self.w(u'<a href="%s">%s</a>' % (url, url))
+
+
 ## default primary ui configuration ###########################################
 
 _pvs = uicfg.primaryview_section
--- a/web/views/sparql.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/sparql.py	Thu Dec 03 17:17:43 2009 +0100
@@ -39,7 +39,7 @@
     __regid__ = 'sparql'
     def call(self):
         form = self._cw.vreg.select('forms', 'sparql', self._cw)
-        self.w(form.form_render())
+        self.w(form.render())
         sparql = self._cw.form.get('sparql')
         vid = self._cw.form.get('resultvid', 'table')
         if sparql:
--- a/web/views/tabs.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/tabs.py	Thu Dec 03 17:17:43 2009 +0100
@@ -179,7 +179,7 @@
         self.w(u'</div>')
 
 
-class TabedPrimaryView(TabsMixin, primary.PrimaryView):
+class TabbedPrimaryView(TabsMixin, primary.PrimaryView):
     __abstract__ = True # don't register
 
     tabs = ['main_tab']
@@ -191,7 +191,7 @@
         # XXX uncomment this in 3.6
         #self.render_entity_toolbox(entity)
         self.render_tabs(self.tabs, self.default_tab, entity)
-
+TabedPrimaryView = TabbedPrimaryView # XXX deprecate that typo!
 
 class PrimaryTab(primary.PrimaryView):
     __regid__ = 'main_tab'
--- a/web/views/timetable.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/timetable.py	Thu Dec 03 17:17:43 2009 +0100
@@ -10,7 +10,7 @@
 
 from cubicweb.interfaces import ITimetableViews
 from cubicweb.selectors import implements
-from cubicweb.utils import date_range
+from cubicweb.utils import date_range, todatetime
 from cubicweb.view import AnyRsetView
 
 
@@ -22,6 +22,7 @@
         self.lines = 1
 
 MIN_COLS = 3  # minimum number of task columns for a single user
+ALL_USERS = object()
 
 class TimeTableView(AnyRsetView):
     __regid__ = 'timetable'
@@ -54,6 +55,7 @@
             elif task.stop:
                 the_dates.append(task.stop)
             for d in the_dates:
+                d = todatetime(d)
                 d_users = dates.setdefault(d, {})
                 u_tasks = d_users.setdefault(user, set())
                 u_tasks.add( task )
@@ -137,7 +139,7 @@
         for user, width in zip(users, widths):
             self.w(u'<th colspan="%s">' % max(MIN_COLS, width))
             if user is ALL_USERS:
-                self.w('*')
+                self.w(u'*')
             else:
                 user.view('oneline', w=self.w)
             self.w(u'</th>')
--- a/web/views/treeview.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/treeview.py	Thu Dec 03 17:17:43 2009 +0100
@@ -119,7 +119,7 @@
     (each item should be expandable if it's not a tree leaf)
     """
     __regid__ = 'treeitemview'
-    __select__ = EntityView.__select__ & implements(ITree)
+    __select__ = implements(ITree)
     default_branch_state_is_open = False
 
     def open_state(self, eeid, treeid):
--- a/web/views/workflow.py	Mon Nov 23 14:13:53 2009 +0100
+++ b/web/views/workflow.py	Thu Dec 03 17:17:43 2009 +0100
@@ -17,7 +17,7 @@
 from cubicweb import Unauthorized, view
 from cubicweb.selectors import (implements, has_related_entities, one_line_rset,
                                 relation_possible, match_form_params,
-                                entity_implements)
+                                entity_implements, score_entity)
 from cubicweb.interfaces import IWorkflowable
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
@@ -64,37 +64,40 @@
     def cell_call(self, row, col):
         entity = self.cw_rset.get_entity(row, col)
         transition = self._cw.entity_from_eid(self._cw.form['treid'])
-        dest = transition.destination()
-        _ = self._cw._
-        # specify both rset/row/col and entity in case implements selector (and
-        # not entity_implements) is used on custom form
-        form = self._cw.vreg['forms'].select(
-            'changestate', self._cw, rset=self.cw_rset, row=row, col=col,
-            entity=entity, transition=transition,
-            redirect_path=self.redirectpath(entity))
+        form = self.get_form(entity, transition)
         self.w(form.error_message())
-        self.w(u'<h4>%s %s</h4>\n' % (_(transition.name),
+        self.w(u'<h4>%s %s</h4>\n' % (self._cw._(transition.name),
                                       entity.view('oneline')))
         msg = _('status will change from %(st1)s to %(st2)s') % {
-            'st1': _(entity.current_state.name),
-            'st2': _(dest.name)}
+            'st1': entity.printable_state,
+            'st2': self._cw._(transition.destination().name)}
         self.w(u'<p>%s</p>\n' % msg)
+        self.w(form.render(formvalues=dict(wf_info_for=entity.eid,
+                                           by_transition=transition.eid)))
+
+    def redirectpath(self, entity):
+        return entity.rest_path()
+
+    def get_form(self, entity, transition, **kwargs):
+        # XXX used to specify both rset/row/col and entity in case implements
+        # selector (and not entity_implements) is used on custom form
+        form = self._cw.vreg['forms'].select(
+            'changestate', self._cw, entity=entity, transition=transition,
+            redirect_path=self.redirectpath(entity), **kwargs)
         trinfo = self._cw.vreg['etypes'].etype_class('TrInfo')(self._cw)
         trinfo.eid = self._cw.varmaker.next()
         subform = self._cw.vreg['forms'].select('edition', self._cw, entity=trinfo,
                                             mainform=False)
         subform.field_by_name('by_transition').widget = fwdgs.HiddenInput()
         form.add_subform(subform)
-        self.w(form.form_render(wf_info_for=entity.eid,
-                                by_transition=transition.eid))
-
-    def redirectpath(self, entity):
-        return entity.rest_path()
+        return form
 
 
 class WFHistoryView(EntityView):
     __regid__ = 'wfhistory'
-    __select__ = relation_possible('wf_info_for', role='object')
+    __select__ = relation_possible('wf_info_for', role='object') & \
+                 score_entity(lambda x: x.workflow_history)
+
     title = _('Workflow history')
 
     def cell_call(self, row, col, view=None):
@@ -247,7 +250,7 @@
     def object_allowed_transition_vocabulary(self, rtype, limit=None):
         if not self.edited_entity.has_eid():
             return self.workflow_states_for_relation('allowed_transition')
-        return self.subject_relation_vocabulary(rtype, limit)
+        return self.object_relation_vocabulary(rtype, limit)
 
 
 class StateEditionForm(autoform.AutomaticEntityForm):
@@ -306,7 +309,6 @@
         for state in self.entity.reverse_state_of:
             state.complete()
             yield state.eid, state
-
         for transition in self.entity.reverse_transition_of:
             transition.complete()
             yield transition.eid, transition