[doc] backport part 3 & 4 of the sytweb's tutorial + to be published part 5 stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Sun, 23 Jan 2011 14:59:04 +0100
branchstable
changeset 6876 4b0b9d8207c5
parent 6875 a166b51d13f8
child 6877 0e8fc441b38b
[doc] backport part 3 & 4 of the sytweb's tutorial + to be published part 5
doc/book/en/images/tutos-photowebsite_background-image.png
doc/book/en/images/tutos-photowebsite_boxes.png
doc/book/en/images/tutos-photowebsite_breadcrumbs.png
doc/book/en/images/tutos-photowebsite_facets.png
doc/book/en/images/tutos-photowebsite_grey-box.png
doc/book/en/images/tutos-photowebsite_index-after.png
doc/book/en/images/tutos-photowebsite_index-before.png
doc/book/en/images/tutos-photowebsite_login-box.png
doc/book/en/images/tutos-photowebsite_prevnext.png
doc/book/en/images/tutos-photowebsite_ui1.png
doc/book/en/images/tutos-photowebsite_ui2.png
doc/book/en/images/tutos-photowebsite_ui3.png
doc/book/en/tutorials/advanced/index.rst
doc/book/en/tutorials/advanced/part01_create-cube.rst
doc/book/en/tutorials/advanced/part02_security.rst
doc/book/en/tutorials/advanced/part03_bfss.rst
doc/book/en/tutorials/advanced/part04_ui-base.rst
doc/book/en/tutorials/advanced/part05_ui-advanced.rst
doc/book/en/tutorials/base/index.rst
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/tutorials/advanced/index.rst	Fri Jan 21 16:38:13 2011 +0100
+++ b/doc/book/en/tutorials/advanced/index.rst	Sun Jan 23 14:59:04 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	Sun Jan 23 14:59:04 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	Sun Jan 23 14:59:04 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	Sun Jan 23 14:59:04 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	Sun Jan 23 14:59:04 2011 +0100
@@ -0,0 +1,455 @@
+Building my photos web site with CubicWeb part IV: 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 box
+    from cubicweb.web.views import basetemplates, 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(box.BoxTemplate, basetemplates.LogFormView):
+	"""display a box containing links to all startup views"""
+	__regid__ = 'sytweb.loginbox'
+	__select__ = box.BoxTemplate.__select__ & anonymous_user()
+
+	title = _('Authenticate yourself')
+	order = 70
+
+	def call(self, **kwargs):
+	    self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % self.title)
+	    self.w(u'<div class="sideBox"><div class="sideBoxBody">')
+	    self.login_form('loginBox')
+	    self.w(u'</div></div>')
+
+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 `box.BoxTemplate`'selector. The HTML
+is written to match default CubicWeb boxes style. To get the actual login form,
+we inherit from the `LogFormView` view which provides a `login_form` method
+(handling some stuff under the cover for us, hence the multiple inheritance), that
+we simply have to call to get the form's HTML.
+
+
+.. 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	Sun Jan 23 14:59:04 2011 +0100
@@ -0,0 +1,357 @@
+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 most desired features. But... I would like to make it look
+somewhat like *my* website, That's not 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 `logo.png` file into the cube's `data`
+directory. As data files are looked at according to cubes order (cubicweb
+resources coming last). The first one being picked, the 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 `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's 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 `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 `cubiweb.web.views.basetemplates`
+module, since I know that global page layouts sits there. I could also have
+grep the "body" tag in `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)
+
+
+Besides that, as you may I've guess, my background image is in a `backgroundjpg`
+file in the cube's `data` directory, there are still some things to explain to
+newcomers here though.
+
+* The `call` method is there the main access point of the view. It's called by
+  the view's `render` method. That's not the onlyu access point for a view, but
+  that will be detailed later.
+
+* Calling `self.w` write something to the output stream. Except for binary view
+  (not generating text), it *must* be an Unicode string.
+
+* The proper way to get a file in `data` directory is to use the `datadir_url`
+  attribute of the incoming request (e.g. `self._cw`).
+
+I won't explain again the `registration_callback` stuff, you should understand it
+know!  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 behaves nicely (that's
+not yet the case at all). I'll put all this in a `cubes.sytweb.css` file, as usual
+in our `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 thing 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've to modify the :file:`uiprops.py` file
+we've encountered 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 additionally 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. Lets first do a few tweaks:
+
+.. 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 view
+gro  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 spaces for the
+photos.  Now, serious things: I want a box similar as the tags box but to handle
+the `Person displayed_on File` relation. We can do this simply by configuring a
+:class:`AjaxEditRelationCtxComponent` subclass 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 by some class attributes. The `fname_*`
+attributes gives name of methods that should be defined on the json control to
+make the AJAX part of the widget working: one to get the vocabulary, one to add a
+relation and another to delete a relation. Those methods must start by a `ks_`
+prefix and are added to the controller using the `@monkeypatch` decorator.Here
+the most complicated is the one to add a relation, since it tries to see if the
+person already exists, and else automatically create it by supposing 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 those two informations.
+
+.. 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 'facet' appearing in the left column. Facets
+provide an intuitive way to build a query incrementally, by proposing to the user
+various way to restrict result set. For instance cubicweb propose a facet to
+restrict according to who's created an entity; the tag cube a facet to restrict
+according to tags. I want to propose similarly a facet to restrict according to
+people displayed on the picture. To do so, there are various classes in the
+:mod:`cubicweb.web.facet` module which you've simple to configure using class
+attributes as we've done for the box. In our case, we'll define a subclass of
+:class:`RelationFacet`:
+
+.. 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 a filter according to the `visibility` attribute. this is
+even more simple, by inheriting from the :class:`AttributeFacet` class:
+
+.. sourcecode:: python
+
+    class VisibilityFacet(facet.AttributeFacet):
+	__regid__ = 'visibility-facet'
+	rtype = 'visibility'
+
+Now if I search some pictures on my site, I get the following facets available:
+
+.. image:: ../../images/tutos-photowebsite_facets.png
+
+.. Note:
+
+  Facets which have no choice to propose (i.e. one or less elements of
+  vocabulary) are not displayed. That's may be 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) stuff as on the python side
+(high level generic classes for components, including boxes and facets). We now
+have, by a few lines of code, a full-featured web site with a personnalized look.
+
+Of course we'll probably want more as the time goes, but we can now start
+concentrate on making good pictures, publishing albums and sharing them with
+friends...
+
+
+
+.. _`CubicWeb 3.10`: https://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/index.rst	Fri Jan 21 16:38:13 2011 +0100
+++ b/doc/book/en/tutorials/base/index.rst	Sun Jan 23 14:59:04 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: