backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 24 Jan 2011 17:02:38 +0100
changeset 6882 b5e34836f84e
parent 6856 ac092197c099 (current diff)
parent 6881 a473bf557456 (diff)
child 6883 1538542a88ed
backport stable
i18n/de.po
i18n/en.po
i18n/es.po
i18n/fr.po
schemas/base.py
server/serverctl.py
server/sources/native.py
view.py
web/formwidgets.py
web/test/unittest_application.py
web/views/calendar.py
--- a/appobject.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/appobject.py	Mon Jan 24 17:02:38 2011 +0100
@@ -214,6 +214,9 @@
         return NotImplementedError("selector %s must implement its logic "
                                    "in its __call__ method" % self.__class__)
 
+    def __repr__(self):
+        return u'<Selector %s at %x>' % (self.__class__.__name__, id(self))
+
 
 class MultiSelector(Selector):
     """base class for compound selector classes"""
--- a/cwconfig.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/cwconfig.py	Mon Jan 24 17:02:38 2011 +0100
@@ -22,7 +22,7 @@
 Resource mode
 -------------
 
-A resource *mode* is a predifined set of settings for various resources
+A resource *mode* is a predefined set of settings for various resources
 directories, such as cubes, instances, etc. to ease development with the
 framework. There are two running modes with *CubicWeb*:
 
@@ -159,14 +159,6 @@
 SMTP_LOCK = Lock()
 
 
-class metaconfiguration(type):
-    """metaclass to automaticaly register configuration"""
-    def __new__(mcs, name, bases, classdict):
-        cls = super(metaconfiguration, mcs).__new__(mcs, name, bases, classdict)
-        if classdict.get('name'):
-            CONFIGURATIONS.append(cls)
-        return cls
-
 def configuration_cls(name):
     """return the configuration class registered with the given name"""
     try:
@@ -290,7 +282,6 @@
 class CubicWebNoAppConfiguration(ConfigurationMixIn):
     """base class for cubicweb configuration without a specific instance directory
     """
-    __metaclass__ = metaconfiguration
     # to set in concrete configuration
     name = None
     # log messages format (see logging module documentation for available keys)
--- a/cwctl.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/cwctl.py	Mon Jan 24 17:02:38 2011 +0100
@@ -235,7 +235,7 @@
                     tinfo = cwcfg.cube_pkginfo(cube)
                     tversion = tinfo.version
                     cfgpb.add_cube(cube, tversion)
-                except ConfigurationError, ex:
+                except (ConfigurationError, AttributeError), ex:
                     tinfo = None
                     tversion = '[missing cube information: %s]' % ex
                 print '* %s %s' % (cube.ljust(namesize), tversion)
--- a/devtools/__init__.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/devtools/__init__.py	Mon Jan 24 17:02:38 2011 +0100
@@ -175,6 +175,7 @@
 
 class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
     repo_method = 'inmemory'
+    name = 'all-in-one' # so it search for all-in-one.conf, not repository.conf
     options = cwconfig.merge_options(TestServerConfiguration.options
                                      + TwistedConfiguration.options)
     cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path
--- a/doc/book/en/admin/setup.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/admin/setup.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -2,7 +2,7 @@
 
 .. _SetUpEnv:
 
-Installation and set-up of a *CubicWeb* environment
+Installation and set-up of a |cubicweb| environment
 ===================================================
 
 Installation of `Cubicweb` and its dependencies
@@ -68,8 +68,8 @@
    `cubicweb with postgresql datatabase`_ and `cubicweb-mysql-support` contains
    necessary dependency for using `cubicweb with mysql database`_ .
 
-There is also a wide variety of :ref:`cubes <Cubes>` listed on the `CubicWeb.org Forge`_
-available as debian packages and tarball.
+There is also a wide variety of :ref:`cubes <AvailableCubes>` listed on the
+`CubicWeb.org Forge`_ available as debian packages and tarball.
 
 The repositories are signed with `Logilab's gnupg key`_. To avoid warning on
 "apt-get update":
--- a/doc/book/en/annexes/index.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/annexes/index.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -16,5 +16,4 @@
    rql/index
    mercurial
    depends
-   javascript-api
    docstrings-conventions
--- a/doc/book/en/annexes/rql/index.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/annexes/rql/index.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -1,4 +1,4 @@
-.. _RQLChapter
+.. _RQLChapter:
 
 Relation Query Language (RQL)
 =============================
--- a/doc/book/en/devrepo/cubes/available-cubes.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/devrepo/cubes/available-cubes.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -1,3 +1,4 @@
+.. _AvailableCubes:
 
 Available cubes
 ---------------
--- a/doc/book/en/devrepo/datamodel/define-workflows.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/devrepo/datamodel/define-workflows.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -8,14 +8,13 @@
 General
 -------
 
-A workflow describes how certain entities have to evolve between
-different states. Hence we have a set of states, and a "transition
-graph", i.e. a set of possible transitions from one state to another
-state.
+A workflow describes how certain entities have to evolve between different
+states. Hence we have a set of states, and a "transition graph", i.e. a set of
+possible transitions from one state to another state.
 
-We will define a simple workflow for a blog, with only the following
-two states: `submitted` and `published`. So first, we create a simple
-|cubicweb| instance in five minutes (see :ref:`BlogFiveMinutes`).
+We will define a simple workflow for a blog, with only the following two states:
+`submitted` and `published`. You may want to take a look at :ref:`_TutosBase` if
+you want to quickly setup an instance running a blog.
 
 Setting up a workflow
 ---------------------
--- a/doc/book/en/devweb/edition/examples.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/devweb/edition/examples.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -117,7 +117,7 @@
 set to 'sendmail', which is our form DOM id as specified by its `domid`
 attribute), another to cancel the form which will go back to the previous page
 using another javascript call. Also we specify an image to use as button icon as a
-resource identifier (see :ref:`external_resources`) given as last argument to
+resource identifier (see :ref:`uiprops`) given as last argument to
 :class:`cubicweb.web.formwidgets.ImgButton`.
 
 To see this form, we still have to wrap it in a view. This is pretty simple:
@@ -131,12 +131,13 @@
 	def call(self):
 	    form = self._cw.vreg['forms'].select('massmailing', self._cw,
 	                                         rset=self.cw_rset)
-	    self.w(form.render())
+	    form.render(w=self.w)
 
 As you see, we simply define a view with proper selector so it only apply to a
 result set containing :class:`IEmailable` entities, and so that only users in the
 managers or users group can use it. Then in the `call()` method for this view we
-simply select the above form and write what its `.render()` method returns.
+simply select the above form and call its `.render()` method with our output
+stream as argument.
 
 When this form is submitted, a controller with id 'sendmail' will be called (as
 specified using `action`). This controller will be responsible to actually send
--- a/doc/book/en/devweb/views/basetemplates.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/devweb/views/basetemplates.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -1,7 +1,5 @@
 .. -*- coding: utf-8 -*-
 
-.. |cubicweb| replace:: *CubicWeb*
-
 .. _templates:
 
 Templates
--- a/doc/book/en/devweb/views/primary.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/devweb/views/primary.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -226,8 +226,6 @@
 
 We'll show you now an example of a ``primary`` view and how to customize it.
 
-We continue along the basic tutorial :ref:`tuto_blog`.
-
 If you want to change the way a ``BlogEntry`` is displayed, just
 override the method ``cell_call()`` of the view ``primary`` in
 ``BlogDemo/views.py``.
@@ -247,7 +245,7 @@
 
 
 The above source code defines a new primary view for
-``BlogEntry``. The `id` class attribute is not repeated there since it
+``BlogEntry``. The `__reid__` class attribute is not repeated there since it
 is inherited through the `primary.PrimaryView` class.
 
 The selector for this view chains the selector of the inherited class
Binary file doc/book/en/images/tutos-photowebsite_background-image.png has changed
Binary file doc/book/en/images/tutos-photowebsite_boxes.png has changed
Binary file doc/book/en/images/tutos-photowebsite_breadcrumbs.png has changed
Binary file doc/book/en/images/tutos-photowebsite_facets.png has changed
Binary file doc/book/en/images/tutos-photowebsite_grey-box.png has changed
Binary file doc/book/en/images/tutos-photowebsite_index-after.png has changed
Binary file doc/book/en/images/tutos-photowebsite_index-before.png has changed
Binary file doc/book/en/images/tutos-photowebsite_login-box.png has changed
Binary file doc/book/en/images/tutos-photowebsite_prevnext.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui1.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui2.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui3.png has changed
--- a/doc/book/en/index.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/index.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -13,19 +13,28 @@
 
 Its main features are:
 
-* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application,
+* an engine driven by the explicit :ref:`data model
+  <TutosBaseCustomizingTheApplicationDataModel>` of the application,
+
 * a query language named :ref:`RQL <RQL>` similar to W3C's SPARQL,
-* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation,
-* a library of reusable :ref:`components <cubes>` (data model and views) that fulfill common needs,
+
+* a :ref:`selection+view <TutosBaseCustomizingTheApplicationCustomViews>`
+  mechanism for semi-automatic XHTML/XML/JSON/text generation,
+
+* a library of reusable :ref:`components <Cube>` (data model and views) that
+  fulfill common needs,
+
 * the power and flexibility of the Python_ programming language,
-* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
+
+* the reliability of SQL databases, LDAP directories, Subversion and Mercurial
+  for storage backends.
 
 Built since 2000 from an R&D effort still continued, supporting 100,000s of
 daily visits at some production sites, |cubicweb| is a proven end to end solution
 for semantic web application development that promotes quality, reusability and
 efficiency.
 
-The unbeliever will read the :ref:`Tutorial`.
+The unbeliever will read the :ref:`Tutorials`.
 
 The hacker will join development at the forge_.
 
--- a/doc/book/en/makefile	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/makefile	Mon Jan 24 17:02:38 2011 +0100
@@ -33,7 +33,7 @@
 
 clean:
 	rm -f *.html
-	-rm -rf ${BUILDDIR}/*
+	-rm -rf ${BUILDDIR}/html ${BUILDDIR}/doctrees
 	-rm -rf ${BUILDJS}
 
 all: html
--- a/doc/book/en/tutorials/advanced/index.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/tutorials/advanced/index.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -1,7 +1,8 @@
-.. _advanced_tutorial:
+
+.. _TutosPhotoWebSite:
 
-Building a photo gallery with CubicWeb
-======================================
+Building a photo gallery with |cubicweb|
+========================================
 
 Desired features
 ----------------
@@ -16,595 +17,13 @@
 * advanced security (not everyone can see everything). More on this later.
 
 
-Cube creation and schema definition
------------------------------------
-
-.. _adv_tuto_create_new_cube:
-
-Step 1: creating a new cube for my web site
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One note about my development environment: I wanted to use the packaged
-version of CubicWeb and cubes while keeping my cube in my user
-directory, let's say `~src/cubes`.  I achieve this by setting the
-following environment variables::
-
-  CW_CUBES_PATH=~/src/cubes
-  CW_MODE=user
-
-I can now create the cube which will hold custom code for this web
-site using::
-
-  cubicweb-ctl newcube --directory=~/src/cubes sytweb
-
-
-.. _adv_tuto_assemble_cubes:
-
-Step 2: pick building blocks into existing cubes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Almost everything I want to handle in my web-site is somehow already modelized in
-existing cubes that I'll extend for my need. So I'll pick the following cubes:
-
-* `folder`, containing the `Folder` entity type, which will be used as
-  both 'album' and a way to map file system folders. Entities are
-  added to a given folder using the `filed_under` relation.
-
-* `file`, containing `File` and `Image` entity types, gallery view,
-  and a file system import utility.
-
-* `zone`, containing the `Zone` entity type for hierarchical geographical
-  zones. Entities (including sub-zones) are added to a given zone using the
-  `situated_in` relation.
-
-* `person`, containing the `Person` entity type plus some basic views.
-
-* `comment`, providing a full commenting system allowing one to comment entity types
-  supporting the `comments` relation by adding a `Comment` entity.
-
-* `tag`, providing a full tagging system as an easy and powerful way to classify
-  entities supporting the `tags` relation by linking the to `Tag` entities. This
-  will allows navigation into a large number of picture.
-
-Ok, now I'll tell my cube requires all this by editing :file:`cubes/sytweb/__pkginfo__.py`:
-
-  .. sourcecode:: python
-
-    __depends__ = {'cubicweb': '>= 3.8.0',
-                   'cubicweb-file': '>= 1.2.0',
-		   'cubicweb-folder': '>= 1.1.0',
-		   'cubicweb-person': '>= 1.2.0',
-		   'cubicweb-comment': '>= 1.2.0',
-		   'cubicweb-tag': '>= 1.2.0',
-		   'cubicweb-zone': None}
-
-Notice that you can express minimal version of the cube that should be used,
-`None` meaning whatever version available. All packages starting with 'cubicweb-'
-will be recognized as being cube, not bare python packages. You can still specify
-this explicitly using instead the `__depends_cubes__` dictionary which should
-contains cube's name without the prefix. So the example below would be written
-as:
-
-  .. sourcecode:: python
+.. toctree::
+   :maxdepth: 2
 
-    __depends__ = {'cubicweb': '>= 3.8.0'}
-    __depends_cubes__ = {'file': '>= 1.2.0',
-		         'folder': '>= 1.1.0',
-		   	 'person': '>= 1.2.0',
-		   	 'comment': '>= 1.2.0',
-		   	 'tag': '>= 1.2.0',
-		   	 'zone': None}
-
-
-Step 3: glue everything together in my cube's schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. sourcecode:: python
-
-    from yams.buildobjs import RelationDefinition
-
-    class comments(RelationDefinition):
-	subject = 'Comment'
-	object = ('File', 'Image')
-	cardinality = '1*'
-	composite = 'object'
-
-    class tags(RelationDefinition):
-	subject = 'Tag'
-	object = ('File', 'Image')
-
-    class filed_under(RelationDefinition):
-	subject = ('File', 'Image')
-	object = 'Folder'
-
-    class situated_in(RelationDefinition):
-	subject = 'Image'
-	object = 'Zone'
-
-    class displayed_on(RelationDefinition):
-	subject = 'Person'
-	object = 'Image'
-
-
-This schema:
-
-* allows to comment and tag on `File` and `Image` entity types by adding the
-  `comments` and `tags` relations. This should be all we've to do for this
-  feature since the related cubes provide 'pluggable section' which are
-  automatically displayed on the primary view of entity types supporting the
-  relation.
-
-* adds a `situated_in` relation definition so that image entities can be
-  geolocalized.
-
-* add a new relation `displayed_on` relation telling who can be seen on a
-  picture.
-
-This schema will probably have to evolve as time goes (for security handling at
-least), but since the possibility to let a schema evolve is one of CubicWeb's
-features (and goals), we won't worry about it for now and see that later when needed.
-
-
-Step 4: creating the instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now that I have a schema, I want to create an instance. To
-do so using this new 'sytweb' cube, I run::
-
-  cubicweb-ctl create sytweb sytweb_instance
-
-Hint: if you get an error while the database is initialized, you can
-avoid having to answer the questions again by running::
-
-   cubicweb-ctl db-create sytweb_instance
-
-This will use your already configured instance and start directly from the create
-database step, thus skipping questions asked by the 'create' command.
-
-Once the instance and database are fully initialized, run ::
-
-  cubicweb-ctl start sytweb_instance
-
-to start the instance, check you can connect on it, etc...
+   part01_create-cube
+   part02_security
+   part03_bfss
+   part04_ui-base
+   part05_ui-advanced
 
 
-Security, testing and migration
--------------------------------
-
-This part will cover various topics:
-
-* configuring security
-* migrating existing instance
-* writing some unit tests
-
-Here is the ``read`` security model I want:
-
-* folders, files, images and comments should have one of the following visibility:
-
-  - ``public``, everyone can see it
-  - ``authenticated``, only authenticated users can see it
-  - ``restricted``, only a subset of authenticated users can see it
-
-* managers (e.g. me) can see everything
-* only authenticated users can see people
-* everyone can see classifier entities, such as tag and zone
-
-Also, unless explicitly specified, the visibility of an image should be the same as
-its parent folder, as well as visibility of a comment should be the same as the
-commented entity. If there is no parent entity, the default visibility is
-``authenticated``.
-
-Regarding write security, that's much easier:
-* anonymous can't write anything
-* authenticated users can only add comment
-* managers will add the remaining stuff
-
-Now, let's implement that!
-
-Proper security in CubicWeb is done at the schema level, so you don't have to
-bother with it in views: users will only see what they can see automatically.
-
-.. _adv_tuto_security:
-
-Step 1: configuring security into the schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In schema, you can grant access according to groups, or to some RQL expressions:
-users get access if the expression returns some results. To implement the read
-security defined earlier, groups are not enough, we'll need some RQL expression. Here
-is the idea:
-
-* add a `visibility` attribute on Folder, Image and Comment, which may be one of
-  the value explained above
-
-* add a `may_be_read_by` relation from Folder, Image and Comment to users,
-  which will define who can see the entity
-
-* security propagation will be done in hook.
-
-So the first thing to do is to modify my cube's schema.py to define those
-relations:
-
-.. sourcecode:: python
-
-    from yams.constraints import StaticVocabularyConstraint
-
-    class visibility(RelationDefinition):
-	subject = ('Folder', 'File', 'Image', 'Comment')
-	object = 'String'
-	constraints = [StaticVocabularyConstraint(('public', 'authenticated',
-						   'restricted', 'parent'))]
-	default = 'parent'
-	cardinality = '11' # required
-
-    class may_be_read_by(RelationDefinition):
-        __permissions__ = {
-	    'read':   ('managers', 'users'),
-	    'add':    ('managers',),
-	    'delete': ('managers',),
-	    }
-
-	subject = ('Folder', 'File', 'Image', 'Comment',)
-	object = 'CWUser'
-
-We can note the following points:
-
-* we've added a new `visibility` attribute to folder, file, image and comment
-  using a `RelationDefinition`
-
-* `cardinality = '11'` means this attribute is required. This is usually hidden
-  under the `required` argument given to the `String` constructor, but we can
-  rely on this here (same thing for StaticVocabularyConstraint, which is usually
-  hidden by the `vocabulary` argument)
-
-* the `parent` possible value will be used for visibility propagation
-
-* think to secure the `may_be_read_by` permissions, else any user can add/delte it
-  by default, which somewhat breaks our security model...
-
-Now, we should be able to define security rules in the schema, based on these new
-attribute and relation. Here is the code to add to *schema.py*:
-
-.. sourcecode:: python
-
-    from cubicweb.schema import ERQLExpression
-
-    VISIBILITY_PERMISSIONS = {
-	'read':   ('managers',
-		   ERQLExpression('X visibility "public"'),
-		   ERQLExpression('X may_be_read_by U')),
-	'add':    ('managers',),
-	'update': ('managers', 'owners',),
-	'delete': ('managers', 'owners'),
-	}
-    AUTH_ONLY_PERMISSIONS = {
-	    'read':   ('managers', 'users'),
-	    'add':    ('managers',),
-	    'update': ('managers', 'owners',),
-	    'delete': ('managers', 'owners'),
-	    }
-    CLASSIFIERS_PERMISSIONS = {
-	    'read':   ('managers', 'users', 'guests'),
-	    'add':    ('managers',),
-	    'update': ('managers', 'owners',),
-	    'delete': ('managers', 'owners'),
-	    }
-
-    from cubes.folder.schema import Folder
-    from cubes.file.schema import File, Image
-    from cubes.comment.schema import Comment
-    from cubes.person.schema import Person
-    from cubes.zone.schema import Zone
-    from cubes.tag.schema import Tag
-
-    Folder.__permissions__ = VISIBILITY_PERMISSIONS
-    File.__permissions__ = VISIBILITY_PERMISSIONS
-    Image.__permissions__ = VISIBILITY_PERMISSIONS
-    Comment.__permissions__ = VISIBILITY_PERMISSIONS.copy()
-    Comment.__permissions__['add'] = ('managers', 'users',)
-    Person.__permissions__ = AUTH_ONLY_PERMISSIONS
-    Zone.__permissions__ = CLASSIFIERS_PERMISSIONS
-    Tag.__permissions__ = CLASSIFIERS_PERMISSIONS
-
-What's important in there:
-
-* `VISIBILITY_PERMISSIONS` provides read access to managers group, if
-  `visibility` attribute's value is 'public', or if user (designed by the 'U'
-  variable in the expression) is linked to the entity (the 'X' variable) through
-  the `may_read` permission
-
-* we modify permissions of the entity types we use by importing them and
-  modifying their `__permissions__` attribute
-
-* notice the `.copy()`: we only want to modify 'add' permission for `Comment`,
-  not for all entity types using `VISIBILITY_PERMISSIONS`!
-
-* the remaining part of the security model is done using regular groups:
-
-  - `users` is the group to which all authenticated users will belong
-  - `guests` is the group of anonymous users
-
-
-.. _adv_tuto_security_propagation:
-
-Step 2: security propagation in hooks
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To fullfill the requirements, we have to implement::
-
-  Also, unless explicity specified, visibility of an image should be the same as
-  its parent folder, as well as visibility of a comment should be the same as the
-  commented entity.
-
-This kind of `active` rule will be done using CubicWeb's hook
-system. Hooks are triggered on database event such as addition of new
-entity or relation.
-
-The tricky part of the requirement is in *unless explicitly specified*, notably
-because when the entity is added, we don't know yet its 'parent'
-entity (e.g. Folder of an Image, Image commented by a Comment). To handle such things,
-CubicWeb provides `Operation`, which allow to schedule things to do at commit time.
-
-In our case we will:
-
-* on entity creation, schedule an operation that will set default visibility
-
-* when a "parent" relation is added, propagate parent's visibility unless the
-  child already has a visibility set
-
-Here is the code in cube's *hooks.py*:
-
-.. sourcecode:: python
-
-    from cubicweb.selectors import is_instance
-    from cubicweb.server import hook
-
-    class SetVisibilityOp(hook.Operation):
-	def precommit_event(self):
-	    for eid in self.session.transaction_data.pop('pending_visibility'):
-		entity = self.session.entity_from_eid(eid)
-		if entity.visibility == 'parent':
-		    entity.set_attributes(visibility=u'authenticated')
-
-    class SetVisibilityHook(hook.Hook):
-	__regid__ = 'sytweb.setvisibility'
-	__select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Image', 'Comment')
-	events = ('after_add_entity',)
-	def __call__(self):
-	    hook.set_operation(self._cw, 'pending_visibility', self.entity.eid,
-			       SetVisibilityOp)
-
-    class SetParentVisibilityHook(hook.Hook):
-	__regid__ = 'sytweb.setparentvisibility'
-	__select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
-	events = ('after_add_relation',)
-
-	def __call__(self):
-	    parent = self._cw.entity_from_eid(self.eidto)
-	    child = self._cw.entity_from_eid(self.eidfrom)
-	    if child.visibility == 'parent':
-		child.set_attributes(visibility=parent.visibility)
-
-Notice:
-
-* hooks are application objects, hence have selectors that should match entity or
-  relation types to which the hook applies. To match a relation type, we use the
-  hook specific `match_rtype` selector.
-
-* usage of `set_operation`: instead of adding an operation for each added entity,
-  set_operation allows to create a single one and to store entity's eids to be
-  processed in session's transaction data. This is a good pratice to avoid heavy
-  operations manipulation cost when creating a lot of entities in the same
-  transaction.
-
-* the `precommit_event` method of the operation will be called at transaction's
-  commit time.
-
-* in a hook, `self._cw` is the repository session, not a web request as usually
-  in views
-
-* according to hook's event, you have access to different attributes on the hook
-  instance. Here:
-
-  - `self.entity` is the newly added entity on 'after_add_entity' events
-
-  - `self.eidfrom` / `self.eidto` are the eid of the subject / object entity on
-    'after_add_relatiohn' events (you may also get the relation type using
-    `self.rtype`)
-
-The `parent` visibility value is used to tell "propagate using parent security"
-because we want that attribute to be required, so we can't use None value else
-we'll get an error before we get any chance to propagate...
-
-Now, we also want to propagate the `may_be_read_by` relation. Fortunately,
-CubicWeb provides some base hook classes for such things, so we only have to add
-the following code to *hooks.py*:
-
-.. sourcecode:: python
-
-    # relations where the "parent" entity is the subject
-    S_RELS = set()
-    # relations where the "parent" entity is the object
-    O_RELS = set(('filed_under', 'comments',))
-
-    class AddEntitySecurityPropagationHook(hook.PropagateSubjectRelationHook):
-	"""propagate permissions when new entity are added"""
-	__regid__ = 'sytweb.addentity_security_propagation'
-	__select__ = (hook.PropagateSubjectRelationHook.__select__
-		      & hook.match_rtype_sets(S_RELS, O_RELS))
-	main_rtype = 'may_be_read_by'
-	subject_relations = S_RELS
-	object_relations = O_RELS
-
-    class AddPermissionSecurityPropagationHook(hook.PropagateSubjectRelationAddHook):
-	"""propagate permissions when new entity are added"""
-	__regid__ = 'sytweb.addperm_security_propagation'
-	__select__ = (hook.PropagateSubjectRelationAddHook.__select__
-		      & hook.match_rtype('may_be_read_by',))
-	subject_relations = S_RELS
-	object_relations = O_RELS
-
-    class DelPermissionSecurityPropagationHook(hook.PropagateSubjectRelationDelHook):
-	__regid__ = 'sytweb.delperm_security_propagation'
-	__select__ = (hook.PropagateSubjectRelationDelHook.__select__
-		      & hook.match_rtype('may_be_read_by',))
-	subject_relations = S_RELS
-	object_relations = O_RELS
-
-* the `AddEntitySecurityPropagationHook` will propagate the relation
-  when `filed_under` or `comments` relations are added
-
-  - the `S_RELS` and `O_RELS` set as well as the `match_rtype_sets` selector are
-    used here so that if my cube is used by another one, it'll be able to
-    configure security propagation by simply adding relation to one of the two
-    sets.
-
-* the two others will propagate permissions changes on parent entities to
-  children entities
-
-
-.. _adv_tuto_tesing_security:
-
-Step 3: testing our security
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Security is tricky. Writing some tests for it is a very good idea. You should
-even write them first, as Test Driven Development recommends!
-
-Here is a small test case that will check the basis of our security
-model, in *test/unittest_sytweb.py*:
-
-.. sourcecode:: python
-
-    from cubicweb.devtools.testlib import CubicWebTC
-    from cubicweb import Binary
-
-    class SecurityTC(CubicWebTC):
-
-	def test_visibility_propagation(self):
-	    # create a user for later security checks
-	    toto = self.create_user('toto')
-	    # init some data using the default manager connection
-	    req = self.request()
-	    folder = req.create_entity('Folder',
-				       name=u'restricted',
-				       visibility=u'restricted')
-	    photo1 = req.create_entity('Image',
-				       data_name=u'photo1.jpg',
-				       data=Binary('xxx'),
-				       filed_under=folder)
-	    self.commit()
-	    photo1.clear_all_caches() # good practice, avoid request cache effects
-	    # visibility propagation
-	    self.assertEquals(photo1.visibility, 'restricted')
-	    # unless explicitly specified
-	    photo2 = req.create_entity('Image',
-				       data_name=u'photo2.jpg',
-				       data=Binary('xxx'),
-				       visibility=u'public',
-				       filed_under=folder)
-	    self.commit()
-	    self.assertEquals(photo2.visibility, 'public')
-	    # test security
-	    self.login('toto')
-	    req = self.request()
-	    self.assertEquals(len(req.execute('Image X')), 1) # only the public one
-	    self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
-	    # may_be_read_by propagation
-	    self.restore_connection()
-	    folder.set_relations(may_be_read_by=toto)
-	    self.commit()
-	    photo1.clear_all_caches()
-	    self.failUnless(photo1.may_be_read_by)
-	    # test security with permissions
-	    self.login('toto')
-	    req = self.request()
-	    self.assertEquals(len(req.execute('Image X')), 2) # now toto has access to photo2
-	    self.assertEquals(len(req.execute('Folder X')), 1) # and to restricted folder
-
-    if __name__ == '__main__':
-	from logilab.common.testlib import unittest_main
-	unittest_main()
-
-It's not complete, but show most things you'll want to do in tests: adding some
-content, creating users and connecting as them in the test, etc...
-
-To run it type:
-
-.. sourcecode:: bash
-
-    $ pytest unittest_sytweb.py
-    ========================  unittest_sytweb.py  ========================
-    -> creating tables [....................]
-    -> inserting default user and default groups.
-    -> storing the schema in the database [....................]
-    -> database for instance data initialized.
-    .
-    ----------------------------------------------------------------------
-    Ran 1 test in 22.547s
-
-    OK
-
-
-The first execution is taking time, since it creates a sqlite database for the
-test instance. The second one will be much quicker:
-
-.. sourcecode:: bash
-    
-    $ pytest unittest_sytweb.py
-    ========================  unittest_sytweb.py  ========================
-    .
-    ----------------------------------------------------------------------
-    Ran 1 test in 2.662s
-
-    OK
-
-If you do some changes in your schema, you'll have to force regeneration of that
-database. You do that by removing the tmpdb files before running the test: ::
-
-    $ rm data/tmpdb*
-
-
-.. Note::
-  pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
-
-.. _`logilab-common`: http://www.logilab.org/project/logilab-common
-
-.. _adv_tuto_migration_script:
-
-Step 4: writing the migration script and migrating the instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Prior to those changes, I  created an instance, feeded it with some data, so I
-don't want to create a new one, but to migrate the existing one. Let's see how to
-do that.
-
-Migration commands should be put in the cube's *migration* directory, in a
-file named file:`<X.Y.Z>_Any.py` ('Any' being there mostly for historical reason).
-
-Here I'll create a *migration/0.2.0_Any.py* file containing the following
-instructions:
-
-.. sourcecode:: python
-
-  add_relation_type('may_be_read_by')
-  add_relation_type('visibility')
-  sync_schema_props_perms()
-
-Then I update the version number in cube's *__pkginfo__.py* to 0.2.0. And
-that's it! Those instructions will:
-
-* update the instance's schema by adding our two new relations and update the
-  underlying database tables accordingly (the two first instructions)
-
-* update schema's permissions definition (the last instruction)
-
-
-To migrate my instance I simply type::
-
-   cubicweb-ctl upgrade sytweb
-
-I'll then be asked some questions to do the migration step by step. You should say
-YES when it asks if a backup of your database should be done, so you can get back
-to initial state if anything goes wrong...
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part01_create-cube.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,153 @@
+.. _TutosPhotoWebSiteCubeCreation:
+
+Cube creation and schema definition
+-----------------------------------
+
+.. _adv_tuto_create_new_cube:
+
+Step 1: creating a new cube for my web site
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One note about my development environment: I wanted to use the packaged
+version of CubicWeb and cubes while keeping my cube in my user
+directory, let's say `~src/cubes`.  I achieve this by setting the
+following environment variables::
+
+  CW_CUBES_PATH=~/src/cubes
+  CW_MODE=user
+
+I can now create the cube which will hold custom code for this web
+site using::
+
+  cubicweb-ctl newcube --directory=~/src/cubes sytweb
+
+
+.. _adv_tuto_assemble_cubes:
+
+Step 2: pick building blocks into existing cubes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Almost everything I want to handle in my web-site is somehow already modelized in
+existing cubes that I'll extend for my need. So I'll pick the following cubes:
+
+* `folder`, containing the `Folder` entity type, which will be used as
+  both 'album' and a way to map file system folders. Entities are
+  added to a given folder using the `filed_under` relation.
+
+* `file`, containing `File` and `Image` entity types, gallery view,
+  and a file system import utility.
+
+* `zone`, containing the `Zone` entity type for hierarchical geographical
+  zones. Entities (including sub-zones) are added to a given zone using the
+  `situated_in` relation.
+
+* `person`, containing the `Person` entity type plus some basic views.
+
+* `comment`, providing a full commenting system allowing one to comment entity types
+  supporting the `comments` relation by adding a `Comment` entity.
+
+* `tag`, providing a full tagging system as an easy and powerful way to classify
+  entities supporting the `tags` relation by linking the to `Tag` entities. This
+  will allows navigation into a large number of picture.
+
+Ok, now I'll tell my cube requires all this by editing :file:`cubes/sytweb/__pkginfo__.py`:
+
+  .. sourcecode:: python
+
+    __depends__ = {'cubicweb': '>= 3.8.0',
+                   'cubicweb-file': '>= 1.2.0',
+		   'cubicweb-folder': '>= 1.1.0',
+		   'cubicweb-person': '>= 1.2.0',
+		   'cubicweb-comment': '>= 1.2.0',
+		   'cubicweb-tag': '>= 1.2.0',
+		   'cubicweb-zone': None}
+
+Notice that you can express minimal version of the cube that should be used,
+`None` meaning whatever version available. All packages starting with 'cubicweb-'
+will be recognized as being cube, not bare python packages. You can still specify
+this explicitly using instead the `__depends_cubes__` dictionary which should
+contains cube's name without the prefix. So the example below would be written
+as:
+
+  .. sourcecode:: python
+
+    __depends__ = {'cubicweb': '>= 3.8.0'}
+    __depends_cubes__ = {'file': '>= 1.2.0',
+		         'folder': '>= 1.1.0',
+		   	 'person': '>= 1.2.0',
+		   	 'comment': '>= 1.2.0',
+		   	 'tag': '>= 1.2.0',
+		   	 'zone': None}
+
+
+Step 3: glue everything together in my cube's schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. sourcecode:: python
+
+    from yams.buildobjs import RelationDefinition
+
+    class comments(RelationDefinition):
+	subject = 'Comment'
+	object = ('File', 'Image')
+	cardinality = '1*'
+	composite = 'object'
+
+    class tags(RelationDefinition):
+	subject = 'Tag'
+	object = ('File', 'Image')
+
+    class filed_under(RelationDefinition):
+	subject = ('File', 'Image')
+	object = 'Folder'
+
+    class situated_in(RelationDefinition):
+	subject = 'Image'
+	object = 'Zone'
+
+    class displayed_on(RelationDefinition):
+	subject = 'Person'
+	object = 'Image'
+
+
+This schema:
+
+* allows to comment and tag on `File` and `Image` entity types by adding the
+  `comments` and `tags` relations. This should be all we've to do for this
+  feature since the related cubes provide 'pluggable section' which are
+  automatically displayed on the primary view of entity types supporting the
+  relation.
+
+* adds a `situated_in` relation definition so that image entities can be
+  geolocalized.
+
+* add a new relation `displayed_on` relation telling who can be seen on a
+  picture.
+
+This schema will probably have to evolve as time goes (for security handling at
+least), but since the possibility to let a schema evolve is one of CubicWeb's
+features (and goals), we won't worry about it for now and see that later when needed.
+
+
+Step 4: creating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now that I have a schema, I want to create an instance. To
+do so using this new 'sytweb' cube, I run::
+
+  cubicweb-ctl create sytweb sytweb_instance
+
+Hint: if you get an error while the database is initialized, you can
+avoid having to answer the questions again by running::
+
+   cubicweb-ctl db-create sytweb_instance
+
+This will use your already configured instance and start directly from the create
+database step, thus skipping questions asked by the 'create' command.
+
+Once the instance and database are fully initialized, run ::
+
+  cubicweb-ctl start sytweb_instance
+
+to start the instance, check you can connect on it, etc...
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part02_security.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,441 @@
+.. _TutosPhotoWebSiteSecurity:
+
+Security, testing and migration
+-------------------------------
+
+This part will cover various topics:
+
+* configuring security
+* migrating existing instance
+* writing some unit tests
+
+Here is the ``read`` security model I want:
+
+* folders, files, images and comments should have one of the following visibility:
+
+  - ``public``, everyone can see it
+  - ``authenticated``, only authenticated users can see it
+  - ``restricted``, only a subset of authenticated users can see it
+
+* managers (e.g. me) can see everything
+* only authenticated users can see people
+* everyone can see classifier entities, such as tag and zone
+
+Also, unless explicitly specified, the visibility of an image should be the same as
+its parent folder, as well as visibility of a comment should be the same as the
+commented entity. If there is no parent entity, the default visibility is
+``authenticated``.
+
+Regarding write security, that's much easier:
+* anonymous can't write anything
+* authenticated users can only add comment
+* managers will add the remaining stuff
+
+Now, let's implement that!
+
+Proper security in CubicWeb is done at the schema level, so you don't have to
+bother with it in views: users will only see what they can see automatically.
+
+.. _adv_tuto_security:
+
+Step 1: configuring security into the schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In schema, you can grant access according to groups, or to some RQL expressions:
+users get access if the expression returns some results. To implement the read
+security defined earlier, groups are not enough, we'll need some RQL expression. Here
+is the idea:
+
+* add a `visibility` attribute on Folder, Image and Comment, which may be one of
+  the value explained above
+
+* add a `may_be_read_by` relation from Folder, Image and Comment to users,
+  which will define who can see the entity
+
+* security propagation will be done in hook.
+
+So the first thing to do is to modify my cube's schema.py to define those
+relations:
+
+.. sourcecode:: python
+
+    from yams.constraints import StaticVocabularyConstraint
+
+    class visibility(RelationDefinition):
+	subject = ('Folder', 'File', 'Image', 'Comment')
+	object = 'String'
+	constraints = [StaticVocabularyConstraint(('public', 'authenticated',
+						   'restricted', 'parent'))]
+	default = 'parent'
+	cardinality = '11' # required
+
+    class may_be_read_by(RelationDefinition):
+        __permissions__ = {
+	    'read':   ('managers', 'users'),
+	    'add':    ('managers',),
+	    'delete': ('managers',),
+	    }
+
+	subject = ('Folder', 'File', 'Image', 'Comment',)
+	object = 'CWUser'
+
+We can note the following points:
+
+* we've added a new `visibility` attribute to folder, file, image and comment
+  using a `RelationDefinition`
+
+* `cardinality = '11'` means this attribute is required. This is usually hidden
+  under the `required` argument given to the `String` constructor, but we can
+  rely on this here (same thing for StaticVocabularyConstraint, which is usually
+  hidden by the `vocabulary` argument)
+
+* the `parent` possible value will be used for visibility propagation
+
+* think to secure the `may_be_read_by` permissions, else any user can add/delete it
+  by default, which somewhat breaks our security model...
+
+Now, we should be able to define security rules in the schema, based on these new
+attribute and relation. Here is the code to add to *schema.py*:
+
+.. sourcecode:: python
+
+    from cubicweb.schema import ERQLExpression
+
+    VISIBILITY_PERMISSIONS = {
+	'read':   ('managers',
+		   ERQLExpression('X visibility "public"'),
+		   ERQLExpression('X may_be_read_by U')),
+	'add':    ('managers',),
+	'update': ('managers', 'owners',),
+	'delete': ('managers', 'owners'),
+	}
+    AUTH_ONLY_PERMISSIONS = {
+	    'read':   ('managers', 'users'),
+	    'add':    ('managers',),
+	    'update': ('managers', 'owners',),
+	    'delete': ('managers', 'owners'),
+	    }
+    CLASSIFIERS_PERMISSIONS = {
+	    'read':   ('managers', 'users', 'guests'),
+	    'add':    ('managers',),
+	    'update': ('managers', 'owners',),
+	    'delete': ('managers', 'owners'),
+	    }
+
+    from cubes.folder.schema import Folder
+    from cubes.file.schema import File, Image
+    from cubes.comment.schema import Comment
+    from cubes.person.schema import Person
+    from cubes.zone.schema import Zone
+    from cubes.tag.schema import Tag
+
+    Folder.__permissions__ = VISIBILITY_PERMISSIONS
+    File.__permissions__ = VISIBILITY_PERMISSIONS
+    Image.__permissions__ = VISIBILITY_PERMISSIONS
+    Comment.__permissions__ = VISIBILITY_PERMISSIONS.copy()
+    Comment.__permissions__['add'] = ('managers', 'users',)
+    Person.__permissions__ = AUTH_ONLY_PERMISSIONS
+    Zone.__permissions__ = CLASSIFIERS_PERMISSIONS
+    Tag.__permissions__ = CLASSIFIERS_PERMISSIONS
+
+What's important in there:
+
+* `VISIBILITY_PERMISSIONS` provides read access to managers group, if
+  `visibility` attribute's value is 'public', or if user (designed by the 'U'
+  variable in the expression) is linked to the entity (the 'X' variable) through
+  the `may_read` permission
+
+* we modify permissions of the entity types we use by importing them and
+  modifying their `__permissions__` attribute
+
+* notice the `.copy()`: we only want to modify 'add' permission for `Comment`,
+  not for all entity types using `VISIBILITY_PERMISSIONS`!
+
+* the remaining part of the security model is done using regular groups:
+
+  - `users` is the group to which all authenticated users will belong
+  - `guests` is the group of anonymous users
+
+
+.. _adv_tuto_security_propagation:
+
+Step 2: security propagation in hooks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To fullfill the requirements, we have to implement::
+
+  Also, unless explicity specified, visibility of an image should be the same as
+  its parent folder, as well as visibility of a comment should be the same as the
+  commented entity.
+
+This kind of `active` rule will be done using CubicWeb's hook
+system. Hooks are triggered on database event such as addition of new
+entity or relation.
+
+The tricky part of the requirement is in *unless explicitly specified*, notably
+because when the entity is added, we don't know yet its 'parent'
+entity (e.g. Folder of an Image, Image commented by a Comment). To handle such things,
+CubicWeb provides `Operation`, which allow to schedule things to do at commit time.
+
+In our case we will:
+
+* on entity creation, schedule an operation that will set default visibility
+
+* when a "parent" relation is added, propagate parent's visibility unless the
+  child already has a visibility set
+
+Here is the code in cube's *hooks.py*:
+
+.. sourcecode:: python
+
+    from cubicweb.selectors import is_instance
+    from cubicweb.server import hook
+
+    class SetVisibilityOp(hook.Operation):
+	def precommit_event(self):
+	    for eid in self.session.transaction_data.pop('pending_visibility'):
+		entity = self.session.entity_from_eid(eid)
+		if entity.visibility == 'parent':
+		    entity.set_attributes(visibility=u'authenticated')
+
+    class SetVisibilityHook(hook.Hook):
+	__regid__ = 'sytweb.setvisibility'
+	__select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Image', 'Comment')
+	events = ('after_add_entity',)
+	def __call__(self):
+	    hook.set_operation(self._cw, 'pending_visibility', self.entity.eid,
+			       SetVisibilityOp)
+
+    class SetParentVisibilityHook(hook.Hook):
+	__regid__ = 'sytweb.setparentvisibility'
+	__select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
+	events = ('after_add_relation',)
+
+	def __call__(self):
+	    parent = self._cw.entity_from_eid(self.eidto)
+	    child = self._cw.entity_from_eid(self.eidfrom)
+	    if child.visibility == 'parent':
+		child.set_attributes(visibility=parent.visibility)
+
+Notice:
+
+* hooks are application objects, hence have selectors that should match entity or
+  relation types to which the hook applies. To match a relation type, we use the
+  hook specific `match_rtype` selector.
+
+* usage of `set_operation`: instead of adding an operation for each added entity,
+  set_operation allows to create a single one and to store entity's eids to be
+  processed in session's transaction data. This is a good pratice to avoid heavy
+  operations manipulation cost when creating a lot of entities in the same
+  transaction.
+
+* the `precommit_event` method of the operation will be called at transaction's
+  commit time.
+
+* in a hook, `self._cw` is the repository session, not a web request as usually
+  in views
+
+* according to hook's event, you have access to different attributes on the hook
+  instance. Here:
+
+  - `self.entity` is the newly added entity on 'after_add_entity' events
+
+  - `self.eidfrom` / `self.eidto` are the eid of the subject / object entity on
+    'after_add_relatiohn' events (you may also get the relation type using
+    `self.rtype`)
+
+The `parent` visibility value is used to tell "propagate using parent security"
+because we want that attribute to be required, so we can't use None value else
+we'll get an error before we get any chance to propagate...
+
+Now, we also want to propagate the `may_be_read_by` relation. Fortunately,
+CubicWeb provides some base hook classes for such things, so we only have to add
+the following code to *hooks.py*:
+
+.. sourcecode:: python
+
+    # relations where the "parent" entity is the subject
+    S_RELS = set()
+    # relations where the "parent" entity is the object
+    O_RELS = set(('filed_under', 'comments',))
+
+    class AddEntitySecurityPropagationHook(hook.PropagateSubjectRelationHook):
+	"""propagate permissions when new entity are added"""
+	__regid__ = 'sytweb.addentity_security_propagation'
+	__select__ = (hook.PropagateSubjectRelationHook.__select__
+		      & hook.match_rtype_sets(S_RELS, O_RELS))
+	main_rtype = 'may_be_read_by'
+	subject_relations = S_RELS
+	object_relations = O_RELS
+
+    class AddPermissionSecurityPropagationHook(hook.PropagateSubjectRelationAddHook):
+	"""propagate permissions when new entity are added"""
+	__regid__ = 'sytweb.addperm_security_propagation'
+	__select__ = (hook.PropagateSubjectRelationAddHook.__select__
+		      & hook.match_rtype('may_be_read_by',))
+	subject_relations = S_RELS
+	object_relations = O_RELS
+
+    class DelPermissionSecurityPropagationHook(hook.PropagateSubjectRelationDelHook):
+	__regid__ = 'sytweb.delperm_security_propagation'
+	__select__ = (hook.PropagateSubjectRelationDelHook.__select__
+		      & hook.match_rtype('may_be_read_by',))
+	subject_relations = S_RELS
+	object_relations = O_RELS
+
+* the `AddEntitySecurityPropagationHook` will propagate the relation
+  when `filed_under` or `comments` relations are added
+
+  - the `S_RELS` and `O_RELS` set as well as the `match_rtype_sets` selector are
+    used here so that if my cube is used by another one, it'll be able to
+    configure security propagation by simply adding relation to one of the two
+    sets.
+
+* the two others will propagate permissions changes on parent entities to
+  children entities
+
+
+.. _adv_tuto_tesing_security:
+
+Step 3: testing our security
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Security is tricky. Writing some tests for it is a very good idea. You should
+even write them first, as Test Driven Development recommends!
+
+Here is a small test case that will check the basis of our security
+model, in *test/unittest_sytweb.py*:
+
+.. sourcecode:: python
+
+    from cubicweb.devtools.testlib import CubicWebTC
+    from cubicweb import Binary
+
+    class SecurityTC(CubicWebTC):
+
+	def test_visibility_propagation(self):
+	    # create a user for later security checks
+	    toto = self.create_user('toto')
+	    # init some data using the default manager connection
+	    req = self.request()
+	    folder = req.create_entity('Folder',
+				       name=u'restricted',
+				       visibility=u'restricted')
+	    photo1 = req.create_entity('Image',
+				       data_name=u'photo1.jpg',
+				       data=Binary('xxx'),
+				       filed_under=folder)
+	    self.commit()
+	    photo1.clear_all_caches() # good practice, avoid request cache effects
+	    # visibility propagation
+	    self.assertEquals(photo1.visibility, 'restricted')
+	    # unless explicitly specified
+	    photo2 = req.create_entity('Image',
+				       data_name=u'photo2.jpg',
+				       data=Binary('xxx'),
+				       visibility=u'public',
+				       filed_under=folder)
+	    self.commit()
+	    self.assertEquals(photo2.visibility, 'public')
+	    # test security
+	    self.login('toto')
+	    req = self.request()
+	    self.assertEquals(len(req.execute('Image X')), 1) # only the public one
+	    self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
+	    # may_be_read_by propagation
+	    self.restore_connection()
+	    folder.set_relations(may_be_read_by=toto)
+	    self.commit()
+	    photo1.clear_all_caches()
+	    self.failUnless(photo1.may_be_read_by)
+	    # test security with permissions
+	    self.login('toto')
+	    req = self.request()
+	    self.assertEquals(len(req.execute('Image X')), 2) # now toto has access to photo2
+	    self.assertEquals(len(req.execute('Folder X')), 1) # and to restricted folder
+
+    if __name__ == '__main__':
+	from logilab.common.testlib import unittest_main
+	unittest_main()
+
+It's not complete, but show most things you'll want to do in tests: adding some
+content, creating users and connecting as them in the test, etc...
+
+To run it type:
+
+.. sourcecode:: bash
+
+    $ pytest unittest_sytweb.py
+    ========================  unittest_sytweb.py  ========================
+    -> creating tables [....................]
+    -> inserting default user and default groups.
+    -> storing the schema in the database [....................]
+    -> database for instance data initialized.
+    .
+    ----------------------------------------------------------------------
+    Ran 1 test in 22.547s
+
+    OK
+
+
+The first execution is taking time, since it creates a sqlite database for the
+test instance. The second one will be much quicker:
+
+.. sourcecode:: bash
+    
+    $ pytest unittest_sytweb.py
+    ========================  unittest_sytweb.py  ========================
+    .
+    ----------------------------------------------------------------------
+    Ran 1 test in 2.662s
+
+    OK
+
+If you do some changes in your schema, you'll have to force regeneration of that
+database. You do that by removing the tmpdb files before running the test: ::
+
+    $ rm data/tmpdb*
+
+
+.. Note::
+  pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
+
+.. _`logilab-common`: http://www.logilab.org/project/logilab-common
+
+.. _adv_tuto_migration_script:
+
+Step 4: writing the migration script and migrating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Prior to those changes, I  created an instance, feeded it with some data, so I
+don't want to create a new one, but to migrate the existing one. Let's see how to
+do that.
+
+Migration commands should be put in the cube's *migration* directory, in a
+file named file:`<X.Y.Z>_Any.py` ('Any' being there mostly for historical reason).
+
+Here I'll create a *migration/0.2.0_Any.py* file containing the following
+instructions:
+
+.. sourcecode:: python
+
+  add_relation_type('may_be_read_by')
+  add_relation_type('visibility')
+  sync_schema_props_perms()
+
+Then I update the version number in cube's *__pkginfo__.py* to 0.2.0. And
+that's it! Those instructions will:
+
+* update the instance's schema by adding our two new relations and update the
+  underlying database tables accordingly (the two first instructions)
+
+* update schema's permissions definition (the last instruction)
+
+
+To migrate my instance I simply type::
+
+   cubicweb-ctl upgrade sytweb
+
+You'll then be asked some questions to do the migration step by step. You should say
+YES when it asks if a backup of your database should be done, so you can get back
+to initial state if anything goes wrong...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part03_bfss.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,134 @@
+Storing images on the file-system
+---------------------------------
+
+Step 1: configuring the BytesFileSystem storage
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To avoid cluttering my database, and to ease file manipulation, I don't want
+them to be stored in the database. I want to be able create File/Image entities
+for some files on the server file system, where those file will be accessed to
+get entities data. To do so I've to set a custom :class:`BytesFileSystemStorage` storage
+for the File/Image 'data' attribute, which hold the actual file's content.
+
+Since the function to register a custom storage needs to have a repository
+instance as first argument, we've to call it in a server startup hook. So I added
+in `cubes/sytweb/hooks.py` :
+
+.. sourcecode:: python
+
+    from os import makedirs
+    from os.path import join, exists
+
+    from cubicweb.server import hook
+    from cubicweb.server.sources import storage
+
+    class ServerStartupHook(hook.Hook):
+	__regid__ = 'sytweb.serverstartup'
+	events = ('server_startup', 'server_maintenance')
+
+	def __call__(self):
+	    bfssdir = join(self.repo.config.appdatahome, 'bfss')
+	    if not exists(bfssdir):
+		makedirs(bfssdir)
+		print 'created', bfssdir
+	    storage = storages.BytesFileSystemStorage(bfssdir)
+	    set_attribute_storage(self.repo, 'File', 'data', storage)
+	    set_attribute_storage(self.repo, 'Image', 'data', storage)
+
+.. Note::
+
+  * how we built the hook's registry identifier (_`_regid__`): you can introduce
+    'namespaces' by using there python module like naming identifiers. This is
+    especially import for hooks where you usually want a new custom hook, not
+    overriding / specializing an existant one, but the concept may be applied to
+    any application objects
+
+  * we catch two events here: "server_startup" and "server_maintenance". The first
+    is called on regular repository startup (eg, as a server), the other for
+    maintenance task such as shell or upgrade. In both cases, we need to have
+    the storage set, else we'll be in trouble...
+
+  * the path given to the storage is the place where file added through the ui
+    (or in the database before migration) will be located
+
+  * be ware that by doing this, you can't anymore write queries that will try to
+    restrict on File and Image `data` attribute. Hopefuly we don't do that usually
+    on file's content or more generally on attributes for the Bytes type
+
+Now, if you've already added some photos through the web ui, you'll have to
+migrate existing data so file's content will be stored on the file-system instead
+of the database. There is a migration command to do so, let's run it in the
+cubicweb shell (in actual life, you'd have to put it in a migration script as we
+seen last time):
+
+::
+
+   $ cubicweb-ctl shell sytweb
+    entering the migration python shell
+    just type migration commands or arbitrary python code and type ENTER to execute it
+    type "exit" or Ctrl-D to quit the shell and resume operation
+    >>> storage_changed('File', 'data')
+    [........................]
+    >>> storage_changed('Image', 'data')
+    [........................]
+
+
+That's it. Now, file added through the web ui will have their content stored on
+the file-system, and you'll also be able to import files from the file-system as
+explained in the next part.
+
+Step 2: importing some data into the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Hey, we start to have some nice features, let give us a try on this new web
+site. For instance if I have a 'photos/201005WePyrenees' containing pictures for
+a particular event, I can import it to my web site by typing ::
+
+  $ cubicweb-ctl fsimport -F sytweb photos/201005WePyrenees/
+  ** importing directory /home/syt/photos/201005WePyrenees
+    importing IMG_8314.JPG
+    importing IMG_8274.JPG
+    importing IMG_8286.JPG
+    importing IMG_8308.JPG
+    importing IMG_8304.JPG
+
+.. Note::
+  The -F option tell that folders should be mapped, hence my photos will be
+  all under a Folder entity corresponding to the file-system folder.
+
+Let's take a look at the web ui:
+
+.. image:: ../../images/tutos-photowebsite_ui1.png
+
+Nothing different, I can't see the new folder... But remember our security model!
+By default, files are only accessible to authenticated users, and I'm looking at
+the site as anonymous, e.g. not authenticated. If I login, I can now see:
+
+.. image:: ../../images/tutos-photowebsite_ui2.png
+
+Yeah, it's there! You can also notice that I can see some entities as well as
+folders and images the anonymous user can't. It just works **everywhere in the
+ui** since it's handled at the repository level, thanks to our security model.
+
+Now if I click on the newly inserted folder, I can see
+
+.. image:: ../../images/tutos-photowebsite_ui3.png
+
+Great! There is even my pictures in the folder. I can know give to this folder a
+nicer name (provided I don't intend to import from it anymore, else already
+imported photos will be reimported), change permissions, title for some pictures,
+etc... Having a good content is much more difficult than having a good web site
+;)
+
+
+Conclusion
+~~~~~~~~~~
+
+We started to see here an advanced feature of our repository: the ability
+to store some parts of our data-model into a custom storage, outside the
+database. There is currently only the :class:`BytesFileSystemStorage` available,
+but you can expect to see more coming in a near future (our write your own!).
+
+Also, we can know start to feed our web-site with some nice pictures!
+The site isn't perfect (far from it actually) but it's usable, and we can
+now start using it and improve it on the way. The Incremental Cubic Way :)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,453 @@
+Let's make it more user friendly
+================================
+
+
+Step 0: updating code to CubicWeb 3.9 / cubicweb-file 1.9
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+CubicWeb 3.9 brings `several improvments`_ that we'll want to use, and the 1.9
+version of the file cube has a major change: the `Image` type has been dropped in
+favor of an `IImage` adapter that makes code globally much cleaner (though we wont
+see that much this here). So the first thing to do is to upgrade our cube to the
+3.9 API. As CubicWeb releases are mostly backward compatible, this is not
+mandatory but it's easier to follow change as they come than having a huge
+upgrade to do at some point. Also, this remove deprecation warnings which are a
+bit tedious...
+
+Also, since we've only a few lines of code yet, this is quite easy to upgrade.
+Actually the main thing we've to do is to upgrade our schema, to remove occurences
+of the `Image` type or replace them by the `File` type. Here is the (striped) diff:
+
+.. sourcecode:: diff
+
+     class comments(RelationDefinition):
+	 subject = 'Comment'
+    -    object = ('File', 'Image')
+    +    object = 'File'
+	 cardinality = '1*'
+	 composite = 'object'
+
+     class tags(RelationDefinition):
+	 subject = 'Tag'
+    -    object = ('File', 'Image')
+    +    object = 'File'
+
+     class displayed_on(RelationDefinition):
+	 subject = 'Person'
+    -    object = 'Image'
+    +    object = 'File'
+
+     class situated_in(RelationDefinition):
+    -    subject = 'Image'
+    +    subject = 'File'
+	 object = 'Zone'
+
+     class filed_under(RelationDefinition):
+    -    subject = ('File', 'Image')
+    +    subject = 'File'
+	 object = 'Folder'
+
+     class visibility(RelationDefinition):
+    -    subject = ('Folder', 'File', 'Image', 'Comment')
+    +    subject = ('Folder', 'File', 'Comment')
+	 object = 'String'
+	 constraints = [StaticVocabularyConstraint(('public', 'authenticated',
+						    'restricted', 'parent'))]
+
+     class may_be_readen_by(RelationDefinition):
+    -    subject = ('Folder', 'File', 'Image', 'Comment',)
+    +    subject = ('Folder', 'File', 'Comment',)
+	 object = 'CWUser'
+
+
+    -from cubes.file.schema import File, Image
+    +from cubes.file.schema import File
+
+     File.__permissions__ = VISIBILITY_PERMISSIONS
+    -Image.__permissions__ = VISIBILITY_PERMISSIONS
+
+Now, let's record that we depends on the versions in the __pkginfo__ file.  As
+`3.8`_ simplify this file, we can merge `__depends_cubes__` (as introduced if the
+`first blog of this series`_) with `__depends__` to get the following result:
+
+.. sourcecode:: python
+
+    __depends__ = {'cubicweb': '>= 3.9.0',
+		   'cubicweb-file': '>= 1.9.0',
+		   'cubicweb-folder': None,
+		   'cubicweb-person': None,
+		   'cubicweb-zone': None,
+		   'cubicweb-comment': None,
+		   'cubicweb-tag': None,
+		   }
+
+If your cube is packaged for debian, it's a good idea to update the
+`debian/control` file at the same time, so you won't forget it.
+
+That's it for the API update, CubicWeb, cubicweb-file will handle other stuff for
+us. Easy, no?
+
+We can now start some more funny stuff...
+
+
+Step 1: let's improve site's usability for our visitors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first thing I've noticed is that people to whom I send links to photos with
+some login/password authentication get lost, because they don't grasp they have
+to login by clicking on the 'authenticate' link. That's much probably because
+they only get a 404 when trying to access an unauthorized folder, and the site
+doesn't make clear that 1. you're not authenticated, 2. you could get more
+content by authenticating yourself.
+
+So, to improve this situation, I decided that I should:
+
+* make a login box appears for anonymous, so they see at a first glance a place
+  to put the login / password information I provided
+
+* customize the 404 page, proposing to login to anonymous.
+
+Here is the code, samples from my cube's `views.py` file:
+
+.. sourcecode:: python
+
+    from cubicweb.selectors import is_instance
+    from cubicweb.web import component
+    from cubicweb.web.views import error
+
+    class FourOhFour(error.FourOhFour):
+	__select__ = error.FourOhFour.__select__ & anonymous_user()
+
+	def call(self):
+	    self.w(u"<h1>%s</h1>" % self._cw._('this resource does not exist'))
+	    self.w(u"<p>%s</p>" % self._cw._('have you tried to login?'))
+
+
+    class LoginBox(component.CtxComponent):
+	"""display a box containing links to all startup views"""
+	__regid__ = 'sytweb.loginbox'
+	__select__ = component.CtxComponent.__select__ & anonymous_user()
+
+	title = _('Authenticate yourself')
+	order = 70
+
+	def render_body(self, w):
+	    cw = self._cw
+	    form = cw.vreg['forms'].select('logform', cw)
+	    form.render(w=w, table_class='', display_progress_div=False)
+
+The first class provides a new specific implementation of the default page you
+get on 404 error, to display an adapted message to anonymous user.
+
+.. Note::
+
+  Thanks to the selection mecanism, it will be selected for anoymous user,
+  since the additional `anonymous_user()` selector gives it a higher score than
+  the default, and not for authenticated since this selector will return 0 in
+  such case (hence the object won't be selectable)
+
+The second class defines a simple box, that will be displayed by default with
+boxes in the left column, thanks to default :class:`component.CtxComponent`
+selector. The HTML is written to match default CubicWeb boxes style. The code
+fetch the actual login form and render it.
+
+
+.. figure:: ../../images/tutos-photowebsite_login-box.png
+   :alt: login box / 404 screenshot
+
+   The login box and the custom 404 page for an anonymous visitor (translated in french)
+
+
+Step 2: providing a custom index page
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Another thing we can easily do to improve the site is... A nicer index page
+(e.g. the first page you get when accessing the web site)! The default one is
+quite intimidating (that should change in a near future). I will provide a much
+simpler index page that simply list available folders (e.g. photo albums in that
+site).
+
+.. sourcecode:: python
+
+    from cubicweb.web.views import startup
+
+    class IndexView(startup.IndexView):
+	def call(self, **kwargs):
+	    self.w(u'<div>\n')
+	    if self._cw.cnx.anonymous_connection:
+		self.w(u'<h4>%s</h4>\n' % self._cw._('Public Albums'))
+	    else:
+		self.w(u'<h4>%s</h4>\n' % self._cw._('Albums for %s') % self._cw.user.login)
+	    self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
+	    self.w(u'</div>\n')
+
+    def registration_callback(vreg):
+	vreg.register_all(globals().values(), __name__, (IndexView,))
+	vreg.register_and_replace(IndexView, startup.IndexView)
+
+As you can see, we override the default index view found in
+`cubicweb.web.views.startup`, geting back nothing but its identifier and selector
+since we override the top level view's `call` method.
+
+.. Note::
+
+  in that case, we want our index view to **replace** the existing one. To do so
+  we've to implements the `registration_callback` function, in which we tell to
+  register everything in the module *but* our IndexView, then we register it
+  instead of the former index view.
+
+Also, we added a title that tries to make it more evident that the visitor is
+authenticated, or not. Hopefuly people will get it now!
+
+
+.. figure:: ../../images/tutos-photowebsite_index-before.png
+   :alt: default index page screenshot
+
+   The default index page
+
+.. figure:: ../../images/tutos-photowebsite_index-after.png
+   :alt: new index page screenshot
+
+   Our simpler, less intimidating, index page (still translated in french)
+
+
+Step 3: more navigation improvments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are still a few problems I want to solve...
+
+* Images in a folder are displayed in a somewhat random order. I would like to
+  have them ordered by file's name (which will usually, inside a given folder,
+  also result ordering photo by their date and time)
+
+* When clicking a photo from an album view, you've to get back to the gallery
+  view to go to the next photo. This is pretty annoying...
+
+* Also, when viewing an image, there is no clue about the folder to which this
+  image belongs to.
+
+I will first try to explain the ordering problem. By default, when accessing related
+entities by using the ORM's API, you should get them ordered according to the target's
+class `fetch_order`. If we take a look at the file cube'schema, we can see:
+
+.. sourcecode:: python
+
+
+    class File(AnyEntity):
+	"""customized class for File entities"""
+	__regid__ = 'File'
+	fetch_attrs, fetch_order = fetch_config(['data_name', 'title'])
+
+By default, `fetch_config` will return a `fetch_order` method that will order on
+the first attribute in the list. So, we could expect to get files ordered by
+their name. But we don't.  What's up doc ?
+
+The problem is that files are related to folder using the `filed_under` relation.
+And that relation is ambiguous, eg it can lead to `File` entities, but also to
+`Folder` entities. In such case, since both entity types doesn't share the
+attribute on which we want to sort, we'll get linked entities sorted on a common
+attribute (usually `modification_date`).
+
+To fix this, we've to help the ORM. We'll do this in the method from the `ITree`
+folder's adapter, used in the folder's primary view to display the folder's
+content. Here's the code, that I've put in our cube's `entities.py` file, since
+it's more logical stuff than view stuff:
+
+.. sourcecode:: python
+
+    from cubes.folder import entities as folder
+
+    class FolderITreeAdapter(folder.FolderITreeAdapter):
+
+	def different_type_children(self, entities=True):
+	    rql = self.entity.cw_related_rql(self.tree_relation,
+					     self.parent_role, ('File',))
+	    rset = self._cw.execute(rql, {'x': self.entity.eid})
+	    if entities:
+		return list(rset.entities())
+	    return rset
+
+    def registration_callback(vreg):
+	vreg.register_and_replace(FolderITreeAdapter, folder.FolderITreeAdapter)
+
+As you can see, we simple inherit from the adapter defined in the `folder` cube,
+then we override the `different_type_children` method to give a clue to the ORM's
+`cw_related_rql` method, that is responsible to generate the rql to get entities
+related to the folder by the `filed_under` relation (the value of the
+`tree_relation` attribute).  The clue is that we only want to consider the `File`
+target entity type. By doing this, we remove the ambiguity and get back a RQL
+query that correctly order files by their `data_name` attribute.
+
+
+.. Note::
+
+    * Adapters have been introduced in CubicWeb 3.9 / cubicweb-folder 1.8.
+
+    * As seen earlier, we want to **replace** the folder's `ITree` adapter by our
+      implementation, hence the custom `registration_callback` method.
+
+
+Ouf. That one was tricky...
+
+Now the easier parts. Let's start by adding some links on the file's primary view
+to see the previous / next image in the same folder. CubicWeb's provide a
+component that do exactly that. To make it appears, one have to be adaptable to
+the `IPrevNext` interface. Here is the related code sample, extracted from our
+cube's `views.py` file:
+
+.. sourcecode:: python
+
+    from cubicweb.selectors import is_instance
+    from cubicweb.web.views import navigation
+
+
+    class FileIPrevNextAdapter(navigation.IPrevNextAdapter):
+	__select__ = is_instance('File')
+
+	def previous_entity(self):
+	    rset = self._cw.execute('File F ORDERBY FDN DESC LIMIT 1 WHERE '
+				    'X filed_under FOLDER, F filed_under FOLDER, '
+				    'F data_name FDN, X data_name > FDN, X eid %(x)s',
+				    {'x': self.entity.eid})
+	    if rset:
+		return rset.get_entity(0, 0)
+
+	def next_entity(self):
+	    rset = self._cw.execute('File F ORDERBY FDN ASC LIMIT 1 WHERE '
+				    'X filed_under FOLDER, F filed_under FOLDER, '
+				    'F data_name FDN, X data_name < FDN, X eid %(x)s',
+				    {'x': self.entity.eid})
+	    if rset:
+		return rset.get_entity(0, 0)
+
+
+The `IPrevNext` interface implemented by the adapter simply consist in the
+`previous_entity` / `next_entity` methods, that should respectivly return the
+previous / next entity or `None`. We make an RQL query to get files in the same
+folder, ordered similarly (eg by their `data_name` attribute). We set
+ascendant/descendant ordering and a strict comparison with current file's name
+(the "X" variable representing the current file).
+
+.. Note::
+
+    * Former `implements` selector should be replaced by one of `is_instance` /
+      `adaptable` selector with CubicWeb >= 3.9. In our case, `is_instance` to
+      tell our adapter is able to adapt `File` entities.
+
+Notice that this query supposes we wont have two files of the same name in the
+same folder, else things may go wrong. Fixing this is out of the scope of this
+blog. And as I would like to have at some point a smarter, context sensitive
+previous/next entity, I'll probably never fix this query (though if I had to, I
+would probably choosing to add a constraint in the schema so that we can't add
+two files of the same name in a folder).
+
+One more thing: by default, the component will be displayed below the content
+zone (the one with the white background). You can change this in the site's
+properties through the ui, but you can also change the default value in the code
+by modifying the `context` attribute of the component:
+
+.. sourcecode:: python
+
+    navigation.NextPrevNavigationComponent.context = 'navcontentbottom'
+
+.. Note::
+
+   `context` may be one of 'navtop', 'navbottom', 'navcontenttop' or
+   'navcontentbottom'; the first two being outside the main content zone, the two
+   others inside it.
+
+.. figure:: ../../images/tutos-photowebsite_prevnext.png
+   :alt: screenshot of the previous/next entity component
+
+   The previous/next entity component, at the bottom of the main content zone.
+
+Now, the only remaining stuff in my todo list is to see the file's folder. I'll use
+the standard breadcrumb component to do so. Similarly as what we've seen before, this
+component is controled by the :class:`IBreadCrumbs` interface, so we'll have to provide a custom
+adapter for `File` entity, telling the a file's parent entity is its folder:
+
+.. sourcecode:: python
+
+    from cubicweb.web.views import ibreadcrumbs
+
+    class FileIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
+	__select__ = is_instance('File')
+
+	def parent_entity(self):
+	    if self.entity.filed_under:
+		return self.entity.filed_under[0]
+
+In that case, we simply use attribute notation provided by the ORM to get the
+folder in which the current file (e.g. `self.entity`) is located.
+
+.. Note::
+   The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
+   :class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
+   at the value returned by its `parent_entity` method. It also provides a
+   default implementation for this method for entities adapting to the `ITree`
+   interface, but as our `File` doesn't, we've to provide a custom adapter.
+
+.. figure:: ../../images/tutos-photowebsite_breadcrumbs.png
+   :alt: screenshot of the breadcrumb component
+
+   The breadcrumb component when on a file entity, now displaying parent folder.
+
+
+Step 4: preparing the release and migrating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Now that greatly enhanced our cube, it's time to release it to upgrade production site.
+I'll probably detail that process later, but I currently simply transfer the new code
+to the server running the web site.
+
+However, I've still today some step to respect to get things done properly...
+
+First, as I've added some translatable string, I've to run: ::
+
+  $ cubicweb-ctl i18ncube sytweb
+
+To update the cube's gettext catalogs (the '.po' files under the cube's `i18n`
+directory). Once the above command is executed, I'll then update translations.
+
+To see if everything is ok on my test instance, I do: ::
+
+  $ cubicweb-ctl i18ninstance sytweb
+  $ cubicweb-ctl start -D sytweb
+
+The first command compile i18n catalogs (e.g. generates '.mo' files) for my test
+instance. The second command start it in debug mode, so I can open my browser and
+navigate through the web site to see if everything is ok...
+
+.. Note::
+   In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
+   in the two other, it refers to the **instance** (if you can't see the
+   difference, reread CubicWeb's concept chapter !).
+
+
+Once I've checked it's ok, I simply have to bump the version number in the
+`__pkginfo__` module to trigger a migration once I'll have updated the code on
+the production site. I can check then check the migration is also going fine, by
+first restoring a dump from the production site, then upgrading my test instance.
+
+To generate a dump from the production site: ::
+
+  $ cubicweb-ctl db-dump sytweb
+  pg_dump -Fc --username=syt --no-owner --file /home/syt/etc/cubicweb.d/sytweb/backup/tmpYIN0YI/system sytweb
+  -> backup file /home/syt/etc/cubicweb.d/sytweb/backup/sytweb-2010-07-13_10-22-40.tar.gz
+
+I can now get back the dump file ('sytweb-2010-07-13_10-22-40.tar.gz') to my test
+machine (using `scp` for instance) to restore it and start migration: ::
+
+  $ cubicweb-ctl db-restore sytweb sytweb-2010-07-13_10-22-40.tar.gz
+  $ cubicweb-ctl upgrade sytweb
+
+You'll have to answer some questions, as we've seen in `an earlier post`_.
+
+Now that everything is tested, I can transfer the new code to the production
+server, `apt-get upgrade` cubicweb 3.9 and its dependencies, and eventually
+upgrade the production instance.
+
+
+.. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
+.. _`3.8`: http://www.cubicweb.org/blogentry/917107
+.. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
+.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,375 @@
+Building my photos web site with |cubicweb| part V: let's make it even more user friendly
+=========================================================================================
+
+We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb
+
+Step 1: tired of the default look?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+OK... Now our site has its most desired features. But... I would like to make it look
+somewhat like *my* website. It is not www.cubicweb.org after all. Let's tackle this
+first!
+
+The first thing we can to is to change the logo. There are various way to achieve
+this. The easiest way is to put a :file:`logo.png` file into the cube's :file:`data`
+directory. As data files are looked at according to cubes order (CubicWeb
+resources coming last), that file will be selected instead of CubicWeb's one.
+
+.. Note::
+   As the location for static resources are cached, you'll have to restart
+   your instance for this to be taken into account.
+
+Though there are some cases where you don't want to use a :file:`logo.png` file.
+For instance if it's a JPEG file. You can still change the logo by defining in
+the cube's :file:`uiprops.py` file:
+
+.. sourcecode:: python
+
+   LOGO = data('logo.jpg')
+
+The uiprops machinery has been introduced in `CubicWeb 3.9`_. It is used to define
+some static file resources, such as the logo, default Javascript / CSS files, as
+well as CSS properties (we'll see that later).
+
+.. Note::
+   This file is imported specifically by |cubicweb|, with a predefined name space,
+   containing for instance the `data` function, telling the file is somewhere
+   in a cube or CubicWeb's data directory.
+
+   One side effect of this is that it can't be imported as a regular python
+   module.
+
+The nice thing is that in debug mode, change to a :file:`uiprops.py` file are detected
+and then automatically reloaded.
+
+Now, as it's a photos web-site, I would like to have a photo of mine as background...
+After some trials I won't detail here, I've found a working recipe explained `here`_.
+All I've to do is to override some stuff of the default CubicWeb user interface to
+apply it as explained.
+
+The first thing to to get the ``<img/>`` tag as first element after the
+``<body>`` tag.  If you know a way to avoid this by simply specifying the image
+in the CSS, tell me!  The easiest way to do so is to override the
+:class:`HTMLPageHeader` view, since that's the one that is directly called once
+the ``<body>`` has been written. How did I find this?  By looking in the
+:mod:`cubiweb.web.views.basetemplates` module, since I know that global page
+layouts sits there. I could also have grep the "body" tag in
+:mod:`cubicweb.web.views`... Finding this was the hardest part. Now all I need is
+to customize it to write that ``img`` tag, as below:
+
+.. sourcecode:: python
+
+    class HTMLPageHeader(basetemplates.HTMLPageHeader):
+	# override this since it's the easier way to have our bg image
+	# as the first element following <body>
+	def call(self, **kwargs):
+            self.w(u'<img id="bg-image" src="%sbackground.jpg" alt="background image"/>'
+                   % self._cw.datadir_url)
+	    super(HTMLPageHeader, self).call(**kwargs)
+
+
+    def registration_callback(vreg):
+	vreg.register_all(globals().values(), __name__, (HTMLPageHeader))
+	vreg.register_and_replace(HTMLPageHeader, basetemplates.HTMLPageHeader)
+
+
+As you may have guessed, my background image is in a :file:`background.jpg` file
+in the cube's :file:`data` directory, but there are still some things to explain
+to newcomers here:
+
+* The :meth:`call` method is there the main access point of the view. It's called by
+  the view's :meth:`render` method. It is not the only access point for a view, but
+  this will be detailed later.
+
+* Calling `self.w` writes something to the output stream. Except for binary views
+  (which do not generate text), it *must* be passed an Unicode string.
+
+* The proper way to get a file in :file:`data` directory is to use the `datadir_url`
+  attribute of the incoming request (e.g. `self._cw`).
+
+I won't explain again the :func:`registration_callback` stuff, you should understand it
+now!  If not, go back to previous posts in the series :)
+
+Fine. Now all I've to do is to add a bit of CSS to get it to behave nicely (which
+is not the case at all for now). I'll put all this in a :file:`cubes.sytweb.css`
+file, stored as usual in our :file:`data` directory:
+
+.. sourcecode:: css
+
+
+    /* fixed full screen background image
+     * as explained on http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
+     *
+     * syt update: set z-index=0 on the img instead of z-index=1 on div#page & co to
+     * avoid pb with the user actions menu
+     */
+    img#bg-image {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 0;
+    }
+
+    div#page, table#header, div#footer {
+	background: transparent;
+	position: relative;
+    }
+
+    /* add some space around the logo
+     */
+    img#logo {
+	padding: 5px 15px 0px 15px;
+    }
+
+    /* more dark font for metadata to have a chance to see them with the background
+     *  image
+     */
+    div.metadata {
+	color: black;
+    }
+
+You can see here stuff explained in the cited page, with only a slight modification
+explained in the comments, plus some additional rules to make things somewhat cleaner:
+
+* a bit of padding around the logo
+
+* darker metadata which appears by default below the content (the white frame in the page)
+
+To get this CSS file used everywhere in the site, I have to modify the :file:`uiprops.py` file
+introduced above:
+
+.. sourcecode:: python
+
+   STYLESHEETS = sheet['STYLESHEETS'] + [data('cubes.sytweb.css')]
+
+.. Note::
+   `sheet` is another predefined variable containing values defined by
+   already process `:file:`uiprops.py`` file, notably the CubicWeb's one.
+
+Here we simply want our CSS in addition to CubicWeb's base CSS files, so we
+redefine the `STYLESHEETS` variable to existing CSS (accessed through the `sheet`
+variable) with our one added. I could also have done:
+
+.. sourcecode:: python
+
+   sheet['STYLESHEETS'].append(data('cubes.sytweb.css'))
+
+But this is less interesting since we don't see the overriding mechanism...
+
+At this point, the site should start looking good, the background image being
+resized to fit the screen.
+
+.. image:: ../../images/tutos-photowebsite_background-image.png
+
+The final touch: let's customize CubicWeb's CSS to get less orange... By simply adding
+
+.. sourcecode:: python
+
+  contextualBoxTitleBg = incontextBoxTitleBg = '#AAAAAA'
+
+and reloading the page we've just seen, we know have a nice greyed box instead of
+the orange one:
+
+.. image:: ../../images/tutos-photowebsite_grey-box.png
+
+This is because CubicWeb's CSS include some variables which are
+expanded by values defined in uiprops file. In our case we controlled the
+properties of the CSS `background` property of boxes with CSS class
+`contextualBoxTitleBg` and `incontextBoxTitleBg`.
+
+
+Step 2: configuring boxes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Boxes present to the user some ways to use the application. Let's first do a few
+user interface tweaks in our :file:`views.py` file:
+
+.. sourcecode:: python
+
+  from cubicweb.selectors import none_rset
+  from cubicweb.web.views import bookmark
+  from cubes.zone import views as zone
+  from cubes.tag import views as tag
+
+  # change bookmarks box selector so it's only displayed on startup views
+  bookmark.BookmarksBox.__select__ = bookmark.BookmarksBox.__select__ & none_rset()
+  # move zone box to the left instead of in the context frame and tweak its order
+  zone.ZoneBox.context = 'left'
+  zone.ZoneBox.order = 100
+  # move tags box to the left instead of in the context frame and tweak its order
+  tag.TagsBox.context = 'left'
+  tag.TagsBox.order = 102
+  # hide similarity box, not interested
+  tag.SimilarityBox.visible = False
+
+The idea is to move all boxes in the left column, so we get more space for the
+photos.  Now, serious things: I want a box similar to the tags box but to handle
+the `Person displayed_on File` relation. We can do this simply by adding a
+:class:`AjaxEditRelationCtxComponent` subclass to our views, as below:
+
+.. sourcecode:: python
+
+    from logilab.common.decorators import monkeypatch
+    from cubicweb import ValidationError
+    from cubicweb.web import uicfg, component
+    from cubicweb.web.views import basecontrollers
+
+    # hide displayed_on relation using uicfg since it will be displayed by the box below
+    uicfg.primaryview_section.tag_object_of(('*', 'displayed_on', '*'), 'hidden')
+
+    class PersonBox(component.AjaxEditRelationCtxComponent):
+	__regid__ = 'sytweb.displayed-on-box'
+	# box position
+	order = 101
+	context = 'left'
+	# define relation to be handled
+	rtype = 'displayed_on'
+	role = 'object'
+	target_etype = 'Person'
+	# messages
+	added_msg = _('person has been added')
+	removed_msg = _('person has been removed')
+	# bind to js_* methods of the json controller
+	fname_vocabulary = 'unrelated_persons'
+	fname_validate = 'link_to_person'
+	fname_remove = 'unlink_person'
+
+
+    @monkeypatch(basecontrollers.JSonController)
+    @basecontrollers.jsonize
+    def js_unrelated_persons(self, eid):
+	"""return tag unrelated to an entity"""
+	rql = "Any F + ' ' + S WHERE P surname S, P firstname F, X eid %(x)s, NOT P displayed_on X"
+	return [name for (name,) in self._cw.execute(rql, {'x' : eid})]
+
+
+    @monkeypatch(basecontrollers.JSonController)
+    def js_link_to_person(self, eid, people):
+	req = self._cw
+	for name in people:
+	    name = name.strip().title()
+	    if not name:
+		continue
+	    try:
+		firstname, surname = name.split(None, 1)
+	    except:
+		raise ValidationError(eid, {('displayed_on', 'object'): 'provide <first name> <surname>'})
+	    rset = req.execute('Person P WHERE '
+			       'P firstname %(firstname)s, P surname %(surname)s',
+			       locals())
+	    if rset:
+		person = rset.get_entity(0, 0)
+	    else:
+		person = req.create_entity('Person', firstname=firstname,
+						surname=surname)
+	    req.execute('SET P displayed_on X WHERE '
+			'P eid %(p)s, X eid %(x)s, NOT P displayed_on X',
+			{'p': person.eid, 'x' : eid})
+
+    @monkeypatch(basecontrollers.JSonController)
+    def js_unlink_person(self, eid, personeid):
+	self._cw.execute('DELETE P displayed_on X WHERE P eid %(p)s, X eid %(x)s',
+			 {'p': personeid, 'x': eid})
+
+
+You basically subclass to configure with some class attributes. The `fname_*`
+attributes give the name of methods that should be defined on the json control to
+make the AJAX part of the widget work: one to get the vocabulary, one to add a
+relation and another to delete a relation. These methods must start by a `js_`
+prefix and are added to the controller using the `@monkeypatch` decorator. In my
+case, the most complicated method is the one which adds a relation, since it
+tries to see if the person already exists, and else automatically create it,
+assuming the user entered "firstname surname".
+
+Let's see how it looks like on a file primary view:
+
+.. image:: ../../images/tutos-photowebsite_boxes.png
+
+Great, it's now as easy for me to link my pictures to people than to tag them.
+Also, visitors get a consistent display of these two pieces of information.
+
+.. Note::
+  The ui component system has been refactored in `CubicWeb 3.10`_, which also
+  introduced the :class:`AjaxEditRelationCtxComponent` class.
+
+
+Step 3: configuring facets
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The last feature we'll add today is facet configuration. If you access to the
+'/file' url, you'll see a set of 'facets' appearing in the left column. Facets
+provide an intuitive way to build a query incrementally, by proposing to the user
+various way to restrict the result set. For instance CubicWeb proposes a facet to
+restrict based on who created an entity; the tag cube proposes a facet to
+restrict based on tags; the zoe cube a facet to restrict based on geographical
+location, and so on. In that gist, I want to propose a facet to restrict based on
+the people displayed on the picture. To do so, there are various classes in the
+:mod:`cubicweb.web.facet` module which simply have to be configured using class
+attributes as we've done for the box. In our case, we'll define a subclass of
+:class:`RelationFacet`.
+
+.. Note::
+
+   Since that's ui stuff, we'll continue to add code below to our
+   :file:`views.py` file. Though we begin to have a lot of various code their, so
+   it's may be a good time to split our views module into submodules of a `view`
+   package. In our case of a simple application (glue) cube, we could start using
+   for instance the layout below: ::
+
+     views/__init__.py   # uicfg configuration, facets
+     views/layout.py     # header/footer/background stuff
+     views/components.py # boxes, adapters
+     views/pages.py      # index view, 404 view
+
+.. sourcecode:: python
+
+    from cubicweb.web import facet
+
+    class DisplayedOnFacet(facet.RelationFacet):
+	__regid__ = 'displayed_on-facet'
+	# relation to be displayed
+	rtype = 'displayed_on'
+	role = 'object'
+	# view to use to display persons
+	label_vid = 'combobox'
+
+Let's say we also want to filter according to the `visibility` attribute. This is
+even simpler as we just have to derive from the :class:`AttributeFacet` class:
+
+.. sourcecode:: python
+
+    class VisibilityFacet(facet.AttributeFacet):
+	__regid__ = 'visibility-facet'
+	rtype = 'visibility'
+
+Now if I search for some pictures on my site, I get the following facets available:
+
+.. image:: ../../images/tutos-photowebsite_facets.png
+
+.. Note::
+
+  By default a facet must be applyable to every entity in the result set and
+  provide at leat two elements of vocabulary to be displayed (for instance you
+  won't see the `created_by` facet if the same user has created all
+  entities). This may explain why you don't see yours...
+
+
+Conclusion
+~~~~~~~~~~
+
+We started to see the power behind the infrastructure provided by the
+framework, both on the pure ui (CSS, Javascript) side and on the Python side
+(high level generic classes for components, including boxes and facets). We now
+have, with a few lines of code, a full-featured web site with a personalized look.
+
+Of course we'll probably want more as time goes, but we can now
+concentrate on making good pictures, publishing albums and sharing them with
+friends...
+
+
+
+.. _`CubicWeb 3.10`: http://www.cubicweb.org/blogentry/1330518
+.. _`CubicWeb 3.9`: http://www.cubicweb.org/blogentry/1179899
+.. _`here`: http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
--- a/doc/book/en/tutorials/base/customizing-the-application.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/tutorials/base/customizing-the-application.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -41,7 +41,7 @@
 
   cubicweb-ctl newcube --directory=~/src/cubes myblog
 
-.. Note:
+.. Note::
 
    We previously used `myblog` as the name of our *instance*. We're now creating
    a *cube* with the same name. Both are different things. We'll now try to
@@ -63,6 +63,7 @@
 
 where the ``None`` means we do not depends on a particular version of the cube.
 
+.. _TutosBaseCustomizingTheApplicationDataModel:
 
 Extending the data model
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -179,6 +180,8 @@
 You'll then be able to redefine each of them according to your needs
 and preferences. We'll now see how to do such thing.
 
+.. _TutosBaseCustomizingTheApplicationCustomViews:
+
 Defining your views
 ~~~~~~~~~~~~~~~~~~~
 
--- a/doc/book/en/tutorials/base/index.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/tutorials/base/index.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -16,7 +16,7 @@
 * discovering the default user interface
 * basic extending and customizing the look and feel of that application
 
-More advanced concepts are covered in :ref:`advanced_tutorial`.
+More advanced concepts are covered in :ref:`TutosPhotoWebSite`.
 
 
 .. _TutosBaseVocab:
--- a/doc/book/en/tutorials/tools/windmill.rst	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/book/en/tutorials/tools/windmill.rst	Mon Jan 24 17:02:38 2011 +0100
@@ -150,7 +150,7 @@
 
 If you want to change cubicweb test server parameters, you can check class
 variables from :class:`CubicWebServerConfig` or inherit it with overriding the
-:var:`configcls` attribute in :class:`CubicWebServerTC` ::
+:attr:`configcls` attribute in :class:`CubicWebServerTC` ::
 
 .. sourcecode:: python
 
--- a/doc/tools/pyjsrest.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/doc/tools/pyjsrest.py	Mon Jan 24 17:02:38 2011 +0100
@@ -4,6 +4,7 @@
 """
 from __future__ import with_statement
 
+import os.path as osp
 import sys, os, getopt, re
 
 def clean_comment(match):
@@ -71,34 +72,48 @@
     if rst_dir is None and len(args) != 1:
         rst_dir = 'apidocs'
     js_dir = opts.get('--jspath') or opts.get('-p')
-    if not os.path.exists(os.path.join(rst_dir)):
-        os.makedirs(os.path.join(rst_dir))
+    if not osp.exists(osp.join(rst_dir)):
+        os.makedirs(osp.join(rst_dir))
 
-    f_index = open(os.path.join(rst_dir, 'index.rst'), 'wb')
-    f_index.write('''
+    index = set()
+    for js_path, js_dirs, js_files in os.walk(js_dir):
+        rst_path = re.sub('%s%s*' % (js_dir, osp.sep), '', js_path)
+        for js_file in js_files:
+            if not js_file.endswith('.js'):
+                continue
+            if js_file in FILES_TO_IGNORE:
+                continue
+            if not osp.exists(osp.join(rst_dir, rst_path)):
+                os.makedirs(osp.join(rst_dir, rst_path))
+            rst_content =  extract_rest(js_path, js_file)
+            filename = osp.join(rst_path, js_file[:-3])
+            # add to index
+            index.add(filename)
+            # save rst file
+            with open(osp.join(rst_dir, filename) + '.rst', 'wb') as f_rst:
+                f_rst.write(rst_content)
+    stream = open(osp.join(rst_dir, 'index.rst'), 'w')
+    stream.write('''
 .. toctree::
     :maxdepth: 1
 
-'''
-)
-    for js_path, js_dirs, js_files in os.walk(js_dir):
-        rst_path = re.sub('%s%s*' % (js_dir, os.path.sep), '', js_path)
-        for js_file in js_files:
-            if not js_file.endswith('.js'):
-                continue
-            if not os.path.exists(os.path.join(rst_dir, rst_path)):
-                os.makedirs(os.path.join(rst_dir, rst_path))
-            rst_content =  extract_rest(js_path, js_file)
-            filename = os.path.join(rst_path, js_file[:-3])
-            # add to index
-            f_index.write('    %s\n' % filename)
-            # save rst file
-            with open(os.path.join(rst_dir, filename) + '.rst', 'wb') as f_rst:
-                f_rst.write(rst_content)
-    f_index.close()
+''')
+    # first write expected files in order
+    for fileid in INDEX_IN_ORDER:
+        try:
+            index.remove(fileid)
+        except:
+            raise Exception(
+        'Bad file id %s referenced in INDEX_IN_ORDER in %s, '
+        'fix this please' % (fileid, __file__))
+        stream.write('    %s\n' % fileid)
+    # append remaining, by alphabetical order
+    for fileid in sorted(index):
+        stream.write('    %s\n' % fileid)
+    stream.close()
 
 def extract_rest(js_dir, js_file):
-    js_filepath = os.path.join(js_dir, js_file)
+    js_filepath = osp.join(js_dir, js_file)
     filecontent = open(js_filepath, 'U').read()
     comments = get_doc_comments(filecontent)
     rst = rest_title(js_file, 0)
@@ -106,5 +121,50 @@
     rst += '\n\n'.join(comments)
     return rst
 
+INDEX_IN_ORDER = [
+    'cubicweb',
+    'cubicweb.python',
+    'cubicweb.htmlhelpers',
+    'cubicweb.ajax',
+
+    'cubicweb.lazy',
+    'cubicweb.tabs',
+    'cubicweb.ajax.box',
+    'cubicweb.facets',
+    'cubicweb.widgets',
+    'cubicweb.image',
+    'cubicweb.flot',
+    'cubicweb.calendar',
+    'cubicweb.preferences',
+    'cubicweb.edition',
+    'cubicweb.reledit',
+    'cubicweb.iprogress',
+    'cubicweb.rhythm',
+    'cubicweb.gmap',
+    'cubicweb.timeline-ext',
+]
+
+FILES_TO_IGNORE = set([
+    'jquery.js',
+    'jquery.treeview.js',
+    'jquery.json.js',
+    'jquery.tablesorter.js',
+    'jquery.timePicker.js',
+    'jquery.flot.js',
+    'jquery.corner.js',
+    'jquery.ui.js',
+    'ui.core.js',
+    'ui.tabs.js',
+    'ui.slider.js',
+    'excanvas.js',
+    'gmap.utility.labeledmarker.js',
+
+    'cubicweb.fckcwconfig.js',
+    'cubicweb.fckcwconfig-full.js',
+    'cubicweb.goa.js',
+    'cubicweb.compat.js',
+    'cubicweb.timeline-bundle.js',
+    ])
+
 if __name__ == '__main__':
     parse_js_files()
--- a/entities/adapters.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/entities/adapters.py	Mon Jan 24 17:02:38 2011 +0100
@@ -68,6 +68,7 @@
 
 
 class INotifiableAdapter(EntityAdapter):
+    __needs_bw_compat__ = True
     __regid__ = 'INotifiable'
     __select__ = is_instance('Any')
 
@@ -157,6 +158,7 @@
 
 class IDownloadableAdapter(EntityAdapter):
     """interface for downloadable entities"""
+    __needs_bw_compat__ = True
     __regid__ = 'IDownloadable'
     __select__ = implements(IDownloadable, warn=False) # XXX for bw compat, else should be abstract
 
@@ -208,6 +210,7 @@
     .. automethod: children_rql
     .. automethod: path
     """
+    __needs_bw_compat__ = True
     __regid__ = 'ITree'
     __select__ = implements(ITree, warn=False) # XXX for bw compat, else should be abstract
 
@@ -335,8 +338,8 @@
             for entity in child.cw_adapt_to('ITree').prefixiter(_done):
                 yield entity
 
+    @implements_adapter_compat('ITree')
     @cached
-    @implements_adapter_compat('ITree')
     def path(self):
         """Returns the list of eids from the root object to this object."""
         path = []
@@ -366,6 +369,7 @@
     You should at least override progress_info an in_progress methods on concret
     implementations.
     """
+    __needs_bw_compat__ = True
     __regid__ = 'IProgress'
     __select__ = implements(IProgress, warn=False) # XXX for bw compat, should be abstract
 
@@ -434,6 +438,7 @@
 
 
 class IMileStoneAdapter(IProgressAdapter):
+    __needs_bw_compat__ = True
     __regid__ = 'IMileStone'
     __select__ = implements(IMileStone, warn=False) # XXX for bw compat, should be abstract
 
--- a/entities/test/unittest_wfobjs.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/entities/test/unittest_wfobjs.py	Mon Jan 24 17:02:38 2011 +0100
@@ -18,20 +18,16 @@
 
 from __future__ import with_statement
 
+from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb import ValidationError
 from cubicweb.server.session import security_enabled
 
+
 def add_wf(self, etype, name=None, default=False):
     if name is None:
         name = etype
-    wf = self.execute('INSERT Workflow X: X name %(n)s', {'n': unicode(name)}).get_entity(0, 0)
-    self.execute('SET WF workflow_of ET WHERE WF eid %(wf)s, ET name %(et)s',
-                 {'wf': wf.eid, 'et': etype})
-    if default:
-        self.execute('SET ET default_workflow WF WHERE WF eid %(wf)s, ET name %(et)s',
-                     {'wf': wf.eid, 'et': etype})
-    return wf
+    return self.shell().add_workflow(name, etype, default=default,
+                                     ensure_workflowable=False)
 
 def parse_hist(wfhist):
     return [(ti.previous_state.name, ti.new_state.name,
--- a/etwist/twconfig.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/etwist/twconfig.py	Mon Jan 24 17:02:38 2011 +0100
@@ -23,7 +23,6 @@
 * the "all-in-one" configuration to get a web instance running in a twisted
   web server integrating a repository server in the same process (only available
   if the repository part of the software is installed
-
 """
 __docformat__ = "restructuredtext en"
 
@@ -31,8 +30,10 @@
 
 from logilab.common.configuration import Method
 
+from cubicweb.cwconfig import CONFIGURATIONS
 from cubicweb.web.webconfig import WebConfiguration, merge_options
 
+
 class TwistedConfiguration(WebConfiguration):
     """web instance (in a twisted web server) client of a RQL server"""
     name = 'twisted'
@@ -98,6 +99,9 @@
         from socket import gethostname
         return 'http://%s:%s/' % (self['host'] or gethostname(), self['port'] or 8080)
 
+
+CONFIGURATIONS.append(TwistedConfiguration)
+
 try:
     from cubicweb.server.serverconfig import ServerConfiguration
 
@@ -114,5 +118,8 @@
             """tell if pyro is activated for the in memory repository"""
             return self['pyro-server']
 
+
+    CONFIGURATIONS.append(AllInOneConfiguration)
+
 except ImportError:
     pass
--- a/i18n/de.po	Thu Jan 20 10:10:22 2011 +0100
+++ b/i18n/de.po	Mon Jan 24 17:02:38 2011 +0100
@@ -1360,6 +1360,9 @@
 msgid "click here to see created entity"
 msgstr "Hier klicken, um die angelegte Entität anzusehen"
 
+msgid "click here to see edited entity"
+msgstr ""
+
 msgid "click on the box to cancel the deletion"
 msgstr "Klicken Sie die Box an, um das Löschen rückgängig zu machen."
 
@@ -3405,13 +3408,9 @@
 msgid "relations deleted"
 msgstr "Relationen entfernt"
 
-msgctxt "CWAttribute"
+msgctxt "CWRType"
 msgid "relations_object"
-msgstr "eingeschränkt durch"
-
-msgctxt "CWRelation"
-msgid "relations_object"
-msgstr "eingeschränkt durch"
+msgstr "Relationen von"
 
 msgid "relations_object"
 msgstr "Relationen von"
@@ -4347,12 +4346,3 @@
 
 msgid "you should probably delete that property"
 msgstr "Sie sollten diese Eigenschaft wahrscheinlich löschen."
-
-#~ msgid "ctxcomponents_loggeduserlink"
-#~ msgstr "Nutzer-Link"
-
-#~ msgid "ctxcomponents_loggeduserlink_description"
-#~ msgstr ""
-#~ "for anonymous users, this is a link pointing to authentication form, for "
-#~ "logged in users, this is a link that makes a box appear and listing some "
-#~ "possible user actions"
--- a/i18n/en.po	Thu Jan 20 10:10:22 2011 +0100
+++ b/i18n/en.po	Mon Jan 24 17:02:38 2011 +0100
@@ -1312,6 +1312,9 @@
 msgid "click here to see created entity"
 msgstr ""
 
+msgid "click here to see edited entity"
+msgstr ""
+
 msgid "click on the box to cancel the deletion"
 msgstr ""
 
--- a/i18n/es.po	Thu Jan 20 10:10:22 2011 +0100
+++ b/i18n/es.po	Mon Jan 24 17:02:38 2011 +0100
@@ -1360,6 +1360,9 @@
 msgid "click here to see created entity"
 msgstr "Ver la entidad creada"
 
+msgid "click here to see edited entity"
+msgstr ""
+
 msgid "click on the box to cancel the deletion"
 msgstr "Seleccione la zona de edición para cancelar la eliminación"
 
@@ -3432,10 +3435,7 @@
 msgid "relations_object"
 msgstr ""
 
-msgctxt "CWRelation"
-msgid "relations_object"
-msgstr ""
-
+msgctxt "CWRType"
 msgid "relations_object"
 msgstr ""
 
--- a/i18n/fr.po	Thu Jan 20 10:10:22 2011 +0100
+++ b/i18n/fr.po	Mon Jan 24 17:02:38 2011 +0100
@@ -1362,6 +1362,9 @@
 msgid "click here to see created entity"
 msgstr "cliquez ici pour voir l'entité créée"
 
+msgid "click here to see edited entity"
+msgstr "cliquez ici pour voir l'entité modifiée"
+
 msgid "click on the box to cancel the deletion"
 msgstr "cliquez dans la zone d'édition pour annuler la suppression"
 
@@ -3433,13 +3436,9 @@
 msgid "relations deleted"
 msgstr "relations supprimées"
 
-msgctxt "CWAttribute"
+msgctxt "CWRType"
 msgid "relations_object"
-msgstr "contraint par"
-
-msgctxt "CWRelation"
-msgid "relations_object"
-msgstr "contraint par"
+msgstr "relations de"
 
 msgid "relations_object"
 msgstr "relations de"
--- a/misc/migration/3.10.7_Any.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/misc/migration/3.10.7_Any.py	Mon Jan 24 17:02:38 2011 +0100
@@ -1,8 +1,9 @@
-add_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRType')
-rql('SET C relations RT WHERE C relations RDEF, RDEF relation_type RT')
-commit()
-drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWAttribute')
-drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRelation')
+if not ('CWUniqueTogetherConstraint', 'CWRType') in schema['relations'].rdefs:
+    add_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRType')
+    rql('SET C relations RT WHERE C relations RDEF, RDEF relation_type RT')
+    commit()
+    drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWAttribute')
+    drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRelation')
 
 add_attribute('TrInfo', 'tr_count')
 sync_schema_props_perms('TrInfo')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.8_Any.py	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,1 @@
+sync_schema_props_perms('CWSource', syncprops=False)
--- a/rset.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/rset.py	Mon Jan 24 17:02:38 2011 +0100
@@ -386,6 +386,19 @@
             if self.rows[i][col] is not None:
                 yield self.get_entity(i, col)
 
+    def iter_rows_with_entities(self):
+        """ iterates over rows, and for each row
+        eids are converted to plain entities
+        """
+        for i, row in enumerate(self):
+            _row = []
+            for j, col in enumerate(row):
+                try:
+                    _row.append(self.get_entity(i, j) if col is not None else col)
+                except NotAnEntity:
+                    _row.append(col)
+            yield _row
+
     def complete_entity(self, row, col=0, skip_bytes=True):
         """short cut to get an completed entity instance for a particular
         row (all instance's attributes have been fetched)
@@ -401,9 +414,9 @@
 
         .. warning::
 
-          Due to the cache wrapping this function, you should NEVER
-          give row as a named parameter (i.e. rset.get_entity(req, 0)
-          is OK but rset.get_entity(row=0, req=req) isn't)
+          Due to the cache wrapping this function, you should NEVER give row as
+          a named parameter (i.e. `rset.get_entity(0, 1)` is OK but
+          `rset.get_entity(row=0, col=1)` isn't)
 
         :type row,col: int, int
         :param row,col:
@@ -421,11 +434,11 @@
         return self._build_entity(row, col)
 
     def _build_entity(self, row, col):
-        """internal method to get a single entity, returns a
-        partially initialized Entity instance.
+        """internal method to get a single entity, returns a partially
+        initialized Entity instance.
 
-        partially means that only attributes selected in the RQL
-        query will be directly assigned to the entity.
+        partially means that only attributes selected in the RQL query will be
+        directly assigned to the entity.
 
         :type row,col: int, int
         :param row,col:
--- a/schema.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/schema.py	Mon Jan 24 17:02:38 2011 +0100
@@ -550,7 +550,11 @@
     def add_entity_type(self, edef):
         edef.name = edef.name.encode()
         edef.name = bw_normalize_etype(edef.name)
-        assert re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name), repr(edef.name)
+        if not re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name):
+            raise BadSchemaDefinition(
+                '%r is not a valid name for an entity type. It should start '
+                'with an upper cased letter and be followed by at least a '
+                'lower cased letter' % edef.name)
         eschema = super(CubicWebSchema, self).add_entity_type(edef)
         if not eschema.final:
             # automatically add the eid relation to non final entity types
@@ -565,7 +569,11 @@
         return eschema
 
     def add_relation_type(self, rdef):
-        rdef.name = rdef.name.lower().encode()
+        if not rdef.name.islower():
+            raise BadSchemaDefinition(
+                '%r is not a valid name for a relation type. It should be '
+                'lower cased' % rdef.name)
+        rdef.name = rdef.name.encode()
         rschema = super(CubicWebSchema, self).add_relation_type(rdef)
         self._eid_index[rschema.eid] = rschema
         return rschema
--- a/schemas/base.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/schemas/base.py	Mon Jan 24 17:02:38 2011 +0100
@@ -242,6 +242,12 @@
 
 
 class CWSource(EntityType):
+    __permissions__ = {
+        'read':   ('managers', 'users', 'guests'),
+        'add':    ('managers',),
+        'update': ('managers',),
+        'delete': ('managers',),
+        }
     name = String(required=True, unique=True, maxsize=128,
                   description=_('name of the source'))
     type = String(required=True, maxsize=20, description=_('type of the source'))
--- a/selectors.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/selectors.py	Mon Jan 24 17:02:38 2011 +0100
@@ -817,6 +817,9 @@
     This is a very useful selector that will usually interest you since it
     allows a lot of things without having to write a specific selector.
 
+    The function can return arbitrary value which will be casted to an integer
+    value at the end.
+
     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
     lookup / score rules according to the input context.
     """
@@ -1142,6 +1145,11 @@
     must use 'X' variable to represent the context entity and may use 'U' to
     represent the request's user.
 
+    .. warning::
+        If simply testing value of some attribute/relation of context entity (X),
+        you should rather use the :class:`score_entity` selector which will
+        benefit from the ORM's request entities cache.
+
     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
     lookup / score rules according to the input context.
     """
@@ -1153,8 +1161,8 @@
             rql = 'Any COUNT(X) WHERE X eid %%(x)s, %s' % expression
         self.rql = rql
 
-    def __repr__(self):
-        return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+    def __str__(self):
+        return '%s(%r)' % (self.__class__.__name__, self.rql)
 
     def score(self, req, rset, row, col):
         try:
@@ -1430,6 +1438,10 @@
     @lltrace
     def __call__(self, cls, req, transition=None, **kwargs):
         # XXX check this is a transition that apply to the object?
+        if transition is None:
+            treid = req.form.get('treid', None)
+            if treid:
+                transition = req.entity_from_eid(treid)
         if transition is not None and getattr(transition, 'name', None) in self.expected:
             return 1
         return 0
--- a/server/hook.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/server/hook.py	Mon Jan 24 17:02:38 2011 +0100
@@ -212,7 +212,7 @@
 
 * ``integrity``, data integrity checking hooks
 
-* ``activeintegrity``, data integrity consistency hooks, that you should *never*
+* ``activeintegrity``, data integrity consistency hooks, that you should **never**
   want to disable
 
 * ``syncsession``, hooks synchronizing existing sessions
--- a/server/serverconfig.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/server/serverconfig.py	Mon Jan 24 17:02:38 2011 +0100
@@ -27,7 +27,7 @@
 from logilab.common.decorators import wproperty, cached
 
 from cubicweb.toolsutils import read_config, restrict_perms_to_user
-from cubicweb.cwconfig import CubicWebConfiguration, merge_options
+from cubicweb.cwconfig import CONFIGURATIONS, CubicWebConfiguration, merge_options
 from cubicweb.server import SOURCE_TYPES
 
 
@@ -346,3 +346,6 @@
         return ServerMigrationHelper(self, schema, interactive=interactive,
                                      cnx=cnx, repo=repo, connect=connect,
                                      verbosity=verbosity)
+
+
+CONFIGURATIONS.append(ServerConfiguration)
--- a/server/serverctl.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/server/serverctl.py	Mon Jan 24 17:02:38 2011 +0100
@@ -25,6 +25,7 @@
 import sys
 import os
 
+from logilab.common import nullobject
 from logilab.common.configuration import Configuration
 from logilab.common.shellutils import ASK
 
@@ -56,16 +57,14 @@
         else:
             print dbname,
     if dbhelper.users_support:
-        if not verbose or (not special_privs and source.get('db-user')):
+        if not special_privs and source.get('db-user'):
             user = source['db-user']
             if verbose:
                 print 'as', user
-            if source.get('db-password'):
-                password = source['db-password']
-            else:
-                password = getpass('password: ')
+            password = source.get('db-password')
         else:
-            print
+            if verbose:
+                print
             if special_privs:
                 print 'WARNING'
                 print ('the user will need the following special access rights '
@@ -74,8 +73,8 @@
                 print
             default_user = source.get('db-user', os.environ.get('USER', ''))
             user = raw_input('Connect as user ? [%r]: ' % default_user)
-            user = user or default_user
-            if user == source.get('db-user') and source.get('db-password'):
+            user = user.strip() or default_user
+            if user == source.get('db-user'):
                 password = source['db-password']
             else:
                 password = getpass('password: ')
@@ -108,22 +107,18 @@
         return source_cnx(source, system_db, special_privs=special_privs, verbose=verbose)
     return source_cnx(source, special_privs=special_privs, verbose=verbose)
 
-def _db_sys_cnx(source, what, db=None, user=None, verbose=True):
-    """return a connection on the RDMS system table (to create/drop a user
-    or a database
+def _db_sys_cnx(source, special_privs, verbose=True):
+    """return a connection on the RDMS system table (to create/drop a user or a
+    database)
     """
     import logilab.common as lgp
     from logilab.database import get_db_helper
     lgp.USE_MX_DATETIME = False
-    special_privs = ''
     driver = source['db-driver']
     helper = get_db_helper(driver)
-    if user is not None and helper.users_support:
-        special_privs += '%s USER' % what
-    if db is not None:
-        special_privs += ' %s DATABASE' % what
     # connect on the dbms system base to create our base
-    cnx = system_source_cnx(source, True, special_privs=special_privs, verbose=verbose)
+    cnx = system_source_cnx(source, True, special_privs=special_privs,
+                            verbose=verbose)
     # disable autocommit (isolation_level(1)) because DROP and
     # CREATE DATABASE can't be executed in a transaction
     try:
@@ -194,6 +189,16 @@
             print ('-> nevermind, you can do it later with '
                    '"cubicweb-ctl db-create %s".' % self.config.appid)
 
+ERROR = nullobject()
+
+def confirm_on_error_or_die(msg, func, *args, **kwargs):
+    try:
+        return func(*args, **kwargs)
+    except Exception, ex:
+        print 'ERROR', ex
+        if not ASK.confirm('An error occurred while %s. Continue anyway?' % msg):
+            raise ExecutionError(str(ex))
+    return ERROR
 
 class RepositoryDeleteHandler(CommandHandler):
     cmdname = 'delete'
@@ -207,19 +212,29 @@
         helper = get_db_helper(source['db-driver'])
         if ASK.confirm('Delete database %s ?' % dbname):
             if source['db-driver'] == 'sqlite':
-                os.unlink(source['db-name'])
+                if confirm_on_error_or_die(
+                    'deleting database file %s' % dbname,
+                    os.unlink, source['db-name']) is not ERROR:
+                    print '-> database %s dropped.' % dbname
                 return
             user = source['db-user'] or None
-            cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
+            cnx = confirm_on_error_or_die('connecting to database %s' % dbname,
+                                          _db_sys_cnx, source, 'DROP DATABASE', user=user)
+            if cnx is ERROR:
+                return
             cursor = cnx.cursor()
             try:
-                cursor.execute('DROP DATABASE %s' % dbname)
-                print '-> database %s dropped.' % dbname
+                if confirm_on_error_or_die(
+                    'dropping database %s' % dbname,
+                    cursor.execute, 'DROP DATABASE "%s"' % dbname) is not ERROR:
+                    print '-> database %s dropped.' % dbname
                 # XXX should check we are not connected as user
                 if user and helper.users_support and \
                        ASK.confirm('Delete user %s ?' % user, default_is_yes=False):
-                    cursor.execute('DROP USER %s' % user)
-                    print '-> user %s dropped.' % user
+                    if confirm_on_error_or_die(
+                        'dropping user %s' % user,
+                        cursor.execute, 'DROP USER %s' % user) is not ERROR:
+                        print '-> user %s dropped.' % user
                 cnx.commit()
             except:
                 cnx.rollback()
@@ -313,7 +328,7 @@
         elif self.config.create_db:
             print '\n'+underline_title('Creating the system database')
             # connect on the dbms system base to create our base
-            dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER', verbose=verbose)
+            dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER', verbose=verbose)
             cursor = dbcnx.cursor()
             try:
                 if helper.users_support:
@@ -333,7 +348,8 @@
             except:
                 dbcnx.rollback()
                 raise
-        cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=verbose)
+        cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE',
+                                verbose=verbose)
         cursor = cnx.cursor()
         helper.init_fti_extensions(cursor)
         # postgres specific stuff
--- a/server/sources/native.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/server/sources/native.py	Mon Jan 24 17:02:38 2011 +0100
@@ -849,8 +849,8 @@
         if self._eid_creation_cnx is None:
             self._eid_creation_cnx = self.get_connection()
         cnx = self._eid_creation_cnx
-        cursor = cnx.cursor()
         try:
+            cursor = cnx.cursor()
             for sql in self.dbhelper.sqls_increment_sequence('entities_id_seq'):
                 cursor.execute(sql)
             eid = cursor.fetchone()[0]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/lowered_etype.py	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,5 @@
+
+from yams.buildobjs import EntityType
+
+class my_etype(EntityType):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/uppered_rtype.py	Mon Jan 24 17:02:38 2011 +0100
@@ -0,0 +1,6 @@
+
+from yams.buildobjs import RelationDefinition
+
+class ARelation(RelationDefinition):
+    subject = 'CWUser'
+    object = 'CWGroup'
--- a/test/unittest_rset.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/test/unittest_rset.py	Mon Jan 24 17:02:38 2011 +0100
@@ -382,6 +382,14 @@
         self.assertEqual(set(e.e_schema.type for e in rset.entities(1)),
                           set(['CWGroup',]))
 
+    def test_iter_rows_with_entities(self):
+        rset = self.execute('Any U,UN,G,GN WHERE U in_group G, U login UN, G name GN')
+        # make sure we have at least one element
+        self.failUnless(rset)
+        out = list(rset.iter_rows_with_entities())[0]
+        self.assertEqual( out[0].login, out[1] )
+        self.assertEqual( out[2].name, out[3] )
+
     def test_printable_rql(self):
         rset = self.execute(u'CWEType X WHERE X final FALSE')
         self.assertEqual(rset.printable_rql(),
--- a/test/unittest_schema.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/test/unittest_schema.py	Mon Jan 24 17:02:38 2011 +0100
@@ -74,7 +74,6 @@
     ('Personne tel Int'),
     ('Personne fax Int'),
     ('Personne datenaiss Date'),
-    ('Personne TEST Boolean'),
     ('Personne promo String'),
     # real relations
     ('Personne  travaille Societe'),
@@ -82,7 +81,7 @@
     ('Societe evaluee   Note'),
     ('Personne  concerne  Affaire'),
     ('Personne  concerne  Societe'),
-    ('Affaire Concerne  Societe'),
+    ('Affaire concerne  Societe'),
     )
 done = {}
 for rel in RELS:
@@ -110,17 +109,6 @@
         self.failIf(issubclass(RQLUniqueConstraint, RQLConstraint))
         self.failUnless(issubclass(RQLConstraint, RQLVocabularyConstraint))
 
-    def test_normalize(self):
-        """test that entities, relations and attributes name are normalized
-        """
-        self.assertEqual(esociete.type, 'Societe')
-        self.assertEqual(schema.has_relation('TEST'), 0)
-        self.assertEqual(schema.has_relation('test'), 1)
-        self.assertEqual(eperson.subjrels['test'].type, 'test')
-        self.assertEqual(schema.has_relation('Concerne'), 0)
-        self.assertEqual(schema.has_relation('concerne'), 1)
-        self.assertEqual(schema.rschema('concerne').type, 'concerne')
-
     def test_entity_perms(self):
         self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests')))
         self.assertEqual(eperson.get_groups('update'), set(('managers', 'owners',)))
@@ -271,7 +259,7 @@
         self.assertEqual([x.expression for x in aschema.get_rqlexprs('update')],
                           ['U has_update_permission X'])
 
-class BadSchemaRQLExprTC(TestCase):
+class BadSchemaTC(TestCase):
     def setUp(self):
         self.loader = CubicWebSchemaLoader()
         self.loader.defined = {}
@@ -285,6 +273,16 @@
             self.loader._build_schema('toto', False)
         self.assertEqual(str(cm.exception), msg)
 
+    def test_lowered_etype(self):
+        self._test('lowered_etype.py',
+                   "'my_etype' is not a valid name for an entity type. It should "
+                   "start with an upper cased letter and be followed by at least "
+                   "a lower cased letter")
+
+    def test_uppered_rtype(self):
+        self._test('uppered_rtype.py',
+                   "'ARelation' is not a valid name for a relation type. It should be lower cased")
+
     def test_rrqlexpr_on_etype(self):
         self._test('rrqlexpr_on_eetype.py',
                    "can't use RRQLExpression on ToTo, use an ERQLExpression")
--- a/test/unittest_selectors.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/test/unittest_selectors.py	Mon Jan 24 17:02:38 2011 +0100
@@ -24,7 +24,7 @@
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.appobject import Selector, AndSelector, OrSelector
 from cubicweb.selectors import (is_instance, adaptable, match_user_groups,
-                                multi_lines_rset)
+                                multi_lines_rset, score_entity)
 from cubicweb.interfaces import IDownloadable
 from cubicweb.web import action
 
@@ -245,6 +245,24 @@
             yield self.assertEqual, selector(None, self.req, self.rset), assertion
 
 
+class ScoreEntitySelectorTC(CubicWebTC):
+
+    def test_intscore_entity_selector(self):
+        req = self.request()
+        selector = score_entity(lambda x: None)
+        rset = req.execute('Any E WHERE E eid 0')
+        self.assertEqual(selector(None, req, rset), 0)
+        selector = score_entity(lambda x: "something")
+        self.assertEqual(selector(None, req, rset), 1)
+        selector = score_entity(lambda x: object)
+        self.assertEqual(selector(None, req, rset), 1)
+        rset = req.execute('Any G LIMIT 2 WHERE G is CWGroup')
+        selector = score_entity(lambda x: 10)
+        self.assertEqual(selector(None, req, rset), 20)
+        selector = score_entity(lambda x: 10, once_is_enough=True)
+        self.assertEqual(selector(None, req, rset), 10)
+
+
 if __name__ == '__main__':
     unittest_main()
 
--- a/view.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/view.py	Mon Jan 24 17:02:38 2011 +0100
@@ -20,6 +20,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+import types, new
 from cStringIO import StringIO
 from warnings import warn
 
@@ -543,17 +544,6 @@
     __registry__ = 'adapters'
 
 
-class EntityAdapter(Adapter):
-    """base class for entity adapters (eg adapt an entity to an interface)"""
-    def __init__(self, _cw, **kwargs):
-        try:
-            self.entity = kwargs.pop('entity')
-        except KeyError:
-            self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
-                                                    kwargs.get('col') or 0)
-        Adapter.__init__(self, _cw, **kwargs)
-
-
 def implements_adapter_compat(iface):
     def _pre39_compat(func):
         def decorated(self, *args, **kwargs):
@@ -568,5 +558,35 @@
                     return member(*args, **kwargs)
                 return member
             return func(self, *args, **kwargs)
+        decorated.decorated = func
         return decorated
     return _pre39_compat
+
+
+def unwrap_adapter_compat(cls):
+    parent = cls.__bases__[0]
+    for member_name in dir(parent):
+        member = getattr(parent, member_name)
+        if isinstance(member, types.MethodType) and hasattr(member.im_func, 'decorated') and not member_name in cls.__dict__:
+            method = new.instancemethod(member.im_func.decorated, None, cls)
+            setattr(cls, member_name, method)
+
+
+class auto_unwrap_bw_compat(type):
+    def __new__(mcs, name, bases, classdict):
+        cls = type.__new__(mcs, name, bases, classdict)
+        if not classdict.get('__needs_bw_compat__'):
+            unwrap_adapter_compat(cls)
+        return cls
+
+
+class EntityAdapter(Adapter):
+    """base class for entity adapters (eg adapt an entity to an interface)"""
+    __metaclass__ = auto_unwrap_bw_compat
+    def __init__(self, _cw, **kwargs):
+        try:
+            self.entity = kwargs.pop('entity')
+        except KeyError:
+            self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
+                                                    kwargs.get('col') or 0)
+        Adapter.__init__(self, _cw, **kwargs)
--- a/vregistry.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/vregistry.py	Mon Jan 24 17:02:38 2011 +0100
@@ -129,6 +129,8 @@
         # or simplify by calling unregister then register here
         if not isinstance(replaced, basestring):
             replaced = classid(replaced)
+        # prevent from misspelling
+        assert obj is not replaced, 'replacing an object by itself: %s' % obj
         registered_objs = self.get(class_regid(obj), ())
         for index, registered in enumerate(registered_objs):
             if classid(registered) == replaced:
--- a/web/controller.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/controller.py	Mon Jan 24 17:02:38 2011 +0100
@@ -148,10 +148,13 @@
             path = self._cw.form['__redirectpath']
             if (self._edited_entity and path != self._edited_entity.rest_path()
                 and '_cwmsgid' in newparams):
-                # XXX may be here on modification?
-                msg = u'(<a href="%s">%s</a>)' % (
-                    xml_escape(self._edited_entity.absolute_url()),
-                    self._cw._('click here to see created entity'))
+                # are we here on creation or modification?
+                if any(eid == self._edited_entity.eid
+                       for eid in self._cw.data.get('eidmap', {}).values()):
+                    msg = self._cw._('click here to see created entity')
+                else:
+                    msg = self._cw._('click here to see edited entity')
+                msg = u'(<a href="%s">%s</a>)' % (xml_escape(self._edited_entity.absolute_url()), msg)
                 self._cw.append_to_redirect_message(msg)
         elif self._after_deletion_path:
             # else it should have been set during form processing
--- a/web/data/cubicweb.ajax.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.ajax.js	Mon Jan 24 17:02:38 2011 +0100
@@ -283,7 +283,7 @@
  * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
  */
 function loadRemote(url, form, reqtype, sync) {
-    if (!url.startswith(baseuri())) {
+    if (!url.toLowerCase().startswith(baseuri())) {
         url = baseuri() + url;
     }
     if (!sync) {
--- a/web/data/cubicweb.calendar.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.calendar.js	Mon Jan 24 17:02:38 2011 +0100
@@ -16,12 +16,14 @@
  * .. class:: Calendar
  *
  * Calendar (graphical) widget
+ *
  * public methods are :
+ *
  *   __init__ :
- *    @param containerId: the DOM node's ID where the calendar will be displayed
- *    @param inputId: which input needs to be updated when a date is selected
- *    @param year, @param month: year and month to be displayed
- *    @param cssclass: CSS class of the calendar widget (default is commandCal)
+ *    :attr:`containerId`: the DOM node's ID where the calendar will be displayed
+ *    :attr:`inputId`: which input needs to be updated when a date is selected
+ *    :attr:`year`, :attr:`month`: year and month to be displayed
+ *    :attr:`cssclass`: CSS class of the calendar widget (default is 'commandCal')
  *
  *   show() / hide():
  *    show or hide the calendar widget
--- a/web/data/cubicweb.compat.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.compat.js	Mon Jan 24 17:02:38 2011 +0100
@@ -32,14 +32,15 @@
 /**
  * .. function:: cw.utils.deprecatedFunction(msg, function)
  *
- * jQUery flattens arrays returned by the mapping function:
- * >>> y = ['a:b:c', 'd:e']
- * >>> jQuery.map(y, function(y) { return y.split(':');})
- * ["a", "b", "c", "d", "e"]
- *  // where one would expect:
- *  [ ["a", "b", "c"], ["d", "e"] ]
- *  XXX why not the same argument order as $.map and forEach ?
+ * jQUery flattens arrays returned by the mapping function: ::
+ *
+ *   >>> y = ['a:b:c', 'd:e']
+ *   >>> jQuery.map(y, function(y) { return y.split(':');})
+ *   ["a", "b", "c", "d", "e"]
+ *   // where one would expect:
+ *   [ ["a", "b", "c"], ["d", "e"] ]
  */
+ // XXX why not the same argument order as $.map and forEach ?
 map = cw.utils.deprecatedFunction(
     '[3.9] map() is deprecated, use $.map instead',
     function(func, array) {
--- a/web/data/cubicweb.htmlhelpers.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.htmlhelpers.js	Mon Jan 24 17:02:38 2011 +0100
@@ -18,9 +18,9 @@
 function baseuri() {
     var uri = document.baseURI;
     if (uri) { // some browsers don't define baseURI
-        return uri;
+        return uri.toLowerCase();
     }
-    return jQuery('base').attr('href');
+    return jQuery('base').attr('href').toLowerCase();
 }
 
 /**
--- a/web/data/cubicweb.preferences.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.preferences.js	Mon Jan 24 17:02:38 2011 +0100
@@ -1,8 +1,6 @@
 /**
- * toggle visibility of an element by its id
- * & set current visibility status in a cookie
- * XXX whenever used outside of preferences, don't forget to
- *     move me in a more appropriate place
+ * toggle visibility of an element by its id & set current visibility status in a cookie
+ *
  */
 
 var prefsValues = {};
--- a/web/data/cubicweb.python.js	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/data/cubicweb.python.js	Mon Jan 24 17:02:38 2011 +0100
@@ -248,9 +248,11 @@
  * this is a js class factory. objects returned by this function behave
  * more or less like a python class. The `class` function prototype is
  * inspired by the python `type` builtin
- * Important notes :
- *  -> methods are _STATICALLY_ attached when the class it created
- *  -> multiple inheritance was never tested, which means it doesn't work ;-)
+ *
+ * .. Note::
+ *
+ *    * methods are _STATICALLY_ attached when the class it created
+ *    * multiple inheritance was never tested, which means it doesn't work ;-)
  */
 function defclass(name, bases, classdict) {
     var baseclasses = bases || [];
--- a/web/formwidgets.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/formwidgets.py	Mon Jan 24 17:02:38 2011 +0100
@@ -28,6 +28,7 @@
 
 .. autoclass:: cubicweb.web.formwidgets.FieldWidget
 
+
 HTML <input> based widgets
 ''''''''''''''''''''''''''
 
@@ -37,6 +38,7 @@
 .. autoclass:: cubicweb.web.formwidgets.FileInput
 .. autoclass:: cubicweb.web.formwidgets.ButtonInput
 
+
 Other standard HTML widgets
 '''''''''''''''''''''''''''
 
@@ -45,6 +47,7 @@
 .. autoclass:: cubicweb.web.formwidgets.CheckBox
 .. autoclass:: cubicweb.web.formwidgets.Radio
 
+
 Date and time widgets
 '''''''''''''''''''''
 
@@ -53,6 +56,7 @@
 .. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
 .. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
 
+
 Ajax / javascript widgets
 '''''''''''''''''''''''''
 
@@ -64,19 +68,22 @@
 .. kill or document LazyRestrictedAutoCompletionWidget
 .. kill or document RestrictedAutoCompletionWidget
 
+
 Other widgets
 '''''''''''''
+
 .. autoclass:: cubicweb.web.formwidgets.PasswordInput
 .. autoclass:: cubicweb.web.formwidgets.IntervalWidget
 .. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget
 .. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
 
+
 Form controls
 '''''''''''''
-Those classes are not proper widget (they are not associated to
-field) but are used as form controls. Their API is similar
-to widgets except that `field` argument given to :meth:`render`
-will be `None`.
+
+Those classes are not proper widget (they are not associated to field) but are
+used as form controls. Their API is similar to widgets except that `field`
+argument given to :meth:`render` will be `None`.
 
 .. autoclass:: cubicweb.web.formwidgets.Button
 .. autoclass:: cubicweb.web.formwidgets.SubmitButton
@@ -107,15 +114,20 @@
 
     :attr:`needs_js`
        list of javascript files needed by the widget.
+
     :attr:`needs_css`
        list of css files needed by the widget.
+
     :attr:`setdomid`
        flag telling if HTML DOM identifier should be set on input.
+
     :attr:`settabindex`
        flag telling if HTML tabindex attribute of inputs should be set.
+
     :attr:`suffix`
        string to use a suffix when generating input, to ease usage as a
        sub-widgets (eg widget used by another widget)
+
     :attr:`vocabulary_widget`
        flag telling if this widget expect a vocabulary
 
@@ -212,7 +224,7 @@
            generating the form)
 
         4. field's typed value (returned by its
-          :meth:`~cubicweb.web.formfields.Field.typed_value` method)
+           :meth:`~cubicweb.web.formfields.Field.typed_value` method)
 
         Values found in 1. and 2. are expected te be already some 'display
         value' (eg a string) while those found in 3. and 4. are expected to be
--- a/web/test/unittest_application.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/test/unittest_application.py	Mon Jan 24 17:02:38 2011 +0100
@@ -17,6 +17,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.web.application"""
 
+from __future__ import with_statement
+
 import base64, Cookie
 import sys
 from urllib import unquote
--- a/web/test/unittest_views_basecontrollers.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/test/unittest_views_basecontrollers.py	Mon Jan 24 17:02:38 2011 +0100
@@ -17,6 +17,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.views.basecontrollers unit tests"""
 
+from __future__ import with_statement
+
 from logilab.common.testlib import unittest_main, mock_object
 
 from cubicweb import Binary, NoSelectableObject, ValidationError
--- a/web/views/calendar.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/calendar.py	Mon Jan 24 17:02:38 2011 +0100
@@ -44,6 +44,7 @@
 
 
 class ICalendarableAdapter(EntityAdapter):
+    __needs_bw_compat__ = True
     __regid__ = 'ICalendarable'
     __select__ = implements(ICalendarable, warn=False) # XXX for bw compat, should be abstract
 
--- a/web/views/editcontroller.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/editcontroller.py	Mon Jan 24 17:02:38 2011 +0100
@@ -34,6 +34,7 @@
 
 
 class IEditControlAdapter(EntityAdapter):
+    __needs_bw_compat__ = True
     __regid__ = 'IEditControl'
     __select__ = is_instance('Any')
 
--- a/web/views/embedding.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/embedding.py	Mon Jan 24 17:02:38 2011 +0100
@@ -41,6 +41,7 @@
 
 class IEmbedableAdapter(EntityAdapter):
     """interface for embedable entities"""
+    __needs_bw_compat__ = True
     __regid__ = 'IEmbedable'
     __select__ = implements(IEmbedable, warn=False) # XXX for bw compat, should be abstract
 
--- a/web/views/igeocodable.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/igeocodable.py	Mon Jan 24 17:02:38 2011 +0100
@@ -26,6 +26,7 @@
 
 class IGeocodableAdapter(EntityAdapter):
     """interface required by geocoding views such as gmap-view"""
+    __needs_bw_compat__ = True
     __regid__ = 'IGeocodable'
     __select__ = implements(IGeocodable, warn=False) # XXX for bw compat, should be abstract
 
--- a/web/views/isioc.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/isioc.py	Mon Jan 24 17:02:38 2011 +0100
@@ -32,6 +32,7 @@
 
 class ISIOCItemAdapter(EntityAdapter):
     """interface for entities which may be represented as an ISIOC items"""
+    __needs_bw_compat__ = True
     __regid__ = 'ISIOCItem'
     __select__ = implements(ISiocItem, warn=False) # XXX for bw compat, should be abstract
 
@@ -63,6 +64,7 @@
 
 class ISIOCContainerAdapter(EntityAdapter):
     """interface for entities which may be represented as an ISIOC container"""
+    __needs_bw_compat__ = True
     __regid__ = 'ISIOCContainer'
     __select__ = implements(ISiocContainer, warn=False) # XXX for bw compat, should be abstract
 
--- a/web/views/navigation.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/navigation.py	Mon Jan 24 17:02:38 2011 +0100
@@ -187,6 +187,7 @@
     """interface for entities which can be linked to a previous and/or next
     entity
     """
+    __needs_bw_compat__ = True
     __regid__ = 'IPrevNext'
     __select__ = implements(IPrevNext, warn=False) # XXX for bw compat, else should be abstract
 
--- a/web/views/xmlrss.py	Thu Jan 20 10:10:22 2011 +0100
+++ b/web/views/xmlrss.py	Mon Jan 24 17:02:38 2011 +0100
@@ -122,6 +122,7 @@
 # RSS stuff ###################################################################
 
 class IFeedAdapter(EntityAdapter):
+    __needs_bw_compat__ = True
     __regid__ = 'IFeed'
     __select__ = is_instance('Any')