backport stable into oldstable: oldstable is now cw 3.7 oldstable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 28 Apr 2010 11:54:13 +0200
brancholdstable
changeset 5422 0865e1e90674
parent 4985 02b52bf9f5f8 (current diff)
parent 5421 8167de96c523 (diff)
child 5424 8ecbcbff9777
backport stable into oldstable: oldstable is now cw 3.7
.hgtags
doc/book/en/annexes/cookbook.rst
doc/book/en/development/cubes/available-cubes.rst
doc/book/en/development/cubes/cc-newcube.rst
doc/book/en/development/cubes/index.rst
doc/book/en/development/cubes/layout.rst
doc/book/en/development/datamodel/baseschema.rst
doc/book/en/development/datamodel/define-workflows.rst
doc/book/en/development/datamodel/definition.rst
doc/book/en/development/datamodel/index.rst
doc/book/en/development/datamodel/metadata.rst
doc/book/en/development/devcore/appobject.rst
doc/book/en/development/devcore/cwconfig.rst
doc/book/en/development/devcore/dbapi.rst
doc/book/en/development/devcore/index.rst
doc/book/en/development/devcore/selectors.rst
doc/book/en/development/devcore/vreg.rst
doc/book/en/development/devrepo/hooks.rst
doc/book/en/development/devrepo/index.rst
doc/book/en/development/devrepo/notifications.rst
doc/book/en/development/devrepo/operations.rst
doc/book/en/development/devrepo/sessions.rst
doc/book/en/development/devrepo/tasks.rst
doc/book/en/development/devweb/controllers.rst
doc/book/en/development/devweb/css.rst
doc/book/en/development/devweb/facets.rst
doc/book/en/development/devweb/form.rst
doc/book/en/development/devweb/httpcaching.rst
doc/book/en/development/devweb/index.rst
doc/book/en/development/devweb/internationalization.rst
doc/book/en/development/devweb/js.rst
doc/book/en/development/devweb/property.rst
doc/book/en/development/devweb/publisher.rst
doc/book/en/development/devweb/request.rst
doc/book/en/development/devweb/rtags.rst
doc/book/en/development/devweb/views.rst
doc/book/en/development/entityclasses/data-as-objects.rst
doc/book/en/development/entityclasses/index.rst
doc/book/en/development/entityclasses/interfaces.rst
doc/book/en/development/entityclasses/load-sort.rst
doc/book/en/development/entityclasses/more.rst
doc/book/en/development/index.rst
doc/book/en/development/migration/index.rst
doc/book/en/development/profiling/index.rst
doc/book/en/development/testing/index.rst
doc/book/en/development/webstdlib/autoform.rst
doc/book/en/development/webstdlib/basetemplates.rst
doc/book/en/development/webstdlib/baseviews.rst
doc/book/en/development/webstdlib/boxes.rst
doc/book/en/development/webstdlib/breadcrumbs.rst
doc/book/en/development/webstdlib/editcontroller.rst
doc/book/en/development/webstdlib/editforms.rst
doc/book/en/development/webstdlib/embedding.rst
doc/book/en/development/webstdlib/facets.rst
doc/book/en/development/webstdlib/idownloadable.rst
doc/book/en/development/webstdlib/index.rst
doc/book/en/development/webstdlib/primary.rst
doc/book/en/development/webstdlib/startup.rst
doc/book/en/development/webstdlib/table.rst
doc/book/en/development/webstdlib/urlpublish.rst
doc/book/en/development/webstdlib/wdoc.rst
doc/book/en/development/webstdlib/xmlrss.rst
doc/book/en/images/03-transitions-view.en.png
doc/book/en/images/archi_globale.en.png
doc/book/en/images/cbw-add-relation-entryof.en.png
doc/book/en/images/cbw-create-blog.en.png
doc/book/en/images/cbw-detail-one-blogentry.en.png
doc/book/en/images/cbw-list-one-blog.en.png
doc/book/en/images/cbw-list-two-blog.en.png
doc/book/en/images/cbw-schema.en.png
doc/book/en/images/cbw-update-primary-view.en.png
doc/book/en/images/lax-book.00-login.en.png
doc/book/en/images/lax-book.01-start.en.png
doc/book/en/images/lax-book.02-cookie-values.en.png
doc/book/en/images/lax-book.02-create-blog.en.png
doc/book/en/images/lax-book.03-list-one-blog.en.png
doc/book/en/images/lax-book.03-site-config-panel.en.png
doc/book/en/images/lax-book.03-state-submitted.en.png
doc/book/en/images/lax-book.03-transitions-view.en.png
doc/book/en/images/lax-book.04-detail-one-blog.en.png
doc/book/en/images/lax-book.05-list-two-blog.en.png
doc/book/en/images/lax-book.06-add-relation-entryof.en.png
doc/book/en/images/lax-book.06-header-no-login.en.png
doc/book/en/images/lax-book.06-main-template-layout.en.png
doc/book/en/images/lax-book.06-main-template-logo.en.png
doc/book/en/images/lax-book.06-simple-main-template.en.png
doc/book/en/images/lax-book.07-detail-one-blogentry.en.png
doc/book/en/images/lax-book.08-schema.en.png
doc/book/en/images/lax-book.09-new-view-blogentry.en.png
doc/book/en/images/lax-book.10-blog-with-two-entries.en.png
doc/book/en/intro/book-map.rst
doc/book/en/intro/concepts/index.rst
doc/book/en/intro/tutorial/blog-in-five-minutes.rst
doc/book/en/intro/tutorial/components.rst
doc/book/en/intro/tutorial/conclusion.rst
doc/book/en/intro/tutorial/create-cube.rst
doc/book/en/intro/tutorial/index.rst
doc/book/en/intro/tutorial/maintemplate.rst
hooks/storages.py
schemas/_regproc_bss.postgres.sql
server/test/data/site_erudi.py
--- a/.hgtags	Wed Mar 24 10:23:31 2010 +0100
+++ b/.hgtags	Wed Apr 28 11:54:13 2010 +0200
@@ -107,5 +107,15 @@
 0a16f07112b90fb61d2e905855fece77e5a7e39c cubicweb-debian-version-3.6.1-2
 bfebe3d14d5390492925fc294dfdafad890a7104 cubicweb-version-3.6.2
 f3b4bb9121a0e7ee5961310ff79e61c890948a77 cubicweb-debian-version-3.6.2-1
+270aba1e6fa21dac6b070e7815e6d1291f9c87cd cubicweb-version-3.7.0
+0c9ff7e496ce344b7e6bf5c9dd2847daf9034e5e cubicweb-debian-version-3.7.0-1
+6b0832bbd1daf27c2ce445af5b5222e1e522fb90 cubicweb-version-3.7.1
+9194740f070e64da5a89f6a9a31050a8401ebf0c cubicweb-debian-version-3.7.1-1
 9c342fa4f1b73e06917d7dc675949baff442108b cubicweb-version-3.6.3
 f9fce56d6a0c2bc6c4b497b66039a8bbbbdc8074 cubicweb-debian-version-3.6.3-1
+d010f749c21d55cd85c5feb442b9cf816282953c cubicweb-version-3.7.2
+8fda29a6c2191ba3cc59242c17b28b34127c75fa cubicweb-debian-version-3.7.2-1
+768beb8e15f15e079f8ee6cfc35125e12b19e140 cubicweb-version-3.7.3
+44c7bf90df71dd562e5a7be5ced3019da603d24f cubicweb-debian-version-3.7.3-1
+ec23f3ebcd34a92b9898b312f44d56cca748d0d6 cubicweb-version-3.7.4
+fefeda65bb83dcc2d775255fe69fdee0e793d135 cubicweb-debian-version-3.7.4-1
--- a/COPYING	Wed Mar 24 10:23:31 2010 +0100
+++ b/COPYING	Wed Apr 28 11:54:13 2010 +0200
@@ -1,165 +1,339 @@
-		   GNU LESSER GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
 
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
+			    Preamble
 
-  This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
-  0. Additional Definitions. 
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
 
-  As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
 
-  "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
 
-  An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
 
-  A "Combined Work" is a work produced by combining or linking an
-Application with the Library.  The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
 
-  The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
-  The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
 
-  1. Exception to Section 3 of the GNU GPL.
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
 
-  You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
 
-  2. Conveying Modified Versions.
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
 
-  If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
 
-   a) under this License, provided that you make a good faith effort to
-   ensure that, in the event an Application does not supply the
-   function or data, the facility still operates, and performs
-   whatever part of its purpose remains meaningful, or
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
 
-   b) under the GNU GPL, with none of the additional permissions of
-   this License applicable to that copy.
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
 
-  3. Object Code Incorporating Material from Library Header Files.
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
 
-  The object code form of an Application may incorporate material from
-a header file that is part of the Library.  You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
 
-   a) Give prominent notice with each copy of the object code that the
-   Library is used in it and that the Library and its use are
-   covered by this License.
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
 
-   b) Accompany the object code with a copy of the GNU GPL and this license
-   document.
-
-  4. Combined Works.
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
 
-  You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
 
-   a) Give prominent notice with each copy of the Combined Work that
-   the Library is used in it and that the Library and its use are
-   covered by this License.
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
 
-   b) Accompany the Combined Work with a copy of the GNU GPL and this license
-   document.
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
 
-   c) For a Combined Work that displays copyright notices during
-   execution, include the copyright notice for the Library among
-   these notices, as well as a reference directing the user to the
-   copies of the GNU GPL and this license document.
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
 
-   d) Do one of the following:
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
 
-       0) Convey the Minimal Corresponding Source under the terms of this
-       License, and the Corresponding Application Code in a form
-       suitable for, and under terms that permit, the user to
-       recombine or relink the Application with a modified version of
-       the Linked Version to produce a modified Combined Work, in the
-       manner specified by section 6 of the GNU GPL for conveying
-       Corresponding Source.
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
 
-       1) Use a suitable shared library mechanism for linking with the
-       Library.  A suitable mechanism is one that (a) uses at run time
-       a copy of the Library already present on the user's computer
-       system, and (b) will operate properly with a modified version
-       of the Library that is interface-compatible with the Linked
-       Version. 
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
 
-   e) Provide Installation Information, but only if you would otherwise
-   be required to provide such information under section 6 of the
-   GNU GPL, and only to the extent that such information is
-   necessary to install and execute a modified version of the
-   Combined Work produced by recombining or relinking the
-   Application with a modified version of the Linked Version. (If
-   you use option 4d0, the Installation Information must accompany
-   the Minimal Corresponding Source and Corresponding Application
-   Code. If you use option 4d1, you must provide the Installation
-   Information in the manner specified by section 6 of the GNU GPL
-   for conveying Corresponding Source.)
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
 
-  5. Combined Libraries.
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
 
-  You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
 
-   a) Accompany the combined library with a copy of the same work based
-   on the Library, uncombined with any other library facilities,
-   conveyed under the terms of this License.
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
 
-   b) Give prominent notice with the combined library that part of it
-   is a work based on the Library, and explaining where to find the
-   accompanying uncombined form of the same work.
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
 
-  6. Revised Versions of the GNU Lesser General Public License.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
 
-  The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
 
-  Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
 
-  If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING.LESSER	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+	51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- a/MANIFEST.in	Wed Mar 24 10:23:31 2010 +0100
+++ b/MANIFEST.in	Wed Apr 28 11:54:13 2010 +0200
@@ -1,4 +1,6 @@
 include README
+include COPYING
+include COPYING.LESSER
 include pylintrc
 include bin/cubicweb-*
 include man/cubicweb-ctl.1
@@ -15,7 +17,7 @@
 recursive-include etwist *.xml *.html
 
 recursive-include i18n *.pot *.po
-recursive-include schemas *.py *.sql.*
+recursive-include schemas *.py *.sql
 
 recursive-include entities/test/data *
 recursive-include sobjects/test/data *
--- a/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """CubicWeb is a generic framework to quickly build applications which describes
 relations between entitites.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: Library General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -112,7 +125,7 @@
 
 CW_EVENT_MANAGER = CubicWebEventManager()
 
-def onevent(event):
+def onevent(event, *args, **kwargs):
     """decorator to ease event / callback binding
 
     >>> from cubicweb import onevent
@@ -123,6 +136,6 @@
     >>>
     """
     def _decorator(func):
-        CW_EVENT_MANAGER.bind(event, func)
+        CW_EVENT_MANAGER.bind(event, func, *args, **kwargs)
         return func
     return _decorator
--- a/__pkginfo__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/__pkginfo__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,29 @@
 # pylint: disable-msg=W0622,C0103
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb global packaging information for the cubicweb knowledge management
 software
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (3, 6, 3)
+numversion = (3, 7, 4)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LGPL'
@@ -30,7 +46,7 @@
 
 web = 'http://www.cubicweb.org'
 ftp = 'ftp://ftp.logilab.org/pub/cubicweb'
-pyversions = ['2.4', '2.5']
+pyversions = ['2.5', '2.6']
 
 classifiers = [
            'Environment :: Web Environment',
@@ -89,6 +105,8 @@
          [join(data_dir, fname) for fname in listdir(data_dir) if not isdir(join(data_dir, fname))]],
         [join('share', 'cubicweb', 'cubes', 'shared', 'data', 'timeline'),
          [join(data_dir, 'timeline', fname) for fname in listdir(join(data_dir, 'timeline'))]],
+        [join('share', 'cubicweb', 'cubes', 'shared', 'data', 'images'),
+         [join(data_dir, 'images', fname) for fname in listdir(join(data_dir, 'images'))]],
         [join('share', 'cubicweb', 'cubes', 'shared', 'wdoc'),
          [join(wdoc_dir, fname) for fname in listdir(wdoc_dir) if not isdir(join(wdoc_dir, fname))]],
         [join('share', 'cubicweb', 'cubes', 'shared', 'wdoc', 'images'),
--- a/_exceptions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/_exceptions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Exceptions shared by different cubicweb packages.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -45,21 +58,19 @@
 
 class ConnectionError(RepositoryError):
     """raised when a bad connection id is given or when an attempt to establish
-    a connection failed"""
+    a connection failed
+    """
 
 class AuthenticationError(ConnectionError):
-    """raised when a bad connection id is given or when an attempt to establish
-    a connection failed
+    """raised when when an attempt to establish a connection failed do to wrong
+    connection information (login / password or other authentication token)
     """
     def __init__(self, *args, **kwargs):
         super(AuthenticationError, self).__init__(*args)
         self.__dict__.update(kwargs)
 
 class BadConnectionId(ConnectionError):
-    """raised when a bad connection id is given or when an attempt to establish
-    a connection failed"""
-
-BadSessionId = BadConnectionId # XXX bw compat for pyro connections
+    """raised when a bad connection id is given"""
 
 class UnknownEid(RepositoryError):
     """the eid is not defined in the system tables"""
@@ -127,8 +138,6 @@
 class UnknownProperty(RegistryException):
     """property found in database but unknown in registry"""
 
-class RegistryOutOfDate(RegistryException):
-    """raised when a source file modification is detected"""
 
 # query exception #############################################################
 
--- a/_gcdebug.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/_gcdebug.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
 import gc, types, weakref
 
--- a/appobject.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/appobject.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,33 @@
-"""Base class for dynamically loaded objects accessible through the vregistry.
-
-You'll also find some convenience classes to build selectors.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+.. _appobject:
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+The `AppObject` class
+---------------------
+
+The AppObject class is the base class for all dynamically loaded objects
+(application objects) accessible through the vregistry.
+
+We can find a certain number of attributes and methods defined in this class and
+common to all the application objects.
+
+.. autoclass:: AppObject
 """
 __docformat__ = "restructuredtext en"
 
@@ -14,19 +36,24 @@
 from warnings import warn
 
 from logilab.common.deprecation import deprecated
+from logilab.common.decorators import classproperty
 from logilab.common.logging_ext import set_log_methods
 
 
 # selector base classes and operations ########################################
 
 def objectify_selector(selector_func):
-    """convenience decorator for simple selectors where a class definition
-    would be overkill::
+    """Most of the time, a simple score function is enough to build a selector.
+    The :func:`objectify_selector` decorator turn it into a proper selector
+    class::
 
         @objectify_selector
-        def one(cls, *args, **kwargs):
+        def one(cls, req, rset=None, **kwargs):
             return 1
 
+        class MyView(View):
+            __select__ = View.__select__ & one()
+
     """
     return type(selector_func.__name__, (Selector,),
                 {'__doc__': selector_func.__doc__,
@@ -48,7 +75,7 @@
 
 class Selector(object):
     """base class for selector classes providing implementation
-    for operators ``&`` and ``|``
+    for operators ``&``, ``|`` and  ``~``
 
     This class is only here to give access to binary operators, the
     selector logic itself should be implemented in the __call__ method
@@ -204,47 +231,88 @@
     selected according to a context (usually at least a request and a result
     set).
 
-    Concrete application objects classes are designed to be loaded by the
-    vregistry and should be accessed through it, not by direct instantiation.
+    The following attributes should be set on concret appobject classes:
 
-    The following attributes should be set on concret appobject classes:
-    :__registry__:
+    :attr:`__registry__`
       name of the registry for this object (string like 'views',
       'templates'...)
-    :__regid__:
+
+    :attr:`__regid__`
       object's identifier in the registry (string like 'main',
       'primary', 'folder_box')
-    :__select__:
+
+    :attr:`__select__`
       class'selector
 
-    Moreover, the `__abstract__` attribute may be set to True to indicate
-    that a appobject is abstract and should not be registered.
+    Moreover, the `__abstract__` attribute may be set to True to indicate that a
+    class is abstract and should not be registered.
 
     At selection time, the following attributes are set on the instance:
 
-    :_cw:
+    :attr:`_cw`
       current request
-    :cw_extra_kwargs:
+    :attr:`cw_extra_kwargs`
       other received arguments
 
-    only if rset is found in arguments (in which case rset/row/col will be
-    removed from cwextra_kwargs):
+    And also the following, only if `rset` is found in arguments (in which case
+    rset/row/col will be removed from `cwextra_kwargs`):
 
-    :cw_rset:
+    :attr:`cw_rset`
       context result set or None
-    :cw_row:
+
+    :attr:`cw_row`
       if a result set is set and the context is about a particular cell in the
       result set, and not the result set as a whole, specify the row number we
       are interested in, else None
-    :cw_col:
+
+    :attr:`cw_col`
       if a result set is set and the context is about a particular cell in the
       result set, and not the result set as a whole, specify the col number we
       are interested in, else None
+
+
+    .. Note::
+
+      * do not inherit directly from this class but from a more specific class
+        such as `AnyEntity`, `EntityView`, `AnyRsetView`, `Action`...
+
+      * to be recordable, a subclass has to define its registry (attribute
+        `__registry__`) and its identifier (attribute `__regid__`). Usually
+        you don't have to take care of the registry since it's set by the base
+        class, only the identifier `id`
+
+      * application objects are designed to be loaded by the vregistry and
+        should be accessed through it, not by direct instantiation, besides
+        to use it as base classe.
+
+
+      * When we inherit from `AppObject` (even not directly), you *always* have
+        to use **super()** to get the methods and attributes of the superclasses,
+        and not use the class identifier.
+
+        For example, instead of writting::
+
+          class Truc(PrimaryView):
+              def f(self, arg1):
+                  PrimaryView.f(self, arg1)
+
+        You must write::
+
+          class Truc(PrimaryView):
+              def f(self, arg1):
+                  super(Truc, self).f(arg1)
+
     """
     __registry__ = None
     __regid__ = None
     __select__ = yes()
 
+    @classproperty
+    def __registries__(cls):
+        if cls.__registry__ is None:
+            return ()
+        return (cls.__registry__,)
+
     @classmethod
     def __registered__(cls, registry):
         """called by the registry when the appobject has been registered.
@@ -258,7 +326,7 @@
         except AttributeError:
             pdefs = getattr(cls, 'cw_property_defs', {})
         else:
-            warn('property_defs is deprecated, use cw_property_defs in %s'
+            warn('[3.6] property_defs is deprecated, use cw_property_defs in %s'
                  % cls, DeprecationWarning)
         for propid, pdef in pdefs.items():
             pdef = pdef.copy() # may be shared
--- a/common/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Common subpackage of cubicweb : defines library functions used both on the
 hg stserver side and on the client side
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
--- a/common/mail.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/mail.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """pre 3.6 bw compat"""
 # pylint: disable-msg=W0614,W0401
 from warnings import warn
--- a/common/mixins.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/mixins.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """pre 3.6 bw compat"""
 # pylint: disable-msg=W0614,W0401
 from warnings import warn
--- a/common/mttransforms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/mttransforms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """pre 3.6 bw compat"""
 # pylint: disable-msg=W0614,W0401
 from warnings import warn
--- a/common/tags.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/tags.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """pre 3.6 bw compat"""
 # pylint: disable-msg=W0614,W0401
 from warnings import warn
--- a/common/uilib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/common/uilib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """pre 3.6 bw compat"""
 # pylint: disable-msg=W0614,W0401
 from warnings import warn
--- a/crypto.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/crypto.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Simple cryptographic routines, based on python-crypto.
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/cwconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/cwconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,46 +1,53 @@
 # -*- coding: utf-8 -*-
-"""common configuration utilities for cubicweb
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+.. _ResourceMode:
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+Resource mode
+-------------
+
+A resource *mode* is a predifined 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*:
+
+* 'user', resources are searched / created in the user home directory:
+
+  - instances are stored in :file:`~/etc/cubicweb.d`
+  - temporary files (such as pid file) in :file:`/tmp`
+
+* 'system', resources are searched / created in the system directories (eg
+  usually requiring root access):
+
+  - instances are stored in :file:`<INSTALL_PREFIX>/etc/cubicweb.d`
+  - temporary files (such as pid file) in :file:`/var/run/cubicweb`
+
+  where `<INSTALL_PREFIX>` is the detected installation prefix ('/usr/local' for
+  instance).
 
 
-If cubicweb is a mercurial checkout (eg `CWDEV` is true), located in
-`CW_SOFTWARE_ROOT`:
-
- * main cubes directory is `<CW_SOFTWARE_ROOT>/../cubes`. You can specify
-   another one with `CW_INSTANCES_DIR` environment variable or simply add some
-   other directories by using `CW_CUBES_PATH`.
-
- * cubicweb migration files are by default searched in
-   `<CW_SOFTWARE_ROOT>/misc/migration` instead of
-   `/usr/share/cubicweb/migration/`(unless another emplacement is specified
-   using `CW_MIGRATION_DIR`.
-
- * Cubicweb will start in 'user' mode (see below)
-
-
-On startup, Cubicweb is using a specific *mode*. A mode corresponds to some
-default setting for various resource directories. There are currently 2 main
-modes : 'system', for system wide installation, and 'user', fur user local
-installation (e.g. no root privileges).
-
-'user' mode is activated automatically when cubicweb is a mercurial checkout
-(e.g.  has a .hg directory). You can also force mode by using the `CW_MODE`
-environment variable, to:
-
-* use system wide installation but user specific instances and all, without root
-  privileges on the system (`export CW_MODE=user`)
-
-* use local checkout of cubicweb on system wide instances (requires root
-  privileges on the system (`export CW_MODE=system`)
-
- Here is the default resource directories settings according to mode:
+Notice that each resource path may be explicitly set using an environment
+variable if the default doesn't suit your needs. Here are the default resource
+directories that are affected according to mode:
 
 * 'system': ::
 
-        CW_INSTANCES_DIR = /etc/cubicweb.d/
+        CW_INSTANCES_DIR = <INSTALL_PREFIX>/etc/cubicweb.d/
         CW_INSTANCES_DATA_DIR = /var/lib/cubicweb/instances/
         CW_RUNTIME_DIR = /var/run/cubicweb/
 
@@ -50,27 +57,79 @@
         CW_INSTANCES_DATA_DIR = ~/etc/cubicweb.d/
         CW_RUNTIME_DIR = /tmp
 
+Cubes search path is also affected, see the :ref:Cube section.
+
+By default, the mode automatically set to 'user' if a :file:`.hg` directory is found
+in the cubicweb package, else it's set to 'system'. You can force this by setting
+the :envvar:`CW_MODE` environment variable to either 'user' or 'system' so you can
+easily:
+
+* use system wide installation but user specific instances and all, without root
+  privileges on the system (`export CW_MODE=user`)
+
+* use local checkout of cubicweb on system wide instances (requires root
+  privileges on the system (`export CW_MODE=system`)
+
+If you've a doubt about the mode you're currently running, check the first line
+outputed by the :command:`cubicweb-ctl list` command.
+
+Also, if cubicweb is a mercurial checkout located in `<CW_SOFTWARE_ROOT>`:
+
+* main cubes directory is `<CW_SOFTWARE_ROOT>/../cubes`. You can specify
+  another one with :envvar:`CW_INSTANCES_DIR` environment variable or simply
+  add some other directories by using :envvar:`CW_CUBES_PATH`
+
+* cubicweb migration files are searched in `<CW_SOFTWARE_ROOT>/misc/migration`
+  instead of `<INSTALL_PREFIX>/share/cubicweb/migration/`.
+
+
+.. _ConfigurationEnv:
+
+Environment configuration
+-------------------------
+
+Python
+``````
+
+If you installed *CubicWeb* by cloning the Mercurial forest or from source
+distribution, then you will need to update the environment variable PYTHONPATH by
+adding the path to the forest `cubicweb`:
+
+Add the following lines to either :file:`.bashrc` or :file:`.bash_profile` to
+configure your development environment ::
+
+    export PYTHONPATH=/full/path/to/cubicweb-forest
+
+If you installed *CubicWeb* with packages, no configuration is required and your
+new cubes will be placed in `/usr/share/cubicweb/cubes` and your instances will
+be placed in `/etc/cubicweb.d`.
+
+
+CubicWeb
+````````
+
+Here are all environment variables that may be used to configure *CubicWeb*:
 
 .. envvar:: CW_MODE
-   Resource mode: user or system
+
+   Resource mode: user or system, as explained in :ref:`ResourceMode`.
 
 .. envvar:: CW_CUBES_PATH
-   Augments the default search path for cubes
+
+   Augments the default search path for cubes. You may specify several
+   directories using ':' as separator (';' under windows environment).
 
 .. envvar:: CW_INSTANCES_DIR
-   Directory where cubicweb instances will be found
+
+   Directory where cubicweb instances will be found.
 
 .. envvar:: CW_INSTANCES_DATA_DIR
-   Directory where cubicweb instances data will be written
+
+   Directory where cubicweb instances data will be written (backup file...)
 
 .. envvar:: CW_RUNTIME_DIR
+
    Directory where pid files will be written
-
-.. envvar:: CW_MIGRATION_DIR
-   Directory where cubicweb migration files will be found
-
-
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -90,7 +149,8 @@
 from logilab.common.configuration import (Configuration, Method,
                                           ConfigurationMixIn, merge_options)
 
-from cubicweb import CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, ConfigurationError
+from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
+                      ConfigurationError, Binary)
 from cubicweb.toolsutils import env_path, create_dir
 
 CONFIGURATIONS = []
@@ -179,7 +239,7 @@
       }),
     ('short-line-size',
      {'type' : 'int',
-      'default': 40,
+      'default': 80,
       'help': _('maximum number of characters in short description'),
       'group': 'navigation',
       }),
@@ -329,7 +389,7 @@
     def available_cubes(cls):
         cubes = set()
         for directory in cls.cubes_search_path():
-            if not os.path.exists(directory):
+            if not exists(directory):
                 cls.error('unexistant directory in cubes search path: %s'
                            % directory)
                 continue
@@ -668,8 +728,6 @@
 
     # for some commands (creation...) we don't want to initialize gettext
     set_language = True
-    # set this to true to avoid false error message while creating an instance
-    creating = False
     # set this to true to allow somethings which would'nt be possible
     repairing = False
 
@@ -972,7 +1030,9 @@
         return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs)
 
     def sendmails(self, msgs):
-        """msgs: list of 2-uple (message object, recipients)"""
+        """msgs: list of 2-uple (message object, recipients). Return False
+        if connection to the smtp server failed, else True.
+        """
         server, port = self['smtp-host'], self['smtp-port']
         SMTP_LOCK.acquire()
         try:
@@ -981,7 +1041,7 @@
             except Exception, ex:
                 self.exception("can't connect to smtp server %s:%s (%s)",
                                server, port, ex)
-                return
+                return False
             heloaddr = '%s <%s>' % (self['sender-name'], self['sender-addr'])
             for msg, recipients in msgs:
                 try:
@@ -992,6 +1052,7 @@
             smtp.close()
         finally:
             SMTP_LOCK.release()
+        return True
 
 set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
 
@@ -1002,7 +1063,7 @@
 
 _EXT_REGISTERED = False
 def register_stored_procedures():
-    from logilab.common.adbh import FunctionDescr
+    from logilab.database import FunctionDescr
     from rql.utils import register_function, iter_funcnode_variables
 
     global _EXT_REGISTERED
@@ -1014,8 +1075,7 @@
         supported_backends = ('postgres', 'sqlite',)
         rtype = 'String'
 
-        @classmethod
-        def st_description(cls, funcnode, mainindex, tr):
+        def st_description(self, funcnode, mainindex, tr):
             return ', '.join(sorted(term.get_description(mainindex, tr)
                                     for term in iter_funcnode_variables(funcnode)))
 
@@ -1027,6 +1087,7 @@
 
     register_function(CONCAT_STRINGS) # XXX bw compat
 
+
     class GROUP_CONCAT(CONCAT_STRINGS):
         supported_backends = ('mysql', 'postgres', 'sqlite',)
 
@@ -1037,8 +1098,7 @@
         supported_backends = ('postgres', 'sqlite',)
         rtype = 'String'
 
-        @classmethod
-        def st_description(cls, funcnode, mainindex, tr):
+        def st_description(self, funcnode, mainindex, tr):
             return funcnode.children[0].get_description(mainindex, tr)
 
     register_function(LIMIT_SIZE)
@@ -1050,9 +1110,25 @@
     register_function(TEXT_LIMIT_SIZE)
 
 
+    class FSPATH(FunctionDescr):
+        """return path of some bytes attribute stored using the Bytes
+        File-System Storage (bfss)
+        """
+        rtype = 'Bytes' # XXX return a String? potential pb with fs encoding
 
-    class FSPATH(FunctionDescr):
-        supported_backends = ('postgres', 'sqlite',)
-        rtype = 'Bytes'
+        def update_cb_stack(self, stack):
+            assert len(stack) == 1
+            stack[0] = self.source_execute
+
+        def as_sql(self, backend, args):
+            raise NotImplementedError('source only callback')
+
+        def source_execute(self, source, value):
+            fpath = source.binary_to_str(value)
+            try:
+                return Binary(fpath)
+            except OSError, ex:
+                self.critical("can't open %s: %s", fpath, ex)
+                return None
 
     register_function(FSPATH)
--- a/cwctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/cwctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,24 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """the cubicweb-ctl tool, based on logilab.common.clcommands to
 provide a pluggable commands system.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -232,9 +245,11 @@
                 if not isinstance(use, dict):
                     use = dict((key, None) for key in use)
                     self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list')
-            else:
+            elif hasattr(info, '__use__'):
                 self.warnings.append('cube %s should define __depends_cubes__' % cube)
                 use = dict((key, None) for key in info.__use__)
+            else:
+                continue
             for name, constraint in use.items():
                 self.constraints.setdefault(name,set())
                 if constraint:
@@ -383,7 +398,6 @@
         cubes = splitstrip(pop_arg(args, 1))
         appid = pop_arg(args)
         # get the configuration and helper
-        cwcfg.creating = True
         config = cwcfg.config_for(appid, configname)
         config.set_language = False
         cubes = config.expand_cubes(cubes)
@@ -797,7 +811,7 @@
         # handle i18n upgrade:
         # * install new languages
         # * recompile catalogs
-        # in the first componant given
+        # XXX search available language in the first cube given
         from cubicweb import i18n
         templdir = cwcfg.cube_dir(config.cubes()[0])
         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
@@ -812,7 +826,12 @@
         print
         print '-> instance migrated.'
         if not (CWDEV or self.config.nostartstop):
-            StartInstanceCommand().start_instance(appid)
+            # restart instance through fork to get a proper environment, avoid
+            # uicfg pb (and probably gettext catalogs, to check...)
+            forkcmd = '%s start %s' % (sys.argv[0], appid)
+            status = system(forkcmd)
+            if status:
+                print '%s exited with status %s' % (forkcmd, status)
         print
 
 
@@ -932,20 +951,11 @@
     def i18ninstance_instance(appid):
         """recompile instance's messages catalogs"""
         config = cwcfg.config_for(appid)
-        try:
-            config.bootstrap_cubes()
-        except IOError, ex:
-            import errno
-            if ex.errno != errno.ENOENT:
-                raise
-            # bootstrap_cubes files doesn't exist
-            # notify this is not a regular start
-            config.repairing = True
-            # create an in-memory repository, will call config.init_cubes()
-            config.repository()
-        except AttributeError:
+        config.quick_start = True # notify this is not a regular start
+        repo = config.repository()
+        if config._cubes is None:
             # web only config
-            config.init_cubes(config.repository().get_cubes())
+            config.init_cubes(repo.get_cubes())
         errors = config.i18ncompile()
         if errors:
             print '\n'.join(errors)
--- a/cwvreg.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/cwvreg.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,193 @@
-"""extend the generic VRegistry with some cubicweb specific stuff
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+""".. VRegistry:
+
+The `VRegistry`
+---------------
+
+The `VRegistry` can be seen as a two levels dictionary. It contains
+all dynamically loaded objects (subclasses of :ref:`appobject`) to
+build a |cubicweb| application. Basically:
+
+* the first level key returns a *registry*. This key corresponds to the
+  `__registry__` attribute of application object classes
+
+* the second level key returns a list of application objects which
+  share the same identifier. This key corresponds to the `__regid__`
+  attribute of application object classes.
+
+A *registry* holds a specific kind of application objects. There is
+for instance a registry for entity classes, another for views, etc...
+
+The `VRegistry` has two main responsibilities:
+
+- being the access point to all registries
+
+- handling the registration process at startup time, and during automatic
+  reloading in debug mode.
+
+.. _AppObjectRecording:
+
+Details of the recording process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. index::
+   vregistry: registration_callback
+
+On startup |cubicweb| loads application objects defined in its library
+and in cubes used by the instance. Application objects from the
+library are loaded first, then those provided by cubes are loaded in
+dependency order (e.g. if your cube depends on an other, objects from
+the dependency will be loaded first). Cube's modules or packages where
+appobject are looked for is explained in :ref:`cubelayout`.
+
+For each module:
+
+* by default all objects are registered automatically
+
+* if some objects have to replace other objects, or have to be
+  included only if some condition is met, you'll have to define a
+  `registration_callback(vreg)` function in your module and explicitly
+  register **all objects** in this module, using the api defined
+  below.
+
+.. Note::
+    Once the function `registration_callback(vreg)` is implemented in a module,
+    all the objects from this module have to be explicitly registered as it
+    disables the automatic objects registration.
+
+
+API for objects registration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here are the registration methods that you can use in the `registration_callback`
+to register your objects to the `VRegistry` instance given as argument (usually
+named `vreg`):
+
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister
+
+Examples:
+
+.. sourcecode:: python
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+   # web/views/basecomponents.py
+   def registration_callback(vreg):
+      # register everything in the module except SeeAlsoComponent
+      vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+      # conditionally register SeeAlsoVComponent
+      if 'see_also' in vreg.schema:
+          vreg.register(SeeAlsoVComponent)
+
+In this example, we register all application object classes defined in the module
+except `SeeAlsoVComponent`. This class is then registered only if the 'see_also'
+relation type is defined in the instance'schema.
+
+.. sourcecode:: python
+
+   # goa/appobjects/sessions.py
+   def registration_callback(vreg):
+      vreg.register(SessionsCleaner)
+      # replace AuthenticationManager by GAEAuthenticationManager
+      vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager)
+      # replace PersistentSessionManager by GAEPersistentSessionManager
+      vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
+
+In this example, we explicitly register classes one by one:
+
+* the `SessionCleaner` class
+* the `GAEAuthenticationManager` to replace the `AuthenticationManager`
+* the `GAEPersistentSessionManager` to replace the `PersistentSessionManager`
+
+If at some point we register a new appobject class in this module, it won't be
+registered at all without modification to the `registration_callback`
+implementation. The previous example will register it though, thanks to the call
+to the `register_all` method.
+
+
+.. _Selection:
+
+Runtime objects selection
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now that we have all application objects loaded, the question is : when
+I want some specific object, for instance the primary view for a given
+entity, how do I get the proper object ? This is what we call the
+**selection mechanism**.
+
+As explained in the :ref:`Concepts` section:
+
+* each application object has a **selector**, defined by its
+  `__select__` class attribute
+
+* this selector is responsible to return a **score** for a given context
+
+  - 0 score means the object doesn't apply to this context
+
+  - else, the higher the score, the better the object suits the context
+
+* the object with the higher score is selected.
+
+.. Note::
+
+  When no score is higher than the others, an exception is raised in development
+  mode to let you know that the engine was not able to identify the view to
+  apply. This error is silenced in production mode and one of the objects with
+  the higher score is picked.
+
+  In such cases you would need to review your design and make sure your selectors
+  or appobjects are properly defined.
+
+For instance, if you are selecting the primary (eg `__regid__ =
+'primary'`) view (eg `__registry__ = 'views'`) for a result set
+containing a `Card` entity, two objects will probably be selectable:
+
+* the default primary view (`__select__ = implements('Any')`), meaning
+  that the object is selectable for any kind of entity type
+
+* the specific `Card` primary view (`__select__ = implements('Card')`,
+  meaning that the object is selectable for Card entities
+
+Other primary views specific to other entity types won't be selectable in this
+case. Among selectable objects, the implements selector will return a higher
+score than the second view since it's more specific, so it will be selected as
+expected.
+
+.. _SelectionAPI:
+
+API for objects selections
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is the selection API you'll get on every registry. Some of them, as the
+'etypes' registry, containing entity classes, extend it. In those methods,
+`*args, **kwargs` is what we call the **context**. Those arguments are given to
+selectors that will inspect there content and return a score accordingly.
+
+.. automethod:: cubicweb.vregistry.Registry.select
+
+.. automethod:: cubicweb.vregistry.Registry.select_or_none
+
+.. automethod:: cubicweb.vregistry.Registry.possible_objects
+
+.. automethod:: cubicweb.vregistry.Registry.object_by_id
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -16,18 +200,15 @@
 
 from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid,
                       ObjectNotFound, NoSelectableObject, RegistryNotFound,
-                      RegistryOutOfDate, CW_EVENT_MANAGER, onevent)
+                      CW_EVENT_MANAGER, onevent)
 from cubicweb.utils import dump_class
 from cubicweb.vregistry import VRegistry, Registry, class_regid
 from cubicweb.rtags import RTAGS
 
-
-@onevent('before-registry-reload')
 def clear_rtag_objects():
     for rtag in RTAGS:
         rtag.clear()
 
-
 def use_interfaces(obj):
     """return interfaces used by the given object by searching for implements
     selectors, with a bw compat fallback to accepts_interfaces attribute
@@ -265,6 +446,11 @@
         self.schema = None
         self.initialized = False
         self.reset()
+        # XXX give force_reload (or refactor [re]loading...)
+        if self.config.mode != 'test':
+            # don't clear rtags during test, this may cause breakage with
+            # manually imported appobject modules
+            CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
 
     def setdefault(self, regid):
         try:
@@ -285,8 +471,8 @@
     def itervalues(self):
         return (value for key, value in self.items())
 
-    def reset(self, path=None, force_reload=None):
-        super(CubicWebVRegistry, self).reset(path, force_reload)
+    def reset(self):
+        super(CubicWebVRegistry, self).reset()
         self._needs_iface = {}
         # two special registries, propertydefs which care all the property
         # definitions, and propertyvals which contains values for those
@@ -296,7 +482,29 @@
             self['propertyvalues'] = self.eprop_values = {}
             for key, propdef in self.config.eproperty_definitions():
                 self.register_property(key, **propdef)
-        if path is not None and force_reload:
+
+    def set_schema(self, schema):
+        """set instance'schema and load application objects"""
+        self._set_schema(schema)
+        # now we can load application's web objects
+        self.reload(self.config.vregistry_path(), force_reload=False)
+        # map lowered entity type names to their actual name
+        self.case_insensitive_etypes = {}
+        for eschema in self.schema.entities():
+            etype = str(eschema)
+            self.case_insensitive_etypes[etype.lower()] = etype
+            clear_cache(eschema, 'ordered_relations')
+            clear_cache(eschema, 'meta_attributes')
+
+    def reload_if_needed(self):
+        path = self.config.vregistry_path()
+        if self.is_reload_needed(path):
+            self.reload(path)
+
+    def reload(self, path, force_reload=True):
+        """modification detected, reset and reload the vreg"""
+        CW_EVENT_MANAGER.emit('before-registry-reload')
+        if force_reload:
             cleanup_sys_modules(path)
             cubes = self.config.cubes()
             # if the fs code use some cubes not yet registered into the instance
@@ -307,21 +515,9 @@
                 if not cube in cubes:
                     cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)])
                     cleanup_sys_modules(cpath)
-
-    def set_schema(self, schema):
-        """set instance'schema and load application objects"""
-        self._set_schema(schema)
-        # now we can load application's web objects
-        searchpath = self.config.vregistry_path()
-        self.reset(searchpath, force_reload=False)
-        self.register_objects(searchpath, force_reload=False)
-        # map lowered entity type names to their actual name
-        self.case_insensitive_etypes = {}
-        for eschema in self.schema.entities():
-            etype = str(eschema)
-            self.case_insensitive_etypes[etype.lower()] = etype
-            clear_cache(eschema, 'ordered_relations')
-            clear_cache(eschema, 'meta_attributes')
+        self.reset()
+        self.register_objects(path, force_reload)
+        CW_EVENT_MANAGER.emit('after-registry-reload')
 
     def _set_schema(self, schema):
         """set instance'schema"""
@@ -339,8 +535,11 @@
                     obj.schema = schema
 
     def register_if_interface_found(self, obj, ifaces, **kwargs):
-        """register an object but remove it if no entity class implements one of
-        the given interfaces at the end of the registration process
+        """register `obj` but remove it if no entity class implements one of
+        the given `ifaces` interfaces at the end of the registration process.
+
+        Extra keyword arguments are given to the
+        :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function.
         """
         self.register(obj, **kwargs)
         if not isinstance(ifaces,  (tuple, list)):
@@ -349,26 +548,23 @@
             self._needs_iface[obj] = ifaces
 
     def register(self, obj, *args, **kwargs):
+        """register `obj` application object into `registryname` or
+        `obj.__registry__` if not specified, with identifier `oid` or
+        `obj.__regid__` if not specified.
+
+        If `clear` is true, all objects with the same identifier will be
+        previously unregistered.
+        """
         super(CubicWebVRegistry, self).register(obj, *args, **kwargs)
         # XXX bw compat
         ifaces = use_interfaces(obj)
         if ifaces:
             self._needs_iface[obj] = ifaces
 
-    def register_objects(self, path, force_reload=None):
+    def register_objects(self, path, force_reload=False):
         """overriden to remove objects requiring a missing interface"""
-        if force_reload is None:
-            force_reload = self.config.debugmode
-        try:
-            super(CubicWebVRegistry, self).register_objects(
-                path, force_reload, self.config.extrapath)
-        except RegistryOutOfDate:
-            CW_EVENT_MANAGER.emit('before-registry-reload')
-            # modification detected, reset and reload
-            self.reset(path, force_reload)
-            super(CubicWebVRegistry, self).register_objects(
-                path, force_reload, self.config.extrapath)
-            CW_EVENT_MANAGER.emit('after-registry-reload')
+        super(CubicWebVRegistry, self).register_objects(
+            path, force_reload, self.config.extrapath)
 
     def initialization_completed(self):
         """cw specific code once vreg initialization is completed:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dataimport.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,708 @@
+# -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""This module provides tools to import tabular data.
+
+
+
+Example of use (run this with `cubicweb-ctl shell instance import-script.py`):
+
+.. sourcecode:: python
+
+  from cubicweb.devtools.dataimport import *
+  # define data generators
+  GENERATORS = []
+
+  USERS = [('Prenom', 'firstname', ()),
+           ('Nom', 'surname', ()),
+           ('Identifiant', 'login', ()),
+           ]
+
+  def gen_users(ctl):
+      for row in ctl.get_data('utilisateurs'):
+          entity = mk_entity(row, USERS)
+          entity['upassword'] = u'motdepasse'
+          ctl.check('login', entity['login'], None)
+          ctl.store.add('CWUser', entity)
+          email = {'address': row['email']}
+          ctl.store.add('EmailAddress', email)
+          ctl.store.relate(entity['eid'], 'use_email', email['eid'])
+          ctl.store.rql('SET U in_group G WHERE G name "users", U eid %(x)s', {'x':entity['eid']})
+
+  CHK = [('login', check_doubles, 'Utilisateurs Login',
+          'Deux utilisateurs ne devraient pas avoir le même login.'),
+         ]
+
+  GENERATORS.append( (gen_users, CHK) )
+
+  # create controller
+  ctl = CWImportController(RQLObjectStore(cnx))
+  ctl.askerror = 1
+  ctl.generators = GENERATORS
+  ctl.data['utilisateurs'] = lazytable(utf8csvreader(open('users.csv')))
+  # run
+  ctl.run()
+
+.. BUG file with one column are not parsable
+.. TODO rollback() invocation is not possible yet
+"""
+__docformat__ = "restructuredtext en"
+
+import sys
+import csv
+import traceback
+import os.path as osp
+from StringIO import StringIO
+from copy import copy
+
+from logilab.common import shellutils
+from logilab.common.date import strptime
+from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
+
+from cubicweb.server.utils import eschema_eid
+
+def ucsvreader_pb(filepath, encoding='utf-8', separator=',', quote='"',
+                  skipfirst=False, withpb=True):
+    """same as ucsvreader but a progress bar is displayed as we iter on rows"""
+    if not osp.exists(filepath):
+        raise Exception("file doesn't exists: %s" % filepath)
+    rowcount = int(shellutils.Execute('wc -l "%s"' % filepath).out.strip().split()[0])
+    if skipfirst:
+        rowcount -= 1
+    if withpb:
+        pb = shellutils.ProgressBar(rowcount, 50)
+    for urow in ucsvreader(file(filepath), encoding, separator, quote, skipfirst):
+        yield urow
+        if withpb:
+            pb.update()
+    print ' %s rows imported' % rowcount
+
+def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
+               skipfirst=False):
+    """A csv reader that accepts files with any encoding and outputs unicode
+    strings
+    """
+    it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
+    if skipfirst:
+        it.next()
+    for row in it:
+        yield [item.decode(encoding) for item in row]
+
+def commit_every(nbit, store, it):
+    for i, x in enumerate(it):
+        yield x
+        if nbit is not None and i % nbit:
+            store.commit()
+    if nbit is not None:
+        store.commit()
+
+def lazytable(reader):
+    """The first row is taken to be the header of the table and
+    used to output a dict for each row of data.
+
+    >>> data = lazytable(utf8csvreader(open(filename)))
+    """
+    header = reader.next()
+    for row in reader:
+        yield dict(zip(header, row))
+
+def mk_entity(row, map):
+    """Return a dict made from sanitized mapped values.
+
+    ValueError can be raised on unexpected values found in checkers
+
+    >>> row = {'myname': u'dupont'}
+    >>> map = [('myname', u'name', (call_transform_method('title'),))]
+    >>> mk_entity(row, map)
+    {'name': u'Dupont'}
+    >>> row = {'myname': u'dupont', 'optname': u''}
+    >>> map = [('myname', u'name', (call_transform_method('title'),)),
+    ...        ('optname', u'MARKER', (optional,))]
+    >>> mk_entity(row, map)
+    {'name': u'Dupont', 'optname': None}
+    """
+    res = {}
+    assert isinstance(row, dict)
+    assert isinstance(map, list)
+    for src, dest, funcs in map:
+        res[dest] = row[src]
+        try:
+            for func in funcs:
+                res[dest] = func(res[dest])
+                if res[dest] is None:
+                    break
+        except ValueError, err:
+            raise ValueError('error with %r field: %s' % (src, err))
+    return res
+
+
+# user interactions ############################################################
+
+def tell(msg):
+    print msg
+
+def confirm(question):
+    """A confirm function that asks for yes/no/abort and exits on abort."""
+    answer = shellutils.ASK.ask(question, ('Y', 'n', 'abort'), 'Y')
+    if answer == 'abort':
+        sys.exit(1)
+    return answer == 'Y'
+
+
+class catch_error(object):
+    """Helper for @contextmanager decorator."""
+
+    def __init__(self, ctl, key='unexpected error', msg=None):
+        self.ctl = ctl
+        self.key = key
+        self.msg = msg
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        if type is not None:
+            if issubclass(type, (KeyboardInterrupt, SystemExit)):
+                return # re-raise
+            if self.ctl.catcherrors:
+                self.ctl.record_error(self.key, None, type, value, traceback)
+                return True # silent
+
+
+# base sanitizing/coercing functions ###########################################
+
+def optional(value):
+    """checker to filter optional field
+
+    If value is undefined (ex: empty string), return None that will
+    break the checkers validation chain
+
+    General use is to add 'optional' check in first condition to avoid
+    ValueError by further checkers
+
+    >>> MAPPER = [(u'value', 'value', (optional, int))]
+    >>> row = {'value': u'XXX'}
+    >>> mk_entity(row, MAPPER)
+    {'value': None}
+    >>> row = {'value': u'100'}
+    >>> mk_entity(row, MAPPER)
+    {'value': 100}
+    """
+    if value:
+        return value
+    return None
+
+def required(value):
+    """raise ValueError is value is empty
+
+    This check should be often found in last position in the chain.
+    """
+    if value:
+        return value
+    raise ValueError("required")
+
+def todatetime(format='%d/%m/%Y'):
+    """return a transformation function to turn string input value into a
+    `datetime.datetime` instance, using given format.
+
+    Follow it by `todate` or `totime` functions from `logilab.common.date` if
+    you want a `date`/`time` instance instead of `datetime`.
+    """
+    def coerce(value):
+        return strptime(value, format)
+    return coerce
+
+def call_transform_method(methodname, *args, **kwargs):
+    """return value returned by calling the given method on input"""
+    def coerce(value):
+        return getattr(value, methodname)(*args, **kwargs)
+    return coerce
+
+def call_check_method(methodname, *args, **kwargs):
+    """check value returned by calling the given method on input is true,
+    else raise ValueError
+    """
+    def check(value):
+        if getattr(value, methodname)(*args, **kwargs):
+            return value
+        raise ValueError('%s not verified on %r' % (methodname, value))
+    return check
+
+# base integrity checking functions ############################################
+
+def check_doubles(buckets):
+    """Extract the keys that have more than one item in their bucket."""
+    return [(k, len(v)) for k, v in buckets.items() if len(v) > 1]
+
+def check_doubles_not_none(buckets):
+    """Extract the keys that have more than one item in their bucket."""
+    return [(k, len(v)) for k, v in buckets.items()
+            if k is not None and len(v) > 1]
+
+
+# object stores #################################################################
+
+class ObjectStore(object):
+    """Store objects in memory for *faster* validation (development mode)
+
+    But it will not enforce the constraints of the schema and hence will miss some problems
+
+    >>> store = ObjectStore()
+    >>> user = {'login': 'johndoe'}
+    >>> store.add('CWUser', user)
+    >>> group = {'name': 'unknown'}
+    >>> store.add('CWUser', group)
+    >>> store.relate(user['eid'], 'in_group', group['eid'])
+    """
+    def __init__(self):
+        self.items = []
+        self.eids = {}
+        self.types = {}
+        self.relations = set()
+        self.indexes = {}
+        self._rql = None
+        self._commit = None
+
+    def _put(self, type, item):
+        self.items.append(item)
+        return len(self.items) - 1
+
+    def add(self, type, item):
+        assert isinstance(item, dict), 'item is not a dict but a %s' % type(item)
+        eid = item['eid'] = self._put(type, item)
+        self.eids[eid] = item
+        self.types.setdefault(type, []).append(eid)
+
+    def relate(self, eid_from, rtype, eid_to, inlined=False):
+        """Add new relation"""
+        relation = eid_from, rtype, eid_to
+        self.relations.add(relation)
+        return relation
+
+    def commit(self):
+        """this commit method do nothing by default
+
+        This is voluntary to use the frequent autocommit feature in CubicWeb
+        when you are using hooks or another
+
+        If you want override commit method, please set it by the
+        constructor
+        """
+        pass
+
+    def rql(self, *args):
+        if self._rql is not None:
+            return self._rql(*args)
+
+    @property
+    def nb_inserted_entities(self):
+        return len(self.eids)
+    @property
+    def nb_inserted_types(self):
+        return len(self.types)
+    @property
+    def nb_inserted_relations(self):
+        return len(self.relations)
+
+    @deprecated("[3.7] index support will disappear")
+    def build_index(self, name, type, func=None, can_be_empty=False):
+        """build internal index for further search"""
+        index = {}
+        if func is None or not callable(func):
+            func = lambda x: x['eid']
+        for eid in self.types[type]:
+            index.setdefault(func(self.eids[eid]), []).append(eid)
+        if not can_be_empty:
+            assert index, "new index '%s' cannot be empty" % name
+        self.indexes[name] = index
+
+    @deprecated("[3.7] index support will disappear")
+    def build_rqlindex(self, name, type, key, rql, rql_params=False,
+                       func=None, can_be_empty=False):
+        """build an index by rql query
+
+        rql should return eid in first column
+        ctl.store.build_index('index_name', 'users', 'login', 'Any U WHERE U is CWUser')
+        """
+        self.types[type] = []
+        rset = self.rql(rql, rql_params or {})
+        if not can_be_empty:
+            assert rset, "new index type '%s' cannot be empty (0 record found)" % type
+        for entity in rset.entities():
+            getattr(entity, key) # autopopulate entity with key attribute
+            self.eids[entity.eid] = dict(entity)
+            if entity.eid not in self.types[type]:
+                self.types[type].append(entity.eid)
+
+        # Build index with specified key
+        func = lambda x: x[key]
+        self.build_index(name, type, func, can_be_empty=can_be_empty)
+
+    @deprecated("[3.7] index support will disappear")
+    def fetch(self, name, key, unique=False, decorator=None):
+        """index fetcher method
+
+        decorator is a callable method or an iterator of callable methods (usually a lambda function)
+        decorator=lambda x: x[:1] (first value is returned)
+        decorator=lambda x: x.lower (lowercased value is returned)
+
+        decorator is handy when you want to improve index keys but without
+        changing the original field
+
+        Same check functions can be reused here.
+        """
+        eids = self.indexes[name].get(key, [])
+        if decorator is not None:
+            if not hasattr(decorator, '__iter__'):
+                decorator = (decorator,)
+            for f in decorator:
+                eids = f(eids)
+        if unique:
+            assert len(eids) == 1, u'expected a single one value for key "%s" in index "%s". Got %i' % (key, name, len(eids))
+            eids = eids[0]
+        return eids
+
+    @deprecated("[3.7] index support will disappear")
+    def find(self, type, key, value):
+        for idx in self.types[type]:
+            item = self.items[idx]
+            if item[key] == value:
+                yield item
+
+    @deprecated("[3.7] checkpoint() deprecated. use commit() instead")
+    def checkpoint(self):
+        self.commit()
+
+
+class RQLObjectStore(ObjectStore):
+    """ObjectStore that works with an actual RQL repository (production mode)"""
+    _rql = None # bw compat
+
+    def __init__(self, session=None, commit=None):
+        ObjectStore.__init__(self)
+        if session is not None:
+            if not hasattr(session, 'set_pool'):
+                # connection
+                cnx = session
+                session = session.request()
+                session.set_pool = lambda : None
+                commit = commit or cnx.commit
+            else:
+                session.set_pool()
+            self.session = session
+            self._commit = commit or session.commit
+        elif commit is not None:
+            self._commit = commit
+            # XXX .session
+
+    @deprecated("[3.7] checkpoint() deprecated. use commit() instead")
+    def checkpoint(self):
+        self.commit()
+
+    def commit(self):
+        txuuid = self._commit()
+        self.session.set_pool()
+        return txuuid
+
+    def rql(self, *args):
+        if self._rql is not None:
+            return self._rql(*args)
+        return self.session.execute(*args)
+
+    def create_entity(self, *args, **kwargs):
+        entity = self.session.create_entity(*args, **kwargs)
+        self.eids[entity.eid] = entity
+        self.types.setdefault(args[0], []).append(entity.eid)
+        return entity
+
+    def _put(self, type, item):
+        query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
+                                                     for k in item)
+        return self.rql(query, item)[0][0]
+
+    def relate(self, eid_from, rtype, eid_to, inlined=False):
+        eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
+            eid_from, rtype, eid_to)
+        self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
+                  {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
+
+
+# the import controller ########################################################
+
+class CWImportController(object):
+    """Controller of the data import process.
+
+    >>> ctl = CWImportController(store)
+    >>> ctl.generators = list_of_data_generators
+    >>> ctl.data = dict_of_data_tables
+    >>> ctl.run()
+    """
+
+    def __init__(self, store, askerror=0, catcherrors=None, tell=tell,
+                 commitevery=50):
+        self.store = store
+        self.generators = None
+        self.data = {}
+        self.errors = None
+        self.askerror = askerror
+        if  catcherrors is None:
+            catcherrors = askerror
+        self.catcherrors = catcherrors
+        self.commitevery = commitevery # set to None to do a single commit
+        self._tell = tell
+
+    def check(self, type, key, value):
+        self._checks.setdefault(type, {}).setdefault(key, []).append(value)
+
+    def check_map(self, entity, key, map, default):
+        try:
+            entity[key] = map[entity[key]]
+        except KeyError:
+            self.check(key, entity[key], None)
+            entity[key] = default
+
+    def record_error(self, key, msg=None, type=None, value=None, tb=None):
+        tmp = StringIO()
+        if type is None:
+            traceback.print_exc(file=tmp)
+        else:
+            traceback.print_exception(type, value, tb, file=tmp)
+        print tmp.getvalue()
+        # use a list to avoid counting a <nb lines> errors instead of one
+        errorlog = self.errors.setdefault(key, [])
+        if msg is None:
+            errorlog.append(tmp.getvalue().splitlines())
+        else:
+            errorlog.append( (msg, tmp.getvalue().splitlines()) )
+
+    def run(self):
+        self.errors = {}
+        for func, checks in self.generators:
+            self._checks = {}
+            func_name = func.__name__
+            self.tell("Run import function '%s'..." % func_name)
+            try:
+                func(self)
+            except:
+                if self.catcherrors:
+                    self.record_error(func_name, 'While calling %s' % func.__name__)
+                else:
+                    self._print_stats()
+                    raise
+            for key, func, title, help in checks:
+                buckets = self._checks.get(key)
+                if buckets:
+                    err = func(buckets)
+                    if err:
+                        self.errors[title] = (help, err)
+        txuuid = self.store.commit()
+        self._print_stats()
+        if self.errors:
+            if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
+                from pprint import pformat
+                for errkey, error in self.errors.items():
+                    self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
+                    self.tell(pformat(sorted(error[1])))
+        if txuuid is not None:
+            print 'transaction id:', txuuid
+    def _print_stats(self):
+        nberrors = sum(len(err[1]) for err in self.errors.values())
+        self.tell('\nImport statistics: %i entities, %i types, %i relations and %i errors'
+                  % (self.store.nb_inserted_entities,
+                     self.store.nb_inserted_types,
+                     self.store.nb_inserted_relations,
+                     nberrors))
+
+    def get_data(self, key):
+        return self.data.get(key)
+
+    def index(self, name, key, value, unique=False):
+        """create a new index
+
+        If unique is set to True, only first occurence will be kept not the following ones
+        """
+        if unique:
+            try:
+                if value in self.store.indexes[name][key]:
+                    return
+            except KeyError:
+                # we're sure that one is the first occurence; so continue...
+                pass
+        self.store.indexes.setdefault(name, {}).setdefault(key, []).append(value)
+
+    def tell(self, msg):
+        self._tell(msg)
+
+    def iter_and_commit(self, datakey):
+        """iter rows, triggering commit every self.commitevery iterations"""
+        return commit_every(self.commitevery, self.store, self.get_data(datakey))
+
+
+
+from datetime import datetime
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
+
+
+class NoHookRQLObjectStore(RQLObjectStore):
+    """ObjectStore that works with an actual RQL repository (production mode)"""
+    _rql = None # bw compat
+
+    def __init__(self, session, metagen=None, baseurl=None):
+        super(NoHookRQLObjectStore, self).__init__(session)
+        self.source = session.repo.system_source
+        self.rschema = session.repo.schema.rschema
+        self.add_relation = self.source.add_relation
+        if metagen is None:
+            metagen = MetaGenerator(session, baseurl)
+        self.metagen = metagen
+        self._nb_inserted_entities = 0
+        self._nb_inserted_types = 0
+        self._nb_inserted_relations = 0
+        self.rql = session.execute
+        # deactivate security
+        session.set_read_security(False)
+        session.set_write_security(False)
+
+    def create_entity(self, etype, **kwargs):
+        for k, v in kwargs.iteritems():
+            kwargs[k] = getattr(v, 'eid', v)
+        entity, rels = self.metagen.base_etype_dicts(etype)
+        entity = copy(entity)
+        entity._related_cache = {}
+        self.metagen.init_entity(entity)
+        entity.update(kwargs)
+        entity.edited_attributes = set(entity)
+        session = self.session
+        self.source.add_entity(session, entity)
+        self.source.add_info(session, entity, self.source, None, complete=False)
+        for rtype, targeteids in rels.iteritems():
+            # targeteids may be a single eid or a list of eids
+            inlined = self.rschema(rtype).inlined
+            try:
+                for targeteid in targeteids:
+                    self.add_relation(session, entity.eid, rtype, targeteid,
+                                      inlined)
+            except TypeError:
+                self.add_relation(session, entity.eid, rtype, targeteids,
+                                  inlined)
+        self._nb_inserted_entities += 1
+        return entity
+
+    def relate(self, eid_from, rtype, eid_to):
+        assert not rtype.startswith('reverse_')
+        self.add_relation(self.session, eid_from, rtype, eid_to,
+                          self.rschema(rtype).inlined)
+        self._nb_inserted_relations += 1
+
+    @property
+    def nb_inserted_entities(self):
+        return self._nb_inserted_entities
+    @property
+    def nb_inserted_types(self):
+        return self._nb_inserted_types
+    @property
+    def nb_inserted_relations(self):
+        return self._nb_inserted_relations
+
+    def _put(self, type, item):
+        raise RuntimeError('use create entity')
+
+
+class MetaGenerator(object):
+    def __init__(self, session, baseurl=None):
+        self.session = session
+        self.source = session.repo.system_source
+        self.time = datetime.now()
+        if baseurl is None:
+            config = session.vreg.config
+            baseurl = config['base-url'] or config.default_base_url()
+        if not baseurl[-1] == '/':
+            baseurl += '/'
+        self.baseurl =  baseurl
+        # attributes/relations shared by all entities of the same type
+        self.etype_attrs = []
+        self.etype_rels = []
+        # attributes/relations specific to each entity
+        self.entity_attrs = ['cwuri']
+        #self.entity_rels = [] XXX not handled (YAGNI?)
+        schema = session.vreg.schema
+        rschema = schema.rschema
+        for rtype in META_RTYPES:
+            if rtype in ('eid', 'cwuri') or rtype in VIRTUAL_RTYPES:
+                continue
+            if rschema(rtype).final:
+                self.etype_attrs.append(rtype)
+            else:
+                self.etype_rels.append(rtype)
+        if not schema._eid_index:
+            # test schema loaded from the fs
+            self.gen_is = self.test_gen_is
+            self.gen_is_instance_of = self.test_gen_is_instanceof
+
+    @cached
+    def base_etype_dicts(self, etype):
+        entity = self.session.vreg['etypes'].etype_class(etype)(self.session)
+        # entity are "surface" copied, avoid shared dict between copies
+        del entity.cw_extra_kwargs
+        for attr in self.etype_attrs:
+            entity[attr] = self.generate(entity, attr)
+        rels = {}
+        for rel in self.etype_rels:
+            rels[rel] = self.generate(entity, rel)
+        return entity, rels
+
+    def init_entity(self, entity):
+        entity.eid = self.source.create_eid(self.session)
+        for attr in self.entity_attrs:
+            entity[attr] = self.generate(entity, attr)
+
+    def generate(self, entity, rtype):
+        return getattr(self, 'gen_%s' % rtype)(entity)
+
+    def gen_cwuri(self, entity):
+        return u'%seid/%s' % (self.baseurl, entity.eid)
+
+    def gen_creation_date(self, entity):
+        return self.time
+    def gen_modification_date(self, entity):
+        return self.time
+
+    def gen_is(self, entity):
+        return entity.e_schema.eid
+    def gen_is_instance_of(self, entity):
+        eids = []
+        for etype in entity.e_schema.ancestors() + [entity.e_schema]:
+            eids.append(entity.e_schema.eid)
+        return eids
+
+    def gen_created_by(self, entity):
+        return self.session.user.eid
+    def gen_owned_by(self, entity):
+        return self.session.user.eid
+
+    # implementations of gen_is / gen_is_instance_of to use during test where
+    # schema has been loaded from the fs (hence entity type schema eids are not
+    # known)
+    def test_gen_is(self, entity):
+        return eschema_eid(self.session, entity.e_schema)
+    def test_gen_is_instanceof(self, entity):
+        eids = []
+        for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+            eids.append(eschema_eid(self.session, eschema))
+        return eids
--- a/dbapi.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/dbapi.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """DB-API 2.0 compliant module
 
 Take a look at http://www.python.org/peps/pep-0249.html
 
 (most parts of this document are reported here in docstrings)
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -57,6 +70,7 @@
     etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes']
     etypescls.etype_class = etypescls.orig_etype_class
 
+
 class ConnectionProperties(object):
     def __init__(self, cnxtype=None, lang=None, close=True, log=False):
         self.cnxtype = cnxtype or 'pyro'
@@ -105,11 +119,50 @@
 def connect(database=None, login=None, host=None, group=None,
             cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs):
     """Constructor for creating a connection to the CubicWeb repository.
-    Returns a Connection object.
+    Returns a :class:`Connection` object.
+
+    Typical usage::
+
+      cnx = connect('myinstance', login='me', password='toto')
+
+    Arguments:
+
+    :database:
+      the instance's pyro identifier.
+
+    :login:
+      the user login to use to authenticate.
+
+    :host:
+      the pyro nameserver host. Will be detected using broadcast query if
+      unspecified.
+
+    :group:
+      the instance's pyro nameserver group. You don't have to specify it unless
+      tweaked in instance's configuration.
 
-    When method is 'pyro', setvreg is True, try to deal with connections to
-    differents instances in the same process unless specified otherwise by
-    setting the mulcnx to False.
+    :cnxprops:
+      an optional :class:`ConnectionProperties` instance, allowing to specify
+      the connection method (eg in memory or pyro). A Pyro connection will be
+      established if you don't specify that argument.
+
+    :setvreg:
+      flag telling if a registry should be initialized for the connection.
+      Don't change this unless you know what you're doing.
+
+    :mulcnx:
+      Will disappear at some point. Try to deal with connections to differents
+      instances in the same process unless specified otherwise by setting this
+      flag to False. Don't change this unless you know what you're doing.
+
+    :initlog:
+      flag telling if logging should be initialized. You usually don't want
+      logging initialization when establishing the connection from a process
+      where it's already initialized.
+
+    :kwargs:
+      there goes authentication tokens. You usually have to specify for
+      instance a password for the given user, using a named 'password' argument.
     """
     config = cwconfig.CubicWebNoAppConfiguration()
     if host:
@@ -203,11 +256,6 @@
             self.pgettext = lambda x, y: y
         self.debug('request default language: %s', self.lang)
 
-    def decorate_rset(self, rset):
-        rset.vreg = self.vreg
-        rset.req = self
-        return rset
-
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         return self.cnx.describe(eid)
@@ -242,7 +290,7 @@
     def get_session_data(self, key, default=None, pop=False):
         """return value associated to `key` in session data"""
         if self.cnx is None:
-            return None # before the connection has been established
+            return default # before the connection has been established
         return self.cnx.get_session_data(key, default, pop)
 
     def set_session_data(self, key, value):
@@ -371,6 +419,16 @@
             return '<Connection %s (anonymous)>' % self.sessionid
         return '<Connection %s>' % self.sessionid
 
+    def __enter__(self):
+        return self.cursor()
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if exc_type is None:
+            self.commit()
+        else:
+            self.rollback()
+            return False #propagate the exception
+
     def request(self):
         return DBAPIRequest(self.vreg, self)
 
@@ -397,15 +455,21 @@
             pass
 
     def check(self):
-        """raise `BadSessionId` if the connection is no more valid"""
+        """raise `BadConnectionId` if the connection is no more valid"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         self._repo.check_session(self.sessionid)
 
     def set_session_props(self, **props):
-        """raise `BadSessionId` if the connection is no more valid"""
+        """raise `BadConnectionId` if the connection is no more valid"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         self._repo.set_session_props(self.sessionid, props)
 
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
 
     def set_shared_data(self, key, value, querydata=False):
@@ -416,6 +480,8 @@
         transaction, and won't be available through the connexion, only on the
         repository side.
         """
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
 
     def get_schema(self):
@@ -501,6 +567,8 @@
     def user(self, req=None, props=None):
         """return the User object associated to this connection"""
         # cnx validity is checked by the call to .user_info
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         eid, login, groups, properties = self._repo.user_info(self.sessionid,
                                                               props)
         if req is None:
@@ -521,6 +589,8 @@
                 pass
 
     def describe(self, eid):
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.describe(self.sessionid, eid)
 
     def close(self):
@@ -535,19 +605,20 @@
         if self._closed:
             raise ProgrammingError('Connection is already closed')
         self._repo.close(self.sessionid)
+        del self._repo # necessary for proper garbage collection
         self._closed = 1
 
     def commit(self):
-        """Commit any pending transaction to the database. Note that if the
-        database supports an auto-commit feature, this must be initially off. An
-        interface method may be provided to turn it back on.
+        """Commit pending transaction for this connection to the repository.
 
-        Database modules that do not support transactions should implement this
-        method with void functionality.
+        may raises `Unauthorized` or `ValidationError` if we attempted to do
+        something we're not allowed to for security or integrity reason.
+
+        If the transaction is undoable, a transaction id will be returned.
         """
         if not self._closed is None:
             raise ProgrammingError('Connection is already closed')
-        self._repo.commit(self.sessionid)
+        return self._repo.commit(self.sessionid)
 
     def rollback(self):
         """This method is optional since not all databases provide transaction
@@ -574,11 +645,78 @@
             req = self.request()
         return self.cursor_class(self, self._repo, req=req)
 
+    # undo support ############################################################
+
+    def undoable_transactions(self, ueid=None, req=None, **actionfilters):
+        """Return a list of undoable transaction objects by the connection's
+        user, ordered by descendant transaction time.
+
+        Managers may filter according to user (eid) who has done the transaction
+        using the `ueid` argument. Others will only see their own transactions.
+
+        Additional filtering capabilities is provided by using the following
+        named arguments:
+
+        * `etype` to get only transactions creating/updating/deleting entities
+          of the given type
+
+        * `eid` to get only transactions applied to entity of the given eid
+
+        * `action` to get only transactions doing the given action (action in
+          'C', 'U', 'D', 'A', 'R'). If `etype`, action can only be 'C', 'U' or
+          'D'.
+
+        * `public`: when additional filtering is provided, their are by default
+          only searched in 'public' actions, unless a `public` argument is given
+          and set to false.
+        """
+        txinfos = self._repo.undoable_transactions(self.sessionid, ueid,
+                                                   **actionfilters)
+        if req is None:
+            req = self.request()
+        for txinfo in txinfos:
+            txinfo.req = req
+        return txinfos
+
+    def transaction_info(self, txuuid, req=None):
+        """Return transaction object for the given uid.
+
+        raise `NoSuchTransaction` if not found or if session's user is not
+        allowed (eg not in managers group and the transaction doesn't belong to
+        him).
+        """
+        txinfo = self._repo.transaction_info(self.sessionid, txuuid)
+        if req is None:
+            req = self.request()
+        txinfo.req = req
+        return txinfo
+
+    def transaction_actions(self, txuuid, public=True):
+        """Return an ordered list of action effectued during that transaction.
+
+        If public is true, return only 'public' actions, eg not ones triggered
+        under the cover by hooks, else return all actions.
+
+        raise `NoSuchTransaction` if the transaction is not found or if
+        session's user is not allowed (eg not in managers group and the
+        transaction doesn't belong to him).
+        """
+        return self._repo.transaction_actions(self.sessionid, txuuid, public)
+
+    def undo_transaction(self, txuuid):
+        """Undo the given transaction. Return potential restoration errors.
+
+        raise `NoSuchTransaction` if not found or if session's user is not
+        allowed (eg not in managers group and the transaction doesn't belong to
+        him).
+        """
+        return self._repo.undo_transaction(self.sessionid, txuuid)
+
 
 # cursor object ###############################################################
 
 class Cursor(object):
-    """These objects represent a database cursor, which is used to manage the
+    """This represents a database cursor, which is used to manage the
     context of a fetch operation. Cursors created from the same connection are
     not isolated, i.e., any changes done to the database by a cursor are
     immediately visible by the other cursors. Cursors created from different
@@ -588,21 +726,18 @@
     """
 
     def __init__(self, connection, repo, req=None):
-        """This read-only attribute return a reference to the Connection
-        object on which the cursor was created.
-        """
+        # This read-only attribute returns a reference to the Connection
+        # object on which the cursor was created.
         self.connection = connection
-        """optionnal issuing request instance"""
+        # optionnal issuing request instance
         self.req = req
 
-        """This read/write attribute specifies the number of rows to fetch at a
-        time with fetchmany(). It defaults to 1 meaning to fetch a single row
-        at a time.
-
-        Implementations must observe this value with respect to the fetchmany()
-        method, but are free to interact with the database a single row at a
-        time. It may also be used in the implementation of executemany().
-        """
+        # This read/write attribute specifies the number of rows to fetch at a
+        # time with fetchmany(). It defaults to 1 meaning to fetch a single row
+        # at a time.
+        # Implementations must observe this value with respect to the fetchmany()
+        # method, but are free to interact with the database a single row at a
+        # time. It may also be used in the implementation of executemany().
         self.arraysize = 1
 
         self._repo = repo
@@ -611,7 +746,6 @@
         self._closed = None
         self._index = 0
 
-
     def close(self):
         """Close the cursor now (rather than whenever __del__ is called).  The
         cursor will be unusable from this point forward; an Error (or subclass)
@@ -646,11 +780,11 @@
         Return values are not defined by the DB-API, but this here it returns a
         ResultSet object.
         """
-        self._res = res = self._repo.execute(self._sessid, operation,
-                                             parameters, eid_key, build_descr)
-        self.req.decorate_rset(res)
+        self._res = rset = self._repo.execute(self._sessid, operation,
+                                              parameters, eid_key, build_descr)
+        rset.req = self.req
         self._index = 0
-        return res
+        return rset
 
 
     def executemany(self, operation, seq_of_parameters):
@@ -781,4 +915,3 @@
         self.connection.executed_queries.append((operation, parameters,
                                                  time() - tstart, clock() - cstart))
         return rset
-
--- a/debian/changelog	Wed Mar 24 10:23:31 2010 +0100
+++ b/debian/changelog	Wed Apr 28 11:54:13 2010 +0200
@@ -1,4 +1,38 @@
-cubicweb (3.6.3-1) unstable; urgency=low
+cubicweb (3.7.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Thu, 15 Apr 2010 18:20:39 +0200
+
+cubicweb (3.7.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 31 Mar 2010 14:55:21 +0200
+
+cubicweb (3.7.2-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 26 Mar 2010 15:53:01 +0100
+
+cubicweb (3.7.1-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 19 Mar 2010 14:47:23 +0100
+
+cubicweb (3.7.0-1) unstable; urgency=low
+
+  * remove postgresql-contrib from cubicweb dependency (using tsearch
+    which is included with postgres >= 8.3)
+  * add postgresql-client | mysql-client to cubicweb-server dependencies using two
+    new cubicweb-[postgresql|mysql]-support virtual packages (necessary for
+    dump/restore of database)
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 16 Mar 2010 17:55:37 +0100
+
+ cubicweb (3.6.3-1) unstable; urgency=low
 
   * remove postgresql-contrib from cubicweb dependency (using tsearch
     which is included with postgres >= 8.3)
--- a/debian/control	Wed Mar 24 10:23:31 2010 +0100
+++ b/debian/control	Wed Apr 28 11:54:13 2010 +0200
@@ -7,10 +7,10 @@
            Adrien Di Mascio <Adrien.DiMascio@logilab.fr>,
            Aurélien Campéas <aurelien.campeas@logilab.fr>,
            Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 5), python-dev (>=2.4), python-central (>= 0.5)
+Build-Depends: debhelper (>= 5), python-dev (>=2.5), python-central (>= 0.5)
 Standards-Version: 3.8.0
 Homepage: http://www.cubicweb.org
-XS-Python-Version: >= 2.4, << 2.6
+XS-Python-Version: >= 2.5, << 2.6
 
 Package: cubicweb
 Architecture: all
@@ -33,8 +33,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-# postgresql/mysql -client packages for backup/restore of non local database
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-indexer (>= 0.6.1), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.0.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
 Recommends: pyro, cubicweb-documentation (= ${source:Version})
 Description: server part of the CubicWeb framework
  CubicWeb is a semantic web application framework.
@@ -51,7 +50,7 @@
 Description: postgres support for the CubicWeb framework
  CubicWeb is a semantic web application framework.
  .
- This virtual package provides dependancies to use postgres for the
+ This virtual package provides dependencies to use postgres for the
  cubicweb repository.
 
 Package: cubicweb-mysql-support
@@ -61,7 +60,7 @@
 Description: mysql support for the CubicWeb framework
  CubicWeb is a semantic web application framework.
  .
- This virtual package provides dependancies to use mysql for the
+ This virtual package provides dependencies to use mysql for the
  cubicweb repository.
 
 
@@ -98,7 +97,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.48.1), python-yams (>= 0.28.0), python-rql (>= 0.24.0), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.49.0), python-yams (>= 0.28.1), python-rql (>= 0.25.0), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/debian/copyright	Wed Mar 24 10:23:31 2010 +0100
+++ b/debian/copyright	Wed Apr 28 11:54:13 2010 +0200
@@ -1,27 +1,31 @@
 This package was debianized by Logilab <contact@logilab.fr>.
+It was downloaded from ftp://ftp.logilab.org/pub/cubicweb
 
 
-Upstream Author: 
+Upstream Author:
 
-  Logilab <contact@logilab.fr>
+    Logilab <contact@logilab.fr>
 
 Copyright:
 
-Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE).
-http://www.logilab.fr/ -- mailto:contact@logilab.fr
+    Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+    http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU Lesser General Public License as published by the Free 
-Software Foundation; either version 2 of the License, or (at your option) any 
-later version.
+License:
 
-This program is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Lesser General Public License as published by the
+    Free Software Foundation; either version 2.1 of the License, or (at your
+    option) any later version.
 
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+    This program is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+    for more details.
+
+    You should have received a copy of the GNU Lessser General Public License
+    along with this program; if not, write to the Free Software Foundation,
+    Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 
 On Debian systems, the complete text of the GNU Lesser General Public License
-may be found in '/usr/share/common-licenses/LGPL'.
+may be found in '/usr/share/common-licenses/LGPL-2.1'.
--- a/devtools/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Test tools for cubicweb
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -81,7 +94,6 @@
     mode = 'test'
     set_language = False
     read_instance_schema = False
-    bootstrap_schema = False
     init_repository = True
     options = cwconfig.merge_options(ServerConfiguration.options + (
         ('anonymous-user',
@@ -106,8 +118,6 @@
         self.init_log(log_threshold, force=True)
         # need this, usually triggered by cubicweb-ctl
         self.load_cwctl_plugins()
-        self.global_set_option('anonymous-user', 'anon')
-        self.global_set_option('anonymous-password', 'anon')
 
     anonymous_user = TwistedConfiguration.anonymous_user.im_func
 
@@ -123,6 +133,8 @@
         super(TestServerConfiguration, self).load_configuration()
         self.global_set_option('anonymous-user', 'anon')
         self.global_set_option('anonymous-password', 'anon')
+        # no undo support in tests
+        self.global_set_option('undo-support', '')
 
     def main_config_file(self):
         """return instance's control configuration file"""
@@ -201,6 +213,8 @@
         init_test_database_sqlite(config)
     elif driver == 'postgres':
         init_test_database_postgres(config)
+    elif driver == 'sqlserver2005':
+        init_test_database_sqlserver2005(config)
     else:
         raise ValueError('no initialization function for driver %r' % driver)
     config._cubes = None # avoid assertion error
@@ -216,6 +230,10 @@
     driver = config.sources()['system']['db-driver']
     if driver == 'sqlite':
         reset_test_database_sqlite(config)
+    elif driver in ('sqlserver2005', 'postgres'):
+        # XXX do something with dump/restore ?
+        print 'resetting the database is not done for', driver
+        print 'you should handle it manually'
     else:
         raise ValueError('no reset function for driver %r' % driver)
 
@@ -223,11 +241,18 @@
 ### postgres test database handling ############################################
 
 def init_test_database_postgres(config):
-    """initialize a fresh sqlite databse used for testing purpose"""
+    """initialize a fresh postgresql databse used for testing purpose"""
     if config.init_repository:
         from cubicweb.server import init_repository
         init_repository(config, interactive=False, drop=True)
 
+### sqlserver2005 test database handling ############################################
+
+def init_test_database_sqlserver2005(config):
+    """initialize a fresh sqlserver databse used for testing purpose"""
+    if config.init_repository:
+        from cubicweb.server import init_repository
+        init_repository(config, interactive=False, drop=True, vreg=vreg)
 
 ### sqlite test database handling ##############################################
 
--- a/devtools/cwtwill.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/cwtwill.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb extensions for twill
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import re
--- a/devtools/dataimport.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/dataimport.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,752 +1,21 @@
-# -*- coding: utf-8 -*-
-"""This module provides tools to import tabular data.
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-
-
-Example of use (run this with `cubicweb-ctl shell instance import-script.py`):
-
-.. sourcecode:: python
-
-  from cubicweb.devtools.dataimport import *
-  # define data generators
-  GENERATORS = []
-
-  USERS = [('Prenom', 'firstname', ()),
-           ('Nom', 'surname', ()),
-           ('Identifiant', 'login', ()),
-           ]
-
-  def gen_users(ctl):
-      for row in ctl.get_data('utilisateurs'):
-          entity = mk_entity(row, USERS)
-          entity['upassword'] = u'motdepasse'
-          ctl.check('login', entity['login'], None)
-          ctl.store.add('CWUser', entity)
-          email = {'address': row['email']}
-          ctl.store.add('EmailAddress', email)
-          ctl.store.relate(entity['eid'], 'use_email', email['eid'])
-          ctl.store.rql('SET U in_group G WHERE G name "users", U eid %(x)s', {'x':entity['eid']})
-
-  CHK = [('login', check_doubles, 'Utilisateurs Login',
-          'Deux utilisateurs ne devraient pas avoir le même login.'),
-         ]
-
-  GENERATORS.append( (gen_users, CHK) )
-
-  # create controller
-  ctl = CWImportController(RQLObjectStore())
-  ctl.askerror = 1
-  ctl.generators = GENERATORS
-  ctl.store._checkpoint = checkpoint
-  ctl.store._rql = rql
-  ctl.data['utilisateurs'] = lazytable(utf8csvreader(open('users.csv')))
-  # run
-  ctl.run()
-  sys.exit(0)
-
-
-.. BUG fichier à une colonne pose un problème de parsing
-.. TODO rollback()
-"""
-__docformat__ = "restructuredtext en"
-
-import sys
-import csv
-import traceback
-import os.path as osp
-from StringIO import StringIO
-from copy import copy
-
-from logilab.common import shellutils
-from logilab.common.date import strptime
-from logilab.common.decorators import cached
-from logilab.common.deprecation import deprecated
-
-
-def ucsvreader_pb(filepath, encoding='utf-8', separator=',', quote='"',
-                  skipfirst=False, withpb=True):
-    """same as ucsvreader but a progress bar is displayed as we iter on rows"""
-    if not osp.exists(filepath):
-        raise Exception("file doesn't exists: %s" % filepath)
-    rowcount = int(shellutils.Execute('wc -l "%s"' % filepath).out.strip().split()[0])
-    if skipfirst:
-        rowcount -= 1
-    if withpb:
-        pb = shellutils.ProgressBar(rowcount, 50)
-    for urow in ucsvreader(file(filepath), encoding, separator, quote, skipfirst):
-        yield urow
-        if withpb:
-            pb.update()
-    print ' %s rows imported' % rowcount
-
-def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
-               skipfirst=False):
-    """A csv reader that accepts files with any encoding and outputs unicode
-    strings
-    """
-    it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
-    if skipfirst:
-        it.next()
-    for row in it:
-        yield [item.decode(encoding) for item in row]
-
-def commit_every(nbit, store, it):
-    for i, x in enumerate(it):
-        yield x
-        if nbit is not None and i % nbit:
-            store.checkpoint()
-    if nbit is not None:
-        store.checkpoint()
-
-def lazytable(reader):
-    """The first row is taken to be the header of the table and
-    used to output a dict for each row of data.
-
-    >>> data = lazytable(utf8csvreader(open(filename)))
-    """
-    header = reader.next()
-    for row in reader:
-        yield dict(zip(header, row))
-
-def mk_entity(row, map):
-    """Return a dict made from sanitized mapped values.
-
-    ValidationError can be raised on unexpected values found in checkers
-
-    >>> row = {'myname': u'dupont'}
-    >>> map = [('myname', u'name', (capitalize_if_unicase,))]
-    >>> mk_entity(row, map)
-    {'name': u'Dupont'}
-    >>> row = {'myname': u'dupont', 'optname': u''}
-    >>> map = [('myname', u'name', (capitalize_if_unicase,)),
-    ...        ('optname', u'MARKER', (optional,))]
-    >>> mk_entity(row, map)
-    {'name': u'Dupont'}
-    """
-    res = {}
-    assert isinstance(row, dict)
-    assert isinstance(map, list)
-    for src, dest, funcs in map:
-        assert not (required in funcs and optional in funcs), \
-               "optional and required checks are exclusive"
-        res[dest] = row[src]
-        try:
-            for func in funcs:
-                res[dest] = func(res[dest])
-                if res[dest] is None:
-                    break
-        except ValueError, err:
-            raise ValueError('error with %r field: %s' % (src, err))
-    return res
-
-
-# user interactions ############################################################
-
-def tell(msg):
-    print msg
-
-def confirm(question):
-    """A confirm function that asks for yes/no/abort and exits on abort."""
-    answer = shellutils.ASK.ask(question, ('Y', 'n', 'abort'), 'Y')
-    if answer == 'abort':
-        sys.exit(1)
-    return answer == 'Y'
-
-
-class catch_error(object):
-    """Helper for @contextmanager decorator."""
-
-    def __init__(self, ctl, key='unexpected error', msg=None):
-        self.ctl = ctl
-        self.key = key
-        self.msg = msg
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type, value, traceback):
-        if type is not None:
-            if issubclass(type, (KeyboardInterrupt, SystemExit)):
-                return # re-raise
-            if self.ctl.catcherrors:
-                self.ctl.record_error(self.key, None, type, value, traceback)
-                return True # silent
-
-
-# base sanitizing/coercing functions ###########################################
-
-def optional(value):
-    """validation error will not been raised if you add this checker in chain"""
-    if value:
-        return value
-    return None
-
-def required(value):
-    """raise ValueError is value is empty
-
-    This check should be often found in last position in the chain.
-    """
-    if value:
-        return value
-    raise ValueError("required")
-
-def todatetime(format='%d/%m/%Y'):
-    """return a transformation function to turn string input value into a
-    `datetime.datetime` instance, using given format.
-
-    Follow it by `todate` or `totime` functions from `logilab.common.date` if
-    you want a `date`/`time` instance instead of `datetime`.
-    """
-    def coerce(value):
-        return strptime(value, format)
-    return coerce
-
-def call_transform_method(methodname, *args, **kwargs):
-    """return value returned by calling the given method on input"""
-    def coerce(value):
-        return getattr(value, methodname)(*args, **kwargs)
-    return coerce
-
-def call_check_method(methodname, *args, **kwargs):
-    """check value returned by calling the given method on input is true,
-    else raise ValueError
-    """
-    def check(value):
-        if getattr(value, methodname)(*args, **kwargs):
-            return value
-        raise ValueError('%s not verified on %r' % (methodname, value))
-    return check
-
-# base integrity checking functions ############################################
-
-def check_doubles(buckets):
-    """Extract the keys that have more than one item in their bucket."""
-    return [(k, len(v)) for k, v in buckets.items() if len(v) > 1]
-
-def check_doubles_not_none(buckets):
-    """Extract the keys that have more than one item in their bucket."""
-    return [(k, len(v)) for k, v in buckets.items()
-            if k is not None and len(v) > 1]
-
-
-# object stores #################################################################
-
-class ObjectStore(object):
-    """Store objects in memory for *faster* validation (development mode)
-
-    But it will not enforce the constraints of the schema and hence will miss some problems
-
-    >>> store = ObjectStore()
-    >>> user = {'login': 'johndoe'}
-    >>> store.add('CWUser', user)
-    >>> group = {'name': 'unknown'}
-    >>> store.add('CWUser', group)
-    >>> store.relate(user['eid'], 'in_group', group['eid'])
-    """
-    def __init__(self):
-        self.items = []
-        self.eids = {}
-        self.types = {}
-        self.relations = set()
-        self.indexes = {}
-        self._rql = None
-        self._checkpoint = None
-
-    def _put(self, type, item):
-        self.items.append(item)
-        return len(self.items) - 1
-
-    def add(self, type, item):
-        assert isinstance(item, dict), 'item is not a dict but a %s' % type(item)
-        eid = item['eid'] = self._put(type, item)
-        self.eids[eid] = item
-        self.types.setdefault(type, []).append(eid)
-
-    def relate(self, eid_from, rtype, eid_to, inlined=False):
-        """Add new relation (reverse type support is available)
-
-        >>> 1,2 = eid_from, eid_to
-        >>> self.relate(eid_from, 'in_group', eid_to)
-        1, 'in_group', 2
-        >>> self.relate(eid_from, 'reverse_in_group', eid_to)
-        2, 'in_group', 1
-        """
-        if rtype.startswith('reverse_'):
-            eid_from, eid_to = eid_to, eid_from
-            rtype = rtype[8:]
-        relation = eid_from, rtype, eid_to
-        self.relations.add(relation)
-        return relation
-
-    def build_index(self, name, type, func=None):
-        index = {}
-        if func is None or not callable(func):
-            func = lambda x: x['eid']
-        for eid in self.types[type]:
-            index.setdefault(func(self.eids[eid]), []).append(eid)
-        assert index, "new index '%s' cannot be empty" % name
-        self.indexes[name] = index
-
-    def build_rqlindex(self, name, type, key, rql, rql_params=False, func=None):
-        """build an index by rql query
-
-        rql should return eid in first column
-        ctl.store.build_index('index_name', 'users', 'login', 'Any U WHERE U is CWUser')
-        """
-        rset = self.rql(rql, rql_params or {})
-        for entity in rset.entities():
-            getattr(entity, key) # autopopulate entity with key attribute
-            self.eids[entity.eid] = dict(entity)
-            if entity.eid not in self.types.setdefault(type, []):
-                self.types[type].append(entity.eid)
-        assert self.types[type], "new index type '%s' cannot be empty (0 record found)" % type
-
-        # Build index with specified key
-        func = lambda x: x[key]
-        self.build_index(name, type, func)
-
-    def fetch(self, name, key, unique=False, decorator=None):
-        """
-            decorator is a callable method or an iterator of callable methods (usually a lambda function)
-            decorator=lambda x: x[:1] (first value is returned)
-
-            We can use validation check function available in _entity
-        """
-        eids = self.indexes[name].get(key, [])
-        if decorator is not None:
-            if not hasattr(decorator, '__iter__'):
-                decorator = (decorator,)
-            for f in decorator:
-                eids = f(eids)
-        if unique:
-            assert len(eids) == 1, u'expected a single one value for key "%s" in index "%s". Got %i' % (key, name, len(eids))
-            eids = eids[0] # FIXME maybe it's better to keep an iterator here ?
-        return eids
-
-    def find(self, type, key, value):
-        for idx in self.types[type]:
-            item = self.items[idx]
-            if item[key] == value:
-                yield item
-
-    def rql(self, *args):
-        if self._rql is not None:
-            return self._rql(*args)
-
-    def checkpoint(self):
-        pass
-
-    @property
-    def nb_inserted_entities(self):
-        return len(self.eids)
-    @property
-    def nb_inserted_types(self):
-        return len(self.types)
-    @property
-    def nb_inserted_relations(self):
-        return len(self.relations)
-
-    @deprecated('[3.6] get_many() deprecated. Use fetch() instead')
-    def get_many(self, name, key):
-        return self.fetch(name, key, unique=False)
-
-    @deprecated('[3.6] get_one() deprecated. Use fetch(..., unique=True) instead')
-    def get_one(self, name, key):
-        return self.fetch(name, key, unique=True)
-
-
-class RQLObjectStore(ObjectStore):
-    """ObjectStore that works with an actual RQL repository (production mode)"""
-    _rql = None # bw compat
-
-    def __init__(self, session=None, checkpoint=None):
-        ObjectStore.__init__(self)
-        if session is not None:
-            if not hasattr(session, 'set_pool'):
-                # connection
-                cnx = session
-                session = session.request()
-                session.set_pool = lambda : None
-                checkpoint = checkpoint or cnx.commit
-            else:
-                session.set_pool()
-            self.session = session
-            self._checkpoint = checkpoint or session.commit
-        elif checkpoint is not None:
-            self._checkpoint = checkpoint
-            # XXX .session
-
-    def checkpoint(self):
-        self._checkpoint()
-        self.session.set_pool()
-
-    def rql(self, *args):
-        if self._rql is not None:
-            return self._rql(*args)
-        return self.session.execute(*args)
-
-    def create_entity(self, *args, **kwargs):
-        entity = self.session.create_entity(*args, **kwargs)
-        self.eids[entity.eid] = entity
-        self.types.setdefault(args[0], []).append(entity.eid)
-        return entity
-
-    def _put(self, type, item):
-        query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
-                                                     for k in item)
-        return self.rql(query, item)[0][0]
-
-    def relate(self, eid_from, rtype, eid_to, inlined=False):
-        # if reverse relation is found, eids are exchanged
-        eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
-            eid_from, rtype, eid_to)
-        self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
-                  {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
-
-
-# the import controller ########################################################
-
-class CWImportController(object):
-    """Controller of the data import process.
-
-    >>> ctl = CWImportController(store)
-    >>> ctl.generators = list_of_data_generators
-    >>> ctl.data = dict_of_data_tables
-    >>> ctl.run()
-    """
-
-    def __init__(self, store, askerror=0, catcherrors=None, tell=tell,
-                 commitevery=50):
-        self.store = store
-        self.generators = None
-        self.data = {}
-        self.errors = None
-        self.askerror = askerror
-        if  catcherrors is None:
-            catcherrors = askerror
-        self.catcherrors = catcherrors
-        self.commitevery = commitevery # set to None to do a single commit
-        self._tell = tell
-
-    def check(self, type, key, value):
-        self._checks.setdefault(type, {}).setdefault(key, []).append(value)
-
-    def check_map(self, entity, key, map, default):
-        try:
-            entity[key] = map[entity[key]]
-        except KeyError:
-            self.check(key, entity[key], None)
-            entity[key] = default
-
-    def record_error(self, key, msg=None, type=None, value=None, tb=None):
-        tmp = StringIO()
-        if type is None:
-            traceback.print_exc(file=tmp)
-        else:
-            traceback.print_exception(type, value, tb, file=tmp)
-        print tmp.getvalue()
-        # use a list to avoid counting a <nb lines> errors instead of one
-        errorlog = self.errors.setdefault(key, [])
-        if msg is None:
-            errorlog.append(tmp.getvalue().splitlines())
-        else:
-            errorlog.append( (msg, tmp.getvalue().splitlines()) )
-
-    def run(self):
-        self.errors = {}
-        for func, checks in self.generators:
-            self._checks = {}
-            func_name = func.__name__[4:]  # XXX
-            self.tell("Import '%s'..." % func_name)
-            try:
-                func(self)
-            except:
-                if self.catcherrors:
-                    self.record_error(func_name, 'While calling %s' % func.__name__)
-                else:
-                    raise
-            for key, func, title, help in checks:
-                buckets = self._checks.get(key)
-                if buckets:
-                    err = func(buckets)
-                    if err:
-                        self.errors[title] = (help, err)
-        self.store.checkpoint()
-        nberrors = sum(len(err[1]) for err in self.errors.values())
-        self.tell('\nImport completed: %i entities, %i types, %i relations and %i errors'
-                  % (self.store.nb_inserted_entities,
-                     self.store.nb_inserted_types,
-                     self.store.nb_inserted_relations,
-                     nberrors))
-        if self.errors:
-            if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
-                from pprint import pformat
-                for errkey, error in self.errors.items():
-                    self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
-                    self.tell(pformat(sorted(error[1])))
-
-    def get_data(self, key):
-        return self.data.get(key)
-
-    def index(self, name, key, value, unique=False):
-        """create a new index
-
-        If unique is set to True, only first occurence will be kept not the following ones
-        """
-        if unique:
-            try:
-                if value in self.store.indexes[name][key]:
-                    return
-            except KeyError:
-                # we're sure that one is the first occurence; so continue...
-                pass
-        self.store.indexes.setdefault(name, {}).setdefault(key, []).append(value)
-
-    def tell(self, msg):
-        self._tell(msg)
-
-    def iter_and_commit(self, datakey):
-        """iter rows, triggering commit every self.commitevery iterations"""
-        return commit_every(self.commitevery, self.store, self.get_data(datakey))
-
-
-
-from datetime import datetime
-from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
-
-
-class NoHookRQLObjectStore(RQLObjectStore):
-    """ObjectStore that works with an actual RQL repository (production mode)"""
-    _rql = None # bw compat
-
-    def __init__(self, session, metagen=None, baseurl=None):
-        super(NoHookRQLObjectStore, self).__init__(session)
-        self.source = session.repo.system_source
-        self.rschema = session.repo.schema.rschema
-        self.add_relation = self.source.add_relation
-        if metagen is None:
-            metagen = MetaGenerator(session, baseurl)
-        self.metagen = metagen
-        self._nb_inserted_entities = 0
-        self._nb_inserted_types = 0
-        self._nb_inserted_relations = 0
-        self.rql = session.unsafe_execute
-
-    def create_entity(self, etype, **kwargs):
-        for k, v in kwargs.iteritems():
-            kwargs[k] = getattr(v, 'eid', v)
-        entity, rels = self.metagen.base_etype_dicts(etype)
-        entity = copy(entity)
-        entity._related_cache = {}
-        self.metagen.init_entity(entity)
-        entity.update(kwargs)
-        session = self.session
-        self.source.add_entity(session, entity)
-        self.source.add_info(session, entity, self.source, complete=False)
-        for rtype, targeteids in rels.iteritems():
-            # targeteids may be a single eid or a list of eids
-            inlined = self.rschema(rtype).inlined
-            try:
-                for targeteid in targeteids:
-                    self.add_relation(session, entity.eid, rtype, targeteid,
-                                      inlined)
-            except TypeError:
-                self.add_relation(session, entity.eid, rtype, targeteids,
-                                  inlined)
-        self._nb_inserted_entities += 1
-        return entity
-
-    def relate(self, eid_from, rtype, eid_to):
-        assert not rtype.startswith('reverse_')
-        self.add_relation(self.session, eid_from, rtype, eid_to,
-                          self.rschema(rtype).inlined)
-        self._nb_inserted_relations += 1
-
-    @property
-    def nb_inserted_entities(self):
-        return self._nb_inserted_entities
-    @property
-    def nb_inserted_types(self):
-        return self._nb_inserted_types
-    @property
-    def nb_inserted_relations(self):
-        return self._nb_inserted_relations
-
-    def _put(self, type, item):
-        raise RuntimeError('use create entity')
-
-
-class MetaGenerator(object):
-    def __init__(self, session, baseurl=None):
-        self.session = session
-        self.source = session.repo.system_source
-        self.time = datetime.now()
-        if baseurl is None:
-            config = session.vreg.config
-            baseurl = config['base-url'] or config.default_base_url()
-        if not baseurl[-1] == '/':
-            baseurl += '/'
-        self.baseurl =  baseurl
-        # attributes/relations shared by all entities of the same type
-        self.etype_attrs = []
-        self.etype_rels = []
-        # attributes/relations specific to each entity
-        self.entity_attrs = ['eid', 'cwuri']
-        #self.entity_rels = [] XXX not handled (YAGNI?)
-        schema = session.vreg.schema
-        rschema = schema.rschema
-        for rtype in META_RTYPES:
-            if rtype in ('eid', 'cwuri') or rtype in VIRTUAL_RTYPES:
-                continue
-            if rschema(rtype).final:
-                self.etype_attrs.append(rtype)
-            else:
-                self.etype_rels.append(rtype)
-        if not schema._eid_index:
-            # test schema loaded from the fs
-            self.gen_is = self.test_gen_is
-            self.gen_is_instance_of = self.test_gen_is_instanceof
-
-    @cached
-    def base_etype_dicts(self, etype):
-        entity = self.session.vreg['etypes'].etype_class(etype)(self.session)
-        # entity are "surface" copied, avoid shared dict between copies
-        del entity.cw_extra_kwargs
-        for attr in self.etype_attrs:
-            entity[attr] = self.generate(entity, attr)
-        rels = {}
-        for rel in self.etype_rels:
-            rels[rel] = self.generate(entity, rel)
-        return entity, rels
-
-    def init_entity(self, entity):
-        for attr in self.entity_attrs:
-            entity[attr] = self.generate(entity, attr)
-        entity.eid = entity['eid']
-
-    def generate(self, entity, rtype):
-        return getattr(self, 'gen_%s' % rtype)(entity)
-
-    def gen_eid(self, entity):
-        return self.source.create_eid(self.session)
-
-    def gen_cwuri(self, entity):
-        return u'%seid/%s' % (self.baseurl, entity['eid'])
-
-    def gen_creation_date(self, entity):
-        return self.time
-    def gen_modification_date(self, entity):
-        return self.time
-
-    def gen_is(self, entity):
-        return entity.e_schema.eid
-    def gen_is_instance_of(self, entity):
-        eids = []
-        for etype in entity.e_schema.ancestors() + [entity.e_schema]:
-            eids.append(entity.e_schema.eid)
-        return eids
-
-    def gen_created_by(self, entity):
-        return self.session.user.eid
-    def gen_owned_by(self, entity):
-        return self.session.user.eid
-
-    # implementations of gen_is / gen_is_instance_of to use during test where
-    # schema has been loaded from the fs (hence entity type schema eids are not
-    # known)
-    def test_gen_is(self, entity):
-        from cubicweb.hooks.metadata import eschema_eid
-        return eschema_eid(self.session, entity.e_schema)
-    def test_gen_is_instanceof(self, entity):
-        from cubicweb.hooks.metadata import eschema_eid
-        eids = []
-        for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
-            eids.append(eschema_eid(self.session, eschema))
-        return eids
-
-
-################################################################################
-
-utf8csvreader = deprecated('[3.6] use ucsvreader instead')(ucsvreader)
-
-@deprecated('[3.6] use required')
-def nonempty(value):
-    return required(value)
-
-@deprecated("[3.6] use call_check_method('isdigit')")
-def alldigits(txt):
-    if txt.isdigit():
-        return txt
-    else:
-        return u''
-
-@deprecated("[3.7] too specific, will move away, copy me")
-def capitalize_if_unicase(txt):
-    if txt.isupper() or txt.islower():
-        return txt.capitalize()
-    return txt
-
-@deprecated("[3.7] too specific, will move away, copy me")
-def yesno(value):
-    """simple heuristic that returns boolean value
-
-    >>> yesno("Yes")
-    True
-    >>> yesno("oui")
-    True
-    >>> yesno("1")
-    True
-    >>> yesno("11")
-    True
-    >>> yesno("")
-    False
-    >>> yesno("Non")
-    False
-    >>> yesno("blablabla")
-    False
-    """
-    if value:
-        return value.lower()[0] in 'yo1'
-    return False
-
-@deprecated("[3.7] use call_check_method('isalpha')")
-def isalpha(value):
-    if value.isalpha():
-        return value
-    raise ValueError("not all characters in the string alphabetic")
-
-@deprecated("[3.7] use call_transform_method('upper')")
-def uppercase(txt):
-    return txt.upper()
-
-@deprecated("[3.7] use call_transform_method('lower')")
-def lowercase(txt):
-    return txt.lower()
-
-@deprecated("[3.7] use call_transform_method('replace', ' ', '')")
-def no_space(txt):
-    return txt.replace(' ','')
-
-@deprecated("[3.7] use call_transform_method('replace', u'\xa0', '')")
-def no_uspace(txt):
-    return txt.replace(u'\xa0','')
-
-@deprecated("[3.7] use call_transform_method('replace', '-', '')")
-def no_dash(txt):
-    return txt.replace('-','')
-
-@deprecated("[3.7] use call_transform_method('strip')")
-def strip(txt):
-    return txt.strip()
-
-@deprecated("[3.7] use call_transform_method('replace', ',', '.'), float")
-def decimal(value):
-    return comma_float(value)
-
-@deprecated('[3.7] use int builtin')
-def integer(value):
-    return int(value)
+# pylint: disable-msg=W0614,W0401
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+from warnings import warn
+warn('moved to cubicweb.dataimport', DeprecationWarning, stacklevel=2)
+from cubicweb.dataimport import *
--- a/devtools/devctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/devctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """additional cubicweb-ctl commands and command handlers for cubicweb and cubicweb's
 cubes development
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -515,19 +528,14 @@
         longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
         if verbose:
             longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ')
+        dependencies = {}
         if verbose:
-            includes = self._ask_for_dependancies()
-            if len(includes) == 1:
-                dependancies = '%r,' % includes[0]
-            else:
-                dependancies = ', '.join(repr(cube) for cube in includes)
-        else:
-            dependancies = ''
+            dependencies = self._ask_for_dependencies()
         context = {'cubename' : cubename,
                    'distname' : distname,
                    'shortdesc' : shortdesc,
                    'longdesc' : longdesc or shortdesc,
-                   'dependancies' : dependancies,
+                   'dependencies' : dict((dep, None) for dep in dependencies),
                    'version'  : cubicwebversion,
                    'year'  : str(datetime.now().year),
                    'author': self['author'],
@@ -536,7 +544,7 @@
                    }
         copy_skeleton(skeldir, cubedir, context)
 
-    def _ask_for_dependancies(self):
+    def _ask_for_dependencies(self):
         from logilab.common.shellutils import ASK
         from logilab.common.textutils import splitstrip
         includes = []
@@ -546,7 +554,7 @@
             if answer == 'y':
                 includes.append(stdtype)
             if answer == 'type':
-                includes = splitstrip(raw_input('type dependancies: '))
+                includes = splitstrip(raw_input('type dependencies: '))
                 break
             elif answer == 'skip':
                 break
@@ -565,47 +573,48 @@
     chances are the lines at the top are the ones that will bring the higher
     benefit after optimisation. Start there.
     """
-    arguments = '< rql.log'
+    arguments = 'rql.log'
     name = 'exlog'
     options = (
         )
 
     def run(self, args):
-        if args:
-            raise BadCommandUsage("no argument expected")
         import re
         requests = {}
-        for lineno, line in enumerate(sys.stdin):
-            if not ' WHERE ' in line:
-                continue
-            #sys.stderr.write( line )
+        for filepath in args:
             try:
-                rql, time = line.split('--')
-                rql = re.sub("(\'\w+': \d*)", '', rql)
-                if '{' in rql:
-                    rql = rql[:rql.index('{')]
-                req = requests.setdefault(rql, [])
-                time.strip()
-                chunks = time.split()
-                clocktime = float(chunks[0][1:])
-                cputime = float(chunks[-3])
-                req.append( (clocktime, cputime) )
-            except Exception, exc:
-                sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
-
+                stream = file(filepath)
+            except OSError, ex:
+                raise BadCommandUsage("can't open rql log file %s: %s"
+                                      % (filepath, ex))
+            for lineno, line in enumerate(file):
+                if not ' WHERE ' in line:
+                    continue
+                try:
+                    rql, time = line.split('--')
+                    rql = re.sub("(\'\w+': \d*)", '', rql)
+                    if '{' in rql:
+                        rql = rql[:rql.index('{')]
+                    req = requests.setdefault(rql, [])
+                    time.strip()
+                    chunks = time.split()
+                    clocktime = float(chunks[0][1:])
+                    cputime = float(chunks[-3])
+                    req.append( (clocktime, cputime) )
+                except Exception, exc:
+                    sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
         stat = []
-        for rql, times in requests.items():
+        for rql, times in requests.iteritems():
             stat.append( (sum(time[0] for time in times),
                           sum(time[1] for time in times),
                           len(times), rql) )
-
         stat.sort()
         stat.reverse()
-
-        total_time = sum(clocktime for clocktime, cputime, occ, rql in stat)*0.01
+        total_time = sum(clocktime for clocktime, cputime, occ, rql in stat) * 0.01
         print 'Percentage;Cumulative Time (clock);Cumulative Time (CPU);Occurences;Query'
         for clocktime, cputime, occ, rql in stat:
-            print '%.2f;%.2f;%.2f;%s;%s' % (clocktime/total_time, clocktime, cputime, occ, rql)
+            print '%.2f;%.2f;%.2f;%s;%s' % (clocktime/total_time, clocktime,
+                                            cputime, occ, rql)
 
 
 class GenerateSchema(Command):
--- a/devtools/fake.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/fake.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,15 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Fake objects to ease testing of cubicweb without a fully working environment
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-from logilab.common.adbh import get_adv_func_helper
-
-from indexer import get_indexer
+from logilab.database import get_db_helper
 
 from cubicweb.req import RequestSessionBase
 from cubicweb.cwvreg import CubicWebVRegistry
@@ -118,17 +129,6 @@
     def validate_cache(self):
         pass
 
-    # session compatibility (in some test are using this class to test server
-    # side views...)
-    def actual_session(self):
-        """return the original parent session if any, else self"""
-        return self
-
-    def unsafe_execute(self, *args, **kwargs):
-        """return the original parent session if any, else self"""
-        kwargs.pop('propagate', None)
-        return self.execute(*args, **kwargs)
-
 
 class FakeUser(object):
     login = 'toto'
@@ -138,18 +138,19 @@
 
 
 class FakeSession(RequestSessionBase):
+    read_security = write_security = True
+    set_read_security = set_write_security = lambda *args, **kwargs: None
+
     def __init__(self, repo=None, user=None):
         self.repo = repo
         self.vreg = getattr(self.repo, 'vreg', CubicWebVRegistry(FakeConfig(), initlog=False))
         self.pool = FakePool()
         self.user = user or FakeUser()
         self.is_internal_session = False
-        self.is_super_session = self.user.eid == -1
         self.transaction_data = {}
 
-    def execute(self, *args):
+    def execute(self, *args, **kwargs):
         pass
-    unsafe_execute = execute
 
     def commit(self, *args):
         self.transaction_data.clear()
@@ -158,11 +159,6 @@
     def system_sql(self, sql, args=None):
         pass
 
-    def decorate_rset(self, rset, propagate=False):
-        rset.vreg = self.vreg
-        rset.req = self
-        return rset
-
     def set_entity_cache(self, entity):
         pass
 
@@ -200,12 +196,7 @@
 
 
 class FakeSource(object):
-    dbhelper = get_adv_func_helper('sqlite')
-    indexer = get_indexer('sqlite', 'UTF8')
-    dbhelper.fti_uid_attr = indexer.uid_attr
-    dbhelper.fti_table = indexer.table
-    dbhelper.fti_restriction_sql = indexer.restriction_sql
-    dbhelper.fti_need_distinct_query = indexer.need_distinct
+    dbhelper = get_db_helper('sqlite')
     def __init__(self, uri):
         self.uri = uri
 
--- a/devtools/fill.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/fill.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """This modules defines func / methods for creating test repositories
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/devtools/htmlparser.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/htmlparser.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """defines a validating HTML parser used in web application tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import re
@@ -74,6 +87,21 @@
         Validator.__init__(self)
         self.parser = etree.XMLParser()
 
+class XMLDemotingValidator(SaxOnlyValidator):
+    """ some views produce html instead of xhtml, using demote_to_html
+
+    this is typically related to the use of external dependencies
+    which do not produce valid xhtml (google maps, ...)
+    """
+
+    def preprocess_data(self, data):
+        if data.startswith('<?xml'):
+            self.parser = etree.XMLParser()
+        else:
+            self.parser = etree.HTMLParser()
+        return data
+
+
 class HTMLValidator(Validator):
 
     def __init__(self):
--- a/devtools/livetest.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/livetest.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provide utilies for web (live) unit testing
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import os
--- a/devtools/realdbtest.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/realdbtest.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from cubicweb import toolsutils
 from cubicweb.devtools import DEFAULT_SOURCES, BaseApptestConfiguration
 
--- a/devtools/repotest.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/repotest.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,24 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some utilities to ease repository testing
 
 This module contains functions to initialize a new repository.
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -95,6 +108,31 @@
     def __iter__(self):
         return iter(sorted(self.origdict, key=self.sortkey))
 
+def schema_eids_idx(schema):
+    """return a dictionary mapping schema types to their eids so we can reread
+    it from the fs instead of the db (too costly) between tests
+    """
+    schema_eids = {}
+    for x in schema.entities():
+        schema_eids[x] = x.eid
+    for x in schema.relations():
+        schema_eids[x] = x.eid
+        for rdef in x.rdefs.itervalues():
+            schema_eids[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+    return schema_eids
+
+def restore_schema_eids_idx(schema, schema_eids):
+    """rebuild schema eid index"""
+    for x in schema.entities():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+    for x in schema.relations():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+        for rdef in x.rdefs.itervalues():
+            rdef.eid = schema_eids[(rdef.subject, rdef.rtype, rdef.object)]
+            schema._eid_index[rdef.eid] = rdef
+
 
 from logilab.common.testlib import TestCase
 from rql import RQLHelper
@@ -150,17 +188,23 @@
         self.pool = self.session.set_pool()
         self.maxeid = self.get_max_eid()
         do_monkey_patch()
+        self._dumb_sessions = []
 
     def get_max_eid(self):
-        return self.session.unsafe_execute('Any MAX(X)')[0][0]
+        return self.session.execute('Any MAX(X)')[0][0]
     def cleanup(self):
-        self.session.unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
+        self.session.set_pool()
+        self.session.execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
 
     def tearDown(self):
         undo_monkey_patch()
         self.session.rollback()
         self.cleanup()
         self.commit()
+        # properly close dumb sessions
+        for session in self._dumb_sessions:
+            session.rollback()
+            session.close()
         self.repo._free_pool(self.pool)
         assert self.session.user.eid != -1
 
@@ -198,6 +242,8 @@
         u._groups = set(groups)
         s = Session(u, self.repo)
         s._threaddata.pool = self.pool
+        # register session to ensure it gets closed
+        self._dumb_sessions.append(s)
         return s
 
     def execute(self, rql, args=None, eid_key=None, build_descr=True):
@@ -223,6 +269,7 @@
         self.sources = self.o._repo.sources
         self.system = self.sources[-1]
         do_monkey_patch()
+        self._dumb_sessions = [] # by hi-jacked parent setup
 
     def add_source(self, sourcecls, uri):
         self.sources.append(sourcecls(self.repo, self.o.schema,
@@ -237,6 +284,9 @@
             del self.repo.sources_by_uri[source.uri]
             self.newsources -= 1
         undo_monkey_patch()
+        for session in self._dumb_sessions:
+            session._threaddata.pool = None
+            session.close()
 
     def _prepare_plan(self, rql, kwargs=None):
         rqlst = self.o.parse(rql, annotate=True)
--- a/devtools/stresstester.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/stresstester.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """ Usage: %s [OPTIONS] <instance id> <queries file>
 
 Stress test a CubicWeb repository
@@ -23,7 +40,6 @@
 
 Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
 http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import os
--- a/devtools/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import EntityType, SubjectRelation, String, Int, Date
 
--- a/devtools/test/data/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/test/data/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """only for unit tests !
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cubicweb.view import EntityView
--- a/devtools/test/unittest_dbfill.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/test/unittest_dbfill.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for database value generator
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import os.path as osp
--- a/devtools/test/unittest_fill.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/test/unittest_fill.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.devtools.fill module
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main
--- a/devtools/test/unittest_testlib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/test/unittest_testlib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,20 +1,33 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unittests for gct.apptest module
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cStringIO import StringIO
 from unittest import TestSuite
 
-
-from logilab.common.testlib import (TestCase, unittest_main, 
+from logilab.common.testlib import (TestCase, unittest_main,
                                     SkipAwareTextTestRunner)
 
 from cubicweb.devtools import htmlparser
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.pytestconf import clean_repo_test_cls
 
 class WebTestTC(TestCase):
 
@@ -37,7 +50,7 @@
         self.assertEquals(result.testsRun, 2)
         self.assertEquals(len(result.errors), 0)
         self.assertEquals(len(result.failures), 1)
-
+        clean_repo_test_cls(MyWebTest)
 
 
 HTML_PAGE = u"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
--- a/devtools/testlib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/devtools/testlib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """this module contains base classes and utilities for cubicweb tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -12,8 +25,7 @@
 import re
 from urllib import unquote
 from math import log
-
-import simplejson
+from contextlib import contextmanager
 
 import yams.schema
 
@@ -31,7 +43,7 @@
 from cubicweb.web import Redirect, application
 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
 from cubicweb.devtools import fake, htmlparser
-
+from cubicweb.utils import json
 
 # low-level utilities ##########################################################
 
@@ -131,17 +143,16 @@
     """abstract class for test using an apptest environment
 
     attributes:
-    `vreg`, the vregistry
-    `schema`, self.vreg.schema
-    `config`, cubicweb configuration
-    `cnx`, dbapi connection to the repository using an admin user
-    `session`, server side session associated to `cnx`
-    `app`, the cubicweb publisher (for web testing)
-    `repo`, the repository object
 
-    `admlogin`, login of the admin user
-    `admpassword`, password of the admin user
-
+    * `vreg`, the vregistry
+    * `schema`, self.vreg.schema
+    * `config`, cubicweb configuration
+    * `cnx`, dbapi connection to the repository using an admin user
+    * `session`, server side session associated to `cnx`
+    * `app`, the cubicweb publisher (for web testing)
+    * `repo`, the repository object
+    * `admlogin`, login of the admin user
+    * `admpassword`, password of the admin user
     """
     appid = 'data'
     configcls = devtools.ApptestConfiguration
@@ -207,6 +218,7 @@
     def _build_repo(cls):
         cls.repo, cls.cnx = devtools.init_test_database(config=cls.config)
         cls.init_config(cls.config)
+        cls.repo.hm.call_hooks('server_startup', repo=cls.repo)
         cls.vreg = cls.repo.vreg
         cls._orig_cnx = cls.cnx
         cls.config.repository = lambda x=None: cls.repo
@@ -228,7 +240,9 @@
     @property
     def session(self):
         """return current server side session (using default manager account)"""
-        return self.repo._sessions[self.cnx.sessionid]
+        session = self.repo._sessions[self.cnx.sessionid]
+        session.set_pool()
+        return session
 
     @property
     def adminsession(self):
@@ -245,12 +259,25 @@
 
     def setUp(self):
         pause_tracing()
-        self._init_repo()
+        previous_failure = self.__class__.__dict__.get('_repo_init_failed')
+        if previous_failure is not None:
+            self.skip('repository is not initialised: %r' % previous_failure)
+        try:
+            self._init_repo()
+        except Exception, ex:
+            self.__class__._repo_init_failed = ex
+            raise
         resume_tracing()
+        self._cnxs = []
         self.setup_database()
         self.commit()
         MAILBOX[:] = [] # reset mailbox
 
+    def tearDown(self):
+        for cnx in self._cnxs:
+            if not cnx._closed:
+                cnx.close()
+
     def setup_database(self):
         """add your database setup code by overriding this method"""
 
@@ -265,20 +292,20 @@
             return req.user
 
     def create_user(self, login, groups=('users',), password=None, req=None,
-                    commit=True):
+                    commit=True, **kwargs):
         """create and return a new user entity"""
         if password is None:
             password = login.encode('utf8')
-        cursor = self._orig_cnx.cursor(req or self.request())
-        rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
-                              {'login': unicode(login), 'passwd': password})
-        user = rset.get_entity(0, 0)
-        cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
-                       % ','.join(repr(g) for g in groups),
-                       {'x': user.eid}, 'x')
+        if req is None:
+            req = self._orig_cnx.request()
+        user = req.create_entity('CWUser', login=unicode(login),
+                                 upassword=password, **kwargs)
+        req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
+                    % ','.join(repr(g) for g in groups),
+                    {'x': user.eid}, 'x')
         user.clear_related_cache('in_group', 'subject')
         if commit:
-            self._orig_cnx.commit()
+            req.cnx.commit()
         return user
 
     def login(self, login, **kwargs):
@@ -291,6 +318,7 @@
             self.cnx = repo_connect(self.repo, unicode(login),
                                     cnxprops=ConnectionProperties('inmemory'),
                                     **kwargs)
+            self._cnxs.append(self.cnx)
         if login == self.vreg.config.anonymous_user()[0]:
             self.cnx.anonymous_connection = True
         return self.cnx
@@ -299,6 +327,7 @@
         if not self.cnx is self._orig_cnx:
             try:
                 self.cnx.close()
+                self._cnxs.remove(self.cnx)
             except ProgrammingError:
                 pass # already closed
         self.cnx = self._orig_cnx
@@ -319,7 +348,10 @@
 
     @nocoverage
     def commit(self):
-        self.cnx.commit()
+        try:
+            return self.cnx.commit()
+        finally:
+            self.session.set_pool() # ensure pool still set after commit
 
     @nocoverage
     def rollback(self):
@@ -327,6 +359,8 @@
             self.cnx.rollback()
         except ProgrammingError:
             pass
+        finally:
+            self.session.set_pool() # ensure pool still set after commit
 
     # # server side db api #######################################################
 
@@ -339,6 +373,17 @@
     def entity(self, rql, args=None, eidkey=None, req=None):
         return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
 
+    @contextmanager
+    def temporary_appobjects(self, *appobjects):
+        self.vreg._loadedmods.setdefault(self.__module__, {})
+        for obj in appobjects:
+            self.vreg.register(obj)
+        try:
+            yield
+        finally:
+            for obj in appobjects:
+                self.vreg.unregister(obj)
+
     # vregistry inspection utilities ###########################################
 
     def pviews(self, req, rset):
@@ -449,7 +494,7 @@
 
     def remote_call(self, fname, *args):
         """remote json call simulation"""
-        dump = simplejson.dumps
+        dump = json.dumps
         args = [dump(arg) for arg in args]
         req = self.request(fname=fname, pageid='123', arg=args)
         ctrl = self.vreg['controllers'].select('json', req)
@@ -458,9 +503,9 @@
     def app_publish(self, req, path='view'):
         return self.app.publish(path, req)
 
-    def ctrl_publish(self, req):
+    def ctrl_publish(self, req, ctrl='edit'):
         """call the publish method of the edit controller"""
-        ctrl = self.vreg['controllers'].select('edit', req)
+        ctrl = self.vreg['controllers'].select(ctrl, req)
         try:
             result = ctrl.publish()
             req.cnx.commit()
@@ -484,7 +529,8 @@
             else:
                 cleanup = lambda p: (p[0], unquote(p[1]))
                 params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
-            path = path[len(req.base_url()):]
+            if path.startswith(req.base_url()): # may be relative
+                path = path[len(req.base_url()):]
             return path, params
         else:
             self.fail('expected a Redirect exception')
--- a/doc/book/en/.static/sphinx-default.css	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/.static/sphinx-default.css	Wed Apr 28 11:54:13 2010 +0200
@@ -3,7 +3,7 @@
  */
 
 html, body {
-    background: white;	
+    background: white;
 }
 
 body {
@@ -115,7 +115,7 @@
 }
 
 div.sphinxsidebar h3 {
-    font-family: 'Verdanda', sans-serif;
+    font-family: Verdana, sans-serif;
     color: black;
     font-size: 1.2em;
     font-weight: normal;
@@ -126,7 +126,7 @@
 }
 
 div.sphinxsidebar h4 {
-    font-family: 'Verdana', sans-serif;
+    font-family: Verdana, sans-serif;
     color: black;
     font-size: 1.1em;
     font-weight: normal;
--- a/doc/book/en/admin/additional-tips.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/additional-tips.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -1,55 +1,64 @@
 
 .. _Additional Tips:
 
-Additional Tips
----------------
-
-Here are some additional tips as far as administration of a CubicWeb is concerned.
-
-Backup, backup, backup
-``````````````````````
+Backups (mostly with postgresql)
+--------------------------------
 
 It is always a good idea to backup. If your system does not do that,
 you should set it up. Note that whenever you do an upgrade,
-`cubicweb-ctl` offers you to backup your database.
+`cubicweb-ctl` offers you to backup your database.  There are a number
+of ways for doing backups.
 
-There are a number of ways for doing backups. Before you go ahead,
-make sure the following permissions are correct ::
+Using postgresql (and only that)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before you
+go ahead, make sure the following permissions are correct ::
 
    # chgrp postgres /var/lib/cubicweb/backup
-
    # chmod g+ws /var/lib/cubicweb/backup
-
    # chgrp postgres /etc/cubicweb.d/*<instance>*/sources
-
    # chmod g+r /etc/cubicweb.d/*<instance>*/sources
 
-**Classic way**
+Simply use the pg_dump in a cron installed for `postgres` user on the database server::
 
-Simply use the pg_dump in a cron ::
-
-    su -c "pg_dump -Fc --username=cubicweb --no-owner" postgres > <your-instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
+    # m h  dom mon dow   command
+    0 2 * * * pg_dump -Fc --username=cubicweb --no-owner <instance> > /var/backups/<instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
 
-**CubicWeb way**
+Using :command:`cubicweb-ctl db-dump`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The CubicWeb way is to use the `db-dump` command. For that, you have to put your passwords in a user-only-readable file at the
-root of the postgres user. The file is `.pgpass` (`chmod 0600`), in this case for a socket run connection to postgres ::
+The CubicWeb way is to use the :command:`db-dump` command. For that,
+you have to put your passwords in a user-only-readable file at the
+home directory of root user.  The file is `.pgpass` (`chmod 0600`), in
+this case for a socket run connection to PostgreSQL ::
 
-    /var/run/postgresql:5432:<instance>:cubicweb:<password>
+    /var/run/postgresql:5432:<instance>:<database user>:<database password>
 
 The postgres documentation for the `.pgpass` format can be found `here`_
 
-Then add the following command to the crontab of the postgres user (`su posgres 'crontab -e'`)::
+Then add the following command to the crontab of the user (`crontab -e`)::
 
     # m h  dom mon dow   command
     0 2 * * * cubicweb-ctl db-dump <instance>
 
-**The automated sysadmin way**
+
+Backup ninja
+~~~~~~~~~~~~
 
-You can use a combination `backup-ninja`_ (which has a postgres script in the example directory), `backuppc`)_ (for versionning).
+You can use a combination `backup-ninja`_ (which has a postgres script in the
+example directory), `backuppc`)_ (for versionning).
 
-Please note that in the *CubicWeb way* it adds a second location for your password which is error-prone.
+Please note that in the *CubicWeb way* it adds a second location for your
+password which is error-prone.
 
 .. _`here` : http://www.postgresql.org/docs/current/static/libpq-pgpass.html
 .. _`backup-ninja` : https://labs.riseup.net/code/projects/show/backupninja/
 .. _`backuppc` : http://backuppc.sourceforge.net/
+
+.. warning::
+
+  Remember that these indications will fail you whenever you use
+  another database backend than postgres. Also it does properly handle
+  externally managed data such as files (using the Bytes File System
+  Storage).
--- a/doc/book/en/admin/create-instance.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/create-instance.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -20,7 +20,7 @@
 sufficient. You can anyway modify the configuration later on by editing
 configuration files. When a user/psswd is requested to access the database
 please use the login you create at the time you configured the database
-(:ref:`ConfigurationPostgresql`).
+(:ref:`PostgresqlConfiguration`).
 
 It is important to distinguish here the user used to access the database and the
 user used to login to the cubicweb instance. When an instance starts, it uses
@@ -36,17 +36,20 @@
 
 start / stop
 ~~~~~~~~~~~~
+
 When this command is completed, the definition of your instance is
-located in :file:`~/etc/cubicweb.d/myinstance/*`. To launch it, you just type ::
+located in :file:`~/etc/cubicweb.d/myinstance/*`. To launch it, you
+just type ::
 
   cubicweb-ctl start -D myinstance
 
-The option `-D` specify the *debug mode* : the instance is not running in
-server mode and does not disconnect from the termnial, which simplifies debugging
-in case the instance is not properly launched. You can see how it looks by
-visiting the URL `http://localhost:8080` (the port number depends of your
-configuration). To login, please use the cubicweb administrator login/psswd you
-defined when you created the instance.
+The option `-D` specifies the *debug mode* : the instance is not
+running in server mode and does not disconnect from the terminal,
+which simplifies debugging in case the instance is not properly
+launched. You can see how it looks by visiting the URL
+`http://localhost:8080` (the port number depends of your
+configuration). To login, please use the cubicweb administrator
+login/psswd you defined when you created the instance.
 
 To shutdown the instance, Crtl-C in the terminal window is enough.
 If you did not use the option `-D`, then type ::
@@ -55,6 +58,12 @@
 
 This is it! All is settled down to start developping your data model...
 
+.. note::
+
+  The output of `cubicweb-ctl start -D myinstance` can be
+  overwhelming. It is possible to reduce the log level with the
+  `--loglevel` parameter as in `cubicweb-ctl start -D myinstance -l
+  info` to filter out all logs under `info` gravity.
 
 upgrade
 ~~~~~~~
@@ -63,5 +72,27 @@
 
   cubicweb-ctl upgrade myinstance
 
-XXX write me
+A series of questions will be asked. It always starts with a proposal
+to make a backup of your sources (where it applies). Unless you know
+exactly what you are doing (i.e. typically fiddling in debug mode, but
+definitely NOT migrating a production instance), you should answer YES
+to that.
+
+The remaining questions concern the migration steps of |cubicweb|,
+then of the cubes that form the whole application, in reverse
+dependency order.
 
+In principle, if the migration scripts have been properly written and
+tested, you should answer YES to all questions.
+
+Somtimes, typically while debugging a migration script, something goes
+wrong and the migration fails. Unfortunately the databse may be in an
+incoherent state. You have two options here:
+
+* fix the bug, restore the database and restart the migration process
+  from scratch (quite recommended in a production environement)
+
+* try to replay the migration up to the last successful commit, that
+  is answering NO to all question up to the step that failed, and
+  finish by answering YES to the remaining questions.
+
--- a/doc/book/en/admin/gae.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/gae.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -210,7 +210,7 @@
 This cookie values needs to be provided to ``laxctl`` commands
 in order to handle datastore administration requests.
 
-.. image:: ../images/lax-book.02-cookie-values.en.png
+.. image:: ../images/lax-book_02-cookie-values_en.png
    :alt: displaying the detailed view of the cookie values returned
 
 
--- a/doc/book/en/admin/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -2,9 +2,9 @@
 
 .. _Part3:
 
--------------------------
-Part III - Administration
--------------------------
+--------------
+Administration
+--------------
 
 This part is for installation and administration of the *CubicWeb* framework and
 instances based on that framework.
@@ -21,6 +21,7 @@
    ldap
    pyro
    gae
+   migration
    additional-tips
 
 RQL logs
--- a/doc/book/en/admin/instance-config.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/instance-config.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -157,7 +157,7 @@
 
 :`ui.encoding`:
     Character encoding to use for the web
-:`navigation.short-line-size`: # XXX should be in ui
+:`navigation.short-line-size`:
     number of characters for "short" display
 :`navigation.page-size`:
     maximum number of entities to show per results page
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/migration.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,46 @@
+.. -*- coding: utf-8 -*-
+
+Migrating cubicweb instances - benefits from a distributed architecture
+=======================================================================
+
+Migrate apache & cubicweb
+-------------------------
+
+**Aim** : do the migration for N cubicweb instances hosted on a server to another with no downtime.
+
+**Prerequisites** : have an explicit definition of the database host (not default or localhost). In our case, the database is hosted on another host. You are not migrating your pyro server. You are not using multisource (more documentation on that soon).
+
+**Steps** :
+
+1. *on new machine* : install your environment (*pseudocode*) ::
+
+     apt-get install cubicweb cubicweb-applications apache2
+
+2. *on old machine* : copy your cubicweb and apache configuration to the new machine ::
+
+    scp /etc/cubicweb.d/ newmachine:/etc/cubicweb.d/
+    scp /etc/apache2/sites-available/ newmachine:/etc/apache2/sites-available/
+
+3. *on new machine* : give new ids to pyro registration so the new instances can register ::
+
+     cd /etc/cubicweb.d/ ; sed -i.bck 's/^pyro-instance-id=.*$/\02/' */all-in-one.conf
+
+4. *on new machine* : start your instances ::
+
+     cubicweb start
+
+5. *on new machine* : enable sites and modules for apache and start it, test it using by modifying your /etc/host file.
+
+6. change dns entry from your oldmachine to newmachine
+
+7. shutdown your *old machine* (if it doesn't host other services or your database)
+
+8. That's it.
+
+**Possible enhancements** : use right from the start a pound server behind your apache, that way you can add backends and smoothily migrate by shuting down backends that pound will take into account.
+
+Migrate apache & cubicweb with pyro
+-----------------------------------
+
+FIXME TODO
+
--- a/doc/book/en/admin/multisources.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/multisources.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -3,4 +3,4 @@
 
 Data sources include SQL, LDAP, RQL, mercurial and subversion.
 
-XXX feed me
+.. XXX feed me
--- a/doc/book/en/admin/setup.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/setup.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -8,16 +8,17 @@
 Installation of `Cubicweb` and its dependencies
 -----------------------------------------------
 
-*CubicWeb* is packaged for Debian and Ubuntu, but can be installed from source
+|cubicweb| is packaged for Debian and Ubuntu, but can be installed from source
 using a tarball or the Mercurial version control system.
 
+
 .. _DebianInstallation:
 
 Debian and Ubuntu packages
 ```````````````````````````
 
-Depending on the distribution you are using, add the appropriate line to your list
-of sources (for example by editing ``/etc/apt/sources.list``).
+Depending on the distribution you are using, add the appropriate line to your
+list of sources (for example by editing ``/etc/apt/sources.list``).
 
 For Debian Lenny::
 
@@ -37,21 +38,26 @@
   apt-get update
   apt-get install cubicweb cubicweb-dev
 
-`cubicweb` installs the framework itself, allowing you to create
-new instances.
+
+`cubicweb` installs the framework itself, allowing you to create new instances.
+
+`cubicweb-dev` installs the development environment allowing you to develop new
+cubes.
 
-`cubicweb-dev` installs the development environment allowing you to
-develop new cubes.
+There is also a wide variety of cubes listed on the `CubicWeb.org Forge`_
+available as debian packages and tarball.
 
-There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball.
+The repositories are signed with `Logilab's gnupg key`_. To avoid warning on
+"apt-get update":
 
-The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update":
 1. become root using sudo
 2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget
 3. run "apt-key add logilab-dists-key.asc"
 4. re-run apt-get update (manually or through the package manager, whichever you prefer)
 
 .. _`Logilab's gnupg key`: http://ftp.logilab.org/dists/logilab-dists-key.asc
+.. _`CubicWeb.org Forge`: http://www.cubicweb.org/project/
+
 
 .. _SourceInstallation:
 
@@ -66,6 +72,11 @@
 
 Make sure you have installed the dependencies (see appendixes for the list).
 
+|cubicweb| should soon be pip_ installable, stay tuned (expected in 3.8).
+
+.. _pip: http://pypi.python.org/pypi/pip
+
+
 Install from version control system
 ```````````````````````````````````
 
@@ -85,32 +96,31 @@
 
 Make sure you have installed the dependencies (see appendixes for the list).
 
+
 .. _WindowsInstallation:
 
 Windows installation
 ````````````````````
 
 Base elements
-_____________
+~~~~~~~~~~~~~
 
-Setting up a windows development environment is not too complicated
-but requires a series of small steps. What is proposed there is only
-an example of what can be done. We assume everything goes into C:\ in
-this document. Adjusting the installation drive should be
-straightforward.
+Setting up a windows development environment is not too complicated but requires
+a series of small steps. What is proposed there is only an example of what can be
+done. We assume everything goes into `C:\\` in this document. Adjusting the
+installation drive should be straightforward.
 
-You should start by downloading and installing the Python(x,y)
-distribution. It contains python 2.5 plus numerous useful third-party
-modules and applications::
+You should start by downloading and installing the Python(x,y) distribution. It
+contains python 2.5 plus numerous useful third-party modules and applications::
 
   http://www.pythonxy.com/download_fr.php
 
-At the time of this writting, one gets version 2.1.15. Among the many
-things provided, one finds Eclipse + pydev (an arguably good IDE for
-python under windows).
+At the time of this writting, one gets version 2.1.15. Among the many things
+provided, one finds Eclipse + pydev (an arguably good IDE for python under
+windows).
 
-Then you must grab Twisted. There is a windows installer directly
-available from this page::
+Then you must grab Twisted. There is a windows installer directly available from
+this page::
 
   http://twistedmatrix.com/trac/
 
@@ -129,11 +139,18 @@
 
   http://www.stickpeople.com/projects/python/win-psycopg/#Version2
 
-Please be careful to select the right python (2.5) and postgres (8.4)
-versions.
+Please be careful to select the right python (2.5) and postgres (8.4) versions.
+
+A windows compiled recent version of gettext::
+
+  http://ftp.logilab.org/pub/gettext/gettext-0.17-win32-setup.exe
 
-Pyro enables remote access to cubicweb repository instances. Get it
-there::
+A pre-compiled version of rql for windows (take care of retrieving the
+most recent version available there)::
+
+  http://ftp.logilab.org/pub/rql/rql-0.23.0.win32-py2.5.exe
+
+Pyro enables remote access to cubicweb repository instances. Get it there::
 
   http://sourceforge.net/projects/pyro/files/
 
@@ -144,26 +161,26 @@
 
 Check out the latest release.
 
-Having graphviz will allow schema drawings, which is quite recommended
-(albeit not mandatory). You should get an msi installer there::
+Having graphviz will allow schema drawings, which is quite recommended (albeit
+not mandatory). You should get an msi installer there::
 
   http://www.graphviz.org/Download_windows.php
 
-Simplejson will be provided within the forest, but a win32 compiled
-version will run much faster::
+Simplejson will be provided within the forest, but a win32 compiled version will
+run much faster::
 
   http://www.osuch.org/python-simplejson%3Awin32
 
 Tools
-_____
+~~~~~
 
-Get mercurial + its standard windows GUI (TortoiseHG) there (the
-latest is the greatest)::
+Get mercurial + its standard windows GUI (TortoiseHG) there (the latest is the
+greatest)::
 
   http://bitbucket.org/tortoisehg/stable/wiki/download
 
-If you need to peruse mercurial over ssh, it can be helpful to get an
-ssh client like Putty::
+If you need to peruse mercurial over ssh, it can be helpful to get an ssh client
+like Putty::
 
   http://www.putty.org/
 
@@ -173,10 +190,9 @@
   http://www.vectrace.com/mercurialeclipse/
 
 Setting up the sources
-______________________
+~~~~~~~~~~~~~~~~~~~~~~
 
-You need to enable the mercurial forest extension. To do this, edit
-the file::
+You need to enable the mercurial forest extension. To do this, edit the file::
 
   C:\Program Files\TortoiseHg\Mercurial.ini
 
@@ -185,8 +201,8 @@
   forest=C:\Program Files\TortoiseHg\ext\forest\forest.py
 
 Now, you need to clone the cubicweb repository. We assume that you use
-Eclipse. From the IDE, choose File -> Import. In the box, select
-`Mercurial/Clone repository using MercurialEclipse`.
+Eclipse. From the IDE, choose File -> Import. In the box, select `Mercurial/Clone
+repository using MercurialEclipse`.
 
 In the import main panel you just have to:
 
@@ -194,28 +210,26 @@
 
 * check the 'Repository is a forest' box.
 
-Then, click on 'Finish'. It might take some time to get it all. Note
-that the `cubicwin32` forest contains additional python packages such
-as yapps, vobject, simplejson and twisted-web2 which are not provided
-with Python(x,y). This is provided for convenience, as we do not
-ensure the up-to-dateness of these packages, especially with respect
-to security fixes.
+Then, click on 'Finish'. It might take some time to get it all. Note that the
+`cubicwin32` forest contains additional python packages such as yapps, vobject,
+simplejson and twisted-web2 which are not provided with Python(x,y). This is
+provided for convenience, as we do not ensure the up-to-dateness of these
+packages, especially with respect to security fixes.
 
 Environment variables
-_____________________
+~~~~~~~~~~~~~~~~~~~~~
 
-You will need some convenience environment variables once all is set
-up. These variables are settable through the GUI by getting at the
-'System properties' window (by righ-clicking on 'My Computer' ->
-properties).
+You will need some convenience environment variables once all is set up. These
+variables are settable through the GUI by getting at the 'System properties'
+window (by righ-clicking on 'My Computer' -> properties).
 
-In the 'advanced' tab, there is an 'Environment variables'
-button. Click on it. That opens a small window allowing edition of
-user-related and system-wide variables.
+In the 'advanced' tab, there is an 'Environment variables' button. Click on
+it. That opens a small window allowing edition of user-related and system-wide
+variables.
 
-We will consider only user variables. First, the PATH variable. You
-should ensure it contains, separated by semi-colons, and assuming you
-are logged in as user Jane::
+We will consider only user variables. First, the PATH variable. You should ensure
+it contains, separated by semi-colons, and assuming you are logged in as user
+Jane::
 
   C:\Documents and Settings\Jane\My Documents\Python\cubicweb\cubicweb\bin
   C:\Program Files\Graphviz2.24\bin
@@ -231,13 +245,13 @@
 ... and get a meaningful output.
 
 Running an instance as a service
---------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This currently assumes that the instances configurations is located
-at C:\\etc\\cubicweb.d.
+This currently assumes that the instances configurations is located at
+C:\\etc\\cubicweb.d.
 
-For a cube 'my_cube', you will then find C:\\etc\\cubicweb.d\\my_cube\\win32svc.py
-that has to be used thusly::
+For a cube 'my_cube', you will then find
+C:\\etc\\cubicweb.d\\my_cube\\win32svc.py that has to be used thusly::
 
   win32svc install
 
@@ -248,17 +262,6 @@
 should start the service.
 
 
-PostgreSQL installation
-```````````````````````
-
-Please refer to the `PostgreSQL project online documentation`_.
-
-.. _`PostgreSQL project online documentation`: http://www.postgresql.org/
-
-You need to install the three following packages: `postgresql-8.3`,
-`postgresql-contrib-8.3` and `postgresql-plpython-8.3`.
-
-
 Other dependencies
 ``````````````````
 
@@ -271,103 +274,115 @@
 
 * `python-ldap` if you plan to use a LDAP source on the server
 
-.. _ConfigurationEnv:
 
-Environment configuration
--------------------------
-
-If you installed *CubicWeb* by cloning the Mercurial forest, then you
-will need to update the environment variable PYTHONPATH by adding
-the path to the forest ``cubicweb``:
-
-Add the following lines to either `.bashrc` or `.bash_profile` to configure
-your development environment ::
-
-    export PYTHONPATH=/full/path/to/cubicweb-forest
-
-If you installed *CubicWeb* with packages, no configuration is required and your
-new cubes will be placed in `/usr/share/cubicweb/cubes` and your instances
-will be placed in `/etc/cubicweb.d`.
-
-You may run a system-wide install of *CubicWeb* in "user mode" and use it for
-development by setting the following environment variable::
-
-    export CW_MODE=user
-    export CW_CUBES_PATH=~/lib/cubes
-    export CW_INSTANCES_DIR=~/etc/cubicweb.d/
-    export CW_INSTANCES_DATA_DIR=$CW_INSTANCES_DIR
-    export CW_RUNTIME_DIR=/tmp
-
-.. note::
-    The values given above are our suggestions but of course
-    can be different.
-
+.. _DatabaseInstallation:
 
 Databases configuration
 -----------------------
 
-.. _ConfigurationPostgresql:
+Whatever the backend used, database connection information are stored in the
+instance's :file:`sources` file. Currently cubicweb has been tested using
+Postgresql (recommanded), MySQL, SQLServer and SQLite.
+
+.. _PostgresqlConfiguration:
 
 PostgreSQL configuration
 ````````````````````````
 
-.. note::
-    If you already have an existing cluster and PostgreSQL server
-    running, you do not need to execute the initilization step
-    of your PostgreSQL database.
+For installation, please refer to the `PostgreSQL project online documentation`_.
+
+.. _`PostgreSQL project online documentation`: http://www.postgresql.org/
+
+You need to install the three following packages: `postgresql-8.X`,
+`postgresql-client-8.X`, and `postgresql-plpython-8.X`. If you run postgres
+version prior to 8.3, you'll also need the `postgresql-contrib-8.X` package for
+full-text search extension.
 
-* First, initialize the database PostgreSQL with the command ``initdb``.
+If you run postgres on another host than the |cubicweb| repository, you should
+install the `postgresql-client` package on the |cubicweb| host, and others on the
+database host.
+
+.. Note::
+
+    If you already have an existing cluster and PostgreSQL server running, you do
+    not need to execute the initilization step of your PostgreSQL database unless
+    you want a specific cluster for |cubicweb| databases or if your existing
+    cluster doesn't use the UTF8 encoding (see note below).
+
+* First, initialize a PostgreSQL cluster with the command ``initdb``.
   ::
 
-    $ initdb -D /path/to/pgsql
+    $ initdb -E UTF8 -D /path/to/pgsql
+
+  Notice the encoding specification. This is necessary since |cubicweb| usually
+  want UTF8 encoded database. If you use a cluster with the wrong encoding, you'll
+  get error like::
 
-  Once initialized, start the database server PostgreSQL
-  with the command::
+    new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
+    HINT:  Use the same encoding as in the template database, or use template0 as template.
+
+
+  Once initialized, start the database server PostgreSQL with the command::
 
     $ postgres -D /path/to/psql
 
-  If you cannot execute this command due to permission issues, please
-  make sure that your username has write access on the database.
-  ::
+  If you cannot execute this command due to permission issues, please make sure
+  that your username has write access on the database.  ::
 
     $ chown username /path/to/pgsql
 
-* The database authentication can be either set to `ident sameuser`
-  or `md5`.
-  If set to `md5`, make sure to use an existing user
-  of your database.
-  If set to `ident sameuser`, make sure that your
-  client's operating system user name has a matching user in
-  the database. If not, please do as follow to create a user::
+* The database authentication can be either set to `ident sameuser` or `md5`.  If
+  set to `md5`, make sure to use an existing user of your database.  If set to
+  `ident sameuser`, make sure that your client's operating system user name has a
+  matching user in the database. If not, please do as follow to create a user::
 
     $ su
     $ su - postgres
     $ createuser -s -P username
 
-  The option `-P` (for password prompt), will encrypt the password with
-  the method set in the configuration file ``pg_hba.conf``.
-  If you do not use this option `-P`, then the default value will be null
-  and you will need to set it with::
+  The option `-P` (for password prompt), will encrypt the password with the
+  method set in the configuration file :file:`pg_hba.conf`.  If you do not use this
+  option `-P`, then the default value will be null and you will need to set it
+  with::
 
     $ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
 
-  This login/password will be requested when you will create an
-  instance with `cubicweb-ctl create` to initialize the database of
-  your instance.
-
-.. note::
-    The authentication method can be configured in ``pg_hba.conf``.
+.. Note::
+    The authentication method can be configured in file:`pg_hba.conf`.
 
 
-.. FIXME Are these steps really necessary? It seemed to work without.
+The above login/password will be requested when you will create an instance with
+`cubicweb-ctl create` to initialize the database of your instance.
 
-* Installation of plain-text index extension ::
+Notice that the `cubicweb-ctl db-create` does database initialization that
+may requires a postgres superuser. That's why a login/password is explicitly asked
+at this step, so you can use there a superuser without using this user when running
+the instance. Things that require special privileges at this step:
+
+* database creation, require the 'create database' permission
+* install the plpython extension language (require superuser)
+* install the tsearch extension for postgres version prior to 8.3 (require superuser)
 
-    cat /usr/share/postgresql/8.3/contrib/tsearch2.sql | psql -U username template1
+To avoid using a super user each time you create an install, a nice trick is to
+install plpython (and tsearch when needed) on the special `template1` database,
+so they will be installed automatically when cubicweb databases are created
+without even with needs for special access rights. To do so, run ::
+
+  # Installation of plpythonu language by default ::
+  $ createlang -U pgadmin plpythonu template1
+  $ psql -U pgadmin template1
+  template1=# update pg_language set lanpltrusted=TRUE where lanname='plpythonu';
 
-* Installation of plpythonu language by default ::
+Where `pgadmin` is a postgres superuser. The last command is necessary since by
+default plpython is an 'untrusted' language and as such can't be used by non
+superuser. This update fix that problem by making it trusted.
 
-    createlang -U pgadmin plpythonu template1
+To install the tsearch plain-text index extension on postgres prior to 8.3, run::
+
+    cat /usr/share/postgresql/8.X/contrib/tsearch2.sql | psql -U username template1
+
+
+.. _MySqlConfiguration:
 
 MySql configuration
 ```````````````````
@@ -378,19 +393,22 @@
     default-character-set=utf8
     max_allowed_packet = 128M
 
-.. note::
+.. Note::
     It is unclear whether mysql supports indexed string of arbitrary lenght or
     not.
 
+
+.. _SQLServerConfiguration:
+
 SQLServer configuration
------------------------
+```````````````````````
 
-As of this writing, sqlserver support is in progress. You should be
-able to connect, create a database and go quite far, but some of the
-generated SQL is still currently not accepted by the backend.
+As of this writing, sqlserver support is in progress. You should be able to
+connect, create a database and go quite far, but some of the generated SQL is
+still currently not accepted by the backend.
 
-The `source` configuration file may look like this (specific parts
-only are shown)::
+The `source` configuration file may look like this (specific parts only are
+shown)::
 
   [system]
   db-driver=sqlserver2005
@@ -402,17 +420,40 @@
   db-encoding=utf8
 
 
+
+.. _SQLiteConfiguration:
+
+SQLite configuration
+````````````````````
+SQLite has the great advantage of requiring almost no configuration. Simply
+use 'sqlite' as db-driver, and set path to the dabase as db-name. Don't specify
+anything for db-user and db-password, they will be ignore anyway.
+
+.. Note::
+  SQLite is great for testing and to play with cubicweb but is not suited for
+  production environments.
+
+
+.. _PyroConfiguration:
+
 Pyro configuration
 ------------------
 
-If you use Pyro, it is required to have a name server Pyro running on your
-network (by default it is detected by a broadcast request).
+If you want to use Pyro to access your instance remotly, or to have multi-source
+or distributed configuration, it is required to have a name server Pyro running
+on your network. By by default it is detected by a broadcast request, but you can
+specify a location in the instance's configuration file.
 
 To do so, you need to :
 
-* launch the server manually before starting cubicweb as a server with
-  `pyro-nsd start`
+* launch the server manually before starting cubicweb as a server with `pyro-nsd
+  start`
+
+* under debian, edit the file :file:`/etc/default/pyro-nsd` so that the name
+  server pyro will be launched automatically when the machine fire up
 
-* edit the file ``/etc/default/pyro-nsd`` so that the name server pyro
-  will be launched automatically when the machine fire up
 
+Cubicweb resources configuration
+--------------------------------
+
+.. autodocstring:: cubicweb.cwconfig
--- a/doc/book/en/admin/site-config.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/admin/site-config.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -3,7 +3,7 @@
 User interface for web site configuration
 =========================================
 
-.. image:: ../images/lax-book.03-site-config-panel.en.png
+.. image:: ../images/lax-book_03-site-config-panel_en.png
 
 This panel allows you to configure the appearance of your instance site.
 Six menus are available and we will go through each of them to explain how
--- a/doc/book/en/annexes/cookbook.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Cook book
-=========
-
-We gathered together some of our tricks and scripts that could make
-life easier.
-
-
-* How to import LDAP users in *CubicWeb*?
-
-  [XXX distribute this script with cubicweb instead]
-
-  Here is a very useful script which enables you to import LDAP users
-  into your *CubicWeb* instance by running the following:
-
-.. sourcecode:: python
-
-    import os
-    import pwd
-    import sys
-
-    from logilab.common.db import get_connection
-
-    def getlogin():
-        """avoid usinng os.getlogin() because of strange tty / stdin problems
-        (man 3 getlogin)
-        Another solution would be to use $LOGNAME, $USER or $USERNAME
-        """
-        return pwd.getpwuid(os.getuid())[0]
-
-
-    try:
-        database = sys.argv[1]
-    except IndexError:
-        print 'USAGE: python ldap2system.py <database>'
-        sys.exit(1)
-
-    if raw_input('update %s db ? [y/n]: ' % database).strip().lower().startswith('y'):
-        cnx = get_connection(user=getlogin(), database=database)
-        cursor = cnx.cursor()
-
-        insert = ('INSERT INTO euser (creation_date, eid, modification_date, login, firstname, surname, last_login_time, upassword) '
-                  "VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, %(firstname)s, %(surname)s, %(mtime)s, './fqEz5LeZnT6');")
-        update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
-        cursor.execute("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'")
-        for eid, type, source, extid, mtime in cursor.fetchall():
-            if type != 'CWUser':
-                print "don't know what to do with entity type", type
-                continue
-            if source != 'ldapuser':
-                print "don't know what to do with source type", source
-                continue
-            ldapinfos = dict(x.strip().split('=') for x in extid.split(','))
-            login = ldapinfos['uid']
-            firstname = ldapinfos['uid'][0].upper()
-            surname = ldapinfos['uid'][1:].capitalize()
-            if login != 'jcuissinat':
-                args = dict(eid=eid, type=type, source=source, login=login,
-                            firstname=firstname, surname=surname, mtime=mtime)
-                print args
-                cursor.execute(insert, args)
-                cursor.execute(update, args)
-
-        cnx.commit()
-        cnx.close()
-
-
-* How to load data from a script?
-
-  The following script aims at loading data within a script assuming pyro-nsd is
-  running and your instance is configured with ``pyro-server=yes``, otherwise
-  you would not be able to use dbapi.
-
-.. sourcecode:: python
-
-    from cubicweb import dbapi
-
-    cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
-    cur = cnx.cursor()
-    for name in ('Personal', 'Professional', 'Computers'):
-        cur.execute('INSERT Blog B: B name %s', name)
-    cnx.commit()
-
-
--- a/doc/book/en/annexes/faq.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/annexes/faq.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -6,11 +6,26 @@
 [XXX 'copy answer from forum' means reusing text from
 http://groups.google.com/group/google-appengine/browse_frm/thread/c9476925f5f66ec6
 and
-http://groups.google.com/group/google-appengine/browse_frm/thread/d791ce17e2716147/eb078f8cfe8426e0
-and
 http://groups.google.com/group/google-appengine/browse_frm/thread/f48cf6099973aef5/c28cd6934dd72457
 ]
 
+Generalities
+````````````
+
+Why do you use the LGPL license to prevent me from doing X ?
+------------------------------------------------------------
+
+LGPL means that *if* you redistribute your application, you need to
+redistribute the changes you made to CubicWeb under the LGPL licence.
+
+Publishing a web site has nothing to do with redistributing source
+code according to the terms of the LGPL. A fair amount of companies
+use modified LGPL code for internal use. And someone could publish a
+*CubicWeb* component under a BSD licence for others to plug into a
+LGPL framework without any problem. The only thing we are trying to
+prevent here is someone taking the framework and packaging it as
+closed source to his own clients.
+
 Why does not CubicWeb have a template language ?
 ------------------------------------------------
 
@@ -26,8 +41,13 @@
 Python is the templating language that we use in *CubicWeb*, but again,
 it does not prevent you from using a templating language.
 
-The reason template languages are not used in this book is that
-experience has proved us that using pure python was less cumbersome.
+Moreover, CubicWeb currently supports `simpletal`_ out of the box and
+it is also possible to use the `cwtags`_ library to build html trees
+using the `with statement`_ with more comfort than raw strings.
+
+.. _`simpletal`: http://www.owlfish.com/software/simpleTAL/
+.. _`cwtags`: http://www.cubicweb.org/project/cwtags
+.. _`with statement`: http://www.python.org/dev/peps/pep-0343/
 
 Why do you think using pure python is better than using a template language ?
 -----------------------------------------------------------------------------
@@ -36,33 +56,11 @@
 already provides a consistent and strong architecture and syntax
 a templating language would not reach.
 
-When doing development, you need a real language and template
-languages are not real languages.
-
 Using Python instead of a template langage for describing the user interface
 makes it to maintain with real functions/classes/contexts without the need of
 learning a new dialect. By using Python, we use standard OOP techniques and
 this is a key factor in a robust application.
 
-The `cwtags` (http://www.cubicweb.org/project/cwtags) package can be
-used in cubes to help generate html from Python with more comfort than
-raw strings.
-
-Why do you use the LGPL license to prevent me from doing X ?
-------------------------------------------------------------
-
-LGPL means that *if* you redistribute your application, you need to
-redistribute the changes you made to CubicWeb under the LGPL licence.
-
-Publishing a web site has nothing to do with redistributing
-source code. A fair amount of companies use modified LGPL code
-for internal use. And someone could publish a *CubicWeb* component
-under a BSD licence for others to plug into a LGPL framework without
-any problem. The only thing we are trying to prevent here is someone
-taking the framework and packaging it as closed source to his own
-clients.
-
-
 CubicWeb looks pretty recent. Is it stable ?
 --------------------------------------------
 
@@ -70,6 +68,10 @@
 2001 and data has been migrated from one schema to the other ever since. There
 is a well-defined way to handle data and schema migration.
 
+You can see the roadmap there:
+http://www.cubicweb.org/project/cubicweb?tab=projectroadmap_tab.
+
+
 Why is the RQL query language looking similar to X ?
 -----------------------------------------------------
 
@@ -77,7 +79,7 @@
 SPARQL. Except that SPARQL did not exist when we started the project.
 With version 3.4, CubicWeb has support for SPARQL.
 
-That RQL language is what is going to make a difference with django-
+The RQL language is what is going to make a difference with django-
 like frameworks for several reasons.
 
 1. accessing data is *much* easier with it. One can write complex
@@ -99,6 +101,315 @@
 that. Additionally, some jQuery plugins are provided (some are
 provided in specific cubes).
 
+Development
+```````````
+
+How to load data from a script ?
+--------------------------------
+
+The following script aims at loading data within a script assuming pyro-nsd is
+running and your instance is configured with ``pyro-server=yes``, otherwise
+you would not be able to use dbapi.
+
+.. sourcecode:: python
+
+    from cubicweb import dbapi
+
+    cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
+    cur = cnx.cursor()
+    for name in ('Personal', 'Professional', 'Computers'):
+        cur.execute('INSERT Blog B: B name %s', name)
+    cnx.commit()
+
+
+How to format an entity date attribute ?
+----------------------------------------
+
+If your schema has an attribute of type Date or Datetime, you might
+want to format it. First, you should define your preferred format using
+the site configuration panel ``http://appurl/view?vid=systempropertiesform``
+and then set ``ui.date`` and/or ``ui.datetime``.
+Then in the view code, use:
+
+.. sourcecode:: python
+
+    self.format_date(entity.date_attribute)
+
+What is the CubicWeb datatype corresponding to GAE datastore's UserProperty ?
+-----------------------------------------------------------------------------
+
+If you take a look at your instance schema and
+click on "display detailed view of metadata" you will see that there
+is a Euser entity in there. That's the one that is modeling users. The
+thing that corresponds to a UserProperty is a relationship between
+your entity and the Euser entity. As in:
+
+.. sourcecode:: python
+
+    class TodoItem(EntityType):
+       text = String()
+       todo_by = SubjectRelation('Euser')
+
+[XXX check that cw handle users better by mapping Google Accounts to local Euser
+entities automatically]
+
+
+How do I translate an msg id defined (and translated) in another cube ?
+-----------------------------------------------------------------------
+
+You should put these translations in the `i18n/static-messages.pot`
+file of your own cube.
+
+
+What is `Error while publishing rest text ...` ?
+------------------------------------------------
+
+While modifying the description of an entity, you get an error message in
+the instance `Error while publishing ...` for Rest text and plain text.
+The server returns a traceback like as follows ::
+
+      2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text
+      Traceback (most recent call last):
+      File "/home/user/src/blogdemo/cubicweb/common/rest.py", line 217, in rest_publish
+      File "/usr/lib/python2.5/codecs.py", line 817, in open
+      file = __builtin__.open(filename, mode, buffering)
+      TypeError: __init__() takes at most 3 arguments (4 given)
+
+This can be fixed by applying the patch described in :
+http://code.google.com/p/googleappengine/issues/detail?id=48
+
+What are hooks used for ?
+-------------------------
+
+Hooks are executed around (actually before or after) events.  The
+most common events are data creation, update and deletion.  They
+permit additional constraint checking (those not expressible at the
+schema level), pre and post computations depending on data
+movements.
+
+As such, they are a vital part of the framework.
+
+Other kinds of hooks, called Operations, are available
+for execution just before commit.
+
+When should you define an HTML template rather than define a graphical component ?
+----------------------------------------------------------------------------------
+
+An HTML template cannot contain code, hence it is only about static
+content.  A component is made of code and operations that apply on a
+well defined context (request, result set). It enables much more
+dynamic views.
+
+How to update a database after a schema modification ?
+------------------------------------------------------
+
+It depends on what has been modified in the schema.
+
+* update the permissions and properties of an entity or a relation:
+  ``sync_schema_props_perms('MyEntityOrRelation')``.
+
+* add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
+
+* add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
+
+
+How to create an anonymous user ?
+---------------------------------
+
+This allows to bypass authentication for your site. In the
+``all-in-one.conf`` file of your instance, define the anonymous user
+as follows ::
+
+  # login of the CubicWeb user account to use for anonymous user (if you want to
+  # allow anonymous)
+  anonymous-user=anon
+
+  # password of the CubicWeb user account matching login
+  anonymous-password=anon
+
+You also must ensure that this `anon` user is a registered user of
+the DB backend. If not, you can create through the administation
+interface of your instance by adding a user with the role `guests`.
+This could be the admin account (for development
+purposes, of course).
+
+.. note::
+    While creating a new instance, you can decide to allow access
+    to anonymous user, which will automatically execute what is
+    decribed above.
+
+
+How to change the instance logo ?
+------------------------------------
+
+There are two ways of changing the logo.
+
+1. The easiest way to use a different logo is to replace the existing
+   ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
+   By default all instance will look for a ``logo.png`` to be
+   rendered in the logo section.
+
+   .. image:: ../images/lax-book_06-main-template-logo_en.png
+
+2. In your cube directory, you can specify which file to use for the logo.
+   This is configurable in ``mycube/data/external_resources``: ::
+
+     LOGO = DATADIR/path/to/mylogo.gif
+
+   where DATADIR is ``mycube/data``.
+
+Configuration
+`````````````
+
+How to configure a LDAP source ?
+--------------------------------
+
+Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
+Configuring an LDAP source is about declaring that source in your
+instance configuration file such as: ::
+
+  [ldapuser]
+  adapter=ldapuser
+  # ldap host
+  host=myhost
+  # base DN to lookup for usres
+  user-base-dn=ou=People,dc=mydomain,dc=fr
+  # user search scope
+  user-scope=ONELEVEL
+  # classes of user
+  user-classes=top,posixAccount
+  # attribute used as login on authentication
+  user-login-attr=uid
+  # name of a group in which ldap users will be by default
+  user-default-group=users
+  # map from ldap user attributes to cubicweb attributes
+  user-attrs-map=gecos:email,uid:login
+
+Any change applied to configuration file requires to restart your
+instance.
+
+You can find additional information in the section :ref:`LDAP`.
+
+How to import LDAP users in |cubicweb| ?
+----------------------------------------
+
+  Here is a useful script which enables you to import LDAP users
+  into your *CubicWeb* instance by running the following:
+
+.. sourcecode:: python
+
+    import os
+    import pwd
+    import sys
+
+    from logilab.common.db import get_connection
+
+    def getlogin():
+        """avoid usinng os.getlogin() because of strange tty / stdin problems
+        (man 3 getlogin)
+        Another solution would be to use $LOGNAME, $USER or $USERNAME
+        """
+        return pwd.getpwuid(os.getuid())[0]
+
+
+    try:
+        database = sys.argv[1]
+    except IndexError:
+        print 'USAGE: python ldap2system.py <database>'
+        sys.exit(1)
+
+    if raw_input('update %s db ? [y/n]: ' % database).strip().lower().startswith('y'):
+        cnx = get_connection(user=getlogin(), database=database)
+        cursor = cnx.cursor()
+
+        insert = ('INSERT INTO euser (creation_date, eid, modification_date, login, '
+                  ' firstname, surname, last_login_time, upassword) '
+                  "VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, %(firstname)s, "
+                  "%(surname)s, %(mtime)s, './fqEz5LeZnT6');")
+        update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
+        cursor.execute("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'")
+        for eid, type, source, extid, mtime in cursor.fetchall():
+            if type != 'CWUser':
+                print "don't know what to do with entity type", type
+                continue
+            if source != 'ldapuser':
+                print "don't know what to do with source type", source
+                continue
+            ldapinfos = dict(x.strip().split('=') for x in extid.split(','))
+            login = ldapinfos['uid']
+            firstname = ldapinfos['uid'][0].upper()
+            surname = ldapinfos['uid'][1:].capitalize()
+            if login != 'jcuissinat':
+                args = dict(eid=eid, type=type, source=source, login=login,
+                            firstname=firstname, surname=surname, mtime=mtime)
+                print args
+                cursor.execute(insert, args)
+                cursor.execute(update, args)
+
+        cnx.commit()
+        cnx.close()
+
+
+I get NoSelectableObject exceptions, how do I debug selectors ?
+---------------------------------------------------------------
+
+You just need to put the appropriate context manager around view/component
+selection (one standard place in in vreg.py):
+
+.. sourcecode:: python
+
+    def possible_objects(self, registry, *args, **kwargs):
+        """return an iterator on possible objects in a registry for this result set
+
+        actions returned are classes, not instances
+        """
+        from cubicweb.selectors import traced_selection
+        with traced_selection():
+            for vobjects in self.registry(registry).values():
+                try:
+                    yield self.select(vobjects, *args, **kwargs)
+                except NoSelectableObject:
+                    continue
+
+Don't forget the 'from __future__ import with_statement' at the module
+top-level.
+
+This will yield additional WARNINGs, like this::
+
+    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
+
+Security
+````````
+
+How to reset the password for user joe ?
+----------------------------------------
+
+If you want to reset the admin password for ``myinstance``, do::
+
+    $ cubicweb-ctl reset-admin-pwd myinstance
+
+You need to generate a new encrypted password::
+
+    $ python
+    >>> from cubicweb.server.utils import crypt_password
+    >>> crypt_password('joepass')
+    'qHO8282QN5Utg'
+    >>>
+
+and paste it in the database::
+
+    $ psql mydb
+    mydb=> update cw_cwuser set cw_upassword='qHO8282QN5Utg' where cw_login='joe';
+    UPDATE 1
+
+I've just created a user in a group and it doesn't work !
+---------------------------------------------------------
+
+You are probably getting errors such as ::
+
+  remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
+
+This is because you have to put your user in the "users" group. The user has to be in both groups.
 
 How is security implemented ?
 ------------------------------
@@ -152,181 +463,6 @@
 
 You can find additional information in the section :ref:`securitymodel`.
 
-
-What is `Error while publishing rest text ...` ?
-------------------------------------------------
-
-While modifying the description of an entity, you get an error message in
-the instance `Error while publishing ...` for Rest text and plain text.
-The server returns a traceback like as follows ::
-
-      2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text
-      Traceback (most recent call last):
-      File "/home/user/src/blogdemo/cubicweb/common/rest.py", line 217, in rest_publish
-      File "/usr/lib/python2.5/codecs.py", line 817, in open
-      file = __builtin__.open(filename, mode, buffering)
-      TypeError: __init__() takes at most 3 arguments (4 given)
-
-This can be fixed by applying the patch described in :
-http://code.google.com/p/googleappengine/issues/detail?id=48
-
-What are hooks used for ?
--------------------------
-
-Hooks are executed around (actually before or after) events.  The
-most common events are data creation, update and deletion.  They
-permit additional constraint checking (those not expressible at the
-schema level), pre and post computations depending on data
-movements.
-
-As such, they are a vital part of the framework.
-
-Other kinds of hooks, called Operations, are available
-for execution just before commit.
-
-When should you define an HTML template rather than define a graphical component ?
-----------------------------------------------------------------------------------
-
-An HTML template cannot contain code, hence it is only about static
-content.  A component is made of code and operations that apply on a
-well defined context (request, result set). It enables much more
-dynamic views.
-
-What is the difference between `AppRsetObject` and `AppObject` ?
-----------------------------------------------------------------
-
-`AppRsetObject` instances are selected on a request and a result
-set. `AppObject` instances are directly selected by id.
-
-How to update a database after a schema modification ?
-------------------------------------------------------
-
-It depends on what has been modified in the schema.
-
-* Update the permissions and properties of an entity or a relation:
-  ``sync_schema_props_perms('MyEntityOrRelation')``.
-
-* Add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
-
-* Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
-
-
-How to create an anonymous user ?
----------------------------------
-
-This allows to bypass authentication for your site. In the
-``all-in-one.conf`` file of your instance, define the anonymous user
-as follows ::
-
-  # login of the CubicWeb user account to use for anonymous user (if you want to
-  # allow anonymous)
-  anonymous-user=anon
-
-  # password of the CubicWeb user account matching login
-  anonymous-password=anon
-
-You also must ensure that this `anon` user is a registered user of
-the DB backend. If not, you can create through the administation
-interface of your instance by adding a user with the role `guests`.
-This could be the admin account (for development
-purposes, of course).
-
-.. note::
-    While creating a new instance, you can decide to allow access
-    to anonymous user, which will automatically execute what is
-    decribed above.
-
-
-How to change the instance logo ?
-------------------------------------
-
-There are two ways of changing the logo.
-
-1. The easiest way to use a different logo is to replace the existing
-   ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
-   By default all instance will look for a ``logo.png`` to be
-   rendered in the logo section.
-
-   .. image:: ../images/lax-book.06-main-template-logo.en.png
-
-2. In your cube directory, you can specify which file to use for the logo.
-   This is configurable in ``mycube/data/external_resources``: ::
-
-     LOGO = DATADIR/path/to/mylogo.gif
-
-   where DATADIR is ``mycube/data``.
-
-
-How to configure a LDAP source ?
---------------------------------
-
-Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
-Configuring an LDAP source is about declaring that source in your
-instance configuration file such as: ::
-
-  [ldapuser]
-  adapter=ldapuser
-  # ldap host
-  host=myhost
-  # base DN to lookup for usres
-  user-base-dn=ou=People,dc=mydomain,dc=fr
-  # user search scope
-  user-scope=ONELEVEL
-  # classes of user
-  user-classes=top,posixAccount
-  # attribute used as login on authentication
-  user-login-attr=uid
-  # name of a group in which ldap users will be by default
-  user-default-group=users
-  # map from ldap user attributes to cubicweb attributes
-  user-attrs-map=gecos:email,uid:login
-
-Any change applied to configuration file requires to restart your
-instance.
-
-You can find additional information in the section :ref:`LDAP`.
-
-I get NoSelectableObject exceptions, how do I debug selectors ?
----------------------------------------------------------------
-
-You just need to put the appropriate context manager around view/component
-selection (one standard place in in vreg.py):
-
-.. sourcecode:: python
-
-    def possible_objects(self, registry, *args, **kwargs):
-        """return an iterator on possible objects in a registry for this result set
-
-        actions returned are classes, not instances
-        """
-        from cubicweb.selectors import traced_selection
-        with traced_selection():
-            for vobjects in self.registry(registry).values():
-                try:
-                    yield self.select(vobjects, *args, **kwargs)
-                except NoSelectableObject:
-                    continue
-
-Don't forget the 'from __future__ import with_statement' at the module
-top-level.
-
-This will yield additional WARNINGs, like this::
-
-    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
-
-How to format an entity date attribute ?
-----------------------------------------
-
-If your schema has an attribute of type Date or Datetime, you might
-want to format it. First, you should define your preferred format using
-the site configuration panel ``http://appurl/view?vid=systempropertiesform``
-and then set ``ui.date`` and/or ``ui.datetime``.
-Then in the view code, use:
-
-.. sourcecode:: python
-
-    self.format_date(entity.date_attribute)
-
 Can PostgreSQL and CubicWeb authentication work with kerberos ?
 ----------------------------------------------------------------
 
@@ -336,68 +472,3 @@
 instance to connect to postgresql with a kerberos ticket.
 
 
-How to load data from a script ?
---------------------------------
-
-The following script aims at loading data within a script assuming pyro-nsd is
-running and your instance is configured with ``pyro-server=yes``, otherwise
-you would not be able to use dbapi.
-
-.. sourcecode:: python
-
-    from cubicweb import dbapi
-
-    cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
-    cur = cnx.cursor()
-    for name in ('Personal', 'Professional', 'Computers'):
-        cur.execute('INSERT Blog B: B name %s', name)
-    cnx.commit()
-
-What is the CubicWeb datatype corresponding to GAE datastore's UserProperty ?
------------------------------------------------------------------------------
-
-If you take a look at your instance schema and
-click on "display detailed view of metadata" you will see that there
-is a Euser entity in there. That's the one that is modeling users. The
-thing that corresponds to a UserProperty is a relationship between
-your entity and the Euser entity. As in:
-
-.. sourcecode:: python
-
-    class TodoItem(EntityType):
-       text = String()
-       todo_by = SubjectRelation('Euser')
-
-[XXX check that cw handle users better by mapping Google Accounts to local Euser
-entities automatically]
-
-
-How to reset the password for user joe ?
-----------------------------------------
-
-If you want to reset the admin password for ``myinstance``, do::
-
-    $ cubicweb-ctl reset-admin-pwd myinstance
-
-You need to generate a new encrypted password::
-
-    $ python
-    >>> from cubicweb.server.utils import crypt_password
-    >>> crypt_password('joepass')
-    'qHO8282QN5Utg'
-    >>>
-
-and paste it in the database::
-
-    $ psql mydb
-    mydb=> update cw_cwuser set cw_upassword='qHO8282QN5Utg' where cw_login='joe';
-    UPDATE 1
-
-I've just created a user in a group and it doesn't work !
----------------------------------------------------------
-
-You are probably getting errors such as ::
-
-  remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
-
-This is because you have to put your user in the "users" group. The user has to be in both groups.
--- a/doc/book/en/annexes/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/annexes/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -2,9 +2,9 @@
 
 .. _Part4:
 
---------------------
-Part IV - Appendixes
---------------------
+----------
+Appendixes
+----------
 
 The following chapters are reference material.
 
@@ -13,7 +13,6 @@
    :numbered:
 
    faq
-   cookbook
    cubicweb-ctl
    rql/index
    mercurial
--- a/doc/book/en/annexes/rql/implementation.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/annexes/rql/implementation.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -2,6 +2,7 @@
 
 Implementation
 --------------
+
 BNF grammar
 ~~~~~~~~~~~
 
@@ -126,8 +127,8 @@
 ~~~~~~~~~~~~~~~~~
 
 - The current implementation does not support linking two relations of type 'is'
-  with a OR. I do not think that the negation is supported on this type of
-  relation (XXX FIXME to be confirmed).
+  with an OR. I do not think that the negation is supported on this type of
+  relation (XXX to be confirmed).
 
 - Relations defining the variables must be left to those using them.  For
   example::
@@ -140,11 +141,11 @@
 
   is not.
 
-- missing proper explicit type conversion,  COALESCE and certainly other things...
+- missing proper explicit type conversion, COALESCE and certainly other things...
 
-- writing a rql query require knowledge of the schema used (with real relation
-  names and entities, not those viewing in the user interface). On the other
-  hand, we can not really bypass that, and it is the job of a user interface to
+- writing an rql query requires knowledge of the used schema (with real relation
+  names and entities, not those viewed in the user interface). On the other
+  hand, we cannot really bypass that, and it is the job of a user interface to
   hide the RQL.
 
 
--- a/doc/book/en/annexes/rql/intro.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/annexes/rql/intro.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,5 @@
+
+.. _rql_intro:
 
 Introduction
 ------------
@@ -5,10 +7,10 @@
 Goals of RQL
 ~~~~~~~~~~~~
 
-The goal is to have a language emphasizing the way of browsing relations. As
-such, attributes will be regarded as cases of special relations (in terms of
-implementation, the user should see no difference between an attribute and a
-relation).
+The goal is to have a language making relations browsing easy. As
+such, attributes will be regarded as cases of special relations (in
+terms of usage, the user should see no syntactic difference between an
+attribute and a relation).
 
 Comparison with existing languages
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -32,10 +34,10 @@
 
 We should look in more detail, but here are already some ideas for the moment
 ... Versa_ is the language most similar to what we wanted to do, but the model
-underlying data being RDF, there is some number of things such as namespaces or
+underlying data being RDF, there are some things such as namespaces or
 handling of the RDF types which does not interest us. On the functionality
 level, Versa_ is very comprehensive including through many functions of
-conversion and basic types manipulation, which may need to be guided at one time
+conversion and basic types manipulation, which we may want to look at one time
 or another.  Finally, the syntax is a little esoteric.
 
 
--- a/doc/book/en/annexes/rql/language.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/annexes/rql/language.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -92,8 +92,7 @@
     Any X WHERE X name IN ( 'chauvat', 'fayolle', 'di mascio', 'thenault')
 
 
-XXX nico: "A trick <> 'bar'" wouldn't it be more convenient than
-"NOT A trick 'bar'" ?
+.. XXX nico: "A trick <> 'bar'" wouldn't it be more convenient than "NOT A trick 'bar'" ?
 
 .. _PriorityOperators:
 
@@ -373,3 +372,15 @@
 
         DELETE X friend Y WHERE X is Person, X name 'foo'
 
+Virtual RQL relations
+~~~~~~~~~~~~~~~~~~~~~
+
+Those relations may only be used in RQL query and are not actual
+attributes of your entities.
+
+* `has_text`: relation to use to query the full text index (only for
+  entities having fulltextindexed attributes).
+
+* `identity`: relation to use to tell that a RQL variable should be
+  the same as another (but you've to use two different rql variables
+  for querying purpose)
--- a/doc/book/en/conf.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/conf.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 #
 # Cubicweb documentation build configuration file, created by
@@ -19,6 +32,7 @@
 # serve to show the default value.
 
 import sys, os
+
 from cubicweb import __pkginfo__ as cw
 
 # If your extensions are in another directory, add it here. If the directory
@@ -31,10 +45,10 @@
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = ['sphinx.ext.autodoc', 'logilab.common.sphinx_ext']
 autoclass_content = 'both'
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['.templates']
+#templates_path = []
 
 # The suffix of source filenames.
 source_suffix = '.rst'
@@ -44,7 +58,7 @@
 
 # General substitutions.
 project = 'CubicWeb'
-copyright = '2008-2010, Logilab'
+copyright = '2001-2010, Logilab'
 
 # The default replacements for |version| and |release|, also used in various
 # other places throughout the built documents.
@@ -91,11 +105,13 @@
 # The style sheet to use for HTML and HTML Help pages. A file of that name
 # must exist either in Sphinx' static/ path, or in one of the custom paths
 # given in html_static_path.
-html_style = 'sphinx-default.css'
+#html_style = 'sphinx-default.css'
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
 html_title = '%s %s' % (project, release)
+html_theme = 'standard_theme'
+html_theme_path = ['.']
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
 #html_short_title = None
@@ -185,3 +201,11 @@
 
 # If false, no module index is generated.
 #latex_use_modindex = True
+
+#aafig_format = dict(latex='pdf', html='svg', text=None)
+
+rst_epilog = """
+.. |cubicweb| replace:: *CubicWeb*
+.. |yams| replace:: *Yams*
+.. |rql| replace:: *RQL*
+"""
--- a/doc/book/en/development/cubes/available-cubes.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-
-Available cubes
----------------
-
-An instance is based on several basic cubes. In the set of available
-basic cubes we can find for example :
-
-Base entity types
-~~~~~~~~~~~~~~~~~
-* addressbook_: PhoneNumber and PostalAddress
-* card_: Card, generic documenting card
-* event_: Event (define events, display them in calendars)
-* file_: File (to allow users to upload and store binary or text files)
-* link_: Link (to collect links to web resources)
-* mailinglist_: MailingList (to reference a mailing-list and the URLs
-  for its archives and its admin interface)
-* person_: Person (easily mixed with addressbook)
-* task_: Task (something to be done between start and stop date)
-* zone_: Zone (to define places within larger places, for example a
-  city in a state in a country)
-
-
-Classification
-~~~~~~~~~~~~~~
-* folder_: Folder (to organize things but grouping them in folders)
-* keyword_: Keyword (to define classification schemes)
-* tag_: Tag (to tag anything)
-
-Other features
-~~~~~~~~~~~~~~
-* basket_: Basket (like a shopping cart)
-* blog_: a blogging system uxing Blog and BlogEntry entity types
-* comment_: system to attach comment threads to entities)
-* email_: archiving management for emails (`Email`, `Emailpart`,
-  `Emailthread`), trigger action in cubicweb through email
-
-
-
-
-
-.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
-.. _basket: http://www.cubicweb.org/project/cubicweb-basket
-.. _card: http://www.cubicweb.org/project/cubicweb-card
-.. _blog: http://www.cubicweb.org/project/cubicweb-blog
-.. _comment: http://www.cubicweb.org/project/cubicweb-comment
-.. _email: http://www.cubicweb.org/project/cubicweb-email
-.. _event: http://www.cubicweb.org/project/cubicweb-event
-.. _file: http://www.cubicweb.org/project/cubicweb-file
-.. _folder: http://www.cubicweb.org/project/cubicweb-folder
-.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
-.. _link: http://www.cubicweb.org/project/cubicweb-link
-.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
-.. _person: http://www.cubicweb.org/project/cubicweb-person
-.. _tag: http://www.cubicweb.org/project/cubicweb-tag
-.. _task: http://www.cubicweb.org/project/cubicweb-task
-.. _zone: http://www.cubicweb.org/project/cubicweb-zone
-
-To declare the use of a component, once installed, add the name of the component
-to the variable `__use__` in the file `__pkginfo__.py` of your own component.
-
-.. note::
-  The listed cubes above are available as debian-packages on `CubicWeb's forge`_.
-
-.. _`CubicWeb's forge`: http://www.cubicweb.org/project?vtitle=All%20cubicweb%20projects
--- a/doc/book/en/development/cubes/cc-newcube.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-Creating a new cube from scratch using :command:`cubicweb-ctl newcube`
-----------------------------------------------------------------------
-
-Let's start by creating the cube environment in which we will develop ::
-
-  cd ~/hg
-  # use cubicweb-ctl to generate a template for the cube
-  cubicweb-ctl newcube mycube  # will ask some questions, most with nice default
-  # makes the cube source code managed by mercurial
-  cd mycube
-  hg init
-  hg add .
-  hg ci
-
-If all went well, you should see the cube you just created in the list
-returned by ``cubicweb-ctl list`` in the section *Available cubes*,
-and if it is not the case please refer to :ref:`ConfigurationEnv`.
-
-To reuse an existing cube, add it to the list named ``__use__`` and defined in
-:file:`__pkginfo__.py`.  This variable is used for the instance packaging
-(dependencies handled by system utility tools such as APT) and the usable cubes
-at the time the base is created (import_erschema('MyCube') will not properly
-work otherwise).
-
-.. note::
-
-    Please note that if you do not wish to use default directory for your cubes
-    library, you should set the :envvar:`CW_CUBES_PATH` environment variable to
-    add extra directories where cubes will be search, and you'll then have to use
-    the option `--directory` to specify where you would like to place the source
-    code of your cube:
-
-    ``cubicweb-ctl newcube --directory=/path/to/cubes/library mycube``
-
-
-.. XXX resurrect once live-server is back
-.. Usage of :command:`cubicweb-ctl liveserver`
-.. -------------------------------------------
-
-.. To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
-.. which allows to create an instance in memory (using an SQLite database by
-.. default) and make it accessible through a web server ::
-
-..   cubicweb-ctl live-server mycube
-
-.. or by using an existing database (SQLite or Postgres)::
-
-..   cubicweb-ctl live-server -s myfile_sources mycube
--- a/doc/book/en/development/cubes/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-Cubes
-=====
-
-This chapter describes how to define your own cubes and reuse already available cubes.
-
-.. toctree::
-   :maxdepth: 1
-
-   layout.rst
-   cc-newcube.rst
-   available-cubes.rst
--- a/doc/book/en/development/cubes/layout.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-
-.. _foundationsCube:
-
-.. _cubelayout:
-
-Standard structure for a cube
------------------------------
-
-A cube is structured as follows:
-
-::
-
-  mycube/
-  |
-  |-- data/
-  |   |-- cubes.mycube.css
-  |   |-- cubes.mycube.js
-  |   `-- external_resources
-  |
-  |-- debian/
-  |   |-- changelog
-  |   |-- compat
-  |   |-- control
-  |   |-- copyright
-  |   |-- cubicweb-mycube.prerm
-  |   `-- rules
-  |
-  |-- entities.py
-  |
-  |-- i18n/
-  |   |-- en.po
-  |   |-- es.po
-  |   `-- fr.po
-  |
-  |-- __init__.py
-  |
-  |-- MANIFEST.in
-  |
-  |-- migration/
-  |   |-- postcreate.py
-  |   `-- precreate.py
-  |
-  |-- __pkginfo__.py
-  |
-  |-- schema.py
-  |
-  |-- setup.py
-  |
-  |-- site_cubicweb.py
-  |
-  |-- hooks.py
-  |
-  |-- test/
-  |   |-- data/
-  |   |   `-- bootstrap_cubes
-  |   |-- pytestconf.py
-  |   |-- realdb_test_mycube.py
-  |   `-- test_mycube.py
-  |
-  `-- views.py
-
-
-We can use subpackages instead of python modules for ``views.py``, ``entities.py``,
-``schema.py`` or ``hooks.py``. For example, we could have:
-
-::
-
-  mycube/
-  |
-  |-- entities.py
-  |-- hooks.py
-  `-- views/
-      |-- forms.py
-      |-- primary.py
-      `-- widgets.py
-
-
-where :
-
-* ``schema`` contains the schema definition (server side only)
-* ``entities`` contains the entities definition (server side and web interface)
-* ``hooks`` contains hooks and/or views notifications (server side only)
-* ``views`` contains the web interface components (web interface only)
-* ``test`` contains tests related to the cube (not installed)
-* ``i18n`` contains message catalogs for supported languages (server side and
-  web interface)
-* ``data`` contains data files for static content (images, css, javascripts)
-  ...(web interface only)
-* ``migration`` contains initialization files for new instances (``postcreate.py``)
-  and a file containing dependencies of the component depending on the version
-  (``depends.map``)
-* ``debian`` contains all the files managing debian packaging (you will find
-  the usual files ``control``, ``rules``, ``changelog``... not installed)
-* file ``__pkginfo__.py`` provides component meta-data, especially the distribution
-  and the current version (server side and web interface) or sub-cubes used by
-  the cube.
-
-
-At least you should have:
-
-* the file ``__pkginfo__.py``
-* the schema definition
-  XXX false, we may want to have cubes which are only adding a service,
-  no persistent data (eg embedding for instance)
-
-
-
-The :file:`__init__.py` and :file:`site_cubicweb.py` files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :file:`__pkginfo__.py` file
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-XXX contains metadata describing your cubes
-    distname / modname
-    version / numversion
-    __use__
-    __recommend__
-
-
-:file:`migration/precreate.py` and :file:`migration/postcreate.py`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-XXX detail steps of instance creation
-
-
-External resources such as image, javascript and css files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-XXX naming convention external_resources file
-
-
-Out-of the box testing
-~~~~~~~~~~~~~~~~~~~~~~
-XXX MANIFEST.in, __pkginfo__.include_dirs, debian
-
-
-
-Packaging and distribution
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-XXX MANIFEST.in, __pkginfo__.include_dirs, debian
-
--- a/doc/book/en/development/datamodel/baseschema.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-.. _CWBaseEntityTypes:
-
-Pre-defined entities in the library
------------------------------------
-
-The library defines a set of entity schemas that are required by the system
-or commonly used in *CubicWeb* instances.
-
-
-Entity types used to store the schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* _`CWEType`, entity type
-* _`CWRType`, relation type
-* _`CWRelation`, relation definition
-* _`CWAttribute`, attribute relation definition
-* _`CWConstraint`,  `CWConstraintType`, `RQLExpression`
-
-Entity types used to manage users and permissions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* _`CWUser`, system users
-* _`CWGroup`, users groups
-* _`CWPermission`, used to configure the security of the instance
-
-Entity types used to manage workflows
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* _`Workflow`, workflow entity, linked to some entity types which may use this workflow
-* _`State`, workflow state
-* _`Transition`, workflow transition
-* _`TrInfo`, record of a transition trafic for an entity
-
-Other entity types
-~~~~~~~~~~~~~~~~~~
-* _`CWCache`, cache entities used to improve performances
-* _`CWProperty`, used to configure the instance
-
-* _`EmailAddress`, email address, used by the system to send notifications
-  to the users and also used by others optionnals schemas
-
-* _`Bookmark`, an entity type used to allow a user to customize his links within
-  the instance
-
-* _`ExternalUri`, used for semantic web site to indicate that an entity is the
-  same as another from an external site
--- a/doc/book/en/development/datamodel/define-workflows.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Workflow:
-
-Define a Workflow
-=================
-
-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.
-
-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 ten minutes (see :ref:`BlogFiveMinutes`).
-
-Set-up a workflow
------------------
-
-We want to create a workflow to control the quality of the BlogEntry
-submitted on the instance. When a BlogEntry is created by a user
-its state should be `submitted`. To be visible to all, it has to
-be in the state `published`. To move it from `submitted` to `published`,
-we need a transition that we can call `approve_blogentry`.
-
-A BlogEntry state should not be modifiable by every user.
-So we have to define a group of users, `moderators`, and
-this group will have appropriate permissions to publish a BlogEntry.
-
-There are two ways to create a workflow: from the user interface, or
-by defining it in ``migration/postcreate.py``. This script is executed
-each time a new ``cubicweb-ctl db-init`` is done.  We strongly
-recommend to create the workflow in ``migration/postcreate.py`` and we
-will now show you how. Read `Two bits of warning`_ to understand why.
-
-The state of an entity is managed by the `in_state` attribute which
-can be added to your entity schema by inheriting from
-`cubicweb.schema.WorkflowableEntityType`.
-
-
-About our example of BlogEntry, we must have:
-
-.. sourcecode:: python
-
-  from cubicweb.schema import WorkflowableEntityType
-
-  class BlogEntry(WorkflowableEntityType):
-      ...
-
-
-Create states, transitions and group permissions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``postcreate.py`` script is executed in a special environment, adding
-several *CubicWeb* primitives that can be used.
-
-They are all defined in the ``class ServerMigrationHelper``.
-We will only discuss the methods we use to create a workflow in this example.
-
-A workflow is a collection of entities of type ``State`` and of type
-``Transition`` which are standard *CubicWeb* entity types.
-
-To define a workflow for BlogDemo, please add the following lines
-to ``migration/postcreate.py``:
-
-.. sourcecode:: python
-
-  _ = unicode
-
-  moderators = add_entity('CWGroup', name=u"moderators")
-
-This adds the `moderators` user group.
-
-.. sourcecode:: python
-
-  wf = add_workflow(u'blog publication workflow', 'BlogEntry')
-
-At first, instanciate a new workflow object with a gentle description
-and the concerned entity types (this one can be a tuple for multiple
-value).
-
-.. sourcecode:: python
-
-  submitted = wf.add_state(_('submitted'), initial=True)
-  published = wf.add_state(_('published'))
-
-This will create two entities of type ``State``, one with name
-'submitted', and the other with name 'published'.
-
-``add_state`` expects as first argument the name of the state you want
-to create and an optional argument to say if it is supposed to be the
-initial state of the entity type.
-
-.. sourcecode:: python
-
-  wf.add_transition(_('approve_blogentry'), (submitted,), published, ('moderators', 'managers'),)
-
-This will create an entity of type ``Transition`` with name
-`approve_blogentry` which will be linked to the ``State`` entities
-created before.
-
-``add_transition`` expects
-
-  * as the first argument: the name of the transition
-  * then the list of states on which the transition can be triggered,
-  * the target state of the transition,
-  * and the permissions
-    (e.g. a list of user groups who can apply the transition; the user
-    has to belong to at least one of the listed group to perform the action).
-
-.. sourcecode:: python
-
-  checkpoint()
-
-.. note::
-  Do not forget to add the `_()` in front of all states and transitions names while creating
-  a workflow so that they will be identified by the i18n catalog scripts.
-
-In addition to the user groups (one of which the user needs to belong
-to), we could have added a RQL condition.  In this case, the user can
-only perform the action if the two conditions are satisfied.
-
-If we use an RQL condition on a transition, we can use the following variables:
-
-* `X`, the entity on which we may pass the transition
-* `U`, the user executing that may pass the transition
-
-
-.. image:: ../../images/03-transitions-view.en.png
-
-You can notice that in the action box of a BlogEntry, the state is now
-listed as well as the possible transitions for the current state
-defined by the workflow.
-
-The transitions will only be displayed for users having the right permissions.
-In our example, the transition `approve_blogentry` will only be displayed
-for the users belonging to the group `moderators` or `managers`.
-
-
-Two bits of warning
-~~~~~~~~~~~~~~~~~~~
-
-We could perfectly use the administration interface to do these
-operations. It is a convenient thing to do at times (when doing
-development, to quick-check things). But it is not recommended beyond
-that because it is a bit complicated to do it right and it will be
-only local to your instance (or, said a bit differently, such a
-workflow only exists in an instance database). Furthermore, you cannot
-write unit tests against deployed instances, and experience shows it
-is mandatory to have tests for any mildly complicated workflow
-setup.
-
-Indeed, if you create the states and transitions through the user
-interface, next time you initialize the database you will have to
-re-create all the workflow entities. The user interface should only be
-a reference for you to view the states and transitions, but is not the
-appropriate interface to define your application workflow.
--- a/doc/book/en/development/datamodel/definition.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,512 +0,0 @@
- .. -*- coding: utf-8 -*-
-
-Yams *schema*
--------------
-
-The **schema** is the core piece of a *CubicWeb* instance as it defines
-the handled data model. It is based on entity types that are either already
-defined in the *CubicWeb* standard library; or more specific types defined
-in cubes. The schema for a cube is defined in a :file:schema.py file or in
-one or more Python files under the :file:`schema` directory (python package).
-
-At this point, it is important to make clear the difference between
-*relation type* and *relation definition*: a *relation type* is only a relation
-name with potentially other additionnal properties (see below), whereas a
-*relation definition* is a complete triplet
-"<subject entity type> <relation type> <object entity type>".
-A relation type could have been implied if none is related to a
-relation definition of the schema.
-
-Also, it should be clear that to properly handle data migration, an instance'schema
-is stored in the database, so the python schema file used to defined it are only readen
-when the instance is created or upgraded.
-
-The following built-in types are available : `String`, `Int`, `Float`,
-`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte`
-and `Password`.
-
-You'll also have access to :ref:`base cubicweb entity types <CWBaseEntityTypes>`.
-
-The instance schema is accessible through the .schema attribute of the
-`vregistry`.  It's an instance of :class:`cubicweb.schema.Schema`, which
-extends :class:`yams.schema.Schema`.
-
-:note:
-  In previous yams versions, almost all classes where available without
-  any import, but the should now be explicitely imported.
-
-
-Entity type
-~~~~~~~~~~~
-It's an instance of :class:`yams.schema.EntitySchema`. Each entity types has
-a set of attributes and relation and some permissions, defining who can add, read,
-update or delete entities of this type.
-
-XXX yams inheritance
-
-Relation type
-~~~~~~~~~~~~~
-It's an instance of :class:`yams.schema.RelationSchema`. A relation type is simply
-a semantic definition of a kind of relationship that may occurs in your application.
-
-It's important to choose a good name, at least to avoid conflicts with some semantically
-different relation defined in other cubes (since we've no namespace yet).
-
-A relation type hold the following properties (which are hence shared between all
-relation definitions of that type):
-
-* `inlined` : boolean handling the physical optimization for archiving
-  the relation in the subject entity table, instead of creating a specific
-  table for the relation. This applies to relations where cardinality
-  of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) for *all* its relation
-  definitions.
-
-* `symmetric` : boolean indicating that the relation is symmetrical, which
-  means that `X relation Y` implies `Y relation X`.
-
-
-Relation definition
-~~~~~~~~~~~~~~~~~~~
-It's an instance of :class:`yams.schema.RelationDefinition`. It is a complete triplet
-"<subject entity type> <relation type> <object entity type>".
-
-Properties
-``````````
-
-* Optional properties for attributes and relations :
-
-  - `description` : a string describing an attribute or a relation. By default
-    this string will be used in the editing form of the entity, which means
-    that it is supposed to help the end-user and should be flagged by the
-    function `_` to be properly internationalized.
-
-  - `constraints` : a list of conditions/constraints that the relation has to
-    satisfy (c.f. `Constraints`_)
-
-  - `cardinality` : a two character string which specify the cardinality of the
-    relation. The first character defines the cardinality of the relation on
-    the subject, and the second on the object. When a relation can have
-    multiple subjects or objects, the cardinality applies to all,
-    not on a one-to-one basis (so it must be consistent...). The possible
-    values are inspired from regular expression syntax :
-
-    * `1`: 1..1
-    * `?`: 0..1
-    * `+`: 1..n
-    * `*`: 0..n
-
-* optional properties for attributes :
-
-  - `unique` : boolean indicating if the value of the attribute has to be unique
-    or not within all entities of the same type (false by default)
-
-  - `indexed` : boolean indicating if an index needs to be created for this
-    attribute in the database (false by default). This is useful only if
-    you know that you will have to run numerous searches on the value of this
-    attribute.
-
-  - `default` : default value of the attribute. In case of date types, the values
-    which could be used correspond to the RQL keywords `TODAY` and `NOW`.
-
-* optional properties of type `String` :
-
-  - `fulltextindexed` : boolean indicating if the attribute is part of
-    the full text index (false by default) (*applicable on the type `Byte`
-    as well*)
-
-  - `internationalizable` : boolean indicating if the value of the attribute
-    is internationalizable (false by default)
-
-* optional properties for relations :
-
-  - `composite` : string indicating that the subject (composite == 'subject')
-    is composed of the objects of the relations. For the opposite case (when
-    the object is composed of the subjects of the relation), we just set
-    'object' as value. The composition implies that when the relation
-    is deleted (so when the composite is deleted, at least), the composed are also deleted.
-
-  - `fti_container`: XXX feed me
-
-Constraints
-```````````
-
-By default, the available constraint types are :
-
-General Constraints
-......................
-
-* `SizeConstraint` : allows to specify a minimum and/or maximum size on
-  string (generic case of `maxsize`)
-
-* `BoundConstraint` : allows to specify a minimum and/or maximum value on
-  numeric types
-
-* `UniqueConstraint` : identical to "unique=True"
-
-* `StaticVocabularyConstraint` : identical to "vocabulary=(...)"
-
-XXX Attribute, TODAY, NOW
-
-RQL Based Constraints
-......................
-
-RQL based constraints may take three arguments. The first one is the ``WHERE``
-clause of a RQL query used by the constraint. The second argument ``mainvars``
-is the ``Any`` clause of the query. By default this include `S` reserved for the
-subject of the relation and `O` for the object. Additional variables could be
-specified using ``mainvars``. The argument expects a single string with all
-variable's name separated by spaces. The last one, ``msg``, is the error message
-displayed when the constraint fails. As RQLVocabularyConstraint never fails the
-third argument is not available.
-
-* `RQLConstraint` : allows to specify a RQL query that has to be satisfied
-  by the subject and/or the object of relation. In this query the variables
-  `S` and `O` are reserved for the entities subject and object of the
-  relation.
-
-* `RQLVocabularyConstraint` : similar to the previous type of constraint except
-  that it does not express a "strong" constraint, which means it is only used to
-  restrict the values listed in the drop-down menu of editing form, but it does
-  not prevent another entity to be selected.
-
-* `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an
-  attribute is unique in a specific context. The Query must **never** return more
-  than a single result to be satisfied. In this query the variables `S` is
-  reserved for the entity subject of the relation. The other variable should be
-  specified with the second constructor argument (mainvars). This constraints
-  should be used when UniqueConstraint doesn't fit. Here is a simple example ::
-
-    # Check that in the same Workflow each state's name is unique.  Using
-    # UniqueConstraint (or unique=True) here would prevent states in different
-    # workflows to have the same name.
-
-    # With: State S, Workflow W, String N ; S state_of W, S name N
-
-    RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N',
-                        mainvars='Y',
-                        msg=_('workflow already have a state of that name'))
-
-
-
-* `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an
-  attribute is unique in a specific context. The Query must **never** return more
-  than a single result to be satisfied. In this query the variables `S` is
-  reserved for the entity subject of the relation. The other variable should be
-  specified with the second constructor argument (mainvars). This constraints
-  should be used when UniqueConstraint doesn't fit. Here is a simple example ::
-
-    # Check that in the same Workflow each state's name is unique.  Using
-    # UniqueConstraint (or unique=True) here would prevent states in different
-    # workflows to have the same name.
-
-    # With: State S, Workflow W, String N ; S state_of W, S name N
-
-    RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N',
-                        mainvars='Y',
-                        msg=_('workflow already have a state of that name'))
-
-
-
-XXX note about how to add new constraint
-
-.. _securitymodel:
-
-
-The security model
-~~~~~~~~~~~~~~~~~~
-
-The security model of `cubicWeb` is based on `Access Control List`.
-The main principles are:
-
-* users and groups of users
-* a user belongs to at least one group of user
-* permissions (read, update, create, delete)
-* permissions are assigned to groups (and not to users)
-
-For *CubicWeb* in particular:
-
-* we associate rights at the enttities/relations schema level
-* for each entity, we distinguish four kind of permissions: read,
-  add, update and delete
-* for each relation, we distinguish three kinds of permissions: read,
-  add and delete (we can not modify a relation)
-* the basic groups are: Administrators, Users and Guests
-* by default, users belong to the group Users
-* there is a virtual group called `Owners` to which we
-  can associate only deletion and update permissions
-* we can not add users to the `Owners` group, they are
-  implicitly added to it according to the context of the objects
-  they own
-* the permissions of this group are only checked on update/deletion
-  actions if all the other groups the user belongs to does not provide
-  those permissions
-
-Setting permissions is done with the attribute `__permissions__` of entities and
-relation types. It defines a dictionary where the keys are the access types
-(action), and the values are the authorized groups or expressions.
-
-For an entity type, the possible actions are `read`, `add`, `update` and
-`delete`.
-
-For a relation type, the possible actions are `read`, `add`, and `delete`.
-
-For each access type, a tuple indicates the name of the authorized groups and/or
-one or multiple RQL expressions to satisfy to grant access. The access is
-provided if the user is in one of the listed groups or one of if the RQL condition
-is satisfied.
-
-The standard user groups
-````````````````````````
-
-* `guests`
-
-* `users`
-
-* `managers`
-
-* `owners` : virtual group corresponding to the entity's owner.
-  This can only be used for the actions `update` and `delete` of an entity
-  type.
-
-It is also possible to use specific groups if they are defined in the
-precreate of the cube (``migration/precreate.py``). Defining groups in
-postcreate or even later makes them NOT available for security
-purposes (in this case, an `sync_schema_props_perms` command have to
-be issued in a CubicWeb shell).
-
-
-Use of RQL expression for write permissions
-```````````````````````````````````````````
-It is possible to define RQL expression to provide update permission
-(`add`, `delete` and `update`) on relation and entity types.
-
-RQL expression for entity type permission :
-
-* you have to use the class `ERQLExpression`
-
-* the used expression corresponds to the WHERE statement of an RQL query
-
-* in this expression, the variables X and U are pre-defined references
-  respectively on the current entity (on which the action is verified) and
-  on the user who send the request
-
-* it is possible to use, in this expression, a special relation
-  "has_<ACTION>_permission" where the subject is the user and the
-  object is any variable, meaning that the user needs to have
-  permission to execute the action <ACTION> on the entities related
-  to this variable
-
-For RQL expressions on a relation type, the principles are the same except
-for the following :
-
-* you have to use the class `RRQLExpression` in the case of a non-final relation
-
-* in the expression, the variables S, O and U are pre-defined references
-  to respectively the subject and the object of the current relation (on
-  which the action is being verified) and the user who executed the query
-
-* we can also define rights over attributes of an entity (non-final relation),
-  knowing that :
-
-  - to define RQL expression, we have to use the class `ERQLExpression`
-    in which X represents the entity the attribute belongs to
-
-  - the permissions `add` and `delete` are equivalent. Only `add`/`read`
-    are actually taken in consideration.
-
-:Note on the use of RQL expression for `add` permission:
-
-  Potentially, the use of an RQL expression to add an entity or a
-  relation can cause problems for the user interface, because if the
-  expression uses the entity or the relation to create, then we are
-  not able to verify the permissions before we actually add the entity
-  (please note that this is not a problem for the RQL server at all,
-  because the permissions checks are done after the creation). In such
-  case, the permission check methods (CubicWebEntitySchema.check_perm
-  and has_perm) can indicate that the user is not allowed to create
-  this entity but can obtain the permission.
-  To compensate this problem, it is usually necessary, for such case,
-  to use an action that reflects the schema permissions but which enables
-  to check properly the permissions so that it would show up if necessary.
-
-
-Use of RQL expression for reading rights
-````````````````````````````````````````
-
-The principles are the same but with the following restrictions :
-
-* we can not use `RRQLExpression` on relation types for reading
-
-* special relations "has_<ACTION>_permission" can not be used
-
-
-
-
-Defining your schema using yams
--------------------------------
-
-Entity type definition
-~~~~~~~~~~~~~~~~~~~~~~
-
-An entity type is defined by a Python class which inherits from `EntityType`.
-The class definition contains the description of attributes and relations
-for the defined entity type.
-The class name corresponds to the entity type name. It is exepected to be
-defined in the module ``mycube.schema``.
-
-When defining a schema using python files, you may use the following shortcuts:
-
-- `required` : boolean indicating if the attribute is required, eg subject cardinality is '1'
-
-- `vocabulary` : specify static possible values of an attribute
-
-- `maxsize` : integer providing the maximum size of a string (no limit by default)
-
-For example:
-
-.. sourcecode:: python
-
-  class Person(EntityType):
-    """A person with the properties and the relations necessary for my
-    application"""
-
-    last_name = String(required=True, fulltextindexed=True)
-    first_name = String(required=True, fulltextindexed=True)
-    title = String(vocabulary=('Mr', 'Mrs', 'Miss'))
-    date_of_birth = Date()
-    works_for = SubjectRelation('Company', cardinality='?*')
-
-
-The entity described above defines three attributes of type String,
-last_name, first_name and title, an attribute of type Date for the date of
-birth and a relation that connects a `Person` to another entity of type
-`Company` through the semantic `works_for`.
-
-The name of the Python attribute corresponds to the name of the attribute
-or the relation in *CubicWeb* application.
-
-An attribute is defined in the schema as follows::
-
-    attr_name = attr_type(properties)
-
-where `attr_type` is one of the type listed above and `properties` is
-a list of the attribute needs to statisfy (see `Properties`_
-for more details).
-
-
-* relations can be defined by using `ObjectRelation` or `SubjectRelation`.
-  The first argument of `SubjectRelation` or `ObjectRelation` gives respectively
-  the object/subject entity type of the relation. This could be :
-
-  * a string corresponding to an entity type
-
-  * a tuple of string corresponding to multiple entity types
-
-  * special string such as follows :
-
-    - "**" : all types of entities
-    - "*" : all types of non-meta entities
-    - "@" : all types of meta entities but not system entities (e.g. used for
-      the basic schema description)
-
-* it is possible to use the attribute `meta` to flag an entity type as a `meta`
-  (e.g. used to describe/categorize other entities)
-
-*Note* : if you end up with an `if` in the definition of your entity, this probably
-means that you need two separate entities that implement the `ITree` interface and
-get the result from `.children()` which ever entity is concerned.
-
-Inheritance
-```````````
-XXX feed me
-
-
-Definition of relations
-~~~~~~~~~~~~~~~~~~~~~~~
-
-XXX add note about defining relation type / definition
-
-A relation is defined by a Python class heriting `RelationType`. The name
-of the class corresponds to the name of the type. The class then contains
-a description of the properties of this type of relation, and could as well
-contain a string for the subject and a string for the object. This allows to create
-new definition of associated relations, (so that the class can have the
-definition properties from the relation) for example ::
-
-  class locked_by(RelationType):
-    """relation on all entities indicating that they are locked"""
-    inlined = True
-    cardinality = '?*'
-    subject = '*'
-    object = 'CWUser'
-
-In the case of simultaneous relations definitions, `subject` and `object`
-can both be equal to the value of the first argument of `SubjectRelation`
-and `ObjectRelation`.
-
-When a relation is not inlined and not symmetrical, and it does not require
-specific permissions, its definition (by using `SubjectRelation` and
-`ObjectRelation`) is all we need.
-
-
-Definition of permissions
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-The entity type `CWPermission` from the standard library
-allows to build very complex and dynamic security architectures. The schema of
-this entity type is as follow :
-
-.. sourcecode:: python
-
-    class CWPermission(EntityType):
-        """entity type that may be used to construct some advanced security configuration
-        """
-        name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
- require_group = SubjectRelation('CWGroup', cardinality='+*',
-                                        description=_('groups to which the permission is granted'))
- require_state = SubjectRelation('State',
-                                        description=_("entity's state in which the permission is applicable"))
-        # can be used on any entity
- require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
-                                            description=_("link a permission to the entity. This "
-                                                          "permission should be used in the security "
-                                                          "definition of the entity's type to be useful."))
-
-
-Example of configuration:
-
-.. sourcecode:: python
-
-    class Version(EntityType):
-        """a version is defining the content of a particular project's release"""
-
-        __permissions__ = {'read':   ('managers', 'users', 'guests',),
-                           'update': ('managers', 'logilab', 'owners',),
-                           'delete': ('managers', ),
-                           'add':    ('managers', 'logilab',
-                                       ERQLExpression('X version_of PROJ, U in_group G,'
-                                                 'PROJ require_permission P, P name "add_version",'
-                                                 'P require_group G'),)}
-
-
-    class version_of(RelationType):
-        """link a version to its project. A version is necessarily linked to one and only one project.
-        """
-        __permissions__ = {'read':   ('managers', 'users', 'guests',),
-                           'delete': ('managers', ),
-                           'add':    ('managers', 'logilab',
-                                  RRQLExpression('O require_permission P, P name "add_version",'
-                                                 'U in_group G, P require_group G'),)
-                       }
-        inlined = True
-
-
-This configuration indicates that an entity `CWPermission` named
-"add_version" can be associated to a project and provides rights to create
-new versions on this project to specific groups. It is important to notice that :
-
-* in such case, we have to protect both the entity type "Version" and the relation
-  associating a version to a project ("version_of")
-
-* because of the genericity of the entity type `CWPermission`, we have to execute
-  a unification with the groups and/or the states if necessary in the expression
-  ("U in_group G, P require_group G" in the above example)
--- a/doc/book/en/development/datamodel/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-Data model
-==========
-
-This chapter describes how you define a schema and how to make it evolves as the time goes.
-
-.. toctree::
-   :maxdepth: 1
-
-   definition
-   metadata
-   baseschema
-   define-workflows
--- a/doc/book/en/development/datamodel/metadata.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-
-Meta-data
----------
-
-.. index::
-   schema: meta-data;
-   schema: eid; creation_date; modification_data; cwuri
-   schema: created_by; owned_by; is; is_instance;
-
-Each entity type in |cubicweb| has at least the following meta-data attributes and relations:
-
-eid
-  entity's identifier which is unique in an instance. We usually call this identifier `eid` for historical reason.
-
-creation_date
-  Date and time of the creation of the entity.
-
-modification_date
-  Date and time of the latest modification of an entity.
-
-cwuri
-  Reference URL of the entity, which is not expected to change.
-
-created_by
-  Relation to the :ref:`users <CWUser>` who has created the entity
-
-owned_by
-  Relation to :ref:`users <CWUser>` whom the entity belongs; usually the creator but not
-  necessary, and it could have multiple owners notably for permission control
-
-is
-  Relation to the :ref:`entity type <CWEType>` of which type the entity is.
-
-is_instance
-  Relation to the :ref:`entity types <CWEType>` of which type the
-  entity is an instance of.
-
-
-Virtual RQL relations
----------------------
-XXX move this to the RQL chapter.
-
-Those relations may only be used in RQL query and are not actual attributes of your entities...
-
-has_text
-  Relation to use to query the full text index (only for entities having fulltextindexed attributes).
-
-identity
-  Relation to use to tell that a RQL variable should be the same as entity another
-  (but you've to use two different rql variables for querying purpose)
-
-
-
-.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
--- a/doc/book/en/development/devcore/appobject.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-
-
-The `AppObject` class
-~~~~~~~~~~~~~~~~~~~~~
-
-In general:
-
-* we do not inherit directly from this class but from a more specific
-  class such as `AnyEntity`, `EntityView`, `AnyRsetView`,
-  `Action`...
-
-* to be recordable, a subclass has to define its own register (attribute
-  `__registry__`) and its identifier (attribute `id`). Usually we do not have
-  to take care of the register, only the identifier `id`.
-
-We can find a certain number of attributes and methods defined in this class
-and common to all the application objects.
-
-At recording time, the following attributes are dynamically added to
-the *subclasses*:
-
-* `vreg`, the `vregistry` of the instance
-* `schema`, the instance schema
-* `config`, the instance configuration
-
-We also find on instances, the following attributes:
-
-* ._cw`, `Request` instance
-* `rset`, the *result set* associated to the object if necessary
-
-:URL handling:
-  * `build_url(*args, **kwargs)`, returns an absolute URL based on the
-    given arguments. The *controller* supposed to handle the response,
-    can be specified through the first positional parameter (the
-    connection is theoretically done automatically :).
-
-:Data manipulation:
-
-  * `entity(row, col=0)`, returns the entity corresponding to the data position
-    in the *result set* associated to the object
-
-  * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but
-    also call the method `complete()` on the entity before returning it
-
-:Data formatting:
-  * `format_date(date, date_format=None, time=False)` returns a string for a
-    date time according to instance's configuration
-  * `format_time(time)` returns a string for a date time according to
-    instance's configuration
-
-:And more...:
-
-  * `tal_render(template, variables)`, renders a precompiled page template with
-    variables in the given dictionary as context
-
-.. note::
-  When we inherit from `AppObject` (even not directly), you *always* have to use
-  **super()** to get the methods and attributes of the superclasses, and not
-  use the class identifier.
-
-  For example, instead of writting: ::
-
-      class Truc(PrimaryView):
-          def f(self, arg1):
-              PrimaryView.f(self, arg1)
-
-  You must write: ::
-
-      class Truc(PrimaryView):
-          def f(self, arg1):
-              super(Truc, self).f(arg1)
--- a/doc/book/en/development/devcore/cwconfig.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-:mod:`Configuration <cubicweb.cwconfig>`
-----------------------------------------
-
-.. automodule:: cubicweb.cwconfig
-   :members:
--- a/doc/book/en/development/devcore/dbapi.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-
-
-API Python/RQL
-~~~~~~~~~~~~~~
-
-The Python API developped to interface with RQL is inspired from the standard db-api,
-with a Connection object having the methods cursor, rollback and commit essentially.
-The most important method is the `execute` method of a cursor :
-
-`execute(rqlstring, args=None, cachekey=None, build_descr=True)`
-
-:rqlstring: the RQL query to execute (unicode)
-:args: if the query contains substitutions, a dictionary containing the values to use
-:cachekey:
-   an implementation detail of the RQL cache implies that if a substitution
-   is used to introduce an eid *susceptible to raise the ambiguities in the query
-   type resolution*, then we have to specify the corresponding key in the dictionary
-   through this argument
-
-
-The `Connection` object owns the methods `commit` and `rollback`. You *should
-never need to use them* during the development of the web interface based on
-the *CubicWeb* framework as it determines the end of the transaction depending
-on the query execution success.
-
-.. note::
-  While executing update queries (SET, INSERT, DELETE), if a query generates
-  an error related to security, a rollback is automatically done on the current
-  transaction.
-
-Executing RQL queries from a view or a hook
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When you're within code of the web interface, the db-api like connexion is
-handled by the request object. You should not have to access it directly, but
-use the `execute` method directly available on the request, eg:
-
-   rset = self._cw.execute(rqlstring, kwargs)
-
-Similarly, on the server side (eg in hooks), there is no db-api connexion (since
-you're directly inside the data-server), so you'll have to use the execute method
-of the session object.
-
-
-Important note about proper usage of .execute
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Let's say you want to get T which is in configuration C, this translates to:
-
-.. sourcecode:: python
-
-   self._cw.execute('Any T WHERE T in_conf C, C eid %s' % entity.eid)
-
-But it can also be written in a syntax that will benefit from the use
-of a cache on the RQL server side:
-
-.. sourcecode:: python
-
-   self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid}, 'x')
-
-Beside proper usage of the `args` argument, notice the latest argument: this is what's called
-the cache key. The cache key should be either a string or a tuple containing the names of keys
-in args which are referencing eids. *YOU MUST SET THIS PROPERLY* if you don't want weird result
-on queries which have ambigous solutions deambiguified by specifing an eid. So the good habit is:
-*always put in the cache key all eid keys*.
-
-The syntax tree is build once for the "generic" RQL and can be re-used
-with a number of different eid.
-
-Alternativelly, some of the common data related to an entity can be obtained from
-the top-level `entity.related()` method (which is used under the hood by the orm
-when you use attribute access notation on an entity to get a relation. The above
-would then be translated to:
-
-.. sourcecode:: python
-
-   entity.related('in_conf', 'object')
-
-The `related()` method, as more generally others orm methods, makes extensive use
-of the cache mechanisms so you don't have to worry about them. Additionnaly this
-use will get you commonly used attributes that you will be able to use in your
-view generation without having to ask the data backend.
-
--- a/doc/book/en/development/devcore/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-Core APIs
-=========
-
-.. toctree::
-   :maxdepth: 1
-
-   vreg.rst
-   appobject.rst
-   selectors.rst
-   dbapi.rst
-   cwconfig.rst
-
--- a/doc/book/en/development/devcore/selectors.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-Base selectors
---------------
-
-Selectors are scoring functions that are called by the registry to tell whenever
-an appobject can be selected in a given context. Selector sets are for instance
-the glue that tie views to the data model. Using them appropriately is an
-essential part of the construction of well behaved cubes.
-
-Of course you may have to write your own set of selectors as your needs grows and
-you get familiar with the framework (see :ref:`CustomSelectors`).
-
-Here is a description of generic selectors provided by CubicWeb that should suit
-most of your needs.
-
-Bare selectors
-~~~~~~~~~~~~~~
-Those selectors are somewhat dumb, which doesn't mean they're not (very) useful.
-
-.. autoclass:: cubicweb.appobject.yes
-.. autoclass:: cubicweb.selectors.match_kwargs
-.. autoclass:: cubicweb.selectors.appobject_selectable
-
-
-Result set selectors
-~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for a result set in the context ('rset' argument or
-the input context) and match or not according to its shape. Some of these
-selectors have different behaviour if a particular cell of the result set is
-specified using 'row' and 'col' arguments of the input context or not.
-
-.. autoclass:: cubicweb.selectors.none_rset
-.. autoclass:: cubicweb.selectors.any_rset
-.. autoclass:: cubicweb.selectors.nonempty_rset
-.. autoclass:: cubicweb.selectors.empty_rset
-.. autoclass:: cubicweb.selectors.one_line_rset
-.. autoclass:: cubicweb.selectors.multi_lines_rset
-.. autoclass:: cubicweb.selectors.multi_columns_rset
-.. autoclass:: cubicweb.selectors.paginated_rset
-.. autoclass:: cubicweb.selectors.sorted_rset
-.. autoclass:: cubicweb.selectors.one_etype_rset
-.. autoclass:: cubicweb.selectors.multi_etypes_rset
-
-
-Entity selectors
-~~~~~~~~~~~~~~~~
-Those selectors are looking for either an `entity` argument in the input context,
-or entity found in the result set ('rset' argument or the input context) and
-match or not according to entity's (instance or class) properties.
-
-.. autoclass:: cubicweb.selectors.non_final_entity
-.. autoclass:: cubicweb.selectors.implements
-.. autoclass:: cubicweb.selectors.score_entity
-.. autoclass:: cubicweb.selectors.rql_condition
-.. autoclass:: cubicweb.selectors.relation_possible
-.. autoclass:: cubicweb.selectors.partial_relation_possible
-.. autoclass:: cubicweb.selectors.has_related_entities
-.. autoclass:: cubicweb.selectors.partial_has_related_entities
-.. autoclass:: cubicweb.selectors.has_permission
-.. autoclass:: cubicweb.selectors.has_add_permission
-
-
-Logged user selectors
-~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for properties of the user issuing the request.
-
-.. autoclass:: cubicweb.selectors.anonymous_user
-.. autoclass:: cubicweb.selectors.authenticated_user
-.. autoclass:: cubicweb.selectors.match_user_groups
-
-
-Web request selectors
-~~~~~~~~~~~~~~~~~~~~~
-Those selectors are looking for properties of *web* request, they can not be
-used on the data repository side.
-
-.. autoclass:: cubicweb.selectors.match_form_params
-.. autoclass:: cubicweb.selectors.match_search_state
-.. autoclass:: cubicweb.selectors.match_context_prop
-.. autoclass:: cubicweb.selectors.match_view
-.. autoclass:: cubicweb.selectors.primary_view
-.. autoclass:: cubicweb.selectors.specified_etype_implements
-
-
-Other selectors
-~~~~~~~~~~~~~~~
-.. autoclass:: cubicweb.selectors.match_transition
-
-You'll also find some other (very) specific selectors hidden in other modules
-than :mod:`cubicweb.selectors`.
--- a/doc/book/en/development/devcore/vreg.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-The VRegistry
---------------
-
-The recording process on startup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Details of the recording process
-````````````````````````````````
-
-.. index::
-   vregistry: registration_callback
-
-On startup, |cubicweb| have to fill the vregistry with appobjects defined
-in its library and in cubes used by the instance. Appobjects from the library
-are loaded first, then appobjects provided by cubes are loaded in an ordered
-way (e.g. if your cube depends on an other, appobjects from the dependancy will
-be loaded first). Cube's modules or packages where appobject are looked at is explained
-in :ref:`cubelayout`.
-
-For each module:
-
-* by default all objects are registered automatically
-
-* if some objects have to replace other objects or be included only if a
-  condition is true, you'll have to define a `registration_callback(vreg)`
-  function in your module and explicitly register *all objects* in this
-  module, using the vregistry api defined below.
-
-.. note::
-    Once the function `registration_callback(vreg)` is implemented, all the objects
-    have to be explicitly registered as it disables the automatic object registering.
-
-
-API d'enregistrement des objets
-```````````````````````````````
-.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all
-.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace
-.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register
-.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found
-.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister
-
-
-Examples
-````````
-.. sourcecode:: python
-
-   # web/views/basecomponents.py
-   def registration_callback(vreg):
-      # register everything in the module except SeeAlsoComponent
-      vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
-      # conditionally register SeeAlsoVComponent
-      if 'see_also' in vreg.schema:
-          vreg.register(SeeAlsoVComponent)
-
-   # goa/appobjects/sessions.py
-   def registration_callback(vreg):
-      vreg.register(SessionsCleaner)
-      # replace AuthenticationManager by GAEAuthenticationManager 
-      vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager)
-      # replace PersistentSessionManager by GAEPersistentSessionManager
-      vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
-
-
-Runtime objects selection
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Using and combining existant selectors
-``````````````````````````````````````
-
-The object's selector is defined by its `__select__` class attribute.
-
-When two selectors are combined using the `&` operator (formerly `chainall`), it
-means that both should return a positive score. On success, the sum of scores is returned.
-
-When two selectors are combined using the `|` operator (former `chainfirst`), it
-means that one of them should return a positive score. On success, the first
-positive score is returned.
-
-You can also "negate"  a selector by precedeing it by the `~` operator.
-
-Of course you can use paren to balance expressions.
-
-
-For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg
-`__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects
-will probably be selectable:
-
-* the default primary view (`__select__ = implements('Any')`), meaning
-  that the object is selectable for any kind of entity type
-
-* the specific `Card` primary view (`__select__ = implements('Card')`,
-  meaning that the object is selectable for Card entities
-
-Other primary views specific to other entity types won't be selectable
-in this case. Among selectable objects, the implements selector will
-return a higher score than the second view since it's more specific,
-so it will be selected as expected.
-
-
-Example
-````````
-
-The goal: when on a Blog, one wants the RSS link to refer to blog
-entries, not to the blog entity itself.
-
-To do that, one defines a method on entity classes that returns the
-RSS stream url for a given entity. The default implementation on
-AnyEntity and a specific implementation on Blog will do what we want.
-
-But when we have a result set containing several Blog entities (or
-different entities), we don't know on which entity to call the
-aforementioned method. In this case, we keep the current behaviour
-(e.g : call to limited_rql).
-
-Hence we have two cases here, one for a single-entity rsets, the other
-for multi-entities rsets.
-
-In web/views/boxes.py lies the RSSIconBox class. Look at its selector ::
-
-  class RSSIconBox(ExtResourcesBoxTemplate):
-    """just display the RSS icon on uniform result set"""
-    __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
-
-It takes into account:
-
-* the inherited selection criteria (one has to look them up in the
-  class hierarchy to know the details)
-
-* non_final_entity, which filters on rsets containing non final
-  entities (a 'final entity' being synonym for entity attribute)
-
-This matches our second case. Hence we have to provide a specific
-component for the first case::
-
-  class EntityRSSIconBox(RSSIconBox):
-    """just display the RSS icon on uniform result set for a single entity"""
-    __select__ = RSSIconBox.__select__ & one_line_rset()
-
-Here, one adds the one_line_rset selector, which filters result sets
-of size 1. When one chains selectors, the final score is the sum of
-the score of each individual selector (unless one of them returns 0,
-in which case the object is non selectable). Thus, on a multiple
-entities selector, one_line_rset makes the EntityRSSIconBox class non
-selectable. For an rset with one entity, the EntityRSSIconBox class
-will have a higher score then RSSIconBox, which is what we wanted.
-
-Of course, once this is done, you have to:
-
-* fill in the call method of EntityRSSIconBox
-
-* provide the default implementation of the method returning the RSS
-  stream url on AnyEntity
-
-* redefine this method on Blog.
-
-When to use selectors?
-``````````````````````
-
-Selectors are to be used whenever arises the need of dispatching on the shape or
-content of a result set or whatever else context (value in request form params,
-authenticated user groups, etc...). That is, almost all the time.
-
-XXX add and example of a single view w/ big "if" inside splitted into two views
-with appropriate selectors.
-
-
-.. CustomSelectors_
-
-Defining your own selectors
-```````````````````````````
-.. autoclass:: cubicweb.appobject.Selector
-   :members: __call__
-
-.. autofunction:: cubicweb.appobject.objectify_selector
-.. autofunction:: cubicweb.selectors.lltrace
-
-Selectors __call__ should *always* return a positive integer, and shall never
-return `None`.
-
-Useful abstract base classes for 'entity' selectors:
-
-.. autoclass:: cubicweb.selectors.EClassSelector
-.. autoclass:: cubicweb.selectors.EntitySelector
-
-
-Debugging
-`````````
-
-Once in a while, one needs to understand why a view (or any AppObject)
-is, or is not selected appropriately. Looking at which selectors fired
-(or did not) is the way. There exists a traced_selection context
-manager to help with that, *if you're running your instance in debug mode*.
-
-Here is an example:
-
-.. sourcecode:: python
-
-     from cubicweb.selectors import traced_selection
-     with traced_selection():
-         mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset)
-
-Don't forget the 'from __future__ import with_statement' at the module
-top-level if you're using python 2.5.
-
-This will yield additional WARNINGs in the logs, like this::
-
-    2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
-
-You can also give to traced_selection the registry ids of objects on which to debug
-you want to debug selection ('wfhistory' in the example above).
-
-Also, if you're using python 2.4, which as no 'with' yet, you'll have to to it
-the following way:
-
-.. sourcecode:: python
-
-         from cubicweb import selectors
-         selectors.TRACED_OIDS = ('wfhistory',)
-         mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset)
-         selectors.TRACED_OIDS = ()
--- a/doc/book/en/development/devrepo/hooks.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _hooks:
-
-Hooks
-=====
-
-XXX FILLME
-
-*Hooks* are executed before or after updating an entity or a relation in the
-repository.
-
-Their prototypes are as follows:
-
-    * after_add_entity     (session, entity)
-    * after_update_entity  (session, entity)
-    * after_delete_entity  (session, eid)
-    * before_add_entity    (session, entity)
-    * before_update_entity (session, entity)
-    * before_delete_entity (session, eid)
-
-    * after_add_relation     (session, fromeid, rtype, toeid)
-    * after_delete_relation  (session, fromeid, rtype, toeid)
-    * before_add_relation    (session, fromeid, rtype, toeid)
-    * before_delete_relation (session, fromeid, rtype, toeid)
-
-    * server_startup
-    * server_shutdown
-
-    * session_open
-    * session_close
-
--- a/doc/book/en/development/devrepo/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Repository customization
-++++++++++++++++++++++++
-.. toctree::
-   :maxdepth: 1
-
-   sessions
-   hooks
-   notifications
-   operations
-   tasks
-
-
--- a/doc/book/en/development/devrepo/notifications.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Notifications management
-========================
-
-XXX FILLME
--- a/doc/book/en/development/devrepo/operations.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Repository operations
-======================
-
-When one needs to perform operations (real world operations like mail
-notifications, file operations, real-world side-effects) at
-transaction commit time, Operations are the way to go.
-
-Possible events are:
-
-* precommit: the pool is preparing to commit. You shouldn't do
-  anything things which has to be reverted if the commit fail at this
-  point, but you can freely do any heavy computation or raise an
-  exception if the commit can't go.  You can add some new operation
-  during this phase but their precommit event won't be triggered
-
-* commit: the pool is preparing to commit. You should avoid to do to
-  expensive stuff or something that may cause an exception in this
-  event
-
-* revertcommit: if an operation failed while commited, this event is
-  triggered for all operations which had their commit event already to
-  let them revert things (including the operation which made fail the
-  commit)
-
-* rollback: the transaction has been either rollbacked either
-
-  - intentionaly
-  - a precommit event failed, all operations are rollbacked
-  - a commit event failed, all operations which are not been triggered
-    for commit are rollbacked
-
-Exceptions signaled from within a rollback are logged and swallowed.
-
-The order of operations may be important, and is controlled according
-to operation's class (see : Operation, LateOperation, SingleOperation,
-SingleLastOperation).
--- a/doc/book/en/development/devrepo/sessions.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Sessions
-========
-
-There are three kinds of sessions.
-
-* `user sessions` are the most common: they are related to users and
-  carry security checks coming with user credentials
-
-* `super sessions` are children of ordinary user sessions and allow to
-  bypass security checks (they are created by calling unsafe_execute
-  on a user session); this is often convenient in hooks which may
-  touch data that is not directly updatable by users
-
-* `internal sessions` have all the powers; they are also used in only a
-  few situations where you don't already have an adequate session at
-  hand, like: user authentication, data synchronisation in
-  multi-source contexts
-
-.. note::
-  Do not confuse the session type with their connection mode, for
-  instance : 'in memory' or 'pyro'.
-
-[WRITE ME]
-
-* authentication and management of sessions
--- a/doc/book/en/development/devrepo/tasks.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tasks
-=========
-
-[WRITE ME]
-
-* repository tasks
-
--- a/doc/book/en/development/devweb/controllers.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-Controllers
------------
-
-Overview
-++++++++
-
-Controllers are responsible for taking action upon user requests
-(loosely following the terminology of the MVC meta pattern).
-
-The following controllers are provided out-of-the box in CubicWeb. We
-list them by category.
-
-Browsing:
-
-* the View controller (web/views/basecontrollers.py) is associated
-  with most browsing actions within a CubicWeb application: it always
-  instantiates a `main template` and lets the ResultSet/Views dispatch
-  system build up the whole content; it handles ObjectNotFound and
-  NoSelectableObject errors that may bubble up to its entry point, in
-  an end-user-friendly way (but other programming errors will slip
-  through)
-
-* the JSon controller (web/views/basecontrollers.py) provides services
-  for Ajax calls, typically using JSON as a serialization format for
-  input, and sometimes using either JSON or XML for output;
-
-* the Login/Logout controllers (web/views/basecontrollers.py) make
-  effective user login or logout requests
-
-Edition:
-
-* the Edit controller (web/views/editcontroller.py) handles CRUD
-  operations in response to a form being submitted; it works in close
-  association with the Forms, to which it delegates some of the work
-
-* the Form validator controller (web/views/basecontrollers.py)
-  provides form validation from Ajax context, using the Edit
-  controller, to implement the classic form handling loop (user edits,
-  hits 'submit/apply', validation occurs server-side by way of the
-  Form validator controller, and the UI is decorated with failure
-  information, either global or per-field , until it is valid)
-
-Other:
-
-* the SendMail controller (web/views/basecontrollers.py) is reponsible
-  for outgoing email notifications
-
-* the MailBugReport controller (web/views/basecontrollers.py) allows
-  to quickly have a `repotbug` feature in one's application
-
-Registration
-++++++++++++
-
-All controllers (should) live in the 'controllers' namespace within
-the global registry.
-
-API
-+++
-
-Most API details should be resolved by source code inspection, as the
-various controllers have differing goals.
-
-`web/controller.py` contains the top-level abstract Controller class and
-its (NotImplemented) entry point `publish(rset=None)` method.
-
-A handful of helpers are also provided there:
-
-* process_rql builds a result set from an rql query typically issued
-  from the browser (and available through _cw.form['rql'])
-
-* validate_cache will force cache validation handling with respect to
-  the HTTP Cache directives (that were typically originally issued
-  from a previous server -> client response); concrete Controller
-  implementations dealing with HTTP (thus, for instance, not the
-  SendMail controller) may very well call this in their publication
-  process.
-
-
-
--- a/doc/book/en/development/devweb/css.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-CSS Stylesheet
----------------
-Conventions
-~~~~~~~~~~~
-
-XXX external_resources variable
-    naming convention
-    request.add_css
-
-
-Extending / overriding existing styles
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We cannot modify the order in which the application is reading the CSS. In
-the case we want to create new CSS style, the best is to define it a in a new
-CSS located under ``myapp/data/`` and use those new styles while writing
-customized views and templates.
-
-If you want to modify an existing CSS styling property, you will have to use
-``!important`` declaration to override the existing property. The application
-apply a higher priority on the default CSS and you can not change that.
-Customized CSS will not be read first.
-
-
-CubicWeb stylesheets
-~~~~~~~~~~~~~~~~~~~~
-XXX explain diffenrent files and main classes
--- a/doc/book/en/development/devweb/facets.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-The facets system
------------------
-XXX feed me more (below is the extracted of adim blog)
-
-
-Recently, for internal purposes, we've made a little cubicweb application to
-help us
-organizing visits to find new office locations. Here's an *excerpt* of the
-schema:
-
-.. sourcecode:: python
-
-  class Office(WorkflowableEntityType):
-      price = Int(description='euros / m2 / HC / HT')
-      surface = Int(description='m2')
-      description = RichString(fulltextindexed=True)
-      has_address = SubjectRelation('PostalAddress', cardinality='1?', composite='subject')
-      proposed_by = SubjectRelation('Agency')
-      comments = ObjectRelation('Comment', cardinality='1*', composite='object')
-      screenshots = SubjectRelation(('File', 'Image'), cardinality='*1',
-                                    composite='subject')
-
-The two other entity types defined in the schema are `Visit` and `Agency` but we
-can also guess from the above that this application uses the two cubes
-`comment`_ and
-`addressbook`_ (remember, cubicweb is only a game where you assemble cubes !).
-
-While we know that just defining the schema in enough to have a full, usable,
-(testable !) application, we also know that every application needs to be
-customized to fulfill the needs it was built for. So in this case, what we
-needed most was some custom filters that would let us restrict searches
-according
-to surfaces, prices or zipcodes. Fortunately for us, Cubicweb provides the
-**facets** (image_) mechanism and a few base classes that make the task quite
-easy:
-
-.. sourcecode:: python
-
-  class PostalCodeFacet(RelationFacet):
-      __regid__ = 'postalcode-facet'             # every registered class must have an id
-      __select__ = implements('Office')   # this facet should only be selected when
-                                          # visualizing offices
-      rtype = 'has_address'               # this facet is a filter on the entity linked to
-                                          # the office thrhough the relation
-                                          # has_address
-      target_attr = 'postalcode'          # the filter's key is the attribute "postal_code"
-                                          # of the target PostalAddress entity
-
-This is a typical `RelationFacet`: we want to be able to filter offices
-according
-to the attribute `postalcode` of their associated `PostalAdress`. Each line in
-the class is explained by the comment on its right.
-
-Now, here is the code to define a filter based on the `surface` attribute of the
-`Office`:
-
-.. sourcecode:: python
-
-  class SurfaceFacet(AttributeFacet):
-      __regid__ = 'surface-facet'              # every registered class must have an id
-      __select__ = implements('Office') # this facet should only be selected when
-                                        # visualizing offices
-      rtype = 'surface'                 # the filter's key is the attribute "surface"
-      comparator = '>='                 # override the default value of operator since
-                                        # we want to filter according to a
-                                        # minimal
-                                        # value, not an exact one
-
-      def rset_vocabulary(self, ___):
-          """override the default vocabulary method since we want to hard-code
-          our threshold values.
-          Not overriding would generate a filter box with all existing surfaces
-          defined in the database.
-          """
-          return [('> 200', '200'), ('> 250', '250'),
-                  ('> 275', '275'), ('> 300', '300')]
-
-
-And that's it: we have two filter boxes automatically displayed on each page
-presenting more than one office. The `price` facet is basically the same as the
-`surface` one but with a different vocabulary and with ``rtype = 'price'``.
-
-(The cube also benefits from the builtin google map views defined by
-cubicweb but that's for another blog).
-
-.. _image: http://www.cubicweb.org/image/197646?vid=download
-.. _comment: http://www.cubicweb.org/project/cubicweb-comment
-.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
-
-CubicWeb has this really nice builtin `facet`_ system to
-define restrictions `filters`_ really as easily as possible.
-
-We've just added two new kind of facets in CubicWeb :
-
-- The **RangeFacet** which displays a slider using `jquery`_
-  to choose a lower bound and an upper bound. The **RangeWidget**
-  works with either numerical values or date values
-
-- The **HasRelationFacet** which displays a simple checkbox and
-  lets you refine your selection in order to get only entities
-  that actually use this relation.
-
-.. image :: http://www.cubicweb.org/Image/343498?vid=download
-
-
-Here's an example of code that defines a facet to filter
-musical works according to their composition date:
-
-.. sourcecode:: python
-
-    class CompositionDateFacet(DateRangeFacet):
-        # 1. make sure this facet is displayed only on Track selection
-        __select__ = DateRangeFacet.__select__ & implements('Track')
-        # 2. give the facet an id required by CubicWeb)
-        __regid__ = 'compdate-facet'
-        # 3. specify the attribute name that actually stores the date in the DB
-        rtype = 'composition_date'
-
-And that's it, on each page displaying tracks, you'll be able to filter them
-according to their composition date with a jquery slider.
-
-All this, brought by CubicWeb (in the next 3.3 version)
-
-.. _facet: http://en.wikipedia.org/wiki/Faceted_browser
-.. _filters: http://www.cubicweb.org/blogentry/154152
-.. _jquery: http://www.jqueryui.com/
-
-To use **HasRelationFacet** on a reverse relation add ``role = 'object'`` in
-it's definitions.
--- a/doc/book/en/development/devweb/form.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-Form construction
-------------------
-
-CubicWeb provides usual form/field/widget/renderer abstraction to
-provde some generic building blocks which will greatly help you in
-building forms properly integrated with CubicWeb (coherent display,
-error handling, etc...).
-
-A form basically only holds a set of fields, and has te be bound to a
-renderer which is responsible to layout them. Each field is bound to a
-widget that will be used to fill in value(s) for that field (at form
-generation time) and 'decode' (fetch and give a proper Python type to)
-values sent back by the browser.
-
-The Field class and basic fields
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autoclass:: cubicweb.web.formfields.Field
-
-Existing field types are:
-
-.. autoclass:: cubicweb.web.formfields.StringField
-.. autoclass:: cubicweb.web.formfields.PasswordField
-.. autoclass:: cubicweb.web.formfields.RichTextField
-.. autoclass:: cubicweb.web.formfields.FileField
-.. autoclass:: cubicweb.web.formfields.EditableFileField
-.. autoclass:: cubicweb.web.formfields.IntField
-.. autoclass:: cubicweb.web.formfields.BooleanField
-.. autoclass:: cubicweb.web.formfields.FloatField
-.. autoclass:: cubicweb.web.formfields.DateField
-.. autoclass:: cubicweb.web.formfields.DateTimeField
-.. autoclass:: cubicweb.web.formfields.TimeField
-.. autoclass:: cubicweb.web.formfields.RelationField
-.. autoclass:: cubicweb.web.formfields.CompoundField
-
-
-Widgets
-~~~~~~~
-Base class for widget is :class:cubicweb.web.formwidgets.FieldWidget class.
-
-Existing widget types are:
-
-.. autoclass:: cubicweb.web.formwidgets.HiddenInput
-.. autoclass:: cubicweb.web.formwidgets.TextInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordInput
-.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
-.. autoclass:: cubicweb.web.formwidgets.FileInput
-.. autoclass:: cubicweb.web.formwidgets.ButtonInput
-.. autoclass:: cubicweb.web.formwidgets.TextArea
-.. autoclass:: cubicweb.web.formwidgets.FCKEditor
-.. autoclass:: cubicweb.web.formwidgets.Select
-.. autoclass:: cubicweb.web.formwidgets.CheckBox
-.. autoclass:: cubicweb.web.formwidgets.Radio
-.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
-.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
-.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
-.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
-.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
-
-Other classes in this module, which are not proper widget (they are not associated to
-field) but are used as form controls, may also be useful: Button, SubmitButton,
-ResetButton, ImgButton,
-
-
-Of course you can not use any widget with any field...
-
-Renderers
-~~~~~~~~~
-
-.. autoclass:: cubicweb.web.views.formrenderers.BaseFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
-.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
-
--- a/doc/book/en/development/devweb/httpcaching.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-HTTP cache management
----------------------
-XXX feedme
\ No newline at end of file
--- a/doc/book/en/development/devweb/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-Web development
-===============
-
-In this chapter, we will describe the core api for web development in the *CubicWeb* framework.
-
-.. toctree::
-   :maxdepth: 1
-
-   request
-   publisher
-   controllers
-   property
-   rtags
-   views
-   gettingdata
-   form
-   facets
-   httpcaching
-   js
-   css
-   internationalization
--- a/doc/book/en/development/devweb/internationalization.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _internationalization:
-
-Internationalization
----------------------
-
-Cubicweb fully supports the internalization of its content and interface.
-
-Cubicweb's interface internationalization is based on the translation project `GNU gettext`_.
-
-.. _`GNU gettext`: http://www.gnu.org/software/gettext/
-
-Cubicweb' internalization involves two steps:
-
-* in your Python code and cubicweb-tal templates : mark translatable strings
-
-* in your instance : handle the translation catalog
-
-String internationalization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-User defined string
-```````````````````
-
-In the Python code and cubicweb-tal templates translatable strings can be
-marked in one of the following ways :
-
- * by using the *built-in* function `_` ::
-
-     class PrimaryView(EntityView):
-         """the full view of an non final entity"""
-         __regid__ = 'primary'
-         title = _('primary')
-
-  OR
-
- * by using the equivalent request's method ::
-
-     class NoResultView(EmptyRsetView):
-         """default view when no result has been found"""
-         __regid__ = 'noresult'
-
-         def call(self, **kwargs):
-             self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
-                 % self._cw._('No result matching query'))
-
-The goal of the *built-in* function `_` is only **to mark the
-translatable strings**, it will only return the string to translate
-itself, but not its translation (it's actually another name for the
-`unicode` builtin).
-
-In the other hand the request's method `self._cw._` is meant to retrieve the
-proper translation of translation strings in the requested language.
-
-Finally you can also use the `__` attribute of request object to get a
-translation for a string *which should not itself added to the catalog*,
-usually in case where the actual msgid is created by string interpolation ::
-
-  self._cw.__('This %s' % etype)
-
-In this example ._cw.__` is used instead of ._cw._` so we don't have 'This %s' in
-messages catalogs.
-
-
-Translations in cubicweb-tal template can also be done with TAL tags
-`i18n:content` and `i18n:replace`.
-
-
-If you need to add messages on top of those that can be found in the source,
-you can create a file named `i18n/static-messages.pot`.
-
-Generated string
-````````````````
-
-We do not need to mark the translation strings of entities/relations used by a
-particular instance's schema as they are generated automatically. String for
-various actions are also generated.
-
-For exemple the following schema ::
-
-  Class EntityA(EntityType):
-      relationa2b = SubjectRelation('EntityB')
-
-  class EntityB(EntityType):
-      pass
-
-May generate the following message ::
-
-  add Execution has_export File subject
-
-This message will be used in views of ``EntityA`` for creation of a new
-``EntityB`` with a preset relation ``relation_a2b`` between the current
-``EntityA`` and the new ``EntityB``. The opposite message ::
-
-  add Execution has_export File object
-
-Is used for similar creation of an ``EntityA`` from a view of ``EntityB``. The
-title of they respective creation form will be ::
-
-  creating EntityB (EntityA %(linkto)s relation_a2b EntityB)
-
-  creating EntityA (EntityA relation_a2b %(linkto)s EntityA)
-
-In the translated string you can use ``%(linkto)s`` for reference to the source
-``entity``.
-
-Handle the translation catalog
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once the internationalization is done in your code, you need to populate and
-update the translation catalog. Cubicweb provides the following commands for this
-purpose:
-
-
-* `i18ncubicweb` updates Cubicweb framework's translation
-  catalogs. Unless you actually work on the framework itself, you
-  don't need to use this command.
-
-* `i18ncube` updates the translation catalogs of *one particular
-  cube* (or of all cubes). After this command is
-  executed you must update the translation files *.po* in the "i18n"
-  directory of your template. This command will of course not remove
-  existing translations still in use.
-
-* `i18ninstance` recompiles the translation catalogs of *one particular
-  instance* (or of all instances) after the translation catalogs of
-  its cubes have been updated. This command is automatically
-  called every time you create or update your instance. The compiled
-  catalogs (*.mo*) are stored in the i18n/<lang>/LC_MESSAGES of
-  instance where `lang` is the language identifier ('en' or 'fr'
-  for exemple).
-
-
-Example
-```````
-You have added and/or modified some translation strings in your cube
-(after creating a new view or modifying the cube's schema for exemple).
-To update the translation catalogs you need to do:
-
-1. `cubicweb-ctl i18ncube <cube>`
-2. Edit the <cube>/i18n/xxx.po  files and add missing translations (empty `msgstr`)
-3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18ninstance <myinstance>`
-
--- a/doc/book/en/development/devweb/js.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Javascript
-----------
-
-*CubicWeb* uses quite a bit of javascript in its user interface and
-ships with jquery (1.3.x) and parts of the jquery UI
-library, plus a number of homegrown files and also other thirparty
-libraries.
-
-All javascript files are stored in cubicweb/web/data/. There are
-around thirty js files there. In a cube it goes to data/.
-
-Obviously one does not want javascript pieces to be loaded all at
-once, hence the framework provides a number of mechanisms and
-conventions to deal with javascript resources.
-
-Conventions
-~~~~~~~~~~~
-
-It is good practice to name cube specific js files after the name of
-the cube, like this : 'cube.mycube.js', so as to avoid name clashes.
-
-XXX external_resources variable (which needs love)
-
-CubicWeb javascript api
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Javascript resources are typically loaded on demand, from views. The
-request object (available as self._cw from most application objects,
-for instance views and entities objects) has a few methods to do that:
-
-* `add_js(self, jsfiles, localfile=True)` which takes a sequence of
-  javascript files and writes proper entries into the HTML header
-  section. The localfile parameter allows to declare resources which
-  are not from web/data (for instance, residing on a content delivery
-  network).
-
-* `add_onload(self, jscode)` which adds one raw javascript code
-  snippet inline in the html headers. This is quite useful for setting
-  up early jQuery(document).ready(...) initialisations.
-
-Overview of what's available
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* jquery.* : jquery and jquery UI library
-
-* cubicweb.python.js : adds a number of practical extension to stdanrd
-  javascript objects (on Date, Array, String, some list and dictionary
-  operations), and a pythonesque way to build classes. Defines a
-  CubicWeb namespace.
-
-* cubicweb.htmlhelpers.js : a small bag of convenience functions used
-  in various other cubicweb javascript resources (baseuri, progress
-  cursor handling, popup login box, html2dom function, etc.)
-
-* cubicweb.ajax.js : concentrates all ajax related facilities (it
-  extends jQuery with the loahxhtml function, provides a handfull of
-  high-level ajaxy operations like asyncRemoteExec, reloadComponent,
-  replacePageChunk, getDomFromResponse)
-
-* cubicweb.widgets.js : provides a widget namespace and constructors
-  and helpers for various widgets (mainly facets and timeline)
-
-* cubicweb.edition.js : used by edition forms
-
-* cubicweb.preferences.js : used by the preference form
-
-* cubicweb.facets.js : used by the facets mechanism
-
-xxx massmailing, gmap, fckcwconfig, timeline-bundle, timeline-ext,
-calendar, goa, flotn tazy, tabs, bookmarks
--- a/doc/book/en/development/devweb/property.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-The property mecanism
----------------------
-XXX CWProperty and co
-
-
-Property API
-~~~~~~~~~~~~
-XXX feed me
-
-Registering and using your own property
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-XXX feed me
--- a/doc/book/en/development/devweb/publisher.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-Publisher
----------
-
-XXX cubicweb.web.application; coop diagram for execution of a http query
\ No newline at end of file
--- a/doc/book/en/development/devweb/request.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-
-
-The `Request` class (`cubicweb.web`)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A request instance is created when an HTTP request is sent to the web server.
-It contains informations such as form parameters, user authenticated, etc.
-
-**Globally, a request represents a user query, either through HTTP or not
-(we also talk about RQL queries on the server side for example).**
-
-An instance of `Request` has the following attributes:
-
-* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
-  user
-* `form`, dictionary containing the values of a web form
-* `encoding`, character encoding to use in the response
-
-But also:
-
-:Session data handling:
-  * `session_data()`, returns a dictionary containing all the session data
-  * `get_session_data(key, default=None)`, returns a value associated to the given
-    key or the value `default` if the key is not defined
-  * `set_session_data(key, value)`, assign a value to a key
-  * `del_session_data(key)`,  suppress the value associated to a key
-
-
-:Cookies handling:
-  * `get_cookie()`, returns a dictionary containing the value of the header
-    HTTP 'Cookie'
-  * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`,
-    with a minimal 5 minutes length of duration by default (`maxage` = None
-    returns a *session* cookie which will expire when the user closes the browser
-    window)
-  * `remove_cookie(cookie, key)`, forces a value to expire
-
-:URL handling:
-  * `url()`, returns the full URL of the HTTP request
-  * `base_url()`, returns the root URL of the web application
-  * `relative_path()`, returns the relative path of the request
-
-:And more...:
-  * `set_content_type(content_type, filename=None)`, adds the header HTTP
-    'Content-Type'
-  * `get_header(header)`, returns the value associated to an arbitrary header
-    of the HTTP request
-  * `set_header(header, value)`, adds an arbitrary header in the response
-  * `cursor()` returns a RQL cursor on the session
-  * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()``
-  * `property_value(key)`, properties management (`CWProperty`)
-  * dictionary `data` to store data to share informations between components
-    *while a request is executed*
-
-Please note that this class is abstract and that a concrete implementation
-will be provided by the *frontend* web used (in particular *twisted* as of
-today). For the views or others that are executed on the server side,
-most of the interface of `Request` is defined in the session associated
-to the client.
--- a/doc/book/en/development/devweb/rtags.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Configuring the generated interface
------------------------------------
-
-
-Relation tags
-~~~~~~~~~~~~~
-.. automodule:: cubicweb.rtags
-
-
-
-The ``uicfg`` module
-~~~~~~~~~~~~~~~~~~~~
-.. automodule:: cubicweb.web.uicfg
-
--- a/doc/book/en/development/devweb/views.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-
-.. _Views:
-
-Views
------
-
-This chapter aims to describe the concept of a `view` used all along
-the development of a web application and how it has been implemented
-in *CubicWeb*.
-
-We'll start with a description of the interface providing you with a basic
-understanding of the classes and methods available, then detail the view
-selection principle which makes *CubicWeb* web interface very flexible.
-
-A `View` is an object applied to another object such as an entity.
-
-Basic class for views
-~~~~~~~~~~~~~~~~~~~~~
-
-Class `View` (`cubicweb.view`)
-`````````````````````````````````````
-
-This class is an abstraction of a view class, used as a base class for every
-renderable object such as views, templates, graphic components, etc.
-
-A `View` is instantiated to render a result set or part of a result set. `View`
-subclasses may be parametrized using the following class attributes:
-
-    * `templatable` indicates if the view may be embeded in a main
-      template or if it has to be rendered standalone (i.e. XML views
-      must not be embeded in the main template for HTML pages)
-    * if the view is not templatable, it should set the `content_type` class
-      attribute to the correct MIME type (text/xhtml by default)
-    * the `category` attribute may be used in the interface to regroup related
-      objects together
-
-At instantiation time, the standard `_cw` and `cw_rset` attributes are
-added and the `w` attribute will be set at rendering time.
-
-A view writes to its output stream thanks to its attribute `w` (an
-`UStreamIO`, except for binary views).
-
-The basic interface for views is as follows (remember that the result set has a
-tabular structure with rows and columns, hence cells):
-
-* `render(**context)`, render the view by calling `call` or
-  `cell_call` depending on the given parameters
-
-* `call(**kwargs)`, call the view for a complete result set or null
-  (the default implementation calls `cell_call()` on each cell of the
-  result set)
-
-* `cell_call(row, col, **kwargs)`, call the view for a given cell of a
-  result set
-
-* `url()`, returns the URL enabling us to get the view with the current
-  result set
-
-* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier
-  `__vid` on the given result set. It is possible to give a view identifier
-  of fallback that will be used if the view requested is not applicable to the
-  result set. This is actually defined on the AppObject class.
-
-* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except
-  the flow is automatically passed in the parameters
-
-* `html_headers()`, returns a list of HTML headers to set by the main template
-
-* `page_title()`, returns the title to use in the HTML header `title`
-
-
-Other basic view classes
-````````````````````````
-Here are some of the subclasses of `View` defined in `cubicweb.common.view`
-that are more concrete as they relate to data rendering within the application:
-
-* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
-* `StartupView`, start view that does not require a result set to apply to
-* `AnyRsetView`, view applicable to any result set
-* `EmptyRsetView`, view applicable to an empty result set
-
-
-Examples of views class
------------------------
-
-- Using `templatable`, `content_type` and HTTP cache configuration
-
-.. sourcecode:: python
-
-    class RSSView(XMLView):
-        __regid__ = 'rss'
-        title = _('rss')
-        templatable = False
-        content_type = 'text/xml'
-        http_cache_manager = MaxAgeHTTPCacheManager
-        cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
-
-- Using custom selector
-
-.. sourcecode:: python
-
-    class SearchForAssociationView(EntityView):
-        """view called by the edition view when the user asks
-        to search for something to link to the edited eid
-        """
-        __regid__ = 'search-associate'
-        title = _('search for association')
-        __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
-
-
-Example of view customization and creation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We'll show you now an example of a ``primary`` view and how to customize it.
-
-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``:
-
-.. sourcecode:: python
-
-  from cubicweb.selectors import implements
-  from cubicweb.web.views.primary improt Primaryview
-
-  class BlogEntryPrimaryView(PrimaryView):
-    __select__ = PrimaryView.__select__ & implements('BlogEntry')
-
-      def render_entity_attributes(self, entity):
-          self.w(u'<p>published on %s</p>' %
-                 entity.publish_date.strftime('%Y-%m-%d'))
-          super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
-
-The above source code defines a new primary view for
-``BlogEntry``. The `id` 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
-with its own specific criterion.
-
-The view method ``self.w()`` is used to output data. Here `lines
-08-09` output HTML for the publication date of the entry.
-
-.. image:: ../../images/lax-book.09-new-view-blogentry.en.png
-   :alt: blog entries now look much nicer
-
-Let us now improve the primary view of a blog
-
-.. sourcecode:: python
-
- from logilab.mtconverter import xml_escape
- from cubicweb.selectors import implements, one_line_rset
- from cubicweb.web.views.primary import Primaryview
-
- class BlogPrimaryView(PrimaryView):
-     __regid__ = 'primary'
-     __select__ = PrimaryView.__select__ & implements('Blog')
-     rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s'
-
-     def render_entity_relations(self, entity):
-         rset = self._cw.execute(self.rql, {'b' : entity.eid})
-         for entry in rset.entities():
-             self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
-
- class BlogEntryInBlogView(EntityView):
-     __regid__ = 'inblogcontext'
-     __select__ = implements('BlogEntry')
-
-     def cell_call(self, row, col):
-         entity = self.cw_rset.get_entity(row, col)
-         self.w(u'<a href="%s" title="%s">%s</a>' %
-                entity.absolute_url(),
-                xml_escape(entity.content[:50]),
-                xml_escape(entity.description))
-
-This happens in two places. First we override the
-render_entity_relations method of a Blog's primary view. Here we want
-to display our blog entries in a custom way.
-
-At `line 10`, a simple request is made to build a result set with all
-the entities linked to the current ``Blog`` entity by the relationship
-``entry_of``. The part of the framework handling the request knows
-about the schema and infers that such entities have to be of the
-``BlogEntry`` kind and retrieves them (in the prescribed publish_date
-order).
-
-The request returns a selection of data called a result set. Result
-set objects have an .entities() method returning a generator on
-requested entities (going transparently through the `ORM` layer).
-
-At `line 13` the view 'inblogcontext' is applied to each blog entry to
-output HTML. (Note that the 'inblogcontext' view is not defined
-whatsoever in *CubicWeb*. You are absolutely free to define whole view
-families.) We juste arrange to wrap each blogentry output in a 'p'
-html element.
-
-Next, we define the 'inblogcontext' view. This is NOT a primary view,
-with its well-defined sections (title, metadata, attribtues,
-relations/boxes). All a basic view has to define is cell_call.
-
-Since views are applied to result sets which can be tables of data, we
-have to recover the entity from its (row,col)-coordinates (`line
-20`). Then we can spit some HTML.
-
-But careful: all strings manipulated in *CubicWeb* are actually
-unicode strings. While web browsers are usually tolerant to incoherent
-encodings they are being served, we should not abuse it. Hence we have
-to properly escape our data. The xml_escape() function has to be used
-to safely fill (X)HTML elements from Python unicode strings.
-
-
-**This is to be compared to interfaces and protocols in object-oriented
-languages. Applying a given view called 'a_view' to all the entities
-of a result set only requires to have for each entity of this result set,
-an available view called 'a_view' which accepts the entity.**
-
-**Instead of merely using type based dispatch, we do predicate dispatch
-which is quite more powerful.**
-
-Assuming we added entries to the blog titled `MyLife`, displaying it
-now allows to read its description and all its entries.
-
-.. image:: ../../images/lax-book.10-blog-with-two-entries.en.png
-   :alt: a blog and all its entries
-
-**Before we move forward, remember that the selection/view principle is
-at the core of *CubicWeb*. Everywhere in the engine, data is requested
-using the RQL language, then HTML/XML/text/PNG is output by applying a
-view to the result set returned by the query. That is where most of the
-flexibility comes from.**
-
-[WRITE ME]
-
-* implementing interfaces, calendar for blog entries
-* show that a calendar view can export data to ical
-
-We will implement the `cubicweb.interfaces.ICalendarable` interfaces on
-entities.BlogEntry and apply the OneMonthCalendar and iCalendar views
-to result sets like "Any E WHERE E is BlogEntry"
-
-* create view "blogentry table" with title, publish_date, category
-
-We will show that by default the view that displays
-"Any E,D,C WHERE E publish_date D, E category C" is the table view.
-Of course, the same can be obtained by calling
-self.wview('table',rset)
-
-* in view blog, select blogentries and apply view "blogentry table"
-* demo ajax by filtering blogentry table on category
-
-we did the same with 'primary', but with tables we can turn on filters
-and show that ajax comes for free.
-[FILLME]
-
-
-XML views, binaries views...
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-For views generating other formats than HTML (an image generated dynamically
-for example), and which can not simply be included in the HTML page generated
-by the main template (see above), you have to:
-
-* set the attribute `templatable` of the class to `False`
-* set, through the attribute `content_type` of the class, the MIME type generated
-  by the view to `application/octet-stream`
-
-For views dedicated to binary content creation (like dynamically generated
-images), we have to set the attribute `binary` of the class to `True` (which
-implies that `templatable == False`, so that the attribute `w` of the view could be
-replaced by a binary flow instead of unicode).
--- a/doc/book/en/development/entityclasses/data-as-objects.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-Access to persistent data
---------------------------
-
-Python-level access to persistent data is provided by the
-:class:`Entity <cubicweb.entity>` class.
-
-An entity class is bound to a schema entity type.  Descriptors are added when
-classes are registered in order to initialize the class according to its schema:
-
-* we can access the defined attributes in the schema thanks to the attributes of
-  the same name on instances (typed value)
-
-* we can access the defined relations in the schema thanks to the relations of
-  the same name on instances (entities instances list)
-
-
-:Formatting and output generation:
-
-  * `view(vid, **kwargs)`, applies the given view to the entity
-
-  * `absolute_url(**kwargs)`, returns an absolute URL to access the primary view
-    of an entity
-
-  * `rest_path()`, returns a relative REST URL to get the entity
-
-  * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`,
-    returns a string enabling the display of an attribute value in a given format
-    (the value is automatically recovered if necessary)
-
-:Data handling:
-
-  * `as_rset()`, converts the entity into an equivalent result set simulating the
-     request `Any X WHERE X eid _eid_`
-
-  * `complete(skip_bytes=True)`, executes a request that recovers all at once
-    all the missing attributes of an entity
-
-  * `get_value(name)`, returns the value associated to the attribute name given
-    in parameter
-
-  * `related(rtype, x='subject', limit=None, entities=False)`, returns a list
-    of entities related to the current entity by the relation given in parameter
-
-  * `unrelated(rtype, targettype, x='subject', limit=None)`, returns a result set
-    corresponding to the entities not related to the current entity by the
-    relation given in parameter and satisfying its constraints
-
-  * `set_attributes(**kwargs)`, updates the attributes list with the corresponding
-    values given named parameters
-
-  * `copy_relations(ceid)`, copies the relations of the entities having the eid
-    given in the parameters on the current entity
-
-  * `delete()` allows to delete the entity
-
-
-The :class:`AnyEntity` class
-----------------------------
-
-To provide a specific behavior for each entity, we have to define a class
-inheriting from `cubicweb.entities.AnyEntity`. In general, we define this class
-in `mycube.entities` module (or in a submodule if we want to split code among
-multiple files) so that it will be available on both server and client side.
-
-The class `AnyEntity` is a sub-class of Entity that add methods to it,
-and helps specializing (by further subclassing) the handling of a
-given entity type.
-
-The methods defined for `AnyEntity`, in addition to `Entity`, are the
-following ones:
-
-:Standard meta-data (Dublin Core):
-
-  * `dc_title()`, returns a unicode string corresponding to the
-    meta-data `Title` (used by default is the first non-meta attribute
-    of the entity schema)
-
-  * `dc_long_title()`, same as dc_title but can return a more
-    detailed title
-
-  * `dc_description(format='text/plain')`, returns a unicode string
-    corresponding to the meta-data `Description` (looks for a
-    description attribute by default)
-
-  * `dc_authors()`, returns a unicode string corresponding to the meta-data
-    `Authors` (owners by default)
-
-  * `dc_date(date_format=None)`, returns a unicode string corresponding to
-    the meta-data `Date` (update date by default)
-
-  * `dc_type(form='')`, returns a string to display the entity type by
-    specifying the preferred form (`plural` for a plural form)
-
-
-Inheritance
------------
-
-When describing a data model, entities can inherit from other entities as is
-common in object-oriented programming.
-
-You have the possibility to adapt some entity attributes, as follow:
-
-.. sourcecode:: python
-
-    from cubes.OTHER_CUBE import entities
-    class EntityExample(entities.EntityExample):
-        def dc_long_title(self):
-            return '%s (%s)' % (self.name, self.description)
-
-Notice this is different than yams schema inheritance.
-
--- a/doc/book/en/development/entityclasses/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-Data as objects
-===============
-
-In this chapter, we will introduce the objects that are used to handle
-the logic associated to the data stored in the database.
-
-.. toctree::
-   :maxdepth: 1
-
-   data-as-objects
-   load-sort
-   interfaces
-   more
--- a/doc/book/en/development/entityclasses/interfaces.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-Interfaces
-----------
-
-Same thing as object-oriented programming interfaces.
-
-Definition of an interface is quite trivial. An example from cubicweb
-itself (found in cubicweb/interfaces.py):
-
-.. sourcecode:: python
-
-    class ITree(Interface):
-
-        def parent(self):
-            """returns the parent entity"""
-
-        def children(self):
-            """returns the item's children"""
-
-        def children_rql(self):
-            """XXX returns RQL to get children"""
-
-        def iterchildren(self):
-            """iterates over the item's children"""
-
-        def is_leaf(self):
-            """returns true if this node as no child"""
-
-        def is_root(self):
-            """returns true if this node has no parent"""
-
-        def root(self):
-            """returns the root object"""
-
-
-Declaration of interfaces implemented by a class
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. sourcecode:: python
-
-  from cubicweb.interfaces import ITree
-  from cubicweb.mixins import TreeMixIn
-
-  class MyEntity(TreeMixIn, AnyEntity):
-      __regid__ = 'MyEntity'
-      __implements__ = AnyEntity.__implements__ + ('ITree',)
-
-      tree_attribute = 'filed_under'
-
-The TreeMixIn here provides a default implementation for the
-interface. The tree_attribute class attribute is actually used by this
-implementation to help implement correct behaviour.
-
-Interfaces (and some implementations as mixins) defined in the library
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. automodule:: cubicweb.interfaces
-   :members:
-
-.. automodule:: cubicweb.mixins
-   :members:
-
-
-
--- a/doc/book/en/development/entityclasses/load-sort.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-
-.. _FetchAttrs:
-
-Loaded attributes and default sorting management
-````````````````````````````````````````````````
-
-* The class attribute `fetch_attrs` allows to define in an entity class a list
-  of names of attributes or relations that should be automatically loaded when
-  entities of this type are fetched from the database. In the case of relations,
-  we are limited to *subject of cardinality `?` or `1`* relations.
-
-* The class method `fetch_order(attr, var)` expects an attribute (or relation)
-  name as a parameter and a variable name, and it should return a string
-  to use in the requirement `ORDERBY` of an RQL query to automatically
-  sort the list of entities of such type according to this attribute, or
-  `None` if we do not want to sort on the attribute given in the parameter.
-  By default, the entities are sorted according to their creation date.
-
-* The class method `fetch_unrelated_order(attr, var)` is similar to
-  the method `fetch_order` except that it is essentially used to
-  control the sorting of drop-down lists enabling relations creation
-  in the editing view of an entity. The default implementation uses
-  the modification date. Here's how to adapt it for one entity (sort
-  on the name attribute): ::
-
-   class MyEntity(AnyEntity):
-       __regid__ = 'MyEntity'
-       fetch_attrs = ('modification_date', 'name')
-
-       @classmethod
-       def fetch_unrelated_order(cls, attr, var):
-           if attr == 'name':
-              return '%s ASC' % var
-           return None
-
-
-The function `fetch_config(fetchattrs, mainattr=None)` simplifies the
-definition of the attributes to load and the sorting by returning a
-list of attributes to pre-load (considering automatically the
-attributes of `AnyEntity`) and a sorting function based on the main
-attribute (the second parameter if specified, otherwise the first
-attribute from the list `fetchattrs`). This function is defined in
-`cubicweb.entities`.
-
-For example: ::
-
-  class Transition(AnyEntity):
-    """..."""
-    __regid__ = 'Transition'
-    fetch_attrs, fetch_order = fetch_config(['name'])
-
-Indicates that for the entity type "Transition", you have to pre-load
-the attribute `name` and sort by default on this attribute.
--- a/doc/book/en/development/entityclasses/more.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Navigation on deletion
-----------------------
-
-XXX after_deletion_path, pre_web_edit
-
-Controlling output url
------------------------
-
-XXX write me
-
-Controling notification references
-----------------------------------
-
-XXX write me
--- a/doc/book/en/development/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-.. _Part2:
-
----------------------
-Part II - Development
----------------------
-
-This part is about developing web applications with the *CubicWeb* framework.
-
-.. toctree::
-   :maxdepth: 2
-   :numbered:
-
-   cubes/index
-   datamodel/index
-   entityclasses/index
-   devcore/index
-   devweb/index
-   devrepo/index
-   testing/index
-   migration/index
-   webstdlib/index
-   profiling/index
--- a/doc/book/en/development/migration/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _migration:
-
-Migration
-=========
-
-One of the main design goals of *CubicWeb* was to support iterative and agile
-development. For this purpose, multiple actions are provided to facilitate the
-improvement of an instance, and in particular to handle the changes to be
-applied to the data model, without loosing existing data.
-
-The current version of a cube (and of cubicweb itself) is provided in the file
-`__pkginfo__.py` as a tuple of 3 integers.
-
-Migration scripts management
-----------------------------
-
-Migration scripts has to be located in the directory `migration` of your
-cube and named accordingly:
-
-::
-
-  <version n° X.Y.Z>[_<description>]_<mode>.py
-
-in which :
-
-* X.Y.Z is the model version number to which the script enables to migrate.
-
-* *mode* (between the last "_" and the extension ".py") is used for
-  distributed installation. It indicates to which part
-  of the application (RQL server, web server) the script applies.
-  Its value could be :
-
-  * `common`, applies to the RQL server as well as the web server and updates
-    files on the hard drive (configuration files migration for example).
-
-  * `web`, applies only to the web server and updates files on the hard drive.
-
-  * `repository`, applies only to the RQL server and updates files on the
-    hard drive.
-
-  * `Any`, applies only to the RQL server and updates data in the database
-    (schema and data migration for example).
-
-Again in the directory `migration`, the file `depends.map` allows to indicate
-that for the migration to a particular model version, you always have to first
-migrate to a particular *CubicWeb* version. This file can contain comments (lines
-starting by `#`) and a dependancy is listed as follows: ::
-
-  <model version n° X.Y.Z> : <cubicweb version n° X.Y.Z>
-
-For example: ::
-
-  0.12.0: 2.26.0
-  0.13.0: 2.27.0
-  # 0.14 works with 2.27 <= cubicweb <= 2.28 at least
-  0.15.0: 2.28.0
-
-Base context
-------------
-
-The following identifiers are pre-defined in migration scripts:
-
-* `config`, instance configuration
-
-* `interactive_mode`, boolean indicating that the script is executed in
-  an interactive mode or not
-
-* `versions_map`, dictionary of migrated versions  (key are cubes
-  names, including 'cubicweb', values are (from version, to version)
-
-* `confirm(question)`, function asking the user and returning true
-  if the user answers yes, false otherwise (always returns true in
-  non-interactive mode)
-
-* the function `_`, it is equivalent to `unicode` allowing to flag the strings
-  to internationalize in the migration scripts.
-
-In the `repository` scripts, the following identifiers are also defined:
-
-* `checkpoint`, request confirming and executing a "commit" at checking point
-
-* `schema`, instance schema (readen from the database)
-
-* `fsschema`, installed schema on the file system (e.g. schema of
-  the updated model and cubicweb)
-
-* `repo`, repository object
-
-* `session`, repository session object
-
-
-Schema migration
-----------------
-The following functions for schema migration are available in `repository`
-scripts:
-
-* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new
-  attribute to an existing entity type. If the attribute type is not specified,
-  then it is extracted from the updated schema.
-
-* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an
-  existing entity type.
-
-* `rename_attribute(etype, oldname, newname, commit=True)`, renames an attribute
-
-* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type.
-  If `auto` is True, all the relations using this entity type and having a known
-  entity type on the other hand will automatically be added.
-
-* `drop_entity_type(etype, commit=True)`, removes an entity type and all the
-  relations using it.
-
-* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type
-
-* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation
-  type. If `addrdef` is True, all the relations definitions of this type will
-  be added.
-
-* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
-  definitions of this type.
-
-* `rename_relation(oldname, newname, commit=True)`, renames a relation.
-
-* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
-  relation definition.
-
-* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes
-  a relation definition.
-
-* `sync_schema_props_perms(ertype=None, syncperms=True, syncprops=True, syncrdefs=True, commit=True)`,
-  synchronizes properties and/or permissions on:
-  - the whole schema if ertype is None
-  - an entity or relation type schema if ertype is a string
-  - a relation definition  if ertype is a 3-uple (subject, relation, object)
-
-* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes
-  properties of a relation definition by using the named parameters of the properties
-  to change.
-
-* `set_widget(etype, rtype, widget, commit=True)`, changes the widget used for the
-  relation <rtype> of entity type <etype>.
-
-* `set_size_constraint(etype, rtype, size, commit=True)`, changes the size constraints
-  for the relation <rtype> of entity type <etype>.
-
-Data migration
---------------
-The following functions for data migration are available in `repository` scripts:
-
-* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
-  query, either to interrogate or update. A result set object is returned.
-
-* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
-  type. The attribute and relation values are specified using the named and
-  positionned parameters.
-
-Workflow creation
------------------
-
-The following functions for workflow creation are available in `repository`
-scripts:
-
-* `add_workflow(label, workflowof, initial=False, commit=False, **kwargs)`, adds a new workflow
-  for a given type(s)
-
-You can find more details about workflows in the chapter :ref:`Workflow` .
-
-Configuration migration
------------------------
-
-The following functions for configuration migration are available in all
-scripts:
-
-* `option_renamed(oldname, newname)`, indicates that an option has been renamed
-
-* `option_group_change(option, oldgroup, newgroup)`, indicates that an option does not
-  belong anymore to the same group.
-
-* `option_added(oldname, newname)`, indicates that an option has been added.
-
-* `option_removed(oldname, newname)`, indicates that an option has been deleted.
-
-
-Others migration functions
---------------------------
-Those functions are only used for low level operations that could not be
-accomplished otherwise or to repair damaged databases during interactive
-session. They are available in `repository` scripts:
-
-* `sql(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query on the system source
-* `add_entity_type_table(etype, commit=True)`
-* `add_relation_type_table(rtype, commit=True)`
-* `uninline_relation(rtype, commit=True)`
-
-
-[FIXME] Add explanation on how to use cubicweb-ctl shell
--- a/doc/book/en/development/profiling/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-Profiling and performance
-=========================
-
-If you feel that one of your pages takes more time than it should to be
-generated, chances are that you're making too many RQL queries.  Obviously,
-there are other reasons but experience tends to show this is the first thing to
-track down. Luckily, CubicWeb provides a configuration option to log RQL
-queries. In your ``all-in-one.conf`` file, set the **query-log-file** option::
-
-    # web application query log file
-    query-log-file=~/myapp-rql.log
-
-Then restart your application, reload your page and stop your application.
-The file ``myapp-rql.log`` now contains the list of RQL queries that were
-executed during your test. It's a simple text file containing lines such as::
-
-    Any A WHERE X eid %(x)s, X lastname A {'x': 448} -- (0.002 sec, 0.010 CPU sec)
-    Any A WHERE X eid %(x)s, X firstname A {'x': 447} -- (0.002 sec, 0.000 CPU sec)
-
-The structure of each line is::
-
-    <RQL QUERY> <QUERY ARGS IF ANY> -- <TIME SPENT>
-
-CubicWeb also provides the **exlog** command to examine and summarize data found
-in such a file:
-
-.. sourcecode:: sh
-
-    $ cubicweb-ctl exlog < ~/myapp-rql.log
-    0.07 50 Any A WHERE X eid %(x)s, X firstname A {}
-    0.05 50 Any A WHERE X eid %(x)s, X lastname A {}
-    0.01 1 Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E employees X, X modification_date AA {}
-    0.01 1 Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s {, }
-    0.01 1 Any B,T,P ORDERBY lower(T) WHERE B is Bookmark,B title T, B path P, B bookmarked_by U, U eid %(x)s {}
-    0.01 1 Any A,B,C,D WHERE A eid %(x)s,A name B,A creation_date C,A modification_date D {}
-
-This command sorts and uniquifies queries so that it's easy to see where
-is the hot spot that needs optimization.
-
-Do not neglect to set the **fetch_attrs** attribute you can define in your
-entity classes because it can greatly reduce the number of queries executed (see
-:ref:`FetchAttrs`).
-
-You should also know about the **profile** option in the ``all-in-on.conf``. If
-set, this option will make your application run in an `hotshot`_ session and
-store the results in the specified file.
-
-.. _hotshot: http://docs.python.org/library/hotshot.html#module-hotshot
-
-Last but no least, if you're using the PostgreSQL database backend, VACUUMing
-your database can significantly improve the performance of the queries (by
-updating the statistics used by the query optimizer). Nowadays, this is done
-automatically from time to time, but if you've just imported a large amount of
-data in your db, you will want to vacuum it (with the analyse option on). Read
-the documentation of your database for more information.
--- a/doc/book/en/development/testing/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tests
-=====
-
-.. toctree::
-   :maxdepth: 1
-
-
-Unit tests
-----------
-
-*CubicWeb* framework provides essentially two Python test classes in the
-module `cubicweb.devtools.apptest`:
-
-* `EnvBasedTC`, to simulate a complete environment (web + repository)
-* `RepositoryBasedTC`, to simulate a repository environment only
-
-Those two classes almost have the same interface and offer numerous
-methods to write tests rapidly and efficiently.
-
-XXX FILLME describe API
-
-In most of the cases, you will inherit `EnvBasedTC` to write Unittest or
-functional tests for your entities, views, hooks, etc...
-
-Managing connections or users
-+++++++++++++++++++++++++++++
-
-Since unit tests are done with the SQLITE backend and this does not
-support multiple connections at a time, you must be careful when
-simulating security, changing users.
-
-By default, tests run with a user with admin privileges. This
-user/connection must never be closed.
-qwq
-Before a self.login, one has to release the connection pool in use with a self.commit, self.rollback or self.close.
-
-When one is logged in as a normal user and wants to switch back to the admin user, one has to use self.restore_connection().
-
-Usually it looks like this:
-
-.. sourcecode:: python
-
-    # execute using default admin connection
-    self.execute(...)
-    # I want to login with another user, ensure to free admin connection pool
-    # (could have used rollback but not close here, we should never close defaut admin connection)
-    self.commit()
-    cnx = self.login('user')
-    # execute using user connection
-    self.execute(...)
-    # I want to login with another user or with admin user
-    self.commit();  cnx.close()
-    # restore admin connection, never use cnx = self.login('admin'), it will return
-    # the default admin connection and one may be tempted to close it
-    self.restore_connection()
-
-Do not use the references kept to the entities created with a connection from another.
-
-
-Email notifications tests
--------------------------
-When running tests potentially generated e-mails are not really
-sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`.
-This list is reset at each test *setUp* (by the setUp of classes `EnvBasedTC`
-and `RepositoryBasedTC`).
-
-
-You can test your notifications by analyzing the contents of this list, which
-contains objects with two attributes:
-* `recipients`, the list of recipients
-* `msg`, object email.Message
-
-
-Automatic testing
------------------
-XXXFILLME
--- a/doc/book/en/development/webstdlib/autoform.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-The automatic entity form (:mod:`cubicweb.web.views.autoform`)
----------------------------------------------------------------
-
-Tags declaration
-~~~~~~~~~~~~~~~~~~~~
-
-It is possible to manage attributes/relations in the simple or multiple
-editing form thanks of the methods bellow ::
-
-  uicfg.autoform_section.tag_subject_of(<relation>, tag)
-  uicfg.autoform_section.tag_object_of(<relation>, tag)
-  uicfg.autoform_field.tag_attribute(<attribut_def>, tag)
-
-Where ``<relation>`` is a three elements tuple ``(Subject Entity Type,
-relation_type, Object Entity Type)``. ``<attribut_def>`` is a two elements tuple
-``(Entity Type, Attribut Name)``. Wildcard ``*`` could be used in place of
-``Entity Type``
-
-Possible tags are detailled below
-
-Simple Tags
-~~~~~~~~~~~~~~~~~~~~
-
-* `primary`, indicates that an attribute or a relation has to be
-  inserted **in the simple or multiple editing forms**. In the case of
-  a relation, the related entity editing form will be included in the
-  editing form and represented as a combobox. Each item of the
-  combobox is a link to an existing entity.
-
-* `secondary`, indicates that an attribute or a relation has to be
-  inserted **in the simple editing form only**. In the case of a
-  relation, the related entity editing form will be included in the
-  editing form and represented as a combobox. Each item of the combobox
-  is a link to an existing entity.
-
-* `inlineview`, includes the target entity's form in the editing form
-  of the current entity. It allows to create the target entity in the
-  same time as the current entity.
-
-* `generic`, indicates that a relation has to be inserted in the simple
-  editing form, in the generic box of relation creation.
-
-* `generated`, indicates that an attribute is dynamically computed
-  or other,  and that it should not be displayed in the editing form.
-
-If necessary, it is possible to overwrite the method
-`relation_category(rtype, x='subject')` to dynamically compute
-a relation editing category.
-
-
-Advanced Tags
-~~~~~~~~~~~~~~~~~~~~
-
-Tag can also reference a custom Field crafted with the help of
-``cubicweb.web.formfields`` and ``cubicweb.web.formwidget``. In the example
-bellow, the field ``path`` of ``ExecData`` entities will be done with a standard
-file input dialogue ::
-
-  from cubicweb.web import uicfg, formfields, formwidgets
-
-  uicfg.autoform_field.tag_attribute(('Execdata', 'path'),
-      formfields.FileField(name='path', widget=formwidgets.FileInput()))
-
-
-
-
-
-
-
--- a/doc/book/en/development/webstdlib/basetemplates.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _templates:
-
-Templates
-=========
-
-[WRITE ME]
-
-* talk about main templates, etc.
-
-
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below :
-
-.. image:: ../../images/lax-book.06-main-template-layout.en.png
-
-In this section we will go through a couple of the primary templates
-you must be interested in, that is to say, the HTMLPageHeader,
-the HTMLPageFooter and the TheMainTemplate.
-
-
-HTMLPageHeader
---------------
-
-Customize header
-~~~~~~~~~~~~~~~~
-
-Let's now move the search box in the header and remove the login form
-from the header. We'll show how to move it to the left column of the application.
-
-Let's say we do not want anymore the login menu to be in the header, but we
-prefer it to be in the left column just below the logo. As the left column is
-rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_.
-
-First, to remove the login menu, we just need to comment out the display of the
-login component such as follows : ::
-
-  class MyHTMLPageHeader(HTMLPageHeader):
-
-      def main_header(self, view):
-          """build the top menu with authentification info and the rql box"""
-          self.w(u'<table id="header"><tr>\n')
-          self.w(u'<td id="firstcolumn">')
-          self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
-          self.w(u'</td>\n')
-          # appliname and breadcrumbs
-          self.w(u'<td id="headtext">')
-          comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w)
-          comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w, view=view)
-          self.w(u'</td>')
-          # logged user and help
-          #self.w(u'<td>\n')
-          #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
-          #comp.dispatch(w=self.w)
-          #self.w(u'</td><td>')
-
-          self.w(u'<td>')
-          helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
-          if helpcomp: # may not be available if Card is not defined in the schema
-              helpcomp.dispatch(w=self.w)
-          self.w(u'</td>')
-          # lastcolumn
-          self.w(u'<td id="lastcolumn">')
-          self.w(u'</td>\n')
-          self.w(u'</tr></table>\n')
-          self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
-                        title=False, message=False)
-
-
-
-.. image:: ../../images/lax-book.06-header-no-login.en.png
-
-Let's now move the search box in the top-right header area. To do so, we will
-first create a method to get the search box display and insert it in the header
-table.
-
-::
-
- from cubicweb.web.views.basetemplates import HTMLPageHeader
- class MyHTMLPageHeader(HTMLPageHeader):
-    def main_header(self, view):
-        """build the top menu with authentification info and the rql box"""
-        self.w(u'<table id="header"><tr>\n')
-        self.w(u'<td id="firstcolumn">')
-        self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
-        self.w(u'</td>\n')
-        # appliname and breadcrumbs
-        self.w(u'<td id="headtext">')
-        comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
-        if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w)
-        comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
-        if comp and comp.propval('visible'):
-            comp.dispatch(w=self.w, view=view)
-        self.w(u'</td>')
-
-        # logged user and help
-        #self.w(u'<td>\n')
-        #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
-        #comp.dispatch(w=self.w)
-        #self.w(u'</td><td>')
-
-        # search box
-        self.w(u'<td>')
-        self.get_searchbox(view, 'left')
-        self.w(u'</td>')
-
-        self.w(u'<td>')
-        helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
-        if helpcomp: # may not be available if Card is not defined in the schema
-            helpcomp.dispatch(w=self.w)
-        self.w(u'</td>')
-        # lastcolumn
-        self.w(u'<td id="lastcolumn">')
-        self.w(u'</td>\n')
-        self.w(u'</tr></table>\n')
-        self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
-                      title=False, message=False)
-
-    def get_searchbox(self, view, context):
-        boxes = list(self._cw.vreg.poss_visible_objects('boxes', self._cw, self.cw_rset,
-                                                    view=view, context=context))
-        if boxes:
-            for box in boxes:
-                if box.__regid__ == 'search_box':
-                    box.dispatch(w=self.w, view=view)
-
-
-
-
-HTMLPageFooter
---------------
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in :
-::
-
-  form cubicweb.web.views.basetemplates import HTMLPageFooter
-  class MyHTMLPageFooter(HTMLPageFooter):
-      def call(self, **kwargs):
-          self.w(u'<div class="footer">')
-          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
-          self.w(u'</div>')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
----------------
-.. _TheMainTemplate:
-
-TheMainTemplate is responsible for the general layout of the entire application.
-It defines the template of ``__regid__ = main`` that is used by the instance.
-
-The default main template (`cubicweb.web.views.basetemplates.TheMainTemplate`)
-builds the page based on the following pattern:
-
-.. image:: ../../images/main_template_layout.png
-
-The rectangle containing `view.dispatch()` represents the area where the content
-view has to be displayed. The others represents sub-templates called to complete
-the page. A default implementation of those is provided in
-`cubicweb.views.basetemplates`. You can, of course, overload those sub-templates
-to implement your own customization of the HTML page.
-
-We can also control certain aspects of the main template thanks to the following
-forms parameters:
-
-* `__notemplate`, if present (whatever the value assigned), only the content view
-  is returned
-* `__force_display`, if present and its value is not null, no navigation
-  whatever the number of entities to display
-* `__method`, if the result set to render contains only one entity and this
-  parameter is set, it refers to a method to call on the entity by passing it
-  the dictionary of the forms parameters, before going the classic way (through
-  step 1 and 2 described juste above)
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
--- a/doc/book/en/development/webstdlib/baseviews.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Base views (:mod:`cubicweb.web.views.baseviews`)
-------------------------------------------------
-
-*CubicWeb* provides a lot of standard views. You can find them in
-``cubicweb/web/views/``.
-
-A certain number of views are used to build the web interface, which apply
-to one or more entities. Their identifier is what distinguish them from
-each others and the main ones are:
-
-HTML views
-~~~~~~~~~~
-Special views
-`````````````
-
-*noresult*
-    This view is the default view used when no result has been found
-    (e.g. empty result set).
-
-*final*
-    Display the value of a cell without trasnformation (in case of a non final
-    entity, we see the eid). Applicable on any result set.
-
-*null*
-    This view is the default view used when nothing needs to be rendered.
-    It is always applicable and it does not return anything
-
-Entity views
-````````````
-*incontext, outofcontext*
-    Those are used to display a link to an entity, depending on the
-    entity having to be displayed in or out of context
-    (of another entity).  By default it respectively returns the
-    result of `textincontext` and `textoutofcontext` wrapped in a link
-    leading to the primary view of the entity.
-
-*oneline*
-    This view is used when we can't tell if the entity should be considered as
-    displayed in or out of context.  By default it returns the result of `text`
-    in a link leading to the primary view of the entity.
-
-List
-`````
-*list*
-    This view displays a list of entities by creating a HTML list (`<ul>`)
-    and call the view `listitem` for each entity of the result set.
-
-*listitem*
-    This view redirects by default to the `outofcontext` view.
-
-*adaptedlist*
-    This view displays a list of entities of the same type, in HTML section (`<div>`)
-    and call the view `adaptedlistitem` for each entity of the result set.
-
-*adaptedlistitem*
-    This view redirects by default to the `outofcontext` view.
-
-*csv*
-    This view applies to entity groups, which are individually
-    displayed using the `incontext` view. It displays each entity as a
-    coma separated list. It is NOT related to the well-known text file
-    format.
-
-Text entity views
-~~~~~~~~~~~~~~~~~
-*text*
-    This is the simplest text view for an entity. By default it returns the
-    result of the `.dc_title` method, which is cut to fit the
-    `navigation.short-line-size` property if necessary.
-
-*textincontext, textoutofcontext*
-    Similar to the `text` view, but called when an entity is considered out or
-    in context. By default it returns respectively the result of the
-    methods `.dc_title` and `.dc_long_title` of the entity.
--- a/doc/book/en/development/webstdlib/boxes.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-Boxes (:mod:`cubicweb.web.views.boxes`)
----------------------------------------------------------------
-
-*sidebox*
-  This view displays usually a side box of some related entities
-  in a primary view.
-
-The action box
-~~~~~~~~~~~~~~~
-
-The ``add_related`` is an automatic menu in the action box that allows to create
-an entity automatically related to the initial entity (context in
-which the box is displayed). By default, the links generated in this
-box are computed from the schema properties of the displayed entity,
-but it is possible to explicitly specify them thanks to the
-`cubicweb.web.uicfg.rmode` *relation tag*:
-
-* `link`, indicates that a relation is in general created pointing
-  to an existing entity and that we should not to display a link
-  for this relation
-
-* `create`, indicates that a relation is in general created pointing
-  to new entities and that we should display a link to create a new
-  entity and link to it automatically
-
-
-
-If necessary, it is possible to overwrite the method
-`relation_mode(rtype, targettype, x='subject')` to dynamically
-compute a relation creation category.
-
-Please note that if at least one action belongs to the `addrelated` category,
-the automatic behavior is desactivated in favor of an explicit behavior
-(e.g. display of `addrelated` category actions only).
--- a/doc/book/en/development/webstdlib/breadcrumbs.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-Breadcrumbs (:mod:`cubicweb.web.views.ibreadcrumbs`)
-----------------------------------------------------
-XXX feedme
\ No newline at end of file
--- a/doc/book/en/development/webstdlib/editcontroller.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-The 'edit' controller (:mod:`cubicweb.web.views.editcontroller`)
-----------------------------------------------------------------
-
-Editing control
-~~~~~~~~~~~~~~~~
-
-Re-requisites: the parameters related to entities to edit are
-specified as follows ::
-
-  <field name>:<entity eid>
-
-where entity eid could be a letter in case of an entity to create. We
-name those parameters as *qualified*.
-
-1. Retrieval of entities to edit by looking for the forms parameters
-   starting by `eid:` and also having a parameter `__type` associated
-   (also *qualified* by eid)
-
-2. For all the attributes and the relations of an entity to edit:
-
-   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
-      qualified in the case of a relation where the entity is object
-   2. if found, the value returned is considered as the initial value
-      for this relaiton and we then look for the new value(s)  in the parameter
-      <relation name> (qualified)
-   3. if the value returned is different from the initial value, an database update
-      request is done
-
-3. For each entity to edit:
-
-   1. if a qualified parameter `__linkto` is specified, its value has to be
-      a string (or a list of string) such as: ::
-
-        <relation type>:<eids>:<target>
-
-      where <target> is either `subject` or `object` and each eid could be
-      separated from the others by a `_`. Target specifies if the *edited entity*
-      is subject or object of the relation and each relation specified will
-      be inserted.
-
-    2. if a qualified parameter `__clone_eid` is specified for an entity, the
-       relations of the specified entity passed as value of this parameter are
-       copied on the edited entity.
-
-    3. if a qualified parameter `__delete` is specified, its value must be
-       a string or a list of string such as follows: ::
-
-          <ssubjects eids>:<relation type>:<objects eids>
-
-       where each eid subject or object can be seperated from the other
-       by `_`. Each relation specified will be deleted.
-
-    4. if a qualified parameter `__insert` is specified, its value should
-       follow the same pattern as `__delete`, but each relation specified is
-       inserted.
-
-4. If the parameters `__insert` and/or `__delete` are found not qualified,
-   they are interpreted as explained above (independantly from the number
-   of entities edited).
-
-5. If no entity is edited but the form contains the parameters `__linkto`
-   and `eid`, this one is interpreted by using the value specified for `eid`
-   to designate the entity on which to add the relations.
-
-
-.. note::
-
-   * If the parameter `__action_delete` is found, all the entities specified
-     as to be edited will be deleted.
-
-   * If the parameter`__action_cancel` is found, no action is completed.
-
-   * If the parameter `__action_apply` is found, the editing is applied
-     normally but the redirection is done on the form
-     (see :ref:`RedirectionControl`).
-
-   * The parameter `__method` is also supported as for the main template
-     (XXX not very consistent, maybe __method should be dealed in the view
-     controller).
-
-   * If no entity is found to be edited and if there is no parameter
-     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
-     `__insert`, an error is raised.
-
-   * Using the parameter `__message` in the form will allow to use its value
-     as a message to provide the user once the editing is completed.
-
-
-.. _RedirectionControl:
-
-Redirection control
-~~~~~~~~~~~~~~~~~~~
-Once editing is completed, there is still an issue left: where should we go
-now? If nothing is specified, the controller will do his job but it does not
-mean we will be happy with the result. We can control that by using the
-following parameters:
-
-* `__redirectpath`: path of the URL (relative to the root URL of the site,
-  no form parameters
-
-* `__redirectparams`: forms parameters to add to the path
-
-* `__redirectrql`: redirection RQL request
-
-* `__redirectvid`: redirection view identifier
-
-* `__errorurl`: initial form URL, used for redirecting in case a validation
-  error is raised during editing. If this one is not specified, an error page
-  is displayed instead of going back to the form (which is, if necessary,
-  responsible for displaying the errors)
-
-* `__form_id`: initial view form identifier, used if `__action_apply` is
-  found
-
-In general we use either `__redirectpath` and `__redirectparams` or
-`__redirectrql` and `__redirectvid`.
-
--- a/doc/book/en/development/webstdlib/editforms.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-Standard forms (:mod:`cubicweb.web.views.editforms`)
-----------------------------------------------------
-XXX feed me
--- a/doc/book/en/development/webstdlib/embedding.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Embedding external pages (:mod:`cubicweb.web.views.embedding`)
----------------------------------------------------------------
-
-including external content
-
-XXX feeed me
-
--- a/doc/book/en/development/webstdlib/facets.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-Facets (:mod:`cubicweb.web.views.facets`)
------------------------------------------
-XXX feed me
--- a/doc/book/en/development/webstdlib/idownloadable.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-The 'download' view (:mod:`cubicweb.web.views.idownloadable`)
----------------------------------------------------------------
-
--- a/doc/book/en/development/webstdlib/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-Standard features for web interface development
-===============================================
-
-This chapter describes generic web features built as CubicWeb application objects.
-
-They are used for CubicWeb default automatic interface, but you're free to use
-them or not for you're own application.
-
-.. toctree::
-   :maxdepth: 1
-
-   basetemplates
-   primary
-   baseviews
-   startup
-   boxes
-   table
-   xmlrss
-   autoform
-   editforms
-   editcontroller
-   urlpublish
-   breadcrumbs
-   facets
-   wdoc
-   embedding
-   idownloadable
\ No newline at end of file
--- a/doc/book/en/development/webstdlib/primary.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-.. _primary:
-
-The default 'primary' view (:mod:`cubicweb.web.views.primary`)
----------------------------------------------------------------
-
-The primary view of an entity is the view called by default when a single
-entity is in the result set and needs to be displayed.
-
-This view is supposed to render a maximum of informations about the entity.
-
-Beware when overriding this top level `cell_call` in a primary because
-you will loose a bunch of functionnality that automatically comes with
-it : `in-context` boxes, related boxes, some navigation, some
-displaying of the metadata, etc. It might be interresting to
-understand the implementation fo the `cell_call` to override specifics
-bits of it.
-
-Rendering methods and attributes for ``PrimaryView``
-----------------------------------------------------
-
-By default, *CubicWeb* provides a primary view for every available
-entity type. This is the first view you might be interested in
-modifying.
-
-Let's have a quick look at the EntityView ``PrimaryView`` as well as
-its rendering method
-
-.. sourcecode:: python
-
-    class PrimaryView(EntityView):
-        """the full view of an non final entity"""
-        __regid__ = 'primary'
-        title = _('primary')
-        show_attr_label = True
-        show_rel_label = True
-        skip_none = True
-        rsection = uicfg.primaryview_section
-        display_ctrl = uicfg.primaryview_display_ctrl
-        main_related_section = True
-
-        ...
-
-    def cell_call(self, row, col):
-        self.row = row
-        self.maxrelated = self._cw.property_value('navigation.related-limit')
-        entity = self.complete_entity(row, col)
-        self.render_entity(entity)
-
-    def render_entity(self, entity):
-        self.render_entity_title(entity)
-        self.render_entity_metadata(entity)
-        # entity's attributes and relations, excluding meta data
-        # if the entity isn't meta itself
-        boxes = self._prepare_side_boxes(entity)
-        if boxes or hasattr(self, 'render_side_related'):
-            self.w(u'<table width="100%"><tr><td style="width: 75%">')
-        self.render_entity_summary(entity)
-        self.w(u'<div class="mainInfo">')
-        self.content_navigation_components('navcontenttop')
-        self.render_entity_attributes(entity)
-        if self.main_related_section:
-            self.render_entity_relations(entity)
-        self.w(u'</div>')
-        # side boxes
-        if boxes or hasattr(self, 'render_side_related'):
-            self.w(u'</td><td>')
-            self.w(u'<div class="primaryRight">')
-            if hasattr(self, 'render_side_related'):
-                warn('render_side_related is deprecated')
-                self.render_side_related(entity, [])
-            self.render_side_boxes(boxes)
-            self.w(u'</div>')
-            self.w(u'</td></tr></table>')
-        self.content_navigation_components('navcontentbottom')
-
-    ...
-
-``cell_call`` is executed for each entity of a result set.
-
-The methods you want to modify while customizing a ``PrimaryView`` are:
-
-*render_entity_title(self, entity)*
-    Renders the entity title based on the assumption that the method
-    ``def dc_title(self)`` is implemented for the given entity type.
-
-*render_entity_metadata(self, entity)*
-    Renders the entity metadata by calling the 'metadata' view on the
-    entity. This generic view is in cubicweb.views.baseviews.
-
-*render_entity_attributes(self, entity)*
-    Renders all the attribute of an entity with the exception of
-    attribute of type `Password` and `Bytes`. The skip_none class
-    attribute controls the display of None valued attributes.
-
-*content_navigation_components(self, context)*
-    This method is applicable only for entity type implementing the interface
-    `IPrevNext`. This interface is for entities which can be linked to a previous
-    and/or next entity. This methods will render the navigation links between
-    entities of this type, either at the top or at the bottom of the page
-    given the context (navcontent{top|bottom}).
-
-*render_entity_relations(self, entity)*
-    Renders all the relations of the entity in the main section of the page.
-
-*render_side_boxes(self, entity, boxes)*
-    Renders all the relations of the entity in a side box. This is equivalent
-    to *render_entity_relations* in addition to render the relations
-    in a box.
-
-Also, please note that by setting the following attributes in your class,
-you can already customize some of the rendering:
-
-*show_attr_label*
-    Renders the attribute label next to the attribute value if set to True.
-    Otherwise, does only display the attribute value.
-
-*show_rel_label*
-    Renders the relation label next to the relation value if set to True.
-    Otherwise, does only display the relation value.
-
-*skip_none*
-    Does not render an attribute value that is None if set to True.
-
-*main_related_section*
-    Renders the relations of the entity if set to True.
-
-A good practice is for you to identify the content of your entity type for which
-the default rendering does not answer your need so that you can focus on the specific
-method (from the list above) that needs to be modified. We do not recommand you to
-overwrite ``render_entity`` as you might potentially loose the benefits of the side
-boxes handling.
-
-.. XXX talk about uicfg.rdisplay
--- a/doc/book/en/development/webstdlib/startup.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-Startup views (:mod:`cubicweb.web.views.startup`)
--------------------------------------------------
-Usual selector: no_rset or yes.
-
-Views that don't apply to a result set
-
-*index*
-    This view defines the home page of your application. It does not require
-    a result set to apply to.
-
-*schema*
-    A view dedicated to the display of the schema of the instance
-
--- a/doc/book/en/development/webstdlib/table.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-Table views (:mod:`cubicweb.web.views.table`)
-----------------------------------------------
-
-*table*
-    Creates a HTML table (`<table>`) and call the view `cell` for each cell of
-    the result set. Applicable on any result set.
-
-*cell*
-    By default redirects to the `final` view if this is a final entity or
-    `outofcontext` view otherwise
--- a/doc/book/en/development/webstdlib/urlpublish.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-URL Rewriting (:mod:`cubicweb.web.views.urlpublish`) and (:mod:`cubicweb.web.views.urlrewrite`)
-------------------------------------------------------------------------------------------------
-
-XXX feed me
-show how urls are mapped to selections and views and explain URLRewriting
--- a/doc/book/en/development/webstdlib/wdoc.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Online documentation system (:mod:`cubicweb.web.views.wdoc`)
--------------------------------------------------------------
-
-XXX  describe the on-line documentation system
-
--- a/doc/book/en/development/webstdlib/xmlrss.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-.. _XmlAndRss:
-
-XML and RSS views (:mod:`cubicweb.web.views.xmlrss`)
-----------------------------------------------------
-
-Overview
-+++++++++
-
-*rss*
-    Creates a RSS/XML view and call the view `rssitem` for each entity of
-    the result set.
-
-*rssitem*
-    Create a RSS/XML view for each entity based on the results of the dublin core
-    methods of the entity (`dc_*`)
-
-
-RSS Channel Example
-++++++++++++++++++++
-
-Assuming you have several blog entries, click on the title of the
-search box in the left column. A larger search box should appear. Enter::
-
-   Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
-
-and you get a list of blog entries.
-
-Click on your login at the top right corner. Chose "user preferences",
-then "boxes", then "possible views box" and check "visible = yes"
-before validating your changes.
-
-Enter the same query in the search box and you will see the same list,
-plus a box titled "possible views" in the left column. Click on
-"entityview", then "RSS".
-
-You just applied the "RSS" view to the RQL selection you requested.
-
-That's it, you have a RSS channel for your blog.
-
-Try again with::
-
-    Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
-    X entry_of B, B title "MyLife"
-
-Another RSS channel, but a bit more focused.
-
-A last one for the road::
-
-    Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
-
-displayed with the RSS view, that's a channel for the last fifteen
-comments posted.
-
-[WRITE ME]
-
-* show that the RSS view can be used to display an ordered selection
-  of blog entries, thus providing a RSS channel
-
-* show that a different selection (by category) means a different channel
-
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/cubes/available-cubes.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,64 @@
+
+Available cubes
+---------------
+
+An instance is based on several basic cubes. In the set of available
+basic cubes we can find for example :
+
+Base entity types
+~~~~~~~~~~~~~~~~~
+* addressbook_: PhoneNumber and PostalAddress
+* card_: Card, generic documenting card
+* event_: Event (define events, display them in calendars)
+* file_: File (to allow users to upload and store binary or text files)
+* link_: Link (to collect links to web resources)
+* mailinglist_: MailingList (to reference a mailing-list and the URLs
+  for its archives and its admin interface)
+* person_: Person (easily mixed with addressbook)
+* task_: Task (something to be done between start and stop date)
+* zone_: Zone (to define places within larger places, for example a
+  city in a state in a country)
+
+
+Classification
+~~~~~~~~~~~~~~
+* folder_: Folder (to organize things but grouping them in folders)
+* keyword_: Keyword (to define classification schemes)
+* tag_: Tag (to tag anything)
+
+Other features
+~~~~~~~~~~~~~~
+* basket_: Basket (like a shopping cart)
+* blog_: a blogging system uxing Blog and BlogEntry entity types
+* comment_: system to attach comment threads to entities)
+* email_: archiving management for emails (`Email`, `Emailpart`,
+  `Emailthread`), trigger action in cubicweb through email
+
+
+
+
+
+.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
+.. _basket: http://www.cubicweb.org/project/cubicweb-basket
+.. _card: http://www.cubicweb.org/project/cubicweb-card
+.. _blog: http://www.cubicweb.org/project/cubicweb-blog
+.. _comment: http://www.cubicweb.org/project/cubicweb-comment
+.. _email: http://www.cubicweb.org/project/cubicweb-email
+.. _event: http://www.cubicweb.org/project/cubicweb-event
+.. _file: http://www.cubicweb.org/project/cubicweb-file
+.. _folder: http://www.cubicweb.org/project/cubicweb-folder
+.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
+.. _link: http://www.cubicweb.org/project/cubicweb-link
+.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
+.. _person: http://www.cubicweb.org/project/cubicweb-person
+.. _tag: http://www.cubicweb.org/project/cubicweb-tag
+.. _task: http://www.cubicweb.org/project/cubicweb-task
+.. _zone: http://www.cubicweb.org/project/cubicweb-zone
+
+To declare the use of a component, once installed, add the name of the component
+to the variable `__use__` in the file `__pkginfo__.py` of your own component.
+
+.. note::
+  The listed cubes above are available as debian-packages on `CubicWeb's forge`_.
+
+.. _`CubicWeb's forge`: http://www.cubicweb.org/project?vtitle=All%20cubicweb%20projects
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/cubes/cc-newcube.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,50 @@
+Creating a new cube from scratch using :command:`cubicweb-ctl newcube`
+----------------------------------------------------------------------
+
+Let's start by creating the cube environment in which we will develop ::
+
+  cd ~/cubes
+  # use cubicweb-ctl to generate a template for the cube
+  # will ask some questions, most with nice default
+  cubicweb-ctl newcube mycube
+  # makes the cube source code managed by mercurial
+  cd mycube
+  hg init
+  hg add .
+  hg ci
+
+If all went well, you should see the cube you just created in the list
+returned by ``cubicweb-ctl list`` in the section *Available cubes*,
+and if it is not the case please refer to :ref:`ConfigurationEnv`.
+
+To reuse an existing cube, add it to the list named
+``__depends_cubes__`` and defined in :file:`__pkginfo__.py`.  This
+variable is used for the instance packaging (dependencies handled by
+system utility tools such as APT) and the usable cubes at the time the
+base is created (import_erschema('MyCube') will not properly work
+otherwise).
+
+.. note::
+
+    Please note that if you do not wish to use default directory for your cubes
+    library, you should set the :envvar:`CW_CUBES_PATH` environment variable to
+    add extra directories where cubes will be search, and you'll then have to use
+    the option `--directory` to specify where you would like to place the source
+    code of your cube:
+
+    ``cubicweb-ctl newcube --directory=/path/to/cubes/library mycube``
+
+
+.. XXX resurrect once live-server is back
+.. Usage of :command:`cubicweb-ctl liveserver`
+.. -------------------------------------------
+
+.. To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
+.. which allows to create an instance in memory (using an SQLite database by
+.. default) and make it accessible through a web server ::
+
+..   cubicweb-ctl live-server mycube
+
+.. or by using an existing database (SQLite or Postgres)::
+
+..   cubicweb-ctl live-server -s myfile_sources mycube
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/cubes/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,11 @@
+Cubes
+=====
+
+This chapter describes how to define your own cubes and reuse already available cubes.
+
+.. toctree::
+   :maxdepth: 1
+
+   layout.rst
+   cc-newcube.rst
+   available-cubes.rst
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/cubes/layout.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,134 @@
+
+.. _foundationsCube:
+
+.. _cubelayout:
+
+Standard structure for a cube
+-----------------------------
+
+A cube is structured as follows:
+
+::
+
+  mycube/
+  |
+  |-- data/
+  |   |-- cubes.mycube.css
+  |   |-- cubes.mycube.js
+  |   `-- external_resources
+  |
+  |-- debian/
+  |   |-- changelog
+  |   |-- compat
+  |   |-- control
+  |   |-- copyright
+  |   |-- cubicweb-mycube.prerm
+  |   `-- rules
+  |
+  |-- entities.py
+  |
+  |-- i18n/
+  |   |-- en.po
+  |   |-- es.po
+  |   `-- fr.po
+  |
+  |-- __init__.py
+  |
+  |-- MANIFEST.in
+  |
+  |-- migration/
+  |   |-- postcreate.py
+  |   `-- precreate.py
+  |
+  |-- __pkginfo__.py
+  |
+  |-- schema.py
+  |
+  |-- setup.py
+  |
+  |-- site_cubicweb.py
+  |
+  |-- hooks.py
+  |
+  |-- test/
+  |   |-- data/
+  |   |   `-- bootstrap_cubes
+  |   |-- pytestconf.py
+  |   |-- realdb_test_mycube.py
+  |   `-- test_mycube.py
+  |
+  `-- views.py
+
+
+We can use subpackages instead of python modules for ``views.py``, ``entities.py``,
+``schema.py`` or ``hooks.py``. For example, we could have:
+
+::
+
+  mycube/
+  |
+  |-- entities.py
+  |-- hooks.py
+  `-- views/
+      |-- forms.py
+      |-- primary.py
+      `-- widgets.py
+
+
+where :
+
+* ``schema`` contains the schema definition (server side only)
+* ``entities`` contains the entities definition (server side and web interface)
+* ``hooks`` contains hooks and/or views notifications (server side only)
+* ``views`` contains the web interface components (web interface only)
+* ``test`` contains tests related to the cube (not installed)
+* ``i18n`` contains message catalogs for supported languages (server side and
+  web interface)
+* ``data`` contains data files for static content (images, css, javascripts)
+  ...(web interface only)
+* ``migration`` contains initialization files for new instances (``postcreate.py``)
+  and a file containing dependencies of the component depending on the version
+  (``depends.map``)
+* ``debian`` contains all the files managing debian packaging (you will find
+  the usual files ``control``, ``rules``, ``changelog``... not installed)
+* file ``__pkginfo__.py`` provides component meta-data, especially the distribution
+  and the current version (server side and web interface) or sub-cubes used by
+  the cube.
+
+
+At least you should have the file ``__pkginfo__.py``.
+
+
+The :file:`__init__.py` and :file:`site_cubicweb.py` files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :file:`__pkginfo__.py` file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It contains metadata describing your cubes, mostly useful for
+packaging.
+
+
+:file:`migration/precreate.py` and :file:`migration/postcreate.py`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. XXX detail steps of instance creation
+
+
+External resources such as image, javascript and css files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. XXX naming convention external_resources file
+
+
+Out-of the box testing
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. XXX MANIFEST.in, __pkginfo__.include_dirs, debian
+
+
+Packaging and distribution
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. XXX MANIFEST.in, __pkginfo__.include_dirs, debian
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/datamodel/baseschema.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,43 @@
+.. _pre_defined_entity_types:
+
+Pre-defined entities in the library
+-----------------------------------
+
+The library defines a set of entity schemas that are required by the system
+or commonly used in *CubicWeb* instances.
+
+
+Entity types used to store the schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* _`CWEType`, entity type
+* _`CWRType`, relation type
+* _`CWRelation`, relation definition
+* _`CWAttribute`, attribute relation definition
+* _`CWConstraint`,  `CWConstraintType`, `RQLExpression`
+
+Entity types used to manage users and permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* _`CWUser`, system users
+* _`CWGroup`, users groups
+* _`CWPermission`, used to configure the security of the instance
+
+Entity types used to manage workflows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* _`Workflow`, workflow entity, linked to some entity types which may use this workflow
+* _`State`, workflow state
+* _`Transition`, workflow transition
+* _`TrInfo`, record of a transition trafic for an entity
+
+Other entity types
+~~~~~~~~~~~~~~~~~~
+* _`CWCache`, cache entities used to improve performances
+* _`CWProperty`, used to configure the instance
+
+* _`EmailAddress`, email address, used by the system to send notifications
+  to the users and also used by others optionnals schemas
+
+* _`Bookmark`, an entity type used to allow a user to customize his links within
+  the instance
+
+* _`ExternalUri`, used for semantic web site to indicate that an entity is the
+  same as another from an external site
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/datamodel/define-workflows.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,161 @@
+.. -*- coding: utf-8 -*-
+
+.. _Workflow:
+
+Defining a Workflow
+===================
+
+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.
+
+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`).
+
+Setting up a workflow
+---------------------
+
+We want to create a workflow to control the quality of the BlogEntry
+submitted on the instance. When a BlogEntry is created by a user
+its state should be `submitted`. To be visible to all, it has to
+be in the state `published`. To move it from `submitted` to `published`,
+we need a transition that we can call `approve_blogentry`.
+
+A BlogEntry state should not be modifiable by every user.
+So we have to define a group of users, `moderators`, and
+this group will have appropriate permissions to publish a BlogEntry.
+
+There are two ways to create a workflow: from the user interface, or
+by defining it in ``migration/postcreate.py``. This script is executed
+each time a new ``cubicweb-ctl db-init`` is done.  We strongly
+recommend to create the workflow in ``migration/postcreate.py`` and we
+will now show you how. Read `Two bits of warning`_ to understand why.
+
+The state of an entity is managed by the `in_state` attribute which
+can be added to your entity schema by inheriting from
+`cubicweb.schema.WorkflowableEntityType`.
+
+
+About our example of BlogEntry, we must have:
+
+.. sourcecode:: python
+
+  from cubicweb.schema import WorkflowableEntityType
+
+  class BlogEntry(WorkflowableEntityType):
+      ...
+
+
+Creating states, transitions and group permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :mod:`postcreate` script is executed in a special environment,
+adding several |cubicweb| primitives that can be used.
+
+They are all defined in the :class:`ServerMigrationHelper` class.
+We will only discuss the methods we use to create a workflow in this example.
+
+A workflow is a collection of entities of type ``State`` and of type
+``Transition`` which are standard *CubicWeb* entity types.
+
+To define a workflow for BlogDemo, please add the following lines
+to ``migration/postcreate.py``:
+
+.. sourcecode:: python
+
+  _ = unicode
+
+  moderators = add_entity('CWGroup', name=u"moderators")
+
+This adds the `moderators` user group.
+
+.. sourcecode:: python
+
+  wf = add_workflow(u'blog publication workflow', 'BlogEntry')
+
+At first, instanciate a new workflow object with a gentle description
+and the concerned entity types (this one can be a tuple for multiple
+value).
+
+.. sourcecode:: python
+
+  submitted = wf.add_state(_('submitted'), initial=True)
+  published = wf.add_state(_('published'))
+
+This will create two entities of type ``State``, one with name
+'submitted', and the other with name 'published'.
+
+``add_state`` expects as first argument the name of the state you want
+to create and an optional argument to say if it is supposed to be the
+initial state of the entity type.
+
+.. sourcecode:: python
+
+  wf.add_transition(_('approve_blogentry'), (submitted,), published, ('moderators', 'managers'),)
+
+This will create an entity of type ``Transition`` with name
+`approve_blogentry` which will be linked to the ``State`` entities
+created before.
+
+``add_transition`` expects
+
+  * as the first argument: the name of the transition
+  * then the list of states on which the transition can be triggered,
+  * the target state of the transition,
+  * and the permissions
+    (e.g. a list of user groups who can apply the transition; the user
+    has to belong to at least one of the listed group to perform the action).
+
+.. sourcecode:: python
+
+  checkpoint()
+
+.. note::
+  Do not forget to add the `_()` in front of all states and
+  transitions names while creating a workflow so that they will be
+  identified by the i18n catalog scripts.
+
+In addition to the user groups (one of which the user needs to belong
+to), we could have added a RQL condition.  In this case, the user can
+only perform the action if the two conditions are satisfied.
+
+If we use an RQL condition on a transition, we can use the following variables:
+
+* `X`, the entity on which we may pass the transition
+* `U`, the user executing that may pass the transition
+
+
+.. image:: ../../images/03-transitions-view_en.png
+
+You can notice that in the action box of a BlogEntry, the state is now
+listed as well as the possible transitions for the current state
+defined by the workflow.
+
+The transitions will only be displayed for users having the right permissions.
+In our example, the transition `approve_blogentry` will only be displayed
+for the users belonging to the group `moderators` or `managers`.
+
+
+Two bits of warning
+~~~~~~~~~~~~~~~~~~~
+
+We could perfectly use the administration interface to do these
+operations. It is a convenient thing to do at times (when doing
+development, to quick-check things). But it is not recommended beyond
+that because it is a bit complicated to do it right and it will be
+only local to your instance (or, said a bit differently, such a
+workflow only exists in an instance database). Furthermore, you cannot
+write unit tests against deployed instances, and experience shows it
+is mandatory to have tests for any mildly complicated workflow
+setup.
+
+Indeed, if you create the states and transitions through the user
+interface, next time you initialize the database you will have to
+re-create all the workflow entities. The user interface should only be
+a reference for you to view the states and transitions, but is not the
+appropriate interface to define your application workflow.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/datamodel/definition.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,631 @@
+ .. -*- coding: utf-8 -*-
+
+Yams *schema*
+-------------
+
+The **schema** is the core piece of a *CubicWeb* instance as it
+defines and handles the data model. It is based on entity types that
+are either already defined in `Yams`_ and the *CubicWeb* standard
+library; or more specific types defined in cubes. The schema for a
+cube is defined in a `schema` python module or package.
+
+.. _`Yams`: http://www.logilab.org/project/yams
+
+Overview
+~~~~~~~~
+
+The core idea of the yams schema is not far from the classical
+`Entity-relationship`_ model. But while an E/R model (or `logical
+model`) traditionally has to be manually translated to a lower-level
+data description language (such as the SQL `create table`
+sublanguage), also often described as the `physical model`, no such
+step is required with |yams| and |cubicweb|.
+
+.. _`Entity-relationship`: http://en.wikipedia.org/wiki/Entity-relationship_model
+
+This is because in addition to high-level, logical |yams| models, one
+uses the |rql| data manipulation language to query, insert, update and
+delete data. |rql| abstracts as much of the underlying SQL database as
+a |yams| schema abstracts from the physical layout. The vagaries of
+SQL are avoided.
+
+As a bonus point, such abstraction make it quite comfortable to build
+or use different backends to which |rql| queries apply.
+
+So, as in the E/R formalism, the building blocks are ``entities``
+(:ref:`EntityType`), ``relationships`` (:ref:`RelationType`,
+:ref:`RelationDefinition`) and ``attributes`` (handled like relation
+with |yams|).
+
+Let us detail a little the divergences between E/R and |yams|:
+
+* all relationship are binary which means that to represent a
+  non-binary relationship, one has to use an entity,
+* relationships do not support attributes (yet, see:
+  http://www.cubicweb.org/ticket/341318), hence the need to reify it
+  as an entity if need arises,
+* all entities have an `eid` attribute (an integer) that is its
+  primary key (but it is possible to declare uniqueness on other
+  attributes)
+
+Also |yams| supports the notions of:
+
+* entity inheritance (quite experimental yet, and completely
+  undocumented),
+* relation type: that is, relationships can be established over a set
+  of couple of entity types (henre the distinction made between
+  `RelationType` and `RelationDefinition` below)
+
+Finally |yams| has a few concepts of its own:
+
+* relationships being oriented and binary, we call the left hand
+  entity type the `subject` and the right hand entity type the
+  `object`
+
+.. note::
+
+   The |yams| schema is available at run time through the .schema
+   attribute of the `vregistry`.  It's an instance of
+   :class:`cubicweb.schema.Schema`, which extends
+   :class:`yams.schema.Schema`.
+
+.. _EntityType:
+
+Entity type
+~~~~~~~~~~~
+
+An entity type is an instance of :class:`yams.schema.EntitySchema`. Each entity type has
+a set of attributes and relations, and some permissions which define who can add, read,
+update or delete entities of this type.
+
+The following built-in types are available: ``String``, ``Int``,
+``Float``, ``Decimal``, ``Boolean``, ``Date``, ``Datetime``, ``Time``,
+``Interval``, ``Byte`` and ``Password``. They can only be used as
+attributes of an other entity type.
+
+You can find more base entity types in
+:ref:`pre_defined_entity_types`.
+
+.. XXX yams inheritance
+
+.. _RelationType:
+
+Relation type
+~~~~~~~~~~~~~
+
+A relation type is an instance of
+:class:`yams.schema.RelationSchema`. A relation type is simply a
+semantic definition of a kind of relationship that may occur in an
+application.
+
+It may be referenced by zero, one or more relation definitions.
+
+It is important to choose a good name, at least to avoid conflicts
+with some semantically different relation defined in other cubes
+(since there's only a shared name space for these names).
+
+A relation type holds the following properties (which are hence shared
+between all relation definitions of that type):
+
+* `inlined`: boolean handling the physical optimization for archiving
+  the relation in the subject entity table, instead of creating a specific
+  table for the relation. This applies to relations where cardinality
+  of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) for *all* its relation
+  definitions.
+
+* `symmetric`: boolean indicating that the relation is symmetrical, which
+  means that `X relation Y` implies `Y relation X`.
+
+.. _RelationDefinition:
+
+Relation definition
+~~~~~~~~~~~~~~~~~~~
+
+A relation definition is an instance of
+:class:`yams.schema.RelationDefinition`. It is a complete triplet
+"<subject entity type> <relation type> <object entity type>".
+
+When creating a new instance of that class, the corresponding
+:class:`RelationType` instance is created on the fly if necessary.
+
+Properties
+``````````
+
+The available properties for relation definitions are enumerated
+here. There are several kind of properties, as some relation
+definitions are actually attribute definitions, and other are not.
+
+Some properties may be completely optional, other may have a default
+value.
+
+Common properties for attributes and relations:
+
+* `description`: an unicode string describing an attribute or a
+  relation. By default this string will be used in the editing form of
+  the entity, which means that it is supposed to help the end-user and
+  should be flagged by the function `_` to be properly
+  internationalized.
+
+* `constraints`: a list of conditions/constraints that the relation has to
+  satisfy (c.f. `Constraints`_)
+
+* `cardinality`: a two character string specifying the cardinality of
+  the relation. The first character defines the cardinality of the
+  relation on the subject, and the second on the object. When a
+  relation can have multiple subjects or objects, the cardinality
+  applies to all, not on a one-to-one basis (so it must be
+  consistent...). Default value is '**'. The possible values are
+  inspired from regular expression syntax:
+
+    * `1`: 1..1
+    * `?`: 0..1
+    * `+`: 1..n
+    * `*`: 0..n
+
+Attributes properties:
+
+* `unique`: boolean indicating if the value of the attribute has to be
+  unique or not within all entities of the same type (false by
+  default)
+
+* `indexed`: boolean indicating if an index needs to be created for
+  this attribute in the database (false by default). This is useful
+  only if you know that you will have to run numerous searches on the
+  value of this attribute.
+
+* `default`: default value of the attribute. In case of date types, the values
+  which could be used correspond to the RQL keywords `TODAY` and `NOW`.
+
+Properties for `String` attributes:
+
+* `fulltextindexed`: boolean indicating if the attribute is part of
+  the full text index (false by default) (*applicable on the type
+  `Byte` as well*)
+
+* `internationalizable`: boolean indicating if the value of the
+  attribute is internationalizable (false by default)
+
+Relation properties:
+
+* `composite`: string indicating that the subject (composite ==
+  'subject') is composed of the objects of the relations. For the
+  opposite case (when the object is composed of the subjects of the
+  relation), we just set 'object' as value. The composition implies
+  that when the relation is deleted (so when the composite is deleted,
+  at least), the composed are also deleted.
+
+* `fulltext_container`: string indicating if the value if the full
+  text indexation of the entity on one end of the relation should be
+  used to find the entity on the other end. The possible values are
+  'subject' or 'object'. For instance the use_email relation has that
+  property set to 'subject', since when performing a full text search
+  people want to find the entity using an email address, and not the
+  entity representing the email address.
+
+Constraints
+```````````
+
+By default, the available constraint types are:
+
+General Constraints
+......................
+
+* `SizeConstraint`: allows to specify a minimum and/or maximum size on
+  string (generic case of `maxsize`)
+
+* `BoundConstraint`: allows to specify a minimum and/or maximum value
+  on numeric types and date
+
+.. sourcecode:: python
+
+   from yams.constraints import BoundConstraint, TODAY
+   BoundConstraint('<=', TODAY())
+
+* `IntervalBoundConstraint`: allows to specify an interval with
+  included values
+
+.. sourcecode:: python
+
+     class Node(EntityType):
+         latitude = Float(constraints=[IntervalBoundConstraint(-90, +90)])
+
+* `UniqueConstraint`: identical to "unique=True"
+
+* `StaticVocabularyConstraint`: identical to "vocabulary=(...)"
+
+.. XXX Attribute, NOW
+
+RQL Based Constraints
+......................
+
+RQL based constraints may take three arguments. The first one is the ``WHERE``
+clause of a RQL query used by the constraint. The second argument ``mainvars``
+is the ``Any`` clause of the query. By default this include `S` reserved for the
+subject of the relation and `O` for the object. Additional variables could be
+specified using ``mainvars``. The argument expects a single string with all
+variable's name separated by spaces. The last one, ``msg``, is the error message
+displayed when the constraint fails. As RQLVocabularyConstraint never fails the
+third argument is not available.
+
+* `RQLConstraint`: allows to specify a RQL query that has to be satisfied
+  by the subject and/or the object of relation. In this query the variables
+  `S` and `O` are reserved for the relation subject and object entities.
+
+* `RQLVocabularyConstraint`: similar to the previous type of constraint except
+  that it does not express a "strong" constraint, which means it is only used to
+  restrict the values listed in the drop-down menu of editing form, but it does
+  not prevent another entity to be selected.
+
+* `RQLUniqueConstraint`: allows to the specify a RQL query that ensure that an
+  attribute is unique in a specific context. The Query must **never** return more
+  than a single result to be satisfied. In this query the variables `S` is
+  reserved for the relation subject entity. The other variables should be
+  specified with the second constructor argument (mainvars). This constraints
+  should be used when UniqueConstraint doesn't fit. Here is a simple example.
+
+.. sourcecode:: python
+
+    # Check that in the same Workflow each state's name is unique.  Using
+    # UniqueConstraint (or unique=True) here would prevent states in different
+    # workflows to have the same name.
+
+    # With: State S, Workflow W, String N ; S state_of W, S name N
+
+    RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N',
+                        mainvars='Y',
+                        msg=_('workflow already has a state of that name'))
+
+.. XXX note about how to add new constraint
+
+.. _securitymodel:
+
+The security model
+~~~~~~~~~~~~~~~~~~
+
+The security model of `CubicWeb` is based on `Access Control List`.
+The main principles are:
+
+* users and groups of users
+* a user belongs to at least one group of user
+* permissions (read, update, create, delete)
+* permissions are assigned to groups (and not to users)
+
+For *CubicWeb* in particular:
+
+* we associate rights at the entities/relations schema level
+* for each entity, we distinguish four kinds of permissions: `read`,
+  `add`, `update` and `delete`
+* for each relation, we distinguish three kinds of permissions: `read`,
+  `add` and `delete` (it is not possible to `modify` a relation)
+* the default groups are: `administrators`, `users` and `guests`
+* by default, users belong to the `users` group
+* there is a virtual group called `owners` to which we
+  can associate only `delete` and `update` permissions
+
+  * we can not add users to the `Owners` group, they are
+    implicitly added to it according to the context of the objects
+    they own
+  * the permissions of this group are only checked on `update`/`delete`
+    actions if all the other groups the user belongs to do not provide
+    those permissions
+
+Setting permissions is done with the attribute `__permissions__` of entities and
+relation types. The value of this attribute is a dictionary where the keys are the access types
+(action), and the values are the authorized groups or expressions.
+
+For an entity type, the possible actions are `read`, `add`, `update` and
+`delete`.
+
+For a relation type, the possible actions are `read`, `add`, and `delete`.
+
+For each access type, a tuple indicates the name of the authorized groups and/or
+one or multiple RQL expressions to satisfy to grant access. The access is
+provided if the user is in one of the listed groups or if one of the RQL condition
+is satisfied.
+
+The standard user groups
+````````````````````````
+
+* `guests`
+
+* `users`
+
+* `managers`
+
+* `owners`: virtual group corresponding to the entity's owner.
+  This can only be used for the actions `update` and `delete` of an entity
+  type.
+
+It is also possible to use specific groups if they are defined in the
+precreate script of the cube (``migration/precreate.py``). Defining groups in
+postcreate script or later makes them unavailable for security
+purposes (in this case, an `sync_schema_props_perms` command has to
+be issued in a CubicWeb shell).
+
+
+Use of RQL expression for write permissions
+```````````````````````````````````````````
+It is possible to define RQL expression to provide update permission
+(`add`, `delete` and `update`) on relation and entity types.
+
+RQL expression for entity type permission:
+
+* you have to use the class `ERQLExpression`
+
+* the used expression corresponds to the WHERE statement of an RQL query
+
+* in this expression, the variables `X` and `U` are pre-defined references
+  respectively on the current entity (on which the action is verified) and
+  on the user who send the request
+
+* it is possible to use, in this expression, a special relation
+  "has_<ACTION>_permission" where the subject is the user and the
+  object is any variable, meaning that the user needs to have
+  permission to execute the action <ACTION> on the entities related
+  to this variable
+
+For RQL expressions on a relation type, the principles are the same except
+for the following:
+
+* you have to use the class `RRQLExpression` in the case of a non-final relation
+
+* in the expression, the variables `S`, `O` and `U` are pre-defined references
+  to respectively the subject and the object of the current relation (on
+  which the action is being verified) and the user who executed the query
+
+* we can also define rights over attributes of an entity (non-final relation),
+  knowing that:
+
+  - to define RQL expression, we have to use the class `ERQLExpression`
+    in which `X` represents the entity the attribute belongs to
+
+  - the permissions `add` and `delete` are equivalent. Only `add`/`read`
+    are actually taken in consideration.
+
+.. note::
+
+  Potentially, the `use of an RQL expression to add an entity or a
+  relation` can cause problems for the user interface, because if the
+  expression uses the entity or the relation to create, then we are
+  not able to verify the permissions before we actually add the entity
+  (please note that this is not a problem for the RQL server at all,
+  because the permissions checks are done after the creation). In such
+  case, the permission check methods (CubicWebEntitySchema.check_perm
+  and has_perm) can indicate that the user is not allowed to create
+  this entity but can obtain the permission.  To compensate this
+  problem, it is usually necessary, for such case, to use an action
+  that reflects the schema permissions but which enables to check
+  properly the permissions so that it would show up if necessary.
+
+
+Use of RQL expression for reading rights
+````````````````````````````````````````
+
+The principles are the same but with the following restrictions:
+
+* we can not use `RRQLExpression` on relation types for reading
+
+* special relations "has_<ACTION>_permission" can not be used
+
+
+
+
+Defining your schema using yams
+-------------------------------
+
+Entity type definition
+~~~~~~~~~~~~~~~~~~~~~~
+
+An entity type is defined by a Python class which inherits from
+:class:`yams.buildobjs.EntityType`.  The class definition contains the
+description of attributes and relations for the defined entity type.
+The class name corresponds to the entity type name. It is expected to
+be defined in the module ``mycube.schema``.
+
+:Note on schema definition:
+
+ The code in ``mycube.schema`` is not meant to be executed. The class
+ EntityType mentioned above is different from the EntitySchema class
+ described in the previous chapter. EntityType is a helper class to
+ make Entity definition easier. Yams will process EntityType classes
+ and create EntitySchema instances from these class definitions. Similar
+ manipulation happen for relations.
+
+When defining a schema using python files, you may use the following shortcuts:
+
+- `required`: boolean indicating if the attribute is required, ed subject cardinality is '1'
+
+- `vocabulary`: specify static possible values of an attribute
+
+- `maxsize`: integer providing the maximum size of a string (no limit by default)
+
+For example:
+
+.. sourcecode:: python
+
+  class Person(EntityType):
+    """A person with the properties and the relations necessary for my
+    application"""
+
+    last_name = String(required=True, fulltextindexed=True)
+    first_name = String(required=True, fulltextindexed=True)
+    title = String(vocabulary=('Mr', 'Mrs', 'Miss'))
+    date_of_birth = Date()
+    works_for = SubjectRelation('Company', cardinality='?*')
+
+
+The entity described above defines three attributes of type String,
+last_name, first_name and title, an attribute of type Date for the date of
+birth and a relation that connects a `Person` to another entity of type
+`Company` through the semantic `works_for`.
+
+:Naming convention:
+
+ Entity class names must start with an uppercase letter. The common
+ usage is to use ``CamelCase`` names.
+
+ Attribute and relation names must start with a lowercase letter. The
+ common usage is to use ``underscore_separated_words``. Attribute and
+ relation names starting with a single underscore are permitted, to
+ denote a somewhat "protected" or "private" attribute.
+
+ In any case, identifiers starting with "CW" or "cw" are reserved for
+ internal use by the framework.
+
+
+The name of the Python attribute corresponds to the name of the attribute
+or the relation in *CubicWeb* application.
+
+An attribute is defined in the schema as follows::
+
+    attr_name = attr_type(properties)
+
+where `attr_type` is one of the type listed above and `properties` is
+a list of the attribute needs to satisfy (see `Properties`_
+for more details).
+
+* it is possible to use the attribute `meta` to flag an entity type as a `meta`
+  (e.g. used to describe/categorize other entities)
+
+.. XXX the paragraph below needs clarification and / or moving out in
+.. another place
+
+*Note*: if you end up with an `if` in the definition of your entity, this probably
+means that you need two separate entities that implement the `ITree` interface and
+get the result from `.children()` which ever entity is concerned.
+
+Inheritance
+```````````
+XXX feed me
+
+
+Definition of relations
+~~~~~~~~~~~~~~~~~~~~~~~
+
+XXX add note about defining relation type / definition
+
+A relation is defined by a Python class heriting `RelationType`. The name
+of the class corresponds to the name of the type. The class then contains
+a description of the properties of this type of relation, and could as well
+contain a string for the subject and a string for the object. This allows to create
+new definition of associated relations, (so that the class can have the
+definition properties from the relation) for example ::
+
+  class locked_by(RelationType):
+    """relation on all entities indicating that they are locked"""
+    inlined = True
+    cardinality = '?*'
+    subject = '*'
+    object = 'CWUser'
+
+If provided, the `subject` and `object` attributes denote the subject
+and object of the various relation definitions related to the relation
+type. Allowed values for these attributes are:
+
+* a string corresponding to an entity type
+* a tuple of string corresponding to multiple entity types
+* special string such as follows:
+
+  - "**": all types of entities
+  - "*": all types of non-meta entities
+  - "@": all types of meta entities but not system entities (e.g. used for
+    the basic schema description)
+
+When a relation is not inlined and not symmetrical, and it does not require
+specific permissions, it can be defined using a `SubjectRelation`
+attribute in the EntityType class. The first argument of `SubjectRelation` gives
+the entity type for the object of the relation.
+
+:Naming convention:
+
+ Although this way of defining relations uses a Python class, the
+ naming convention defined earlier prevails over the PEP8 conventions
+ used in the framework: relation type class names use
+ ``underscore_separated_words``.
+
+:Historical note:
+
+   It has been historically possible to use `ObjectRelation` which
+   defines a relation in the opposite direction. This feature is soon to be
+   deprecated and therefore should not be used in newly written code.
+
+:Future deprecation note:
+
+  In an even more remote future, it is quite possible that the
+  SubjectRelation shortcut will become deprecated, in favor of the
+  RelationType declaration which offers some advantages in the context
+  of reusable cubes.
+
+Definition of permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+The entity type `CWPermission` from the standard library
+allows to build very complex and dynamic security architectures. The schema of
+this entity type is as follow:
+
+.. sourcecode:: python
+
+    class CWPermission(EntityType):
+        """entity type that may be used to construct some advanced security configuration
+        """
+        name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
+        require_group = SubjectRelation('CWGroup', cardinality='+*',
+                                        description=_('groups to which the permission is granted'))
+        require_state = SubjectRelation('State',
+                                        description=_("entity's state in which the permission is applicable"))
+        # can be used on any entity
+        require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
+                                            description=_("link a permission to the entity. This "
+                                                          "permission should be used in the security "
+                                                          "definition of the entity's type to be useful."))
+
+
+Example of configuration:
+
+.. sourcecode:: python
+
+    class Version(EntityType):
+        """a version is defining the content of a particular project's release"""
+
+        __permissions__ = {'read':   ('managers', 'users', 'guests',),
+                           'update': ('managers', 'logilab', 'owners',),
+                           'delete': ('managers', ),
+                           'add':    ('managers', 'logilab',
+                                       ERQLExpression('X version_of PROJ, U in_group G,'
+                                                 'PROJ require_permission P, P name "add_version",'
+                                                 'P require_group G'),)}
+
+
+    class version_of(RelationType):
+        """link a version to its project. A version is necessarily linked to one and only one project.
+        """
+        __permissions__ = {'read':   ('managers', 'users', 'guests',),
+                           'delete': ('managers', ),
+                           'add':    ('managers', 'logilab',
+                                  RRQLExpression('O require_permission P, P name "add_version",'
+                                                 'U in_group G, P require_group G'),)
+                       }
+        inlined = True
+
+
+This configuration indicates that an entity `CWPermission` named
+"add_version" can be associated to a project and provides rights to create
+new versions on this project to specific groups. It is important to notice that:
+
+* in such case, we have to protect both the entity type "Version" and the relation
+  associating a version to a project ("version_of")
+
+* because of the genericity of the entity type `CWPermission`, we have to execute
+  a unification with the groups and/or the states if necessary in the expression
+  ("U in_group G, P require_group G" in the above example)
+
+
+
+Handling schema changes
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Also, it should be clear that to properly handle data migration, an
+instance's schema is stored in the database, so the python schema file
+used to defined it is only read when the instance is created or
+upgraded.
+
+.. XXX complete me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/datamodel/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,12 @@
+Data model
+==========
+
+This chapter describes how you define a schema and how to make it evolves as the time goes.
+
+.. toctree::
+   :maxdepth: 1
+
+   definition
+   metadata
+   baseschema
+   define-workflows
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/datamodel/metadata.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,37 @@
+
+Metadata
+--------
+
+.. index::
+   schema: meta-data;
+   schema: eid; creation_date; modification_data; cwuri
+   schema: created_by; owned_by; is; is_instance;
+
+Each entity type in |cubicweb| has at least the following meta-data attributes and relations:
+
+`eid`
+  entity's identifier which is unique in an instance. We usually call this identifier `eid` for historical reason.
+
+`creation_date`
+  Date and time of the creation of the entity.
+
+`modification_date`
+  Date and time of the latest modification of an entity.
+
+`cwuri`
+  Reference URL of the entity, which is not expected to change.
+
+`created_by`
+  Relation to the :ref:`users <CWUser>` who has created the entity
+
+`owned_by`
+  Relation to :ref:`users <CWUser>` whom the entity belongs; usually the creator but not
+  necessary, and it could have multiple owners notably for permission control
+
+`is`
+  Relation to the :ref:`entity type <CWEType>` of which type the entity is.
+
+`is_instance`
+  Relation to the :ref:`entity types <CWEType>` of which type the
+  entity is an instance of.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/devcore/cwconfig.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,5 @@
+Configuration
+-------------
+
+.. automodule:: cubicweb.cwconfig
+      :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/devcore/dbapi.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,121 @@
+.. _dbapi:
+
+Python/RQL API
+~~~~~~~~~~~~~~
+
+The Python API developped to interface with RQL is inspired from the standard db-api,
+with a Connection object having the methods cursor, rollback and commit essentially.
+The most important method is the `execute` method of a cursor.
+
+.. sourcecode:: python
+
+   execute(rqlstring, args=None, build_descr=True)
+
+:rqlstring: the RQL query to execute (unicode)
+:args: if the query contains substitutions, a dictionary containing the values to use
+
+The `Connection` object owns the methods `commit` and `rollback`. You
+*should never need to use them* during the development of the web
+interface based on the *CubicWeb* framework as it determines the end
+of the transaction depending on the query execution success. They are
+however useful in other contexts such as tests or custom controllers.
+
+.. note::
+
+  While executing update queries (SET, INSERT, DELETE), if a query generates
+  an error related to security, a rollback is automatically done on the current
+  transaction.
+
+Executing RQL queries from a view or a hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you're within code of the web interface, the db-api like connexion is
+handled by the request object. You should not have to access it directly, but
+use the `execute` method directly available on the request, eg:
+
+.. sourcecode:: python
+
+   rset = self._cw.execute(rqlstring, kwargs)
+
+Similarly, on the server side (eg in hooks), there is no db-api connexion (since
+you're directly inside the data-server), so you'll have to use the execute method
+of the session object.
+
+
+Proper usage of `.execute`
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's say you want to get T which is in configuration C, this translates to:
+
+.. sourcecode:: python
+
+   self._cw.execute('Any T WHERE T in_conf C, C eid %s' % entity.eid)
+
+But it must be written in a syntax that will benefit from the use
+of a cache on the RQL server side:
+
+.. sourcecode:: python
+
+   self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid})
+
+The syntax tree is built once for the "generic" RQL and can be re-used
+with a number of different eids. There rql IN operator is an exception
+to this rule.
+
+.. sourcecode:: python
+
+   self._cw.execute('Any T WHERE T in_conf C, C name IN (%s)'
+                    % ','.join(['foo', 'bar']))
+
+Alternativelly, some of the common data related to an entity can be
+obtained from the `entity.related()` method (which is used under the
+hood by the orm when you use attribute access notation on an entity to
+get a relation. The initial request would then be translated to:
+
+.. sourcecode:: python
+
+   entity.related('in_conf', 'object')
+
+Additionnaly this benefits from the fetch_attrs policy (see
+:ref:`FetchAttrs`) eventually defined on the class element, which says
+which attributes must be also loaded when the entity is loaded through
+the orm.
+
+
+.. _resultset:
+
+The `ResultSet` API
+~~~~~~~~~~~~~~~~~~~
+
+ResultSet instances are a very commonly manipulated object. They have
+a rich API as seen below, but we would like to highlight a bunch of
+methods that are quite useful in day-to-day practice:
+
+* `__str__()` (applied by `print`) gives a very useful overview of both
+  the underlying RQL expression and the data inside; unavoidable for
+  debugging purposes
+
+* `printable_rql()` produces back a well formed RQL expression as a
+  string; it is very useful to build views
+
+* `entities()` returns a generator on all entities of the result set
+
+* `get_entity(row, col)` gets the entity at row, col coordinates; one
+  of the most used result set method
+
+.. autoclass:: cubicweb.rset.ResultSet
+   :members:
+
+
+The `Cursor` API
+~~~~~~~~~~~~~~~~
+
+The whole cursor API is developped below.
+
+.. note::
+
+  In practice we use the `.execute` method on the _cw object of
+  appobjects. Usage of other methods is quite rare.
+
+.. autoclass:: cubicweb.dbapi.Cursor
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/devcore/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,10 @@
+Core APIs
+=========
+
+.. toctree::
+   :maxdepth: 1
+
+   dbapi.rst
+   reqbase.rst
+   cwconfig.rst
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/devcore/reqbase.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,40 @@
+Request and ResultSet methods
+-----------------------------
+
+Those are methods you'll find on both request objects and on
+repository session.
+
+Request methods
+~~~~~~~~~~~~~~~
+
+`URL handling`:
+
+* `build_url(*args, **kwargs)`, returns an absolute URL based on the
+  given arguments. The *controller* supposed to handle the response,
+  can be specified through the first positional parameter (the
+  connection is theoretically done automatically :).
+
+`Data formatting`:
+
+* `format_date(date, date_format=None, time=False)` returns a string for a
+  date time according to instance's configuration
+
+* `format_time(time)` returns a string for a date time according to
+  instance's configuration
+
+`And more...`:
+
+* `tal_render(template, variables)`, renders a precompiled page template with
+  variables in the given dictionary as context
+
+
+Result set methods
+~~~~~~~~~~~~~~~~~~
+
+* `get_entity(row, col)`, returns the entity corresponding to the data position
+  in the *result set*
+
+* `complete_entity(row, col, skip_bytes=True)`, is equivalent to `get_entity` but
+  also call the method `complete()` on the entity before returning it
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,166 @@
+How to use entities objects
+---------------------------
+
+The previous chapters detailed the classes and methods available to
+the developper at the so-called `ORM`_ level. However they say little
+about the common patterns of usage of these objects.
+
+.. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
+
+Entities objects are used in the repository and web sides of
+CubicWeb. On the repository side of things, one should manipulate them
+in Hooks and Operations.
+
+Hooks and Operations provide support for the implementation of rules
+such as computed attributes, coherency invariants, etc (they play the
+same role as database triggers, but in a way that is independant of
+the actual data sources).
+
+So a lot of an application's business rules will be written in Hooks
+(or Operations).
+
+On the web side, views also typically operate using entity
+objects. Obvious entity methods for use in views are the dublin code
+method like dc_title, etc. For separation of concerns reasons, one
+should ensure no ui logic pervades the entities level, and also no
+business logic should creep into the views.
+
+In the duration of a transaction, entities objects can be instantiated
+many times, in views and hooks, even for the same database entity. For
+instance, in a classic CubicWeb deployment setup, the repository and
+the web frontend are separated process communicating over the
+wire. There is no way state can be shared between these processes
+(there is a specific API for that). Hence, it is not possible to use
+entity objects as messengers between these components of an
+application. It means that an attribute set as in `obj.x = 42`,
+whether or not x is actually an entity schema attribute, has a short
+life span, limited to the hook, operation or view within which the
+object was built.
+
+Setting an attribute or relation value can be done in the context of a
+Hook/Operation, using the obj.set_attributes(x=42) notation or a plain
+RQL SET expression.
+
+In views, it would be preferable to encapsulate the necessary logic in
+a method of the concerned entity class(es). But of course, this advice
+is also reasonnable for Hooks/Operations, though the separation of
+concerns here is less stringent than in the case of views.
+
+This leads to the practical role of entity objects: it's where an
+important part of the application logic lie (the other part being
+located in the Hook/Operations).
+
+Anatomy of an entity class
+--------------------------
+
+We can look now at a real life example coming from the `tracker`_
+cube. Let us begin to study the entities/project.py content.
+
+.. sourcecode:: python
+
+    class Project(TreeMixIn, AnyEntity):
+        __regid__ = 'Project'
+        __implements__ = AnyEntity.__implements__ + (ITree,)
+        fetch_attrs, fetch_order = fetch_config(('name', 'description',
+                                                 'description_format', 'summary'))
+
+        TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")'
+
+        tree_attribute = 'subproject_of'
+        parent_target = 'subject'
+        children_target = 'object'
+
+        def dc_title(self):
+            return self.name
+
+First we see that it uses an ITree interface and the TreeMixIn default
+implementation. The attributes `tree_attribute`, `parent_target` and
+`children_target` are used by the TreeMixIn code. This is typically
+used in views concerned with the representation of tree-like
+structures (CubicWeb provides several such views).
+
+It is important that the views themselves try not to implement this
+logic, not only because such views would be hardly applyable to other
+tree-like relations, but also because it is perfectly fine and useful
+to use such an interface in Hooks.
+
+In fact, Tree nature is a property of the data model that cannot be
+fully and portably expressed at the level of database entities (think
+about the transitive closure of the child relation). This is a further
+argument to implement it at entity class level.
+
+The `dc_title` method provides a (unicode string) value likely to be
+consummed by views, but note that here we do not care about output
+encodings. We care about providing data in the most universal format
+possible, because the data could be used by a web view (which would be
+responsible of ensuring XHTML compliance), or a console or file
+oriented output (which would have the necessary context about the
+needed byte stream encoding).
+
+The fetch_attrs, fetch_order class attributes are parameters of the
+`ORM`_ layer. They tell which attributes should be loaded at once on
+entity object instantiation (by default, only the eid is known, other
+attributes are loaded on demand), and which attribute is to be used to
+order the .related() and .unrelated() methods output.
+
+Finally, we can observe the big TICKET_DEFAULT_STATE_RESTR is a pure
+application domain piece of data. There is, of course, no limitation
+to the amount of class attributes of this kind.
+
+Let us now dig into more substantial pieces of code.
+
+.. sourcecode:: python
+
+    def latest_version(self, states=('published',), reverse=None):
+        """returns the latest version(s) for the project in one of the given
+        states.
+
+        when no states specified, returns the latest published version.
+        """
+        order = 'DESC'
+        if reverse is not None:
+            warn('reverse argument is deprecated',
+                 DeprecationWarning, stacklevel=1)
+            if reverse:
+                order = 'ASC'
+        rset = self.versions_in_state(states, order, True)
+        if rset:
+            return rset.get_entity(0, 0)
+        return None
+
+    def versions_in_state(self, states, order='ASC', limit=False):
+        """returns version(s) for the project in one of the given states, sorted
+        by version number.
+
+        If limit is true, limit result to one version.
+        If reverse, versions are returned from the smallest to the greatest.
+        """
+        if limit:
+            order += ' LIMIT 1'
+        rql = 'Any V,N ORDERBY version_sort_value(N) %s ' \
+              'WHERE V num N, V in_state S, S name IN (%s), ' \
+              'V version_of P, P eid %%(p)s' % (order, ','.join(repr(s) for s in states))
+        return self._cw.execute(rql, {'p': self.eid})
+
+.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker/
+
+These few lines exhibit the important properties we want to outline:
+
+* entity code is concerned with the application domain
+
+* it is NOT concerned with database coherency (this is the realm of
+  Hooks/Operations); in other words, it assumes a coherent world
+
+* it is NOT concerned with end-user interfaces
+
+* however it can be used in both contexts
+
+* it does not create or manipulate the internal object's state
+
+* it plays freely with RQL expression as needed
+
+* it is not concerned with internationalization
+
+* it does not raise exceptions
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,143 @@
+Access to persistent data
+--------------------------
+
+Python-level access to persistent data is provided by the
+:class:`Entity <cubicweb.entity>` class.
+
+An entity class is bound to a schema entity type.  Descriptors are added when
+classes are registered in order to initialize the class according to its schema:
+
+* we can access the defined attributes in the schema thanks to the attributes of
+  the same name on instances (typed value)
+
+* we can access the defined relations in the schema thanks to the relations of
+  the same name on instances (entities instances list)
+
+
+`Formatting and output generation`:
+
+* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+  (and returns an unicode string)
+
+* `absolute_url(*args, **kwargs)`, returns an absolute URL to access the primary view
+  of an entity
+
+* `rest_path()`, returns a relative REST URL to get the entity
+
+* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+  returns a string enabling the display of an attribute value in a given format
+  (the value is automatically recovered if necessary)
+
+`Data handling`:
+
+* `as_rset()`, converts the entity into an equivalent result set simulating the
+   request `Any X WHERE X eid _eid_`
+
+* `complete(skip_bytes=True)`, executes a request that recovers at
+  once all the missing attributes of an entity
+
+* `get_value(name)`, returns the value associated to the attribute name given
+  in parameter
+
+* `related(rtype, role='subject', limit=None, entities=False)`,
+  returns a list of entities related to the current entity by the
+  relation given in parameter
+
+* `unrelated(rtype, targettype, role='subject', limit=None)`,
+  returns a result set corresponding to the entities not (yet)
+  related to the current entity by the relation given in parameter
+  and satisfying its constraints
+
+* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
+  values given named parameters
+
+* `set_relations(**kwargs)`, add relations to the given object. To
+   set a relation where this entity is the object of the relation,
+   use `reverse_<relation>` as argument name.  Values may be an
+   entity, a list of entities, or None (meaning that all relations of
+   the given type from or to this object should be deleted).
+
+* `copy_relations(ceid)`, copies the relations of the entities having the eid
+  given in the parameters on the current entity
+
+* `delete()` allows to delete the entity
+
+
+The :class:`AnyEntity` class
+----------------------------
+
+To provide a specific behavior for each entity, we have to define a class
+inheriting from `cubicweb.entities.AnyEntity`. In general, we define this class
+in `mycube.entities` module (or in a submodule if we want to split code among
+multiple files) so that it will be available on both server and client side.
+
+The class `AnyEntity` is a sub-class of Entity that add methods to it,
+and helps specializing (by further subclassing) the handling of a
+given entity type.
+
+Most methods defined for `AnyEntity`, in addition to `Entity`, add
+support for the `Dublin Core`_ metadata.
+
+.. _`Dublin Core`: http://dublincore.org/
+
+`Standard meta-data (Dublin Core)`:
+
+* `dc_title()`, returns a unicode string corresponding to the
+  meta-data `Title` (used by default is the first non-meta attribute
+  of the entity schema)
+
+* `dc_long_title()`, same as dc_title but can return a more
+  detailed title
+
+* `dc_description(format='text/plain')`, returns a unicode string
+  corresponding to the meta-data `Description` (looks for a
+  description attribute by default)
+
+* `dc_authors()`, returns a unicode string corresponding to the meta-data
+  `Authors` (owners by default)
+
+* `dc_creator()`, returns a unicode string corresponding to the
+  creator of the entity
+
+* `dc_date(date_format=None)`, returns a unicode string corresponding to
+  the meta-data `Date` (update date by default)
+
+* `dc_type(form='')`, returns a string to display the entity type by
+  specifying the preferred form (`plural` for a plural form)
+
+* `dc_language()`, returns the language used by the entity
+
+
+`Misc methods`:
+
+* `after_deletion_path`, return (path, parameters) which should be
+   used as redirect information when this entity is being deleted
+
+* `pre_web_edit`, callback called by the web editcontroller when an
+  entity will be created/modified, to let a chance to do some entity
+  specific stuff (does nothing by default)
+
+Inheritance
+-----------
+
+When describing a data model, entities can inherit from other entities as is
+common in object-oriented programming.
+
+You have the possibility to redefine whatever pleases you, as follow:
+
+.. sourcecode:: python
+
+    from cubes.OTHER_CUBE import entities
+
+    class EntityExample(entities.EntityExample):
+
+        def dc_long_title(self):
+            return '%s (%s)' % (self.name, self.description)
+
+The most specific entity definition will always the one used by the
+ORM. For instance, the new EntityExample above in mycube replaces the
+one in OTHER_CUBE. These types are stored in the `etype` section of
+the `vregistry`.
+
+Notice this is different than yams schema inheritance.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/entityclasses/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,13 @@
+Data as objects
+===============
+
+In this chapter, we will introduce the objects that are used to handle
+the logic associated to the data stored in the database.
+
+.. toctree::
+   :maxdepth: 1
+
+   data-as-objects
+   load-sort
+   interfaces
+   application-logic
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/entityclasses/interfaces.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,65 @@
+Interfaces
+----------
+
+This is the same thing as object-oriented programming `interfaces`_.
+
+.. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
+
+Definition of an interface is quite trivial. An example from cubicweb
+itself (found in cubicweb/interfaces.py):
+
+.. sourcecode:: python
+
+    class ITree(Interface):
+
+        def parent(self):
+            """returns the parent entity"""
+
+        def children(self):
+            """returns the item's children"""
+
+        def children_rql(self):
+            """returns RQL to get children"""
+
+        def iterchildren(self):
+            """iterates over the item's children"""
+
+        def is_leaf(self):
+            """returns true if this node as no child"""
+
+        def is_root(self):
+            """returns true if this node has no parent"""
+
+        def root(self):
+            """returns the root object"""
+
+
+Declaration of interfaces implemented by a class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. sourcecode:: python
+
+  from cubicweb.interfaces import ITree
+  from cubicweb.mixins import TreeMixIn
+
+  class MyEntity(TreeMixIn, AnyEntity):
+      __regid__ = 'MyEntity'
+      __implements__ = AnyEntity.__implements__ + ('ITree',)
+
+      tree_attribute = 'filed_under'
+
+The TreeMixIn here provides a default implementation for the
+interface. The tree_attribute class attribute is actually used by this
+implementation to help implement correct behaviour.
+
+Interfaces (and some implementations as mixins) defined in the library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: cubicweb.interfaces
+   :members:
+
+.. automodule:: cubicweb.mixins
+   :members:
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/entityclasses/load-sort.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,53 @@
+
+.. _FetchAttrs:
+
+Loaded attributes and default sorting management
+````````````````````````````````````````````````
+
+* The class attribute `fetch_attrs` allows to define in an entity class a list
+  of names of attributes or relations that should be automatically loaded when
+  entities of this type are fetched from the database. In the case of relations,
+  we are limited to *subject of cardinality `?` or `1`* relations.
+
+* The class method `fetch_order(attr, var)` expects an attribute (or relation)
+  name as a parameter and a variable name, and it should return a string
+  to use in the requirement `ORDERBY` of an RQL query to automatically
+  sort the list of entities of such type according to this attribute, or
+  `None` if we do not want to sort on the attribute given in the parameter.
+  By default, the entities are sorted according to their creation date.
+
+* The class method `fetch_unrelated_order(attr, var)` is similar to
+  the method `fetch_order` except that it is essentially used to
+  control the sorting of drop-down lists enabling relations creation
+  in the editing view of an entity. The default implementation uses
+  the modification date. Here's how to adapt it for one entity (sort
+  on the name attribute): ::
+
+   class MyEntity(AnyEntity):
+       __regid__ = 'MyEntity'
+       fetch_attrs = ('modification_date', 'name')
+
+       @classmethod
+       def fetch_unrelated_order(cls, attr, var):
+           if attr == 'name':
+              return '%s ASC' % var
+           return None
+
+
+The function `fetch_config(fetchattrs, mainattr=None)` simplifies the
+definition of the attributes to load and the sorting by returning a
+list of attributes to pre-load (considering automatically the
+attributes of `AnyEntity`) and a sorting function based on the main
+attribute (the second parameter if specified, otherwise the first
+attribute from the list `fetchattrs`). This function is defined in
+`cubicweb.entities`.
+
+For example: ::
+
+  class Transition(AnyEntity):
+    """..."""
+    __regid__ = 'Transition'
+    fetch_attrs, fetch_order = fetch_config(['name'])
+
+Indicates that for the entity type "Transition", you have to pre-load
+the attribute `name` and sort by default on this attribute.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,23 @@
+.. _Part2:
+
+----------------------
+Repository development
+----------------------
+
+This part is about developing applications with the *CubicWeb*
+framework. It is not concerned with the web system, which is a
+separate layer and has its own whole chapter.
+
+.. toctree::
+   :maxdepth: 2
+   :numbered:
+
+   cubes/index
+   vreg.rst
+   datamodel/index
+   entityclasses/index
+   devcore/index
+   repo/index
+   testing.rst
+   migration.rst
+   profiling.rst
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/migration.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,198 @@
+.. -*- coding: utf-8 -*-
+
+.. _migration:
+
+Migration
+=========
+
+One of the main design goals of *CubicWeb* was to support iterative and agile
+development. For this purpose, multiple actions are provided to facilitate the
+improvement of an instance, and in particular to handle the changes to be
+applied to the data model, without loosing existing data.
+
+The current version of a cube (and of cubicweb itself) is provided in the file
+`__pkginfo__.py` as a tuple of 3 integers.
+
+Migration scripts management
+----------------------------
+
+Migration scripts has to be located in the directory `migration` of your
+cube and named accordingly:
+
+::
+
+  <version n° X.Y.Z>[_<description>]_<mode>.py
+
+in which :
+
+* X.Y.Z is the model version number to which the script enables to migrate.
+
+* *mode* (between the last "_" and the extension ".py") is used for
+  distributed installation. It indicates to which part
+  of the application (RQL server, web server) the script applies.
+  Its value could be :
+
+  * `common`, applies to the RQL server as well as the web server and updates
+    files on the hard drive (configuration files migration for example).
+
+  * `web`, applies only to the web server and updates files on the hard drive.
+
+  * `repository`, applies only to the RQL server and updates files on the
+    hard drive.
+
+  * `Any`, applies only to the RQL server and updates data in the database
+    (schema and data migration for example).
+
+Again in the directory `migration`, the file `depends.map` allows to indicate
+that for the migration to a particular model version, you always have to first
+migrate to a particular *CubicWeb* version. This file can contain comments (lines
+starting by `#`) and a dependancy is listed as follows: ::
+
+  <model version n° X.Y.Z> : <cubicweb version n° X.Y.Z>
+
+For example: ::
+
+  0.12.0: 2.26.0
+  0.13.0: 2.27.0
+  # 0.14 works with 2.27 <= cubicweb <= 2.28 at least
+  0.15.0: 2.28.0
+
+Base context
+------------
+
+The following identifiers are pre-defined in migration scripts:
+
+* `config`, instance configuration
+
+* `interactive_mode`, boolean indicating that the script is executed in
+  an interactive mode or not
+
+* `versions_map`, dictionary of migrated versions  (key are cubes
+  names, including 'cubicweb', values are (from version, to version)
+
+* `confirm(question)`, function asking the user and returning true
+  if the user answers yes, false otherwise (always returns true in
+  non-interactive mode)
+
+* `_()` is equivalent to `unicode` allowing to flag the strings to
+  internationalize in the migration scripts.
+
+In the `repository` scripts, the following identifiers are also defined:
+
+* `commit(ask_confirm=True)`, request confirming and executing a "commit"
+
+* `schema`, instance schema (readen from the database)
+
+* `fsschema`, installed schema on the file system (e.g. schema of
+  the updated model and cubicweb)
+
+* `repo`, repository object
+
+* `session`, repository session object
+
+
+Schema migration
+----------------
+The following functions for schema migration are available in `repository`
+scripts:
+
+* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new
+  attribute to an existing entity type. If the attribute type is not specified,
+  then it is extracted from the updated schema.
+
+* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an
+  existing entity type.
+
+* `rename_attribute(etype, oldname, newname, commit=True)`, renames an attribute
+
+* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type.
+  If `auto` is True, all the relations using this entity type and having a known
+  entity type on the other hand will automatically be added.
+
+* `drop_entity_type(etype, commit=True)`, removes an entity type and all the
+  relations using it.
+
+* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type
+
+* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation
+  type. If `addrdef` is True, all the relations definitions of this type will
+  be added.
+
+* `drop_relation_type(rtype, commit=True)`, removes a relation type and all the
+  definitions of this type.
+
+* `rename_relation(oldname, newname, commit=True)`, renames a relation.
+
+* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, adds a new
+  relation definition.
+
+* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes
+  a relation definition.
+
+* `sync_schema_props_perms(ertype=None, syncperms=True, syncprops=True, syncrdefs=True, commit=True)`,
+  synchronizes properties and/or permissions on:
+  - the whole schema if ertype is None
+  - an entity or relation type schema if ertype is a string
+  - a relation definition  if ertype is a 3-uple (subject, relation, object)
+
+* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes
+  properties of a relation definition by using the named parameters of the properties
+  to change.
+
+* `set_widget(etype, rtype, widget, commit=True)`, changes the widget used for the
+  relation <rtype> of entity type <etype>.
+
+* `set_size_constraint(etype, rtype, size, commit=True)`, changes the size constraints
+  for the relation <rtype> of entity type <etype>.
+
+Data migration
+--------------
+The following functions for data migration are available in `repository` scripts:
+
+* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
+  query, either to interrogate or update. A result set object is returned.
+
+* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
+  type. The attribute and relation values are specified using the named and
+  positionned parameters.
+
+Workflow creation
+-----------------
+
+The following functions for workflow creation are available in `repository`
+scripts:
+
+* `add_workflow(label, workflowof, initial=False, commit=False, **kwargs)`, adds a new workflow
+  for a given type(s)
+
+You can find more details about workflows in the chapter :ref:`Workflow` .
+
+Configuration migration
+-----------------------
+
+The following functions for configuration migration are available in all
+scripts:
+
+* `option_renamed(oldname, newname)`, indicates that an option has been renamed
+
+* `option_group_change(option, oldgroup, newgroup)`, indicates that an option does not
+  belong anymore to the same group.
+
+* `option_added(oldname, newname)`, indicates that an option has been added.
+
+* `option_removed(oldname, newname)`, indicates that an option has been deleted.
+
+
+Others migration functions
+--------------------------
+Those functions are only used for low level operations that could not be
+accomplished otherwise or to repair damaged databases during interactive
+session. They are available in `repository` scripts:
+
+* `sql(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query on the system source
+* `add_entity_type_table(etype, commit=True)`
+* `add_relation_type_table(rtype, commit=True)`
+* `uninline_relation(rtype, commit=True)`
+
+
+[FIXME] Add explanation on how to use cubicweb-ctl shell
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/profiling.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,55 @@
+Profiling and performance
+=========================
+
+If you feel that one of your pages takes more time than it should to be
+generated, chances are that you're making too many RQL queries.  Obviously,
+there are other reasons but experience tends to show this is the first thing to
+track down. Luckily, CubicWeb provides a configuration option to log RQL
+queries. In your ``all-in-one.conf`` file, set the **query-log-file** option::
+
+    # web application query log file
+    query-log-file=~/myapp-rql.log
+
+Then restart your application, reload your page and stop your application.
+The file ``myapp-rql.log`` now contains the list of RQL queries that were
+executed during your test. It's a simple text file containing lines such as::
+
+    Any A WHERE X eid %(x)s, X lastname A {'x': 448} -- (0.002 sec, 0.010 CPU sec)
+    Any A WHERE X eid %(x)s, X firstname A {'x': 447} -- (0.002 sec, 0.000 CPU sec)
+
+The structure of each line is::
+
+    <RQL QUERY> <QUERY ARGS IF ANY> -- <TIME SPENT>
+
+CubicWeb also provides the **exlog** command to examine and summarize data found
+in such a file:
+
+.. sourcecode:: sh
+
+    $ cubicweb-ctl exlog < ~/myapp-rql.log
+    0.07 50 Any A WHERE X eid %(x)s, X firstname A {}
+    0.05 50 Any A WHERE X eid %(x)s, X lastname A {}
+    0.01 1 Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E employees X, X modification_date AA {}
+    0.01 1 Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s {, }
+    0.01 1 Any B,T,P ORDERBY lower(T) WHERE B is Bookmark,B title T, B path P, B bookmarked_by U, U eid %(x)s {}
+    0.01 1 Any A,B,C,D WHERE A eid %(x)s,A name B,A creation_date C,A modification_date D {}
+
+This command sorts and uniquifies queries so that it's easy to see where
+is the hot spot that needs optimization.
+
+Do not neglect to set the **fetch_attrs** attribute you can define in your
+entity classes because it can greatly reduce the number of queries executed (see
+:ref:`FetchAttrs`).
+
+You should also know about the **profile** option in the ``all-in-on.conf``. If
+set, this option will make your application run in an `hotshot`_ session and
+store the results in the specified file.
+
+.. _hotshot: http://docs.python.org/library/hotshot.html#module-hotshot
+
+Last but no least, if you're using the PostgreSQL database backend, VACUUMing
+your database can significantly improve the performance of the queries (by
+updating the statistics used by the query optimizer). Nowadays, this is done
+automatically from time to time, but if you've just imported a large amount of
+data in your db, you will want to vacuum it (with the analyse option on). Read
+the documentation of your database for more information.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/repo/hooks.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,436 @@
+.. -*- coding: utf-8 -*-
+
+.. _hooks:
+
+Hooks and Operations
+====================
+
+Generalities
+------------
+
+Paraphrasing the `emacs`_ documentation, let us say that hooks are an
+important mechanism for customizing an application. A hook is
+basically a list of functions to be called on some well-defined
+occasion (this is called `running the hook`).
+
+.. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
+
+In CubicWeb, hooks are subclasses of the Hook class in
+`server/hook.py`, implementing their own `call` method, and selected
+over a set of pre-defined `events` (and possibly more conditions,
+hooks being selectable AppObjects like views and components).
+
+There are two families of events: data events and server events. In a
+typical application, most of the Hooks are defined over data
+events.
+
+The purpose of data hooks is to complement the data model as defined
+in the schema.py, which is static by nature, with dynamic or value
+driven behaviours. It is functionally equivalent to a `database
+trigger`_, except that database triggers definition languages are not
+standardized, hence not portable (for instance, PL/SQL works with
+Oracle and PostgreSQL but not SqlServer nor Sqlite).
+
+.. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
+
+Data hooks can serve the following purposes:
+
+* enforcing constraints that the static schema cannot express
+  (spanning several entities/relations, exotic value ranges and
+  cardinalities, etc.)
+
+* implement computed attributes
+
+Operations are Hook-like objects that may be created by Hooks and
+scheduled to happen just before (or after) the `commit` event. Hooks
+being fired immediately on data operations, it is sometime necessary
+to delay the actual work down to a time where all other Hooks have
+run, for instance a validation check which needs that all relations be
+already set on an entity. Also while the order of execution of Hooks
+is data dependant (and thus hard to predict), it is possible to force
+an order on Operations.
+
+Operations also may be used to process various side effects associated
+with a transaction such as filesystem udpates, mail notifications,
+etc.
+
+Operations are subclasses of the Operation class in `server/hook.py`,
+implementing `precommit_event` and other standard methods (wholly
+described in :ref:`operations_api`).
+
+Events
+------
+
+Hooks are mostly defined and used to handle `dataflow`_ operations. It
+means as data gets in (entities added, updated, relations set or
+unset), specific events are issued and the Hooks matching these events
+are called.
+
+.. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
+
+Below comes a list of the dataflow events related to entities operations:
+
+* before_add_entity
+
+* before_update_entity
+
+* before_delete_entity
+
+* after_add_entity
+
+* after_update_entity
+
+* after_delete_entity
+
+These define ENTTIES HOOKS. RELATIONS HOOKS are defined
+over the following events:
+
+* after_add_relation
+
+* after_delete_relation
+
+* before_add_relation
+
+* before_delete_relation
+
+This is an occasion to remind us that relations support the add/delete
+operation, but no update.
+
+Non data events also exist. These are called SYSTEM HOOKS.
+
+* server_startup
+
+* server_shutdown
+
+* server_maintenance
+
+* server_backup
+
+* server_restore
+
+* session_open
+
+* session_close
+
+
+Using dataflow Hooks
+--------------------
+
+Dataflow hooks either automate data operations or maintain the
+consistency of the data model. In the later case, we must use a
+specific exception named ValidationError
+
+Validation Errors
+~~~~~~~~~~~~~~~~~
+
+When a condition is not met in a Hook/Operation, it must raise a
+`ValidationError`. Raising anything but a (subclass of)
+ValidationError is a programming error. Raising a ValidationError
+entails aborting the current transaction.
+
+The ValidationError exception is used to convey enough information up
+to the user interface. Hence its constructor is different from the
+default Exception constructor. It accepts, positionally:
+
+* an entity eid,
+
+* a dict whose keys represent attribute (or relation) names and values
+  an end-user facing message (hence properly translated) relating the
+  problem.
+
+An entity hook
+~~~~~~~~~~~~~~
+
+We will use a very simple example to show hooks usage. Let us start
+with the following schema.
+
+.. sourcecode:: python
+
+   class Person(EntityType):
+       age = Int(required=True)
+
+We would like to add a range constraint over a person's age. Let's
+write an hook. It shall be placed into mycube/hooks.py. If this file
+were to grow too much, we can easily have a mycube/hooks/... package
+containing hooks in various modules.
+
+.. sourcecode:: python
+
+   from cubicweb import ValidationError
+   from cubicweb.selectors import implements
+   from cubicweb.server.hook import Hook
+
+   class PersonAgeRange(Hook):
+        __regid__ = 'person_age_range'
+        events = ('before_add_entity', 'before_update_entity')
+        __select__ = Hook.__select__ & implements('Person')
+
+        def __call__(self):
+            if 0 >= self.entity.age <= 120:
+               return
+            msg = self._cw._('age must be between 0 and 120')
+            raise ValidationError(self.entity.eid, {'age': msg})
+
+Hooks being AppObjects like views, they have a __regid__ and a
+__select__ class attribute. The base __select__ is augmented with an
+`implements` selector matching the desired entity type. The `events`
+tuple is used by the Hook.__select__ base selector to dispatch the
+hook on the right events. In an entity hook, it is possible to
+dispatch on any entity event (e.g. 'before_add_entity',
+'before_update_entity') at once if needed.
+
+Like all appobjects, hooks have the `self._cw` attribute which
+represents the current session. In entity hooks, a `self.entity`
+attribute is also present.
+
+
+A relation hook
+~~~~~~~~~~~~~~~
+
+Let us add another entity type with a relation to person (in
+mycube/schema.py).
+
+.. sourcecode:: python
+
+   class Company(EntityType):
+        name = String(required=True)
+        boss = SubjectRelation('Person', cardinality='1*')
+
+We would like to constrain the company's bosses to have a minimum
+(legal) age. Let's write an hook for this, which will be fired when
+the `boss` relation is established.
+
+.. sourcecode:: python
+
+   class CompanyBossLegalAge(Hook):
+        __regid__ = 'company_boss_legal_age'
+        events = ('before_add_relation',)
+        __select__ = Hook.__select__ & match_rtype('boss')
+
+        def __call__(self):
+            boss = self._cw.entity_from_eid(self.eidto)
+            if boss.age < 18:
+                msg = self._cw._('the minimum age for a boss is 18')
+                raise ValidationError(self.eidfrom, {'boss': msg})
+
+We use the `match_rtype` selector to select the proper relation type.
+
+The essential difference with respect to an entity hook is that there
+is no self.entity, but `self.eidfrom` and `self.eidto` hook attributes
+which represent the subject and object eid of the relation.
+
+
+Using Operations
+----------------
+
+Let's augment our example with a new `subsidiary_of` relation on Company.
+
+.. sourcecode:: python
+
+   class Company(EntityType):
+        name = String(required=True)
+        boss = SubjectRelation('Person', cardinality='1*')
+        subsidiary_of = SubjectRelation('Company', cardinality='*?')
+
+Base example
+~~~~~~~~~~~~
+
+We would like to check that there is no cycle by the `subsidiary_of`
+relation. This is best achieved in an Operation since all relations
+are likely to be set at commit time.
+
+.. sourcecode:: python
+
+    def check_cycle(self, session, eid, rtype, role='subject'):
+        parents = set([eid])
+        parent = session.entity_from_eid(eid)
+        while parent.related(rtype, role):
+            parent = parent.related(rtype, role)[0]
+            if parent.eid in parents:
+                msg = session._('detected %s cycle' % rtype)
+                raise ValidationError(eid, {rtype: msg})
+            parents.add(parent.eid)
+
+    class CheckSubsidiaryCycleOp(Operation):
+
+        def precommit_event(self):
+            check_cycle(self.session, self.eidto, 'subsidiary_of')
+
+
+    class CheckSubsidiaryCycleHook(Hook):
+        __regid__ = 'check_no_subsidiary_cycle'
+        events = ('after_add_relation',)
+        __select__ = Hook.__select__ & match_rtype('subsidiary_of')
+
+        def __call__(self):
+            CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto)
+
+The operation is instantiated in the Hook.__call__ method.
+
+An operation always takes a session object as first argument
+(accessible as `.session` from the operation instance), and optionally
+all keyword arguments needed by the operation. These keyword arguments
+will be accessible as attributes from the operation instance.
+
+Like in Hooks, ValidationError can be raised in Operations. Other
+exceptions are programming errors.
+
+Notice how our hook will instantiate an operation each time the Hook
+is called, i.e. each time the `subsidiary_of` relation is set.
+
+Using set_operation
+~~~~~~~~~~~~~~~~~~~
+
+There is an alternative method to schedule an Operation from a Hook,
+using the `set_operation` function.
+
+.. sourcecode:: python
+
+   from cubicweb.server.hook import set_operation
+
+   class CheckSubsidiaryCycleHook(Hook):
+       __regid__ = 'check_no_subsidiary_cycle'
+       events = ('after_add_relation',)
+       __select__ = Hook.__select__ & match_rtype('subsidiary_of')
+
+       def __call__(self):
+           set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto,
+                         CheckSubsidiaryCycleOp, rtype=self.rtype)
+
+   class CheckSubsidiaryCycleOp(Operation):
+
+       def precommit_event(self):
+           for eid in self._cw.transaction_data['subsidiary_cycle_detection']:
+               check_cycle(self.session, eid, self.rtype)
+
+Here, we call set_operation with a session object, a specially forged
+key, a value that is the actual payload of an individual operation (in
+our case, the object of the subsidiary_of relation) , the class of the
+Operation, and more optional parameters to give to the operation (here
+the rtype which do not vary accross operations).
+
+The body of the operation must then iterate over the values that have
+been mapped in the transaction_data dictionary to the forged key.
+
+This mechanism is especially useful on two occasions (not shown in our
+example):
+
+* massive data import (reduced memory consumption within a large
+  transaction)
+
+* when several hooks need to instantiate the same operation (e.g. an
+  entity and a relation hook).
+
+.. note::
+
+  A more realistic example can be found in the advanced tutorial
+  chapter :ref:`adv_tuto_security_propagation`.
+
+.. _operations_api:
+
+Operation: a small API overview
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: cubicweb.server.hook.Operation
+.. autoclass:: cubicweb.server.hook.LateOperation
+.. autofunction:: cubicweb.server.hook.set_operation
+
+Hooks writing rules
+-------------------
+
+Remainder
+~~~~~~~~~
+
+Never, ever use the `entity.foo = 42` notation to update an entity. It
+will not work.
+
+How to choose between a before and an after event ?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before hooks give you access to the old attribute (or relation)
+values. By definition the database is not yet updated in a before
+hook.
+
+To access old and new values in an before_update_entity hook, one can
+use the `server.hook.entity_oldnewvalue` function which returns a
+tuple of the old and new values. This function takes an entity and an
+attribute name as parameters.
+
+In a 'before_add|update_entity' hook the self.entity contains the new
+values. One is allowed to further modify them before database
+operations, using the dictionary notation.
+
+.. sourcecode:: python
+
+   self.entity['age'] = 42
+
+This is because using self.entity.set_attributes(age=42) will
+immediately update the database (which does not make sense in a
+pre-database hook), and will trigger any existing
+before_add|update_entity hook, thus leading to infinite hook loops or
+such awkward situations.
+
+Beyond these specific cases, updating an entity attribute or relation
+must *always* be done using `set_attributes` and `set_relations`
+methods.
+
+(Of course, ValidationError will always abort the current transaction,
+whetever the event).
+
+Peculiarities of inlined relations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some relations are defined in the schema as `inlined` (see
+:ref:`RelationType` for details). In this case, they are inserted in
+the database at the same time as entity attributes.
+
+Hence in the case of before_add_relation, such relations already exist
+in the database.
+
+Edited attributes
+~~~~~~~~~~~~~~~~~
+
+On udpates, it is possible to ask the `entity.edited_attributes`
+variable whether one attribute has been updated.
+
+.. sourcecode:: python
+
+  if 'age' not in entity.edited_attribute:
+      return
+
+Deleted in transaction
+~~~~~~~~~~~~~~~~~~~~~~
+
+The session object has a deleted_in_transaction method, which can help
+writing deletion Hooks.
+
+.. sourcecode:: python
+
+   if self._cw.deleted_in_transaction(self.eidto):
+      return
+
+Given this predicate, we can avoid scheduling an operation.
+
+Disabling hooks
+~~~~~~~~~~~~~~~
+
+It is sometimes convenient to disable some hooks. For instance to
+avoid infinite Hook loops. One uses the `hooks_control` context
+manager.
+
+This can be controlled more finely through the `category` Hook class
+attribute, which is a string.
+
+.. sourcecode:: python
+
+   with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, <category>):
+       # ... do stuff
+
+.. autoclass:: cubicweb.server.session.hooks_control
+
+The existing categories are: ``email``, ``syncsession``,
+``syncschema``, ``bookmark``, ``security``, ``worfklow``,
+``metadata``, ``notification``, ``integrity``, ``activeintegrity``.
+
+Nothing precludes one to invent new categories and use the
+hooks_control context manager to filter them (in or out).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/repo/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,13 @@
+.. -*- coding: utf-8 -*-
+
+Repository customization
+++++++++++++++++++++++++
+.. toctree::
+   :maxdepth: 1
+
+   sessions
+   hooks
+   notifications
+   tasks
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/repo/notifications.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,6 @@
+.. -*- coding: utf-8 -*-
+
+Notifications management
+========================
+
+.. XXX FILLME
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/repo/sessions.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,27 @@
+.. -*- coding: utf-8 -*-
+
+Sessions
+========
+
+There are three kinds of sessions.
+
+* `user sessions` are the most common: they are related to users and
+  carry security checks coming with user credentials
+
+* `super sessions` are children of ordinary user sessions and allow to
+  bypass security checks (they are created by calling unsafe_execute
+  on a user session); this is often convenient in hooks which may
+  touch data that is not directly updatable by users
+
+* `internal sessions` have all the powers; they are also used in only a
+  few situations where you don't already have an adequate session at
+  hand, like: user authentication, data synchronisation in
+  multi-source contexts
+
+.. note::
+  Do not confuse the session type with their connection mode, for
+  instance : 'in memory' or 'pyro'.
+
+[WRITE ME]
+
+* authentication and management of sessions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/repo/tasks.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Tasks
+=========
+
+[WRITE ME]
+
+* repository tasks
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/testing.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,258 @@
+.. -*- coding: utf-8 -*-
+
+Tests
+=====
+
+Unit tests
+----------
+
+The *CubicWeb* framework provides the `CubicWebTC` test base class in
+the module `cubicweb.devtools.testlib`.
+
+Tests shall be put into the mycube/test directory. Additional test
+data shall go into mycube/test/data.
+
+It is much advised to write tests concerning entities methods, hooks
+and operations, security. The CubicWebTC base class has convenience
+methods to help test all of this.
+
+.. note::
+
+  In the realm of views, there is not much to do but check that the
+  views are valid XHTML.  See :ref:`automatic_views_tests` for
+  details. Integration of CubicWeb tests with UI testing tools such as
+  `selenium`_ are currently under invesitgation.
+
+.. _selenium: http://seleniumhq.org/projects/ide/
+
+Most unit tests need a live database to work against. This is achieved
+by CubicWeb using automatically sqlite (bundled with Python, see
+http://docs.python.org/library/sqlite3.html) as a backend.
+
+The database is stored in the mycube/test/tmpdb,
+mycube/test/tmpdb-template files. If it does not (yet) exists, it will
+be built automatically when the test suit starts.
+
+.. warning::
+
+  Whenever the schema changes (new entities, attributes, relations)
+  one must delete these two files. Changes concerned only with entity
+  or relation type properties (constraints, cardinalities,
+  permissions) and generally dealt with using the
+  `sync_schema_props_perms()` fonction of the migration environment
+  need not a database regeneration step.
+
+Unit test by example
+````````````````````
+
+We start with an example extracted from the keyword cube (available
+from http://www.cubicweb.org/project/cubicweb-keyword).
+
+.. sourcecode:: python
+
+    from cubicweb.devtools.testlib import CubicWebTC
+    from cubicweb import ValidationError
+
+    class ClassificationHooksTC(CubicWebTC):
+
+        def setup_database(self):
+            req = self.request()
+            group_etype = req.execute('Any X WHERE X name "CWGroup"').get_entity(0,0)
+            c1 = req.create_entity('Classification', name=u'classif1',
+                                   classifies=group_etype)
+            user_etype = req.execute('Any X WHERE X name "CWUser"').get_entity(0,0)
+            c2 = req.create_entity('Classification', name=u'classif2',
+                                   classifies=user_etype)
+            self.kw1 = req.create_entity('Keyword', name=u'kwgroup', included_in=c1)
+            self.kw2 = req.create_entity('Keyword', name=u'kwuser', included_in=c2)
+
+        def test_cannot_create_cycles(self):
+            # direct obvious cycle
+            self.assertRaises(ValidationError, self.kw1.set_relations,
+                              subkeyword_of=self.kw1)
+            # testing indirect cycles
+            kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
+                               'SK subkeyword_of K WHERE C name "classif1", K eid %s'
+                               % self.kw1.eid).get_entity(0,0)
+            self.kw1.set_relations(subkeyword_of=kw3)
+            self.assertRaises(ValidationError, self.commit)
+
+The test class defines a `setup_database` method which populates the
+database with initial data. Each test of the class runs with this
+pre-populated database.
+
+The test case itself checks that an Operation does it job of
+preventing cycles amongst Keyword entities.
+
+You can see an example of security tests in the
+:ref:`adv_tuto_security`.
+
+It is possible to have these tests run continuously using `apycot`_.
+
+.. _apycot: http://www.logilab.org/project/apycot
+
+Managing connections or users
++++++++++++++++++++++++++++++
+
+Since unit tests are done with the SQLITE backend and this does not
+support multiple connections at a time, you must be careful when
+simulating security, changing users.
+
+By default, tests run with a user with admin privileges. This
+user/connection must never be closed.
+
+Before a self.login, one has to release the connection pool in use
+with a self.commit, self.rollback or self.close.
+
+The `login` method returns a connection object that can be used as a
+context manager:
+
+.. sourcecode:: python
+
+   with self.login('user1') as user:
+       req = user.req
+       req.execute(...)
+
+On exit of the context manager, either a commit or rollback is issued,
+which releases the connection.
+
+When one is logged in as a normal user and wants to switch back to the
+admin user without committing, one has to use
+self.restore_connection().
+
+Usage with restore_connection:
+
+.. sourcecode:: python
+
+    # execute using default admin connection
+    self.execute(...)
+    # I want to login with another user, ensure to free admin connection pool
+    # (could have used rollback but not close here
+    # we should never close defaut admin connection)
+    self.commit()
+    cnx = self.login('user')
+    # execute using user connection
+    self.execute(...)
+    # I want to login with another user or with admin user
+    self.commit();  cnx.close()
+    # restore admin connection, never use cnx = self.login('admin'), it will return
+    # the default admin connection and one may be tempted to close it
+    self.restore_connection()
+
+.. warning::
+
+   Do not use the references kept to the entities created with a
+   connection from another !
+
+Email notifications tests
+-------------------------
+
+When running tests potentially generated e-mails are not really sent
+but is found in the list `MAILBOX` of module
+`cubicweb.devtools.testlib`.
+
+You can test your notifications by analyzing the contents of this list, which
+contains objects with two attributes:
+
+* `recipients`, the list of recipients
+* `msg`, object email.Message
+
+Let us look at simple example from the ``blog`` cube.
+
+.. sourcecode:: python
+
+    from cubicweb.devtools.testlib import CubicWebTC, MAILBOX
+
+    class BlogTestsCubicWebTC(CubicWebTC):
+        """test blog specific behaviours"""
+
+        def test_notifications(self):
+            req = self.request()
+            cubicweb_blog = req.create_entity('Blog', title=u'cubicweb',
+                                description=u'cubicweb is beautiful')
+            blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
+                                             content=u'cubicweb hop')
+            blog_entry_1.set_relations(entry_of=cubicweb_blog)
+            blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
+                                             content=u'cubicweb yes')
+            blog_entry_2.set_relations(entry_of=cubicweb_blog)
+            self.assertEquals(len(MAILBOX), 0)
+            self.commit()
+            self.assertEquals(len(MAILBOX), 2)
+            mail = MAILBOX[0]
+            self.assertEquals(mail.subject, '[data] hop')
+            mail = MAILBOX[1]
+            self.assertEquals(mail.subject, '[data] yes')
+
+.. _automatic_views_tests:
+
+Automatic views testing
+-----------------------
+
+This is done automatically with the AutomaticWebTest class. At cube
+creation time, the mycube/test/test_mycube.py file contains such a
+test. The code here has to be uncommented to be usable, without
+further modification.
+
+The ``auto_populate`` method uses a smart algorithm to create
+pseudo-random data in the database, thus enabling the views to be
+invoked and tested.
+
+Depending on the schema, hooks and operations constraints, it is not
+always possible for the automatic auto_populate to proceed.
+
+It is possible of course to completely redefine auto_populate. A
+lighter solution is to give hints (fill some class attributes) about
+what entities and relations have to be skipped by the auto_populate
+mechanism. These are:
+
+* `no_auto_populate`, may contain a list of entity types to skip
+* `ignored_relations`, may contain a list of relation types to skip
+* `application_rql`, may contain a list of rql expressions that
+  auto_populate cannot guess by itself; these must yield resultsets
+  against which views may be selected.
+
+
+Test APIS
+---------
+
+Using Pytest
+````````````
+
+The `pytest` utility (shipping with `logilab-common`_, which is a
+mandatory dependency of CubicWeb) extends the Python unittest
+functionality and is the preferred way to run the CubicWeb test
+suites. Bare unittests also work the usual way.
+
+.. _logilab-common: http://www.logilab.org/project/logilab-common
+
+To use it, you may:
+
+* just launch `pytest` in your cube to execute all tests (it will
+  discover them automatically)
+* launch `pytest unittest_foo.py` to execute one test file
+* launch `pytest unittest_foo.py bar` to execute all test methods and
+  all test cases whose name contain `bar`
+
+Additionally, the `-x` option tells pytest to exit at the first error
+or failure. The `-i` option tells pytest to drop into pdb whenever an
+exception occurs in a test.
+
+When the `-x` option has been used and the run stopped on a test, it
+is possible, after having fixed the test, to relaunch pytest with the
+`-R` option to tell it to start testing again from where it previously
+failed.
+
+Using the `TestCase` base class
+```````````````````````````````
+
+The base class of CubicWebTC is logilab.common.testlib.TestCase, which
+provides a lot of convenient assertion methods.
+
+.. autoclass:: logilab.common.testlib.TestCase
+   :members:
+
+CubicWebTC API
+``````````````
+.. autoclass:: cubicweb.devtools.testlib.CubicWebTC
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/vreg.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,107 @@
+The VRegistry, selectors and application objects
+================================================
+
+This chapter talks about core concepts of the |cubicweb| framework,
+that make it different from other frameworks (and maybe not easy to
+grasp at a first glance). To be able to do advanced development with
+|cubicweb| you need a good understanding of what is explained below.
+
+This chapter goes deep into details. You don't have to remember them
+all but keep it in mind so you can go back there later.
+
+An overview of AppObjects, the VRegistry and Selectors is given in the
+:ref:`VRegistryIntro` chapter.
+
+.. autodocstring:: cubicweb.cwvreg
+.. autodocstring:: cubicweb.selectors
+.. automodule:: cubicweb.appobject
+
+Base selectors
+--------------
+
+Selectors are scoring functions that are called by the registry to tell whenever
+an appobject can be selected in a given context. Selector sets are for instance
+the glue that tie views to the data model. Using them appropriately is an
+essential part of the construction of well behaved cubes.
+
+Of course you may have to write your own set of selectors as your needs grows and
+you get familiar with the framework (see :ref:`CustomSelectors`).
+
+Here is a description of generic selectors provided by CubicWeb that should suit
+most of your needs.
+
+Bare selectors
+~~~~~~~~~~~~~~
+Those selectors are somewhat dumb, which doesn't mean they're not (very) useful.
+
+.. autoclass:: cubicweb.appobject.yes
+.. autoclass:: cubicweb.selectors.match_kwargs
+.. autoclass:: cubicweb.selectors.appobject_selectable
+
+
+Result set selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for a result set in the context ('rset' argument or
+the input context) and match or not according to its shape. Some of these
+selectors have different behaviour if a particular cell of the result set is
+specified using 'row' and 'col' arguments of the input context or not.
+
+.. autoclass:: cubicweb.selectors.none_rset
+.. autoclass:: cubicweb.selectors.any_rset
+.. autoclass:: cubicweb.selectors.nonempty_rset
+.. autoclass:: cubicweb.selectors.empty_rset
+.. autoclass:: cubicweb.selectors.one_line_rset
+.. autoclass:: cubicweb.selectors.multi_lines_rset
+.. autoclass:: cubicweb.selectors.multi_columns_rset
+.. autoclass:: cubicweb.selectors.paginated_rset
+.. autoclass:: cubicweb.selectors.sorted_rset
+.. autoclass:: cubicweb.selectors.one_etype_rset
+.. autoclass:: cubicweb.selectors.multi_etypes_rset
+
+
+Entity selectors
+~~~~~~~~~~~~~~~~
+Those selectors are looking for either an `entity` argument in the input context,
+or entity found in the result set ('rset' argument or the input context) and
+match or not according to entity's (instance or class) properties.
+
+.. autoclass:: cubicweb.selectors.non_final_entity
+.. autoclass:: cubicweb.selectors.implements
+.. autoclass:: cubicweb.selectors.score_entity
+.. autoclass:: cubicweb.selectors.rql_condition
+.. autoclass:: cubicweb.selectors.relation_possible
+.. autoclass:: cubicweb.selectors.partial_relation_possible
+.. autoclass:: cubicweb.selectors.has_related_entities
+.. autoclass:: cubicweb.selectors.partial_has_related_entities
+.. autoclass:: cubicweb.selectors.has_permission
+.. autoclass:: cubicweb.selectors.has_add_permission
+
+
+Logged user selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for properties of the user issuing the request.
+
+.. autoclass:: cubicweb.selectors.anonymous_user
+.. autoclass:: cubicweb.selectors.authenticated_user
+.. autoclass:: cubicweb.selectors.match_user_groups
+
+
+Web request selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for properties of *web* request, they can not be
+used on the data repository side.
+
+.. autoclass:: cubicweb.selectors.match_form_params
+.. autoclass:: cubicweb.selectors.match_search_state
+.. autoclass:: cubicweb.selectors.match_context_prop
+.. autoclass:: cubicweb.selectors.match_view
+.. autoclass:: cubicweb.selectors.primary_view
+.. autoclass:: cubicweb.selectors.specified_etype_implements
+
+
+Other selectors
+~~~~~~~~~~~~~~~
+.. autoclass:: cubicweb.selectors.match_transition
+
+You'll also find some other (very) specific selectors hidden in other modules
+than :mod:`cubicweb.selectors`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/controllers.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,201 @@
+.. _controllers:
+
+Controllers
+-----------
+
+Overview
+++++++++
+
+Controllers are responsible for taking action upon user requests
+(loosely following the terminology of the MVC meta pattern).
+
+The following controllers are provided out-of-the box in CubicWeb. We
+list them by category. They are all defined in
+(:mod:`cubicweb.web.views.basecontrollers`).
+
+`Browsing`:
+
+* the View controlleris associated with most browsing actions within a
+  CubicWeb application: it always instantiates a
+  :ref:`the_main_template` and lets the ResultSet/Views dispatch
+  system build up the whole content; it handles ObjectNotFound and
+  NoSelectableObject errors that may bubble up to its entry point, in
+  an end-user-friendly way (but other programming errors will slip
+  through)
+
+* the JSon controller (same module) provides services for Ajax calls,
+  typically using JSON as a serialization format for input, and
+  sometimes using either JSON or XML for output;
+
+* the Login/Logout controllers make effective user login or logout
+  requests
+
+`Edition`:
+
+* the Edit controller (see :ref:`edit_controller`) handles CRUD
+  operations in response to a form being submitted; it works in close
+  association with the Forms, to which it delegates some of the work
+
+* the Form validator controller provides form validation from Ajax
+  context, using the Edit controller, to implement the classic form
+  handling loop (user edits, hits 'submit/apply', validation occurs
+  server-side by way of the Form validator controller, and the UI is
+  decorated with failure information, either global or per-field ,
+  until it is valid)
+
+`Other`:
+
+* the SendMail controller (web/views/basecontrollers.py) is reponsible
+  for outgoing email notifications
+
+* the MailBugReport controller (web/views/basecontrollers.py) allows
+  to quickly have a `repotbug` feature in one's application
+
+Registration
+++++++++++++
+
+All controllers (should) live in the 'controllers' namespace within
+the global registry.
+
+API
++++
+
+Most API details should be resolved by source code inspection, as the
+various controllers have differing goals.
+
+`web/controller.py` contains the top-level abstract Controller class and
+its (NotImplemented) entry point `publish(rset=None)` method.
+
+A handful of helpers are also provided there:
+
+* process_rql builds a result set from an rql query typically issued
+  from the browser (and available through _cw.form['rql'])
+
+* validate_cache will force cache validation handling with respect to
+  the HTTP Cache directives (that were typically originally issued
+  from a previous server -> client response); concrete Controller
+  implementations dealing with HTTP (thus, for instance, not the
+  SendMail controller) may very well call this in their publication
+  process.
+
+
+.. _edit_controller:
+
+The `edit controller`
++++++++++++++++++++++
+
+It can be found in (:mod:`cubicweb.web.views.editcontroller`).
+
+Editing control
+~~~~~~~~~~~~~~~~
+
+.. XXX this look obsolete
+
+The parameters related to entities to edit are specified as follows ::
+
+  <field name>:<entity eid>
+
+where entity eid could be a letter in case of an entity to create. We
+name those parameters as *qualified*.
+
+1. Retrieval of entities to edit by looking for the forms parameters
+   starting by `eid:` and also having a parameter `__type` associated
+   (also *qualified* by eid)
+
+2. For all the attributes and the relations of an entity to edit:
+
+   1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
+      qualified in the case of a relation where the entity is object
+   2. if found, the value returned is considered as the initial value
+      for this relaiton and we then look for the new value(s)  in the parameter
+      <relation name> (qualified)
+   3. if the value returned is different from the initial value, an database update
+      request is done
+
+3. For each entity to edit:
+
+   1. if a qualified parameter `__linkto` is specified, its value has to be
+      a string (or a list of string) such as: ::
+
+        <relation type>:<eids>:<target>
+
+      where <target> is either `subject` or `object` and each eid could be
+      separated from the others by a `_`. Target specifies if the *edited entity*
+      is subject or object of the relation and each relation specified will
+      be inserted.
+
+    2. if a qualified parameter `__clone_eid` is specified for an entity, the
+       relations of the specified entity passed as value of this parameter are
+       copied on the edited entity.
+
+    3. if a qualified parameter `__delete` is specified, its value must be
+       a string or a list of string such as follows: ::
+
+          <ssubjects eids>:<relation type>:<objects eids>
+
+       where each eid subject or object can be seperated from the other
+       by `_`. Each relation specified will be deleted.
+
+    4. if a qualified parameter `__insert` is specified, its value should
+       follow the same pattern as `__delete`, but each relation specified is
+       inserted.
+
+4. If the parameters `__insert` and/or `__delete` are found not qualified,
+   they are interpreted as explained above (independantly from the number
+   of entities edited).
+
+5. If no entity is edited but the form contains the parameters `__linkto`
+   and `eid`, this one is interpreted by using the value specified for `eid`
+   to designate the entity on which to add the relations.
+
+
+.. note::
+
+   * If the parameter `__action_delete` is found, all the entities specified
+     as to be edited will be deleted.
+
+   * If the parameter `__action_cancel` is found, no action is completed.
+
+   * If the parameter `__action_apply` is found, the editing is
+     applied normally but the redirection is done on the form (see
+     :ref:`RedirectionControl`).
+
+   * The parameter `__method` is also supported as for the main template
+
+   * If no entity is found to be edited and if there is no parameter
+     `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
+     `__insert`, an error is raised.
+
+   * Using the parameter `__message` in the form will allow to use its value
+     as a message to provide the user once the editing is completed.
+
+
+.. _RedirectionControl:
+
+Redirection control
+~~~~~~~~~~~~~~~~~~~
+Once editing is completed, there is still an issue left: where should we go
+now? If nothing is specified, the controller will do his job but it does not
+mean we will be happy with the result. We can control that by using the
+following parameters:
+
+* `__redirectpath`: path of the URL (relative to the root URL of the site,
+  no form parameters
+
+* `__redirectparams`: forms parameters to add to the path
+
+* `__redirectrql`: redirection RQL request
+
+* `__redirectvid`: redirection view identifier
+
+* `__errorurl`: initial form URL, used for redirecting in case a validation
+  error is raised during editing. If this one is not specified, an error page
+  is displayed instead of going back to the form (which is, if necessary,
+  responsible for displaying the errors)
+
+* `__form_id`: initial view form identifier, used if `__action_apply` is
+  found
+
+In general we use either `__redirectpath` and `__redirectparams` or
+`__redirectrql` and `__redirectvid`.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/css.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,30 @@
+.. -*- coding: utf-8 -*-
+
+CSS Stylesheet
+---------------
+Conventions
+~~~~~~~~~~~
+
+.. XXX external_resources variable
+..    naming convention
+..    request.add_css
+
+
+Extending / overriding existing styles
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We cannot modify the order in which the application is reading the CSS. In
+the case we want to create new CSS style, the best is to define it a in a new
+CSS located under ``myapp/data/`` and use those new styles while writing
+customized views and templates.
+
+If you want to modify an existing CSS styling property, you will have to use
+``!important`` declaration to override the existing property. The application
+apply a higher priority on the default CSS and you can not change that.
+Customized CSS will not be read first.
+
+
+CubicWeb stylesheets
+~~~~~~~~~~~~~~~~~~~~
+
+.. XXX explain diffenrent files and main classes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/facets.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,172 @@
+The facets system
+-----------------
+
+Facets allow to restrict searches according to some criteria. CubicWeb
+has a builtin `facet`_ system to define restrictions `filters`_ really
+as easily as possible. A few base classes for facets are provided in
+``cubicweb.web.facet.py``. All classes inherits from the base class
+``AbstractFacet``.
+
+Here is an overview of the facets rendering pick from the `tracker` cube:
+
+.. image:: ../images/facet_overview.png
+
+Facets will appear on each page presenting more than one entity.
+
+
+
+VocabularyFacet
+~~~~~~~~~~~~~~~~
+The ``VocabularyFacet`` inherits from the ``AbstractFacet``.
+A class which inherits from VocabularyFacets must redefine these methods:
+
+.. automethod:: cubicweb.web.facet.VocabularyFacet.vocabulary
+.. automethod:: cubicweb.web.facet.VocabularyFacet.possible_values
+
+RelationFacet
+~~~~~~~~~~~~~~
+
+The ``RelationFacet`` inherits from the ``VocabularyFacet``. It allows to filter entities according to certain relation's values. Generally, you just have to define some class attributes like:
+
+- rtype: the name of the relation
+- role: the default value is set to `subject`
+- target_attr: needed if it is not the default attribute of the entity
+
+
+To illustrate this facet, let's take for example an *excerpt* of the schema of an office location search application:
+
+.. sourcecode:: python
+
+  class Office(WorkflowableEntityType):
+      price = Int(description='euros / m2 / HC / HT')
+      surface = Int(description='m2')
+      description = RichString(fulltextindexed=True)
+      has_address = SubjectRelation('PostalAddress',
+                                    cardinality='1?',
+                                    composite='subject')
+      proposed_by = SubjectRelation('Agency')
+      comments = ObjectRelation('Comment',
+                                cardinality='1*',
+                                composite='object')
+      screenshots = SubjectRelation(('File', 'Image'),
+                                    cardinality='*1',
+                                    composite='subject')
+
+
+We define a facet to filter offices according to the attribute
+`postalcode` of their associated `PostalAdress`.
+
+.. sourcecode:: python
+
+  class PostalCodeFacet(RelationFacet):
+      __regid__ = 'postalcode-facet'      # every registered class must have an id
+      __select__ = implements('Office')   # this facet should only be selected when
+                                          # visualizing offices
+      rtype = 'has_address'               # this facet is a filter on the entity linked to
+                                          # the office thrhough the relation
+                                          # has_address
+      target_attr = 'postalcode'          # the filter's key is the attribute "postal_code"
+                                          # of the target PostalAddress entity
+
+
+AttributeFacet
+~~~~~~~~~~~~~~
+
+The ``AttributeFacet`` inherits from the ``RelationFacet``. It allows to filter entities according to certain attribute's values.
+
+The example below resumes the former schema. We define now a filter based on the `surface` attribute of the
+`Office`.
+
+.. sourcecode:: python
+
+  class SurfaceFacet(AttributeFacet):
+      __regid__ = 'surface-facet'       # every registered class must have an id
+      __select__ = implements('Office') # this facet should only be selected when
+                                        # visualizing offices
+      rtype = 'surface'                 # the filter's key is the attribute "surface"
+      comparator = '>='                 # override the default value of operator since
+                                        # we want to filter according to a
+                                        # minimal
+                                        # value, not an exact one
+
+      def rset_vocabulary(self, ___):
+          """override the default vocabulary method since we want to hard-code
+          our threshold values.
+          Not overriding would generate a filter box with all existing surfaces
+          defined in the database.
+          """
+          return [('> 200', '200'), ('> 250', '250'),
+                  ('> 275', '275'), ('> 300', '300')]
+
+RangeFacet
+~~~~~~~~~~
+The ``RangeFacet`` inherits from the ``AttributeFacet``. It allows to filter entities according to certain attributes of numerical type.
+
+The ``RangeFacet`` displays a slider using `jquery`_ to choose a lower bound and an upper bound.
+
+The example below defines a facet to filter a selection of books according to their number of pages.
+
+.. sourcecode:: python
+
+   class BookPagesFacet(RangeFacet):
+       __regid__ = 'priority-facet'
+       __select__ = RangeFacet.__select__ & implements('Book')
+       rtype = 'pages'
+
+The image below display the rendering of the ``RangeFacet``:
+
+.. image:: ../images/facet_range.png
+
+DateRangeFacet
+~~~~~~~~~~~~~~
+The ``DateRangeFacet`` inherits from the ``RangeFacet``. It allows to filter entities according to certain attributes of date type.
+
+Here is an example of code that defines a facet to filter
+musical works according to their composition date:
+
+.. sourcecode:: python
+
+    class CompositionDateFacet(DateRangeFacet):
+        # 1. make sure this facet is displayed only on Track selection
+        __select__ = DateRangeFacet.__select__ & implements('Track')
+        # 2. give the facet an id required by CubicWeb)
+        __regid__ = 'compdate-facet'
+        # 3. specify the attribute name that actually stores the date in the DB
+        rtype = 'composition_date'
+
+With this facet, on each page displaying tracks, you'll be able to filter them
+according to their composition date with a jquery slider.
+
+The image below display the rendering of the ``DateRangeFacet``:
+
+.. image:: ../images/facet_date_range.png
+
+
+HasRelationFacet
+~~~~~~~~~~~~~~~~
+
+The ``DateRangeFacet`` inherits from the ``AbstractFacet``. It will
+display a simple checkbox and lets you refine your selection in order
+to get only entities that actually use this relation.
+
+Here is an example of the rendering of the ``HasRelationFacet`` to
+filter entities with image and the corresponding code:
+
+.. image:: ../images/facet_has_image.png
+
+.. sourcecode:: python
+
+  class HasImageFacet(HasRelationFacet):
+      __regid__ = 'hasimage-facet'
+      __select__ = HasRelationFacet.__select__ & implements('Book')
+      rtype = 'has_image'
+
+
+
+To use ``HasRelationFacet`` on a reverse relation add ``role = 'object'`` in
+it's definitions.
+
+.. _facet: http://en.wikipedia.org/wiki/Faceted_browser
+.. _filters: http://www.cubicweb.org/blogentry/154152
+.. _jquery: http://www.jqueryui.com/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/form.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,216 @@
+HTML form construction
+----------------------
+
+CubicWeb provides the somewhat usual form / field / widget / renderer abstraction
+to provide generic building blocks which will greatly help you in building forms
+properly integrated with CubicWeb (coherent display, error handling, etc...),
+while keeping things as flexible as possible.
+
+A **form** basically only holds a set of **fields**, and has te be bound to a
+**renderer** which is responsible to layout them. Each field is bound to a
+**widget** that will be used to fill in value(s) for that field (at form
+generation time) and 'decode' (fetch and give a proper Python type to) values
+sent back by the browser.
+
+The **field** should be used according to the type of what you want to edit.
+E.g. if you want to edit some date, you'll have to use the
+:class:`cubicweb.web.formfields.DateField`. Then you can choose among multiple
+widgets to edit it, for instance :class:`cubicweb.web.formwidgets.TextInput` (a
+bare text field), :class:`~cubicweb.web.formwidgets.DateTimePicker` (a simple
+calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
+calendar).  You can of course also write your own widget.
+
+
+.. automodule:: cubicweb.web.views.autoform
+
+
+Example of bare fields form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We want to define a form doing something else than editing an entity. The idea is
+to propose a form to send an email to entities in a resultset which implements
+:class:`IEmailable`.  Let's take a simplified version of what you'll find in
+:mod:`cubicweb.web.views.massmailing`.
+
+Here is the source code:
+
+.. sourcecode:: python
+
+    def sender_value(form):
+	return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
+
+    def recipient_choices(form, field):
+	return [(e.get_email(), e.eid) for e in form.cw_rset.entities()
+		 if e.get_email()]
+
+    def recipient_value(form):
+	return [e.eid for e in form.cw_rset.entities() if e.get_email()]
+
+    class MassMailingForm(forms.FieldsForm):
+	__regid__ = 'massmailing'
+
+	needs_js = ('cubicweb.widgets.js',)
+	domid = 'sendmail'
+	action = 'sendmail'
+
+	sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
+				label=_('From:'),
+				value=sender_value)
+
+	recipient = ff.StringField(widget=CheckBox(),
+	                           label=_('Recipients:'),
+				   choices=recipient_choices,
+				   value=recipients_value)
+
+	subject = ff.StringField(label=_('Subject:'), max_length=256)
+
+	mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
+						    inputid='mailbody'))
+
+	form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
+				  _('send email'), 'SEND_EMAIL_ICON'),
+			ImgButton('cancelbutton', "javascript: history.back()",
+				  stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+
+Let's detail what's going on up there. Our form will hold four fields:
+
+* a sender field, which is disabled and will simply contains the user's name and
+  email
+
+* a recipients field, which will be displayed as a list of users in the context
+  result set with checkboxes so user can still choose who will receive his mailing
+  by checking or not the checkboxes. By default all of them will be checked since
+  field's value return a list containing same eids as those returned by the
+  vocabulary function.
+
+* a subject field, limited to 256 characters (hence we know a
+  :class:`~cubicweb.web.formwidgets.TextInput` will be used, as explained in
+  :class:`~cubicweb.web.formfields.StringField`)
+
+* a mailbody field. This field use an ajax widget, defined in `cubicweb.widgets.js`,
+  and whose definition won't be shown here. Notice though that we tell this form
+  need this javascript file by using `needs_js`
+
+Last but not least, we add two buttons control: one to post the form using
+javascript (`$('#sendmail')` being the jQuery call to get the element with DOM id
+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
+:class:`cubicweb.web.formwidgets.ImgButton`.
+
+To see this form, we still have to wrap it in a view. This is pretty simple:
+
+.. sourcecode:: python
+
+    class MassMailingFormView(form.FormViewMixIn, EntityView):
+	__regid__ = 'massmailing'
+	__select__ = implements(IEmailable) & authenticated_user()
+
+	def call(self):
+	    form = self._cw.vreg['forms'].select('massmailing', self._cw,
+	                                         rset=self.cw_rset)
+	    self.w(form.render())
+
+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.
+
+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
+the mail to specified recipients.
+
+Here is what it looks like:
+
+.. sourcecode:: python
+
+   class SendMailController(Controller):
+       __regid__ = 'sendmail'
+       __select__ = authenticated_user() & match_form_params('recipient', 'mailbody', 'subject')
+
+       def publish(self, rset=None):
+           body = self._cw.form['mailbody']
+           subject = self._cw.form['subject']
+           eids = self._cw.form['recipient']
+           # eids may be a string if only one recipient was specified
+           if isinstance(eids, basestring):
+               rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids})
+           else:
+               rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids)))
+           recipients = list(rset.entities())
+           msg = format_mail({'email' : self._cw.user.get_email(),
+                              'name' : self._cw.user.dc_title()},
+                             recipients, body, subject)
+           if not self._cw.vreg.config.sendmails([(msg, recipients]):
+               msg = self._cw._('could not connect to the SMTP server')
+           else:
+               msg = self._cw._('emails successfully sent')
+           raise Redirect(self._cw.build_url(__message=msg))
+
+
+The entry point of a controller is the publish method. In that case we simply get
+back post values in request's `form` attribute, get user instances according
+to eids found in the 'recipient' form value, and send email after calling
+:func:`format_mail` to get a proper email message. If we can't send email or
+if we successfully sent email, we redirect to the index page with proper message
+to inform the user.
+
+Also notice that our controller has a selector that deny access to it to
+anonymous users (we don't want our instance to be used as a spam relay), but also
+check expected parameters are specified in forms. That avoids later defensive
+programming (though it's not enough to handle all possible error cases).
+
+To conclude our example, suppose we wish a different form layout and that existent
+renderers are not satisfying (we would check that first of course :). We would then
+have to define our own renderer:
+
+.. sourcecode:: python
+
+    class MassMailingFormRenderer(formrenderers.FormRenderer):
+        __regid__ = 'massmailing'
+
+        def _render_fields(self, fields, w, form):
+            w(u'<table class="headersform">')
+            for field in fields:
+                if field.name == 'mailbody':
+                    w(u'</table>')
+                    w(u'<div id="toolbar">')
+                    w(u'<ul>')
+                    for button in form.form_buttons:
+                        w(u'<li>%s</li>' % button.render(form))
+                    w(u'</ul>')
+                    w(u'</div>')
+                    w(u'<div>')
+                    w(field.render(form, self))
+                    w(u'</div>')
+                else:
+                    w(u'<tr>')
+                    w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
+                    w(u'<td class="hvalue">')
+                    w(field.render(form, self))
+                    w(u'</td></tr>')
+
+        def render_buttons(self, w, form):
+            pass
+
+We simply override the `_render_fields` and `render_buttons` method of the base form renderer
+to arrange fields as we desire it: here we'll have first a two columns table with label and
+value of the sender, recipients and subject field (form order respected), then form controls,
+then a div containing the textarea for the email's content.
+
+To bind this renderer to our form, we should add to our form definition above:
+
+.. sourcecode:: python
+
+    form_renderer_id = 'massmailing'
+
+API
+~~~
+
+.. automodule:: cubicweb.web.formfields
+.. automodule:: cubicweb.web.formwidgets
+.. automodule:: cubicweb.web.views.forms
+.. automodule:: cubicweb.web.views.formrenderers
+
+.. Example of entity fields form
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/httpcaching.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,3 @@
+HTTP cache management
+---------------------
+XXX feedme
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,21 @@
+Web side development
+====================
+
+In this chapter, we will describe the core APIs for web development in
+the *CubicWeb* framework.
+
+.. toctree::
+   :maxdepth: 2
+
+   publisher
+   controllers
+   request
+   views/index
+   rtags
+   js
+   css
+   form
+   facets
+   internationalization
+..   property
+   httpcaching
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/internationalization.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,222 @@
+.. -*- coding: utf-8 -*-
+
+.. _internationalization:
+
+Internationalization
+---------------------
+
+Cubicweb fully supports the internalization of its content and interface.
+
+Cubicweb's interface internationalization is based on the translation project `GNU gettext`_.
+
+.. _`GNU gettext`: http://www.gnu.org/software/gettext/
+
+Cubicweb' internalization involves two steps:
+
+* in your Python code and cubicweb-tal templates : mark translatable strings
+
+* in your instance : handle the translation catalog, edit translations
+
+String internationalization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+User defined string
+```````````````````
+
+In the Python code and cubicweb-tal templates translatable strings can be
+marked in one of the following ways :
+
+ * by using the *built-in* function `_` ::
+
+     class PrimaryView(EntityView):
+         """the full view of an non final entity"""
+         __regid__ = 'primary'
+         title = _('primary')
+
+  OR
+
+ * by using the equivalent request's method ::
+
+     class NoResultView(View):
+         """default view when no result has been found"""
+         __regid__ = 'noresult'
+
+         def call(self, **kwargs):
+             self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
+                 % self._cw._('No result matching query'))
+
+The goal of the *built-in* function `_` is only **to mark the
+translatable strings**, it will only return the string to translate
+itself, but not its translation (it's actually another name for the
+`unicode` builtin).
+
+In the other hand the request's method `self._cw._` is also meant to
+retrieve the proper translation of translation strings in the
+requested language.
+
+Finally you can also use the `__` attribute of request object to get a
+translation for a string *which should not itself added to the catalog*,
+usually in case where the actual msgid is created by string interpolation ::
+
+  self._cw.__('This %s' % etype)
+
+In this example ._cw.__` is used instead of ._cw._` so we don't have 'This %s' in
+messages catalogs.
+
+Translations in cubicweb-tal template can also be done with TAL tags
+`i18n:content` and `i18n:replace`.
+
+If you need to add messages on top of those that can be found in the source,
+you can create a file named `i18n/static-messages.pot`.
+
+You could put there messages not found in the python sources or
+overrides for some messages of used cubes.
+
+Generated string
+````````````````
+
+We do not need to mark the translation strings of entities/relations used by a
+particular instance's schema as they are generated automatically. String for
+various actions are also generated.
+
+For exemple the following schema ::
+
+  Class EntityA(EntityType):
+      relation_a2b = SubjectRelation('EntityB')
+
+  class EntityB(EntityType):
+      pass
+
+May generate the following message ::
+
+  add EntityA relation_a2b EntityB subject
+
+This message will be used in views of ``EntityA`` for creation of a new
+``EntityB`` with a preset relation ``relation_a2b`` between the current
+``EntityA`` and the new ``EntityB``. The opposite message ::
+
+  add EntityA relation_a2b EntityB object
+
+Is used for similar creation of an ``EntityA`` from a view of ``EntityB``. The
+title of they respective creation form will be ::
+
+  creating EntityB (EntityA %(linkto)s relation_a2b EntityB)
+
+  creating EntityA (EntityA relation_a2b %(linkto)s EntityA)
+
+In the translated string you can use ``%(linkto)s`` for reference to the source
+``entity``.
+
+Handling the translation catalog
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once the internationalization is done in your code, you need to populate and
+update the translation catalog. Cubicweb provides the following commands for this
+purpose:
+
+
+* `i18ncubicweb` updates Cubicweb framework's translation
+  catalogs. Unless you actually work on the framework itself, you
+  don't need to use this command.
+
+* `i18ncube` updates the translation catalogs of *one particular cube*
+  (or of all cubes). After this command is executed you must update
+  the translation files *.po* in the "i18n" directory of your
+  cube. This command will of course not remove existing translations
+  still in use. It will mark unused translation but not remove them.
+
+* `i18ninstance` recompiles the translation catalogs of *one particular
+  instance* (or of all instances) after the translation catalogs of
+  its cubes have been updated. This command is automatically
+  called every time you create or update your instance. The compiled
+  catalogs (*.mo*) are stored in the i18n/<lang>/LC_MESSAGES of
+  instance where `lang` is the language identifier ('en' or 'fr'
+  for exemple).
+
+
+Example
+```````
+
+You have added and/or modified some translation strings in your cube
+(after creating a new view or modifying the cube's schema for exemple).
+To update the translation catalogs you need to do:
+
+1. `cubicweb-ctl i18ncube <cube>`
+2. Edit the <cube>/i18n/xxx.po  files and add missing translations (empty `msgstr`)
+3. `hg ci -m "updated i18n catalogs"`
+4. `cubicweb-ctl i18ninstance <myinstance>`
+
+Editing po files
+~~~~~~~~~~~~~~~~
+
+Using a PO aware editor
+````````````````````````
+
+Many tools exist to help maintain .po (PO) files. Common editors or
+development environment provides modes for these. One can also find
+dedicated PO files editor, such as `poedit`_.
+
+.. _`poedit`:  http://www.poedit.net/
+
+While usage of such a tool is commendable, PO files are perfectly
+editable with a (unicode aware) plain text editor. It is also useful
+to know their structure for troubleshooting purposes.
+
+Structure of a PO file
+``````````````````````
+
+In this section, we selectively quote passages of the `GNU gettext`_
+manual chapter on PO files, available there::
+
+ http://www.gnu.org/software/hello/manual/gettext/PO-Files.html
+
+One PO file entry has the following schematic structure::
+
+     white-space
+     #  translator-comments
+     #. extracted-comments
+     #: reference...
+     #, flag...
+     #| msgid previous-untranslated-string
+     msgid untranslated-string
+     msgstr translated-string
+
+
+A simple entry can look like this::
+
+     #: lib/error.c:116
+     msgid "Unknown system error"
+     msgstr "Error desconegut del sistema"
+
+It is also possible to have entries with a context specifier. They
+look like this::
+
+     white-space
+     #  translator-comments
+     #. extracted-comments
+     #: reference...
+     #, flag...
+     #| msgctxt previous-context
+     #| msgid previous-untranslated-string
+     msgctxt context
+     msgid untranslated-string
+     msgstr translated-string
+
+
+The context serves to disambiguate messages with the same
+untranslated-string. It is possible to have several entries with the
+same untranslated-string in a PO file, provided that they each have a
+different context. Note that an empty context string and an absent
+msgctxt line do not mean the same thing.
+
+Contexts and CubicWeb
+`````````````````````
+
+CubicWeb PO files have both non-contextual and contextual msgids.
+
+Contextual entries are automatically used in some cases. For instance,
+entity.dc_type(), eschema.display_name(req) or display_name(etype,
+req, form, context) methods/function calls will use them.
+
+It is also possible to explicitly use the with _cw.pgettext(context,
+msgid).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/js.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,355 @@
+.. -*- coding: utf-8 -*-
+
+Javascript
+----------
+
+*CubicWeb* uses quite a bit of javascript in its user interface and
+ships with jquery (1.3.x) and parts of the jquery UI library, plus a
+number of homegrown files and also other third party libraries.
+
+All javascript files are stored in cubicweb/web/data/. There are
+around thirty js files there. In a cube it goes to data/.
+
+Obviously one does not want javascript pieces to be loaded all at
+once, hence the framework provides a number of mechanisms and
+conventions to deal with javascript resources.
+
+Conventions
+~~~~~~~~~~~
+
+It is good practice to name cube specific js files after the name of
+the cube, like this : 'cube.mycube.js', so as to avoid name clashes.
+
+.. XXX external_resources variable (which needs love)
+
+CubicWeb javascript API
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Javascript resources are typically loaded on demand, from views. The
+request object (available as self._cw from most application objects,
+for instance views and entities objects) has a few methods to do that:
+
+* `add_js(self, jsfiles, localfile=True)` which takes a sequence of
+  javascript files and writes proper entries into the HTML header
+  section. The localfile parameter allows to declare resources which
+  are not from web/data (for instance, residing on a content delivery
+  network).
+
+* `add_onload(self, jscode)` which adds one raw javascript code
+  snippet inline in the html headers. This is quite useful for setting
+  up early jQuery(document).ready(...) initialisations.
+
+CubicWeb javascript events
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* ``server-response``: this event is triggered on HTTP responses (both
+  standard and ajax). The two following extra parameters are passed
+  to callbacks :
+
+  - ``ajax``: a boolean that says if the reponse was issued by an
+    ajax request
+
+  - ``node``: the DOM node returned by the server in case of an
+    ajax request, otherwise the document itself for standard HTTP
+    requests.
+
+Important AJAX APIS
+~~~~~~~~~~~~~~~~~~~
+
+* `asyncRemoteExec` and `remoteExec` are the base building blocks for
+  doing arbitrary async (resp. sync) communications with the server
+
+* `reloadComponent` is a convenience function to replace a DOM node
+  with server supplied content coming from a specific registry (this
+  is quite handy to refresh the content of some boxes for instances)
+
+* `jQuery.fn.loadxhtml` is an important extension to jQuery which
+  allows proper loading and in-place DOM update of xhtml views. It is
+  suitably augmented to trigger necessary events, and process CubicWeb
+  specific elements such as the facet system, fckeditor, etc.
+
+
+A simple example with asyncRemoteExec
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the python side, we have to extend the BaseController class. The
+@jsonize decorator ensures that the `return value` of the method is
+encoded as JSON data. By construction, the JSonController inputs
+everything in JSON format.
+
+.. sourcecode: python
+
+    from cubicweb.web.views.basecontrollers import JSonController, jsonize
+
+    @monkeypatch(JSonController)
+    @jsonize
+    def js_say_hello(self, name):
+        return u'hello %s' % name
+
+In the javascript side, we do the asynchronous call. Notice how it
+creates a `deferred` object. Proper treatment of the return value or
+error handling has to be done through the addCallback and addErrback
+methods.
+
+.. sourcecode: javascript
+
+    function asyncHello(name) {
+        var deferred = asyncRemoteExec('say_hello', name);
+        deferred.addCallback(function (response) {
+            alert(response);
+        });
+        deferred.addErrback(function (error) {
+            alert('something fishy happened');
+        });
+     }
+
+     function syncHello(name) {
+         alert( remoteExec('say_hello', name) );
+     }
+
+Anatomy of a reloadComponent call
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`reloadComponent` allows to dynamically replace some DOM node with new
+elements. It has the following signature:
+
+* `compid` (mandatory) is the name of the component to be reloaded
+
+* `rql` (optional) will be used to generate a result set given as
+  argument to the selected component
+
+* `registry` (optional) defaults to 'components' but can be any other
+  valid registry name
+
+* `nodeid` (optional) defaults to compid + 'Component' but can be any
+  explicitly specified DOM node id
+
+* `extraargs` (optional) should be a dictionary of values that will be
+  given to the cell_call method of the component
+
+A simple reloadComponent example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The server side implementation of `reloadComponent` is the
+js_component method of the JSonController.
+
+The following function implements a two-steps method to delete a
+standard bookmark and refresh the UI, while keeping the UI responsive.
+
+.. sourcecode:: javascript
+
+    function removeBookmark(beid) {
+        d = asyncRemoteExec('delete_bookmark', beid);
+        d.addCallback(function(boxcontent) {
+	    reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
+            document.location.hash = '#header';
+            updateMessage(_("bookmark has been removed"));
+         });
+    }
+
+`reloadComponent` is called with the id of the bookmark box as
+argument, no rql expression (because the bookmarks display is actually
+independant of any dataset context), a reference to the 'boxes'
+registry (which hosts all left, right and contextual boxes) and
+finally an explicit 'bookmarks_box' nodeid argument that stipulates
+the target DOM node.
+
+Anatomy of a loadxhtml call
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`jQuery.fn.loadxhtml` is an important extension to jQuery which allows
+proper loading and in-place DOM update of xhtml views. The existing
+`jQuery.load`_ function does not handle xhtml, hence the addition. The
+API of loadxhtml is roughly similar to that of `jQuery.load`_.
+
+.. _`jQuery.load`: http://api.jquery.com/load/
+
+
+* `url` (mandatory) should be a complete url (typically referencing
+  the JSonController, but this is not strictly mandatory)
+
+* `data` (optional) is a dictionary of values given to the
+  controller specified through an `url` argument; some keys may have a
+  special meaning depending on the choosen controller (such as `fname`
+  for the JSonController); the `callback` key, if present, must refer
+  to a function to be called at the end of loadxhtml (more on this
+  below)
+
+* `reqtype` (optional) specifies the request method to be used (get or
+  post); if the argument is 'post', then the post method is used,
+  otherwise the get method is used
+
+* `mode` (optional) is one of `replace` (the default) which means the
+  loaded node will replace the current node content, `swap` to replace
+  the current node with the loaded node, and `append` which will
+  append the loaded node to the current node content
+
+About the `callback` option:
+
+* it is called with two parameters: the current node, and a list
+  containing the loaded (and post-processed node)
+
+* whenever is returns another function, this function is called in
+  turn with the same parameters as above
+
+This mechanism allows callback chaining.
+
+
+A simple example with loadxhtml
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here we are concerned with the retrieval of a specific view to be
+injected in the live DOM. The view will be of course selected
+server-side using an entity eid provided by the client side.
+
+.. sourcecode:: python
+
+    from cubicweb import typed_eid
+    from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
+
+    @monkeypatch(JSonController)
+    @xhtmlize
+    def js_frob_status(self, eid, frobname):
+        entity = self._cw.entity_from_eid(typed_eid(eid))
+        return entity.view('frob', name=frobname)
+
+.. sourcecode:: javascript
+
+    function update_some_div(divid, eid, frobname) {
+        var params = {fname:'frob_status', eid: eid, frobname:frobname};
+        jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
+     }
+
+In this example, the url argument is the base json url of a cube
+instance (it should contain something like
+`http://myinstance/json?`). The actual JSonController method name is
+encoded in the `params` dictionary using the `fname` key.
+
+A more real-life example from CubicWeb
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A frequent use case of Web 2 applications is the delayed (or
+on-demand) loading of pieces of the DOM. This is typically achieved
+using some preparation of the initial DOM nodes, jQuery event handling
+and proper use of loadxhtml.
+
+We present here a skeletal version of the mecanism used in CubicWeb
+and available in web/views/tabs.py, in the `LazyViewMixin` class.
+
+.. sourcecode:: python
+
+    def lazyview(self, vid, rql=None):
+        """ a lazy version of wview """
+        w = self.w
+        self._cw.add_js('cubicweb.lazy.js')
+        urlparams = {'vid' : vid, 'fname' : 'view'}
+        if rql is not None:
+            urlparams['rql'] = rql
+        w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
+            vid, xml_escape(self._cw.build_url('json', **urlparams))))
+        w(u'</div>')
+        self._cw.add_onload(u"""
+            jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
+                   load_now('#lazy-%(vid)s');});"""
+            % {'event': 'load_%s' % vid, 'vid': vid})
+
+This creates a `div` with a specific event associated to it.
+
+The full version deals with:
+
+* optional parameters such as an entity eid, an rset
+
+* the ability to further reload the fragment
+
+* the ability to display a spinning wheel while the fragment is still
+  not loaded
+
+* handling of browsers that do not support ajax (search engines,
+  text-based browsers such as lynx, etc.)
+
+The javascript side is quite simple, due to loadxhtml awesomeness.
+
+.. sourcecode:: javascript
+
+    function load_now(eltsel) {
+        var lazydiv = jQuery(eltsel);
+        lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
+    }
+
+This is all significantly different of the previous `simple example`
+(albeit this example actually comes from real-life code).
+
+Notice how the `cubicweb:loadurl` is used to convey the url
+information. The base of this url is similar to the global javascript
+JSON_BASE_URL. According to the pattern described earlier,
+the `fname` parameter refers to the standard `js_view` method of the
+JSonController. This method renders an arbitrary view provided a view
+id (or `vid`) is provided, and most likely an rql expression yielding
+a result set against which a proper view instance will be selected.
+
+The `cubicweb:loadurl` is one of the 29 attributes extensions to XHTML
+in a specific cubicweb namespace. It is a means to pass information
+without breaking HTML nor XHTML compliance and without resorting to
+ungodly hacks.
+
+Given all this, it is easy to add a small nevertheless useful feature
+to force the loading of a lazy view (for instance, a very
+computation-intensive web page could be scinded into one fast-loading
+part and a delayed part).
+
+On the server side, a simple call to a javascript function is
+sufficient.
+
+.. sourcecode:: python
+
+    def forceview(self, vid):
+        """trigger an event that will force immediate loading of the view
+        on dom readyness
+        """
+        self._cw.add_onload("trigger_load('%s');" % vid)
+
+The browser-side definition follows.
+
+.. sourcecode:: javascript
+
+    function trigger_load(divid) {
+        jQuery('#lazy-' + divd).trigger('load_' + divid);
+    }
+
+
+
+
+.. XXX reloadComponent
+.. XXX userCallback / user_callback
+
+Javascript library: overview
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* jquery.* : jquery and jquery UI library
+
+* cubicweb.ajax.js : concentrates all ajax related facilities (it
+  extends jQuery with the loahxhtml function, provides a handfull of
+  high-level ajaxy operations like asyncRemoteExec, reloadComponent,
+  replacePageChunk, getDomFromResponse)
+
+* cubicweb.python.js : adds a number of practical extension to stdanrd
+  javascript objects (on Date, Array, String, some list and dictionary
+  operations), and a pythonesque way to build classes. Defines a
+  CubicWeb namespace.
+
+* cubicweb.htmlhelpers.js : a small bag of convenience functions used
+  in various other cubicweb javascript resources (baseuri, progress
+  cursor handling, popup login box, html2dom function, etc.)
+
+* cubicweb.widgets.js : provides a widget namespace and constructors
+  and helpers for various widgets (mainly facets and timeline)
+
+* cubicweb.edition.js : used by edition forms
+
+* cubicweb.preferences.js : used by the preference form
+
+* cubicweb.facets.js : used by the facets mechanism
+
+There is also javascript support for massmailing, gmap (google maps),
+fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over
+AppEngine), flot (charts drawing), tabs and bookmarks.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/property.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,13 @@
+The property mecanism
+---------------------
+
+.. XXX CWProperty and co
+
+
+Property API
+~~~~~~~~~~~~
+.. XXX feed me
+
+Registering and using your own property
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. XXX feed me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/publisher.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,65 @@
+.. _publisher:
+
+Publisher
+---------
+
+What happens when an HTTP request is issued ?
+
+The story begins with the ``CubicWebPublisher.main_publish``
+method. We do not get upper in the bootstrap process because it is
+dependant on the used HTTP library. With `twisted`_ however,
+``cubicweb.etwist.server.CubicWebRootResource.render_request`` is the
+real entry point.
+
+.. _`twisted`: http://twistedmatrix.com/trac/
+
+What main_publish does:
+
+* get a controller id and a result set from the path (this is actually
+  delegated to the `urlpublisher` component)
+
+* the controller is then selected (if not, this is considered an
+  authorization failure and signaled as such) and called
+
+* then either a proper result is returned, in which case the
+  request/connection object issues a ``commit`` and returns the result
+
+* or error handling must happen:
+
+  * ``ValidationErrors`` pop up there and may lead to a redirect to a
+    previously arranged url or standard error handling applies
+  * an HTTP 500 error (`Internal Server Error`) is issued
+
+
+Now, let's turn to the controller. There are many of them in
+:mod:`cubicweb.web.views.basecontrollers`. We can just follow the
+default `view` controller that is selected on a `view` path. See the
+:ref:`controllers` chapter for more information on controllers.
+
+The `View` controller's entry point is the `publish` method. It does
+the following:
+
+* compute the `main` view to be applied, using either the given result
+  set or building one from a user provided rql string (`rql` and `vid`
+  can be forced from the url GET parameters), that is:
+
+    * compute the `vid` using the result set and the schema (see
+      `cubicweb.web.views.vid_from_rset`)
+    * handle all error cases that could happen in this phase
+
+* do some cache management chores
+
+* select a main template (typically `TheMainTemplate`, see chapter
+  :ref:`templates`)
+
+* call it with the result set and the computed view.
+
+What happens next actually depends on the template and the view, but
+in general this is the rendering phase.
+
+
+CubicWebPublisher API
+`````````````````````
+
+.. autoclass:: cubicweb.web.application.CubicWebPublisher
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/request.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,79 @@
+The `Request` class (`cubicweb.web`)
+------------------------------------
+
+Overview
+````````
+
+A request instance is created when an HTTP request is sent to the web server.
+It contains informations such as form parameters, user authenticated, etc.
+
+**Globally, a request represents a user query, either through HTTP or not
+(we also talk about RQL queries on the server side for example).**
+
+An instance of `Request` has the following attributes:
+
+* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
+  user
+* `form`, dictionary containing the values of a web form
+* `encoding`, character encoding to use in the response
+
+But also:
+
+* `Session data handling`
+
+  * `session_data()`, returns a dictionary containing all the session data
+  * `get_session_data(key, default=None)`, returns a value associated to the given
+    key or the value `default` if the key is not defined
+  * `set_session_data(key, value)`, assign a value to a key
+  * `del_session_data(key)`,  suppress the value associated to a key
+
+*  `Cookies handling`
+
+  * `get_cookie()`, returns a dictionary containing the value of the header
+    HTTP 'Cookie'
+  * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`,
+    with a minimal 5 minutes length of duration by default (`maxage` = None
+    returns a *session* cookie which will expire when the user closes the browser
+    window)
+  * `remove_cookie(cookie, key)`, forces a value to expire
+
+* `URL handling`
+
+  * `url()`, returns the full URL of the HTTP request
+  * `base_url()`, returns the root URL of the web application
+  * `relative_path()`, returns the relative path of the request
+
+* `And more...`
+
+  * `set_content_type(content_type, filename=None)`, adds the header HTTP
+    'Content-Type'
+  * `get_header(header)`, returns the value associated to an arbitrary header
+    of the HTTP request
+  * `set_header(header, value)`, adds an arbitrary header in the response
+  * `cursor()` returns a RQL cursor on the session
+  * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()``
+  * `property_value(key)`, properties management (`CWProperty`)
+  * dictionary `data` to store data to share informations between components
+    *while a request is executed*
+
+Please note that this class is abstract and that a concrete implementation
+will be provided by the *frontend* web used (in particular *twisted* as of
+today). For the views or others that are executed on the server side,
+most of the interface of `Request` is defined in the session associated
+to the client.
+
+API
+```
+
+The elements we gave in overview for above are built in three layers,
+from ``cubicweb.req.RequestSessionBase``, ``cubicweb.dbapi.DBAPIRequest`` and
+``cubicweb.web.CubicWebRequestBase``.
+
+.. autoclass:: cubicweb.req.RequestSessionBase
+   :members:
+
+.. autoclass:: cubicweb.dbapi.DBAPIRequest
+   :members:
+
+.. autoclass:: cubicweb.web.request.CubicWebRequestBase
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/rtags.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,21 @@
+Configuring the user interface
+------------------------------
+
+.. _relation_tags:
+
+Relation tags
+~~~~~~~~~~~~~
+.. automodule:: cubicweb.rtags
+
+.. _uicfg:
+
+The ``uicfg`` module
+~~~~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ The part of uicfg that deals with primary views is in the
+ :ref:`primary_view_configuration` chapter.
+
+.. automodule:: cubicweb.web.uicfg
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/basetemplates.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,115 @@
+.. -*- coding: utf-8 -*-
+
+.. |cubicweb| replace:: *CubicWeb*
+
+.. _templates:
+
+Templates
+=========
+
+Templates are the entry point for the |cubicweb| view system. As seen
+in :ref:`views_base_class`, there are two kinds of views: the
+templatable and non-templatable.
+
+Non-templatable views are standalone. They are responsible for all the
+details such as setting a proper content type (or mime type), the
+proper document headers, namespaces, etc. Examples are pure xml views
+such as RSS or Semantic Web views (`SIOC`_, `DOAP`_, `FOAF`_, `Linked
+Data`_, etc.).
+
+.. _`SIOC`: http://sioc-project.org/
+.. _`DOAP`: http://trac.usefulinc.com/doap
+.. _`FOAF`: http://www.foaf-project.org/
+.. _`Linked Data`: http://linkeddata.org/
+
+Templatable views are not concerned with such pesky details. They
+leave it to the template. Conversely, the template's main job is to:
+
+* set up the proper document header and content type
+* define the general layout of a document
+* invoke adequate views in the various sections of the document
+
+
+Look at :mod:`cubicweb.web.views.basetemplates` and you will find the
+base templates used to generate (X)HTML for your application. The most
+important template there is `TheMainTemplate`.
+
+.. _the_main_template_layout:
+
+TheMainTemplate
+---------------
+
+.. _the_main_template_sections:
+
+Layout and sections
+```````````````````
+
+A page is composed as indicated on the schema below :
+
+.. image:: ../../images/main_template.png
+
+The sections dispatches specific views:
+
+* `header`: the rendering of the header is delegated to the
+  `htmlheader` view, whose default implementation can be found in
+  ``basetemplates.py`` and which does the following things:
+
+    * inject the favicon if there is one
+    * inject the global style sheets and javascript resources
+    * call and display a link to an rss component if there is one available
+
+  it also sets up the page title, and fills the actual
+  `header` section with top-level components, using the `header` view, which:
+
+    * tries to display a logo, the name of the application and the `breadcrumbs`
+    * provides a login status area
+    * provides a login box (hiden by default)
+
+* `left column`: this is filled with all selectable boxes matching the
+  `left` context (there is also a right column but nowadays it is
+  seldom used due to bad usability)
+
+* `contentcol`: this is the central column; it is filled with:
+
+    * the `rqlinput` view (hidden by default)
+    * the `applmessages` component
+    * the `contentheader` view which in turns dispatches all available
+      content navigation components having the `navtop` context (this
+      is used to navigate through entities implementing the IPrevNext
+      interface)
+    * the view that was given as input to the template's `call`
+      method, also dealing with pagination concerns
+    * the `contentfooter`
+
+* `footer`: adds all footer actions
+
+.. note::
+
+  How and why a view object is given to the main template is explained
+  in the :ref:`publisher` chapter.
+
+Class attributes
+````````````````
+
+We can also control certain aspects of the main template thanks to the following
+forms parameters:
+
+* `__notemplate`, if present (whatever the value assigned), only the content view
+  is returned
+* `__force_display`, if present and its value is not null, no navigation
+  whatever the number of entities to display
+* `__method`, if the result set to render contains only one entity and this
+  parameter is set, it refers to a method to call on the entity by passing it
+  the dictionary of the forms parameters, before going the classic way (through
+  step 1 and 2 described juste above)
+
+Other templates
+---------------
+
+Other standard templates include:
+
+* `login` and `logout`
+
+* `error-template` specializes TheMainTemplate to do proper end-user
+  output if an error occurs during the computation of TheMainTemplate
+  (it is a fallback view).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/baseviews.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,84 @@
+.. -*- coding: utf-8 -*-
+
+Base views
+----------
+
+*CubicWeb* provides a lot of standard views, that can be found in
+ :mod:`cubicweb.web.views` and :mod:`cubicweb.web.views.baseviews`.
+
+A certain number of views are used to build the web interface, which
+apply to one or more entities. Their identifier is what distinguish
+them from each others and the main ones are:
+
+HTML views
+~~~~~~~~~~
+
+Special views
+`````````````
+
+*noresult*
+    This view is the default view used when no result has been found
+    (e.g. empty result set).
+
+*final*
+    Display the value of a cell without trasnformation (in case of a non final
+    entity, we see the eid). Applicable on any result set.
+
+.. note::
+
+   `final` entities are merely attributes.
+
+*null*
+    This view is the default view used when nothing needs to be rendered.
+    It is always applicable.
+
+Entity views
+````````````
+
+*incontext, outofcontext*
+    Those are used to display a link to an entity, depending on the
+    entity having to be displayed in or out of context
+    (of another entity).  By default it respectively produces the
+    result of `textincontext` and `textoutofcontext` wrapped in a link
+    leading to the primary view of the entity.
+
+*oneline*
+    This view is used when we can't tell if the entity should be considered as
+    displayed in or out of context.  By default it produces the result of `text`
+    in a link leading to the primary view of the entity.
+
+List
+`````
+
+*list*
+    This view displays a list of entities by creating a HTML list (`<ul>`)
+    and call the view `listitem` for each entity of the result set.
+
+*listitem*
+    This view redirects by default to the `outofcontext` view.
+
+*sameetypelist*
+    This view displays a list of entities of the same type, in HTML section (`<div>`)
+    and call the view `sameetypelistitem` for each entity of the result set.
+
+*sameetypelistitem*
+    This view redirects by default to the `listitem` view.
+
+*csv*
+    This view applies to entity groups, which are individually
+    displayed using the `incontext` view. It displays each entity as a
+    coma separated list. It is NOT related to the well-known text file
+    format.
+
+Text entity views
+~~~~~~~~~~~~~~~~~
+
+*text*
+    This is the simplest text view for an entity. By default it returns the
+    result of the `.dc_title` method, which is cut to fit the
+    `navigation.short-line-size` property if necessary.
+
+*textincontext, textoutofcontext*
+    Similar to the `text` view, but called when an entity is considered out or
+    in context. By default it returns respectively the result of the
+    methods `.dc_title` and `.dc_long_title` of the entity.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/boxes.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,36 @@
+Boxes
+-----
+
+(:mod:`cubicweb.web.views.boxes`)
+
+*sidebox*
+  This view displays usually a side box of some related entities
+  in a primary view.
+
+The action box
+~~~~~~~~~~~~~~~
+
+The ``add_related`` is an automatic menu in the action box that allows to create
+an entity automatically related to the initial entity (context in
+which the box is displayed). By default, the links generated in this
+box are computed from the schema properties of the displayed entity,
+but it is possible to explicitly specify them thanks to the
+`cubicweb.web.uicfg.rmode` *relation tag*:
+
+* `link`, indicates that a relation is in general created pointing
+  to an existing entity and that we should not to display a link
+  for this relation
+
+* `create`, indicates that a relation is in general created pointing
+  to new entities and that we should display a link to create a new
+  entity and link to it automatically
+
+
+If necessary, it is possible to overwrite the method
+`relation_mode(rtype, targettype, x='subject')` to dynamically
+compute a relation creation category.
+
+Please note that if at least one action belongs to the `addrelated` category,
+the automatic behavior is desactivated in favor of an explicit behavior
+(e.g. display of `addrelated` category actions only).
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/breadcrumbs.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,58 @@
+Breadcrumbs
+-----------
+
+Breadcrumbs are a navigation component to help the user locate himself
+along a path of entities.
+
+Display
+~~~~~~~
+
+Breadcrumbs are displayed by default in the header section (see
+:ref:`the_main_template_sections`).  With the default main
+template, the header section is composed by the logo, the application
+name, breadcrumbs and, at the most right, the login box. Breadcrumbs
+are displayed just next to the application name, thus breadcrumbs
+begin with a separator.
+
+Here is the header section of the CubicWeb's forge:
+
+.. image:: ../../images/breadcrumbs_header.png
+
+There are three breadcrumbs components defined in
+:mod:`cubicweb.web.views.ibreadcrumbs`:
+
+- `BreadCrumbEntityVComponent`: displayed for a result set with one line
+  if the entity implements the ``IBreadCrumbs`` interface.
+- `BreadCrumbETypeVComponent`: displayed for a result set with more than
+  one line, but with all entities of the same type which implement the
+  ``IBreadCrumbs`` interface.
+- `BreadCrumbAnyRSetVComponent`: displayed for any other result set.
+
+Building breadcrumbs
+~~~~~~~~~~~~~~~~~~~~
+
+The ``IBreadCrumbs`` interface is defined in the
+:mod:`cubicweb.interfaces` module. It specifies that an entity which
+implements this interface must have a ``breadcrumbs`` method.
+
+.. note::
+
+   Redefining the breadcrumbs is the hammer way to do it. Another way
+   is to define the `parent` method on an entity (as defined in the
+   `ITree` interface). If available, it will be used to compute
+   breadcrumbs.
+
+Here is the API of the ``breadcrumbs`` method:
+
+.. automethod:: cubicweb.interfaces.IBreadCrumbs.breadcrumbs
+
+If the breadcrumbs method return a list of entities, the
+``cubicweb.web.views.ibreadcrumbs.BreadCrumbView`` is used to display
+the elements.
+
+By default, for any entity, if recurs=True, breadcrumbs method returns
+a list of entities, else a list of a simple string.
+
+In order to see a hierarchical breadcrumbs, entities must have a
+``parent`` method which returns the parent entity. By default this
+method doesn't exist on entity, given that it can not be guessed.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/editforms.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,6 @@
+Standard forms
+--------------
+
+ (:mod:`cubicweb.web.views.editforms`)
+
+XXX feed me
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/embedding.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Embedding external pages
+------------------------
+
+(:mod:`cubicweb.web.views.embedding`)
+
+including external content
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/idownloadable.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,5 @@
+The 'download' view
+-------------------
+
+(:mod:`cubicweb.web.views.idownloadable`)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,30 @@
+The View system
+===============
+
+This chapter aims to describe the concept of a `view` used all along
+the development of a web application and how it has been implemented
+in |cubicweb|.
+
+
+.. toctree::
+   :maxdepth: 3
+
+   views
+   basetemplates
+   primary
+   baseviews
+   startup
+   boxes
+   table
+   xmlrss
+..   editforms
+
+.. toctree::
+   :maxdepth: 3
+
+   urlpublish
+   breadcrumbs
+..   wdoc
+..   embedding
+..   idownloadable
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/primary.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,314 @@
+.. _primary_view:
+
+The Primary View
+-----------------
+
+By default, *CubicWeb* provides a view that fits every available
+entity type. This is the first view you might be interested in
+modifying. It is also one of the richest and most complex.
+
+It is automatically selected on a one line result set containing an
+entity.
+
+This view is supposed to render a maximum of informations about the
+entity.
+
+It lives in the :mod:`cubicweb.web.views.primary` module.
+
+.. _primary_view_layout:
+
+Layout
+``````
+
+The primary view has the following layout.
+
+.. image:: ../../images/primaryview_template.png
+
+.. _primary_view_configuration:
+
+Primary view configuration
+``````````````````````````
+
+If you want to customize the primary view of an entity, overriding the primary
+view class may not be necessary. For simple adjustments (attributes or relations
+display locations and styles), a much simpler way is to use uicfg.
+
+Attributes/relations display location
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the primary view, there are 3 sections where attributes and
+relations can be displayed (represented in pink in the image above):
+
+* attributes
+* relations
+* sideboxes
+
+**Attributes** can only be displayed in the attributes section (default
+  behavior). They can also be hidden.
+
+For instance, to hide the ``title`` attribute of the ``Blog`` entity:
+
+.. sourcecode:: python
+
+   from cubicweb.web import uicfg
+   uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden')
+
+**Relations** can be either displayed in one of the three sections or hidden.
+
+For relations, there are two methods:
+
+* ``tag_object_of`` for modifying the primary view of the object
+* ``tag_subject_of`` for modifying the primary view of the subject
+
+These two methods take two arguments:
+
+* a triplet ``(subject, relation_name, object)``, where subject or object can be replaced with ``'*'``
+* the section name or ``hidden``
+
+.. sourcecode:: python
+
+   pv_section = uicfg.primaryview_section
+   # hide every relation `entry_of` in the `Blog` primary view
+   pv_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden')
+
+   # display `entry_of` relations in the `relations`
+   # section in the `BlogEntry` primary view
+   pv_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations')
+
+
+Display content
+^^^^^^^^^^^^^^^
+
+You can use ``primaryview_display_ctrl`` to customize the display of attributes
+or relations. Values of ``primaryview_display_ctrl`` are dictionaries.
+
+
+Common keys for attributes and relations are:
+
+* ``vid``: specifies the regid of the view for displaying the attribute or the relation.
+
+  If ``vid`` is not specified, the default value depends on the section:
+    * ``attributes`` section: 'reledit' view
+    * ``relations`` section: 'autolimited' view
+    * ``sideboxes`` section: 'sidebox' view
+
+* ``order``: int used to control order within a section. When not specified,
+  automatically set according to order in which tags are added.
+
+.. sourcecode:: python
+
+   # let us remind the schema of a blog entry
+   class BlogEntry(EntityType):
+       title = String(required=True, fulltextindexed=True, maxsize=256)
+       publish_date = Date(default='TODAY')
+       content = String(required=True, fulltextindexed=True)
+       entry_of = SubjectRelation('Blog', cardinality='?*')
+
+   # now, we want to show attributes
+   # with an order different from that in the schema definition
+   view_ctrl = uicfg.primaryview_display_ctrl
+   for index, attr in enumerate('title', 'content', 'publish_date'):
+       view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index})
+
+Keys for relations only:
+
+* ``label``: label for the relations section or side box
+
+* ``showlabel``: boolean telling whether the label is displayed
+
+* ``limit``: boolean telling if the results should be limited. If so, a link to all results is displayed
+
+* ``filter``: callback taking the related result set as argument and returning it filtered
+
+.. sourcecode:: python
+
+   pv_section = uicfg.primaryview_section
+   # in `CWUser` primary view, display `created_by`
+   # relations in relations section
+   pv_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations')
+
+   # display this relation as a list, sets the label,
+   # limit the number of results and filters on comments
+   def filter_comment(rset):
+       return rset.filtered_rset(lambda x: x.e_schema == 'Comment')
+   pv_ctrl = uicfg.primaryview_display_ctrl
+   pv_ctrl.tag_object_of(('*', 'created_by', 'CWUser'),
+                         {'vid': 'list', 'label': _('latest comment(s):'),
+                          'limit': True,
+                          'filter': filter_comment})
+
+.. warning:: with the ``primaryview_display_ctrl`` rtag, the subject or the
+   object of the relation is ignored for respectively ``tag_object_of`` or
+   ``tag_subject_of``. To avoid warnings during execution, they should be set to
+   ``'*'``.
+
+Rendering methods and attributes
+````````````````````````````````
+
+The basic layout of a primary view is as in the
+:ref:`primary_view_layout` section. This layout is actually drawn by
+the `render_entity` method.
+
+The methods you may want to modify while customizing a ``PrimaryView``
+are:
+
+*render_entity_title(self, entity)*
+    Renders the entity title using the ``def dc_title(self)`` method.
+
+*render_entity_metadata(self, entity)*
+    Renders the entity metadata by calling the ``metadata`` view on the
+    entity. This generic view is in cubicweb.views.baseviews.
+
+*render_entity_attributes(self, entity)*
+    Renders all the attribute of an entity with the exception of
+    attribute of type `Password` and `Bytes`. The skip_none class
+    attribute controls the display of None valued attributes.
+
+*render_entity_relations(self, entity)*
+    Renders all the relations of the entity in the main section of the page.
+
+*render_side_boxes(self, entity, boxes)*
+    Renders relations of the entity in a side box.
+
+The placement of relations in the relations section or in side boxes
+can be controlled through the :ref:`primary_view_configuration` mechanism.
+
+*content_navigation_components(self, context)*
+    This method is applicable only for entity type implementing the interface
+    `IPrevNext`. This interface is for entities which can be linked to a previous
+    and/or next entity. This method will render the navigation links between
+    entities of this type, either at the top or at the bottom of the page
+    given the context (navcontent{top|bottom}).
+
+Also, please note that by setting the following attributes in your
+subclass, you can already customize some of the rendering:
+
+*show_attr_label*
+    Renders the attribute label next to the attribute value if set to True.
+    Otherwise, does only display the attribute value.
+
+*show_rel_label*
+    Renders the relation label next to the relation value if set to True.
+    Otherwise, does only display the relation value.
+
+*skip_none*
+    Does not render an attribute value that is None if set to True.
+
+*main_related_section*
+    Renders the relations of the entity if set to True.
+
+A good practice is for you to identify the content of your entity type for which
+the default rendering does not answer your need so that you can focus on the specific
+method (from the list above) that needs to be modified. We do not advise you to
+overwrite ``render_entity`` unless you want a completely different layout.
+
+Example of customization and creation
+`````````````````````````````````````
+
+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``.
+
+.. sourcecode:: python
+
+   from cubicweb.selectors import implements
+   from cubicweb.web.views.primary import Primaryview
+
+   class BlogEntryPrimaryView(PrimaryView):
+     __select__ = PrimaryView.__select__ & implements('BlogEntry')
+
+       def render_entity_attributes(self, entity):
+           self.w(u'<p>published on %s</p>' %
+                  entity.publish_date.strftime('%Y-%m-%d'))
+           super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
+
+
+The above source code defines a new primary view for
+``BlogEntry``. The `id` 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
+with its own specific criterion.
+
+The view method ``self.w()`` is used to output data. Here `lines
+08-09` output HTML for the publication date of the entry.
+
+.. image:: ../../images/lax-book_09-new-view-blogentry_en.png
+   :alt: blog entries now look much nicer
+
+Let us now improve the primary view of a blog
+
+.. sourcecode:: python
+
+ from logilab.mtconverter import xml_escape
+ from cubicweb.selectors import implements, one_line_rset
+ from cubicweb.web.views.primary import Primaryview
+
+ class BlogPrimaryView(PrimaryView):
+     __regid__ = 'primary'
+     __select__ = PrimaryView.__select__ & implements('Blog')
+     rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s'
+
+     def render_entity_relations(self, entity):
+         rset = self._cw.execute(self.rql, {'b' : entity.eid})
+         for entry in rset.entities():
+             self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
+
+ class BlogEntryInBlogView(EntityView):
+     __regid__ = 'inblogcontext'
+     __select__ = implements('BlogEntry')
+
+     def cell_call(self, row, col):
+         entity = self.cw_rset.get_entity(row, col)
+         self.w(u'<a href="%s" title="%s">%s</a>' %
+                entity.absolute_url(),
+                xml_escape(entity.content[:50]),
+                xml_escape(entity.description))
+
+This happens in two places. First we override the
+render_entity_relations method of a Blog's primary view. Here we want
+to display our blog entries in a custom way.
+
+At `line 10`, a simple request is made to build a result set with all
+the entities linked to the current ``Blog`` entity by the relationship
+``entry_of``. The part of the framework handling the request knows
+about the schema and infers that such entities have to be of the
+``BlogEntry`` kind and retrieves them (in the prescribed publish_date
+order).
+
+The request returns a selection of data called a result set. Result
+set objects have an .entities() method returning a generator on
+requested entities (going transparently through the `ORM` layer).
+
+At `line 13` the view 'inblogcontext' is applied to each blog entry to
+output HTML. (Note that the 'inblogcontext' view is not defined
+whatsoever in *CubicWeb*. You are absolutely free to define whole view
+families.) We juste arrange to wrap each blogentry output in a 'p'
+html element.
+
+Next, we define the 'inblogcontext' view. This is NOT a primary view,
+with its well-defined sections (title, metadata, attribtues,
+relations/boxes). All a basic view has to define is cell_call.
+
+Since views are applied to result sets which can be tables of data, we
+have to recover the entity from its (row,col)-coordinates (`line
+20`). Then we can spit some HTML.
+
+.. warning::
+
+  Be careful: all strings manipulated in *CubicWeb* are actually
+  unicode strings. While web browsers are usually tolerant to
+  incoherent encodings they are being served, we should not abuse
+  it. Hence we have to properly escape our data. The xml_escape()
+  function has to be used to safely fill (X)HTML elements from Python
+  unicode strings.
+
+Assuming we added entries to the blog titled `MyLife`, displaying it
+now allows to read its description and all its entries.
+
+.. image:: ../../images/lax-book_10-blog-with-two-entries_en.png
+   :alt: a blog and all its entries
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/startup.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,15 @@
+Startup views
+-------------
+
+(:mod:`cubicweb.web.views.startup`)
+
+The usual selectors are no_rset or yes. These views don't apply to a
+result set.
+
+*index*
+    This view defines the home page of your application. It does not require
+    a result set to apply to.
+
+*schema*
+    A view dedicated to the display of the schema of the instance
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/table.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,19 @@
+Table view
+----------
+
+(:mod:`cubicweb.web.views.tableview`)
+
+*table*
+    Creates a HTML table (`<table>`) and call the view `cell` for each cell of
+    the result set. Applicable on any result set.
+
+*cell*
+    By default redirects to the `final` view if this is a final entity or
+    `outofcontext` view otherwise
+
+
+API
+```
+
+.. autoclass:: cubicweb.web.views.tableview.TableView
+   :members:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/urlpublish.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,73 @@
+.. -*- coding: utf-8 -*-
+
+URL publishing
+--------------
+
+(:mod:`cubicweb.web.views.urlpublishing`)
+
+.. automodule:: cubicweb.web.views.urlpublishing
+
+.. autoclass:: cubicweb.web.views.urlpublishing.URLPublisherComponent
+   :members:
+
+URL rewriting
+-------------
+
+(:mod:`cubicweb.web.views.urlrewrite`)
+
+.. autoclass:: cubicweb.web.views.urlrewrite.URLRewriter
+   :members:
+
+.. autoclass:: cubicweb.web.views.urlrewrite.SimpleReqRewriter
+   :members:
+
+.. autoclass:: cubicweb.web.views.urlrewrite.SchemaBasedRewriter
+   :members:
+
+
+``SimpleReqRewriter`` is enough for a certain number of simple cases. If it is not sufficient, ``SchemaBasedRewriter`` allows to do more elaborate things.
+
+Here is an example of ``SimpleReqRewriter`` usage with plain string:
+
+.. sourcecode:: python
+
+   from cubicweb.web.views.urlrewrite import SimpleReqRewriter
+   class TrackerSimpleReqRewriter(SimpleReqRewriter):
+       rules = [
+        ('/versions', dict(vid='versionsinfo')),
+        ]
+
+When the url is `<base_url>/versions`, the view with the __regid__ `versionsinfo` is displayed.
+
+Here is an example of ``SimpleReqRewriter`` usage with regular expressions:
+
+.. sourcecode:: python
+
+    from cubicweb.web.views.urlrewrite import (
+        SimpleReqRewriter, rgx)
+
+    class BlogReqRewriter(SimpleReqRewriter):
+        rules = [
+            (rgx('/blogentry/([a-z_]+)\.rss'),
+             dict(rql=('Any X ORDERBY CD DESC LIMIT 20 WHERE X is BlogEntry,'
+                       'X creation_date CD, X created_by U, '
+                       'U login "%(user)s"'
+                       % {'user': r'\1'}, vid='rss'))),
+            ]
+
+When a url matches the regular expression, the view with the __regid__
+`rss` which match the result set is displayed.
+
+Here is an example of ``SchemaBasedRewriter`` usage:
+
+.. sourcecode:: python
+
+    from cubicweb.web.views.urlrewrite import (
+        SchemaBasedRewriter, rgx, build_rset)
+
+    class TrackerURLRewriter(SchemaBasedRewriter):
+        rules = [
+            (rgx('/project/([^/]+)/([^/]+)/tests'),
+             build_rset(rql='Version X WHERE X version_of P, P name %(project)s, X num %(num)s',
+                        rgxgroups=[('project', 1), ('num', 2)], vid='versiontests')),
+            ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/views.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,142 @@
+
+.. _Views:
+
+Principles
+----------
+
+We'll start with a description of the interface providing a basic
+understanding of the available classes and methods, then detail the
+view selection principle.
+
+A `View` is an object responsible for the rendering of data from the
+model into an end-user consummable form. They typically churn out an
+XHTML stream, but there are views concerned with email other non-html
+outputs.
+
+.. _views_base_class:
+
+Discovering possible views
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is possible to configure the web user interface to have a left box
+showing all the views than can be applied to the current result set.
+
+To enable this, click on your login at the top right corner. Chose
+"user preferences", then "boxes", then "possible views box" and check
+"visible = yes" before validating your changes.
+
+The views listed there we either not selected because of a lower
+score, or they were deliberately excluded by the main template logic.
+
+
+Basic class for views
+~~~~~~~~~~~~~~~~~~~~~
+
+Class `View` (`cubicweb.view`)
+```````````````````````````````
+
+This class is an abstraction of a view class, used as a base class for
+every renderable object such as views, templates and other user
+interface components.
+
+A `View` is instantiated to render a result set or part of a result
+set. `View` subclasses may be parametrized using the following class
+attributes:
+
+* `templatable` indicates if the view may be embedded in a main
+  template or if it has to be rendered standalone (i.e. pure XML views
+  must not be embedded in the main template of HTML pages)
+
+* if the view is not templatable, it should set the `content_type`
+  class attribute to the correct MIME type (text/xhtml being the
+  default)
+
+* the `category` attribute may be used in the interface to regroup
+  related view kinds together
+
+A view writes to its output stream thanks to its attribute `w` (the
+append method of an `UStreamIO`, except for binary views).
+
+At instantiation time, the standard `_cw` and `cw_rset` attributes are
+added and the `w` attribute will be set at rendering time.
+
+The basic interface for views is as follows (remember that the result
+set has a tabular structure with rows and columns, hence cells):
+
+* `render(**context)`, render the view by calling `call` or
+  `cell_call` depending on the context
+
+* `call(**kwargs)`, call the view for a complete result set or null
+  (the default implementation calls `cell_call()` on each cell of the
+  result set)
+
+* `cell_call(row, col, **kwargs)`, call the view for a given cell of a
+  result set (`row` and `col` being integers used to access the cell)
+
+* `url()`, returns the URL enabling us to get the view with the current
+  result set
+
+* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of
+  identifier `__vid` on the given result set. It is possible to give a
+  fallback view identifier that will be used if the requested view is
+  not applicable to the result set.
+
+* `html_headers()`, returns a list of HTML headers to be set by the
+  main template
+
+* `page_title()`, returns the title to use in the HTML header `title`
+
+Other basic view classes
+````````````````````````
+Here are some of the subclasses of `View` defined in `cubicweb.common.view`
+that are more concrete as they relate to data rendering within the application:
+
+* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
+* `StartupView`, start view that does not require a result set to apply to
+* `AnyRsetView`, view applicable to any result set
+
+Examples of views class
+```````````````````````
+
+- Using `templatable`, `content_type` and HTTP cache configuration
+
+.. sourcecode:: python
+
+    class RSSView(XMLView):
+        __regid__ = 'rss'
+        title = _('rss')
+        templatable = False
+        content_type = 'text/xml'
+        http_cache_manager = MaxAgeHTTPCacheManager
+        cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+
+- Using a custom selector
+
+.. sourcecode:: python
+
+    class SearchForAssociationView(EntityView):
+        """view called by the edition view when the user asks
+        to search for something to link to the edited eid
+        """
+        __regid__ = 'search-associate'
+        title = _('search for association')
+        __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
+
+
+XML views, binaries views...
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For views generating other formats than HTML (an image generated dynamically
+for example), and which can not simply be included in the HTML page generated
+by the main template (see above), you have to:
+
+* set the attribute `templatable` of the class to `False`
+* set, through the attribute `content_type` of the class, the MIME
+  type generated by the view to `application/octet-stream` or any
+  relevant and more specialised mime type
+
+For views dedicated to binary content creation (like dynamically generated
+images), we have to set the attribute `binary` of the class to `True` (which
+implies that `templatable == False`, so that the attribute `w` of the view could be
+replaced by a binary flow instead of unicode).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/wdoc.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,9 @@
+.. -*- coding: utf-8 -*-
+
+Online documentation system
+---------------------------
+
+(:mod:`cubicweb.web.views.wdoc`)
+
+XXX  describe the on-line documentation system
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/views/xmlrss.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,60 @@
+.. _XmlAndRss:
+
+XML and RSS views
+-----------------
+
+(:mod:`cubicweb.web.views.xmlrss`)
+
+Overview
++++++++++
+
+*rss*
+    Creates a RSS/XML view and call the view `rssitem` for each entity of
+    the result set.
+
+*rssitem*
+    Create a RSS/XML view for each entity based on the results of the dublin core
+    methods of the entity (`dc_*`)
+
+RSS Channel Example
+++++++++++++++++++++
+
+Assuming you have several blog entries, click on the title of the
+search box in the left column. A larger search box should appear. Enter::
+
+   Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
+
+and you get a list of blog entries.
+
+Click on your login at the top right corner. Chose "user preferences",
+then "boxes", then "possible views box" and check "visible = yes"
+before validating your changes.
+
+Enter the same query in the search box and you will see the same list,
+plus a box titled "possible views" in the left column. Click on
+"entityview", then "RSS".
+
+You just applied the "RSS" view to the RQL selection you requested.
+
+That's it, you have a RSS channel for your blog.
+
+Try again with::
+
+    Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
+    X entry_of B, B title "MyLife"
+
+Another RSS channel, but a bit more focused.
+
+A last one for the road::
+
+    Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
+
+displayed with the RSS view, that's a channel for the last fifteen
+comments posted.
+
+[WRITE ME]
+
+* show that the RSS view can be used to display an ordered selection
+  of blog entries, thus providing a RSS channel
+
+* show that a different selection (by category) means a different channel
Binary file doc/book/en/images/03-transitions-view.en.png has changed
Binary file doc/book/en/images/03-transitions-view_en.png has changed
Binary file doc/book/en/images/archi_globale.en.png has changed
Binary file doc/book/en/images/archi_globale_en.png has changed
Binary file doc/book/en/images/breadcrumbs_header.png has changed
Binary file doc/book/en/images/cbw-add-relation-entryof.en.png has changed
Binary file doc/book/en/images/cbw-add-relation-entryof_en.png has changed
Binary file doc/book/en/images/cbw-create-blog.en.png has changed
Binary file doc/book/en/images/cbw-create-blog_en.png has changed
Binary file doc/book/en/images/cbw-detail-one-blogentry.en.png has changed
Binary file doc/book/en/images/cbw-detail-one-blogentry_en.png has changed
Binary file doc/book/en/images/cbw-list-one-blog.en.png has changed
Binary file doc/book/en/images/cbw-list-one-blog_en.png has changed
Binary file doc/book/en/images/cbw-list-two-blog.en.png has changed
Binary file doc/book/en/images/cbw-list-two-blog_en.png has changed
Binary file doc/book/en/images/cbw-schema.en.png has changed
Binary file doc/book/en/images/cbw-schema_en.png has changed
Binary file doc/book/en/images/cbw-update-primary-view.en.png has changed
Binary file doc/book/en/images/cbw-update-primary-view_en.png has changed
Binary file doc/book/en/images/facet_date_range.png has changed
Binary file doc/book/en/images/facet_has_image.png has changed
Binary file doc/book/en/images/facet_overview.png has changed
Binary file doc/book/en/images/facet_range.png has changed
Binary file doc/book/en/images/lax-book.00-login.en.png has changed
Binary file doc/book/en/images/lax-book.01-start.en.png has changed
Binary file doc/book/en/images/lax-book.02-cookie-values.en.png has changed
Binary file doc/book/en/images/lax-book.02-create-blog.en.png has changed
Binary file doc/book/en/images/lax-book.03-list-one-blog.en.png has changed
Binary file doc/book/en/images/lax-book.03-site-config-panel.en.png has changed
Binary file doc/book/en/images/lax-book.03-state-submitted.en.png has changed
Binary file doc/book/en/images/lax-book.03-transitions-view.en.png has changed
Binary file doc/book/en/images/lax-book.04-detail-one-blog.en.png has changed
Binary file doc/book/en/images/lax-book.05-list-two-blog.en.png has changed
Binary file doc/book/en/images/lax-book.06-add-relation-entryof.en.png has changed
Binary file doc/book/en/images/lax-book.06-header-no-login.en.png has changed
Binary file doc/book/en/images/lax-book.06-main-template-layout.en.png has changed
Binary file doc/book/en/images/lax-book.06-main-template-logo.en.png has changed
Binary file doc/book/en/images/lax-book.06-simple-main-template.en.png has changed
Binary file doc/book/en/images/lax-book.07-detail-one-blogentry.en.png has changed
Binary file doc/book/en/images/lax-book.08-schema.en.png has changed
Binary file doc/book/en/images/lax-book.09-new-view-blogentry.en.png has changed
Binary file doc/book/en/images/lax-book.10-blog-with-two-entries.en.png has changed
Binary file doc/book/en/images/lax-book_00-login_en.png has changed
Binary file doc/book/en/images/lax-book_01-start_en.png has changed
Binary file doc/book/en/images/lax-book_02-cookie-values_en.png has changed
Binary file doc/book/en/images/lax-book_02-create-blog_en.png has changed
Binary file doc/book/en/images/lax-book_03-list-one-blog_en.png has changed
Binary file doc/book/en/images/lax-book_03-site-config-panel_en.png has changed
Binary file doc/book/en/images/lax-book_03-state-submitted_en.png has changed
Binary file doc/book/en/images/lax-book_03-transitions-view_en.png has changed
Binary file doc/book/en/images/lax-book_04-detail-one-blog_en.png has changed
Binary file doc/book/en/images/lax-book_05-list-two-blog_en.png has changed
Binary file doc/book/en/images/lax-book_06-add-relation-entryof_en.png has changed
Binary file doc/book/en/images/lax-book_06-header-no-login_en.png has changed
Binary file doc/book/en/images/lax-book_06-main-template-layout_en.png has changed
Binary file doc/book/en/images/lax-book_06-main-template-logo_en.png has changed
Binary file doc/book/en/images/lax-book_06-simple-main-template_en.png has changed
Binary file doc/book/en/images/lax-book_07-detail-one-blogentry_en.png has changed
Binary file doc/book/en/images/lax-book_08-schema_en.png has changed
Binary file doc/book/en/images/lax-book_09-new-view-blogentry_en.png has changed
Binary file doc/book/en/images/lax-book_10-blog-with-two-entries_en.png has changed
Binary file doc/book/en/images/main_template.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/images/main_template.svg	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1036.6421"
+   height="845.07812"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="main_template.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   version="1.0"
+   inkscape:export-filename="/home/auc/cw/doc/book/en/images/main_template.png"
+   inkscape:export-xdpi="60.659016"
+   inkscape:export-ydpi="60.659016">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.80355603"
+     inkscape:cx="510.91495"
+     inkscape:cy="422.53906"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="925"
+     inkscape:window-height="1168"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:snap-bbox="true" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(162.2968,90.697922)">
+    <rect
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.775;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect2439"
+       width="854.37006"
+       height="698.2019"
+       x="20.307629"
+       y="-20.575344" />
+    <rect
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.775;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3301"
+       width="816.3457"
+       height="508.15628"
+       x="31.751091"
+       y="96.33345" />
+    <g
+       id="g3220"
+       transform="matrix(1.0035394,0,0,1,0.5745006,0)">
+      <rect
+         y="-89.447922"
+         x="-161.0468"
+         height="55.714287"
+         width="1031.1713"
+         id="rect3240"
+         style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.50000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <text
+         id="text3264"
+         y="-51.771908"
+         x="757.85767"
+         style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           id="tspan3266"
+           y="-51.771908"
+           x="757.85767"
+           sodipodi:role="line">header</tspan></text>
+    </g>
+    <rect
+       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.775;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3270"
+       width="167.87744"
+       height="707.71222"
+       x="-160.02441"
+       y="-24.671618" />
+    <g
+       id="g2434"
+       transform="matrix(0.975467,0,0,1,0.6942419,-3.6587365)">
+      <rect
+         y="35.365849"
+         x="29.548275"
+         height="55.714287"
+         width="842.59979"
+         id="rect3279"
+         style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <text
+         id="text3281"
+         y="72.885193"
+         x="681.65283"
+         style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           id="tspan3283"
+           y="72.885193"
+           x="681.65283"
+           sodipodi:role="line">contentheader</tspan></text>
+    </g>
+    <g
+       id="g3170"
+       transform="matrix(1.0023324,0,0,1,-2.0421673,-10.976211)">
+      <rect
+         y="698.6355"
+         x="-158.28485"
+         height="55.714287"
+         width="1032.5997"
+         id="rect3285"
+         style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <text
+         id="text3287"
+         y="736.52045"
+         x="770.28204"
+         style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           id="tspan3289"
+           y="736.52045"
+           x="770.28204"
+           sodipodi:role="line">footer</tspan></text>
+    </g>
+    <g
+       id="g3211" />
+    <g
+       id="g3215"
+       transform="matrix(0.9712065,0,0,1,0.7659296,-17.074106)">
+      <rect
+         style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3291"
+         width="844.62012"
+         height="55.714287"
+         x="27.850754"
+         y="629.88562" />
+      <text
+         id="text3293"
+         y="666.60339"
+         x="692.85773"
+         style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           id="tspan3295"
+           y="666.60339"
+           x="692.85773"
+           sodipodi:role="line">contentfooter</tspan></text>
+    </g>
+    <text
+       xml:space="preserve"
+       style="font-size:23.38711166px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="-143.67273"
+       y="20.58094"
+       id="text3297"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan2432"
+         x="-143.67273"
+         y="20.58094">left column</tspan></text>
+    <text
+       transform="scale(0.9876573,1.0124969)"
+       id="text3175"
+       y="12.071429"
+       x="721.0575"
+       style="font-size:23.09845161px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         id="tspan3177"
+         y="12.071429"
+         x="721.0575"
+         sodipodi:role="line">contentcol</tspan></text>
+    <text
+       transform="scale(0.9876573,1.0124969)"
+       id="text3179"
+       y="126.27104"
+       x="701.45959"
+       style="font-size:23.09845161px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         id="tspan3181"
+         y="126.27104"
+         x="701.45959"
+         sodipodi:role="line">contentmain</tspan></text>
+  </g>
+</svg>
Binary file doc/book/en/images/primaryview_template.png has changed
--- a/doc/book/en/images/primaryview_template.svg	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/images/primaryview_template.svg	Wed Apr 28 11:54:13 2010 +0200
@@ -36,16 +36,16 @@
      borderopacity="1.0"
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
-     inkscape:zoom="0.49497475"
-     inkscape:cx="432.61573"
-     inkscape:cy="370.11733"
+     inkscape:zoom="0.9357135"
+     inkscape:cx="518.32104"
+     inkscape:cy="337.0428"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="false"
-     inkscape:window-width="824"
-     inkscape:window-height="1094"
+     inkscape:window-width="1307"
+     inkscape:window-height="1168"
      inkscape:window-x="0"
-     inkscape:window-y="45" />
+     inkscape:window-y="0" />
   <metadata
      id="metadata7">
     <rdf:RDF>
@@ -62,193 +62,224 @@
      inkscape:groupmode="layer"
      id="layer1"
      transform="translate(162.2968,90.697922)">
+    <g
+       id="g3869"
+       transform="matrix(1,0,0,1.0373644,0,-72.039777)"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449">
+      <rect
+         y="-15.840891"
+         x="-159.08963"
+         height="770.11017"
+         width="1033.0049"
+         id="rect3301"
+         style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.90144825;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <text
+         id="text3865"
+         y="19.784882"
+         x="-150.07172"
+         style="font-size:28.67479324px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           id="tspan3867"
+           y="19.784882"
+           x="-150.07172"
+           sodipodi:role="line">contentmain</tspan></text>
+    </g>
     <rect
-       style="fill:#ffffff;fill-rule:evenodd;stroke:#ff0000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3301"
-       width="842.59973"
-       height="562.81085"
-       x="28.555748"
-       y="53.761448" />
-    <rect
-       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.45654476;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
        id="rect2383"
-       width="629.62366"
-       height="54.69112"
-       x="54.112095"
-       y="73.831123" />
+       width="772.32111"
+       height="43.888428"
+       x="-131.1837"
+       y="86.559296"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="500.19885"
-       y="104.27108"
-       id="text2385"><tspan
+       style="font-size:16px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%"
+       x="-122.69418"
+       y="115.50363"
+       id="text2385"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="500.19885"
-         y="104.27108"
+         x="-122.69418"
+         y="115.50363"
          id="tspan3163">navcontenttop</tspan></text>
     <rect
-       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:3.06523442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
        id="rect3167"
-       width="628.2298"
-       height="165.69759"
-       x="54.809006"
-       y="142.37053" />
+       width="770.26868"
+       height="203.16078"
+       x="-125.88269"
+       y="172.90417"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="311.65164"
-       y="283.88312"
-       id="text3169"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="348.26724"
+       y="205.34305"
+       id="text3169"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="311.65164"
-         y="283.88312"
-         id="tspan3171">view.render_entity_attributes()</tspan></text>
+         x="348.26724"
+         y="205.34305"
+         id="tspan3171">render_entity_attributes()</tspan></text>
     <rect
-       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:3.06523442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
        id="rect3173"
-       width="627.95807"
-       height="193.9873"
-       x="56.373432"
-       y="320.51138" />
+       width="769.93549"
+       height="237.84663"
+       x="-125.03326"
+       y="391.32156"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="320.29282"
-       y="488.45456"
-       id="text3175"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="360.99954"
+       y="428.38055"
+       id="text3175"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="320.29282"
-         y="488.45456"
-         id="tspan3177">view.render_entity_relations()</tspan></text>
+         x="360.99954"
+         y="428.38055"
+         id="tspan3177">render_entity_relations()</tspan></text>
     <rect
-       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:1.76090598;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       style="fill:#ffd5d5;fill-rule:evenodd;stroke:#000000;stroke-width:2.15903592;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
        id="rect3185"
-       width="145.94266"
-       height="499.44452"
-       x="702.43958"
-       y="70.384262" />
+       width="178.93939"
+       height="612.36584"
+       x="667.10443"
+       y="84.64225"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="74.177475"
-       y="-823.00977"
+       style="font-size:22px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%"
+       x="105.32364"
+       y="-810.65997"
        id="text3187"
-       transform="matrix(0,1,-1,0,0,0)"><tspan
+       transform="matrix(0,1,-1,0,0,0)"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="74.177475"
-         y="-823.00977"
-         id="tspan3189">view.render_side_boxes()</tspan></text>
+         id="tspan2408">render_side_boxes()</tspan></text>
     <rect
-       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:2.50000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:3.0652349;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
        id="rect3191"
-       width="629.62366"
-       height="45.386246"
-       x="54.112095"
-       y="524.98816" />
+       width="771.97766"
+       height="55.647793"
+       x="-127.80586"
+       y="642.0293"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="456.07504"
-       y="561.66559"
-       id="text3181"><tspan
+       style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="-121.22153"
+       y="674.1748"
+       id="text3181"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="456.07504"
-         y="561.66559"
+         x="-121.22153"
+         y="674.1748"
          id="tspan3183">navcontentbottom</tspan></text>
     <rect
-       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.50000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3240"
-       width="1031.1713"
-       height="55.714287"
-       x="-161.0468"
-       y="-89.447922" />
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.68198514;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3881"
+       width="986.90503"
+       height="45.800392"
+       x="-128.34428"
+       y="-31.574066"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="757.85767"
-       y="-51.771908"
-       id="text3264"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="355.60541"
+       y="-2.7424495"
+       id="text3883"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="757.85767"
-         y="-51.771908"
-         id="tspan3266">header</tspan></text>
+         x="355.60541"
+         y="-2.7424495"
+         id="tspan3885">render_entity_toolbox(), render_entity_title()</tspan></text>
     <rect
-       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.76090598;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3270"
-       width="167.89117"
-       height="696.74976"
-       x="-160.03128"
-       y="-13.70227" />
-    <rect
-       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3279"
-       width="842.59979"
-       height="55.714287"
-       x="28.328695"
-       y="-13.41731" />
+       style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1.68198514;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3890"
+       width="986.90503"
+       height="45.800392"
+       x="-128.87863"
+       y="19.723684"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449" />
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="668.23749"
-       y="25.321617"
-       id="text3281"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="565.71027"
+       y="50.135612"
+       id="text3892"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="668.23749"
-         y="25.321617"
-         id="tspan3283">contentheader</tspan></text>
-    <rect
-       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3285"
-       width="1032.5997"
-       height="55.714287"
-       x="-159.50443"
-       y="697.41589" />
+         x="565.71027"
+         y="50.135612"
+         id="tspan3894">render_entity_summary()</tspan></text>
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="770.28204"
-       y="736.52045"
-       id="text3287"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="87.154541"
+       y="114.2578"
+       id="text3899"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="770.28204"
-         y="736.52045"
-         id="tspan3289">footer</tspan></text>
-    <rect
-       style="fill:#dfdfdf;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="rect3291"
-       width="844.62012"
-       height="55.714287"
-       x="27.850754"
-       y="627.44647" />
+         id="tspan3903"
+         x="87.154541"
+         y="114.2578">content_navigation_components('navcontenttop')</tspan></text>
     <text
        xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="680.66193"
-       y="669.04254"
-       id="text3293"><tspan
+       style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+       x="88.46772"
+       y="675.71582"
+       id="text2410"
+       sodipodi:linespacing="125%"
+       inkscape:export-filename="/home/auc/src/fcw/cubicweb/doc/book/en/images/primaryview_template.png"
+       inkscape:export-xdpi="60.912449"
+       inkscape:export-ydpi="60.912449"><tspan
          sodipodi:role="line"
-         x="680.66193"
-         y="669.04254"
-         id="tspan3295">contentfooter</tspan></text>
-    <text
-       xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="-130.25735"
-       y="24.239677"
-       id="text3297"><tspan
-         sodipodi:role="line"
-         x="-130.25735"
-         y="24.239677"
-         id="tspan3299">leftcolumn</tspan></text>
-    <text
-       xml:space="preserve"
-       style="font-size:23.38711166px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
-       x="373.18518"
-       y="610.24188"
-       id="text3351"><tspan
-         sodipodi:role="line"
-         x="373.18518"
-         y="610.24188"
-         id="tspan3353">view.render()</tspan></text>
+         id="tspan2412"
+         x="88.46772"
+         y="675.71582">content_navigation_components('navcontenttop')</tspan></text>
   </g>
 </svg>
--- a/doc/book/en/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -40,22 +40,27 @@
 .. _`mailing-list`: http://lists.cubicweb.org/mailman/listinfo/cubicweb
 .. _blog: http://www.cubicweb.org/blog/1238
 
-Table of Contents
-=================
+.. toctree::
+   :maxdepth: 2
+
+   intro/index
+   tutorials/index
+
+.. toctree::
+   :maxdepth: 3
+
+   devrepo/index
+   devweb/index
 
 .. toctree::
    :maxdepth: 2
 
-   intro/index
-   development/index
    admin/index
    annexes/index
 
 See also:
 
-* the complete :ref:`TOC`,
 * the :ref:`genindex`,
 * the :ref:`modindex`,
-* and the :ref:`search`.
 
-.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
+.. |cubicweb| replace:: *CubicWeb*
--- a/doc/book/en/intro/book-map.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Book map
-========
-
-[XXX WRITE ME]
-
-* explain how to use this book and what chapters to read in what order depending on the
-  objectives of the reader
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/concepts.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,343 @@
+.. -*- coding: utf-8 -*-
+
+.. _Concepts:
+
+The Core Concepts of |cubicweb|
+===============================
+
+This section defines some terms and core concepts of the |cubicweb| framework. To
+avoid confusion while reading this book, take time to go through the following
+definitions and use this section as a reference during your reading.
+
+
+.. _Cube:
+
+Cubes
+-----
+
+A cube is a software component made of three parts: its data model
+(:mod:`schema`), its logic (:mod:`entities`) and its user interface
+(:mod:`views`).
+
+A cube can use other cubes as building blocks and assemble them to provide a
+whole with richer functionnalities than its parts. The cubes `cubicweb-blog`_ and
+`cubicweb-comment`_ could be used to make a cube named *myblog* with commentable
+blog entries.
+
+The `CubicWeb.org Forge`_ offers a large number of cubes developed by the community
+and available under a free software license.
+
+The command :command:`cubicweb-ctl list` displays the list of cubes installed on
+your system.
+
+On a Unix system, the available cubes are usually stored in the directory
+:file:`/usr/share/cubicweb/cubes`. If you're using the cubicweb forest
+(:ref:SourceInstallation), the cubes are searched in the directory
+:file:`/path/to/cubicweb_forest/cubes`. The environment variable
+:envvar:`CW_CUBES_PATH` gives additionnal locations where to search for cubes.
+
+.. _`CubicWeb.org Forge`: http://www.cubicweb.org/project/
+.. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
+.. _`cubicweb-comment`: http://www.cubicweb.org/project/cubicweb-comment
+
+
+.. _Instance:
+
+Instances
+---------
+
+An instance is a runnable application installed on a computer and based on a
+cube.
+
+The instance directory contains the configuration files. Several instances can be
+created and based on the same cube. For exemple, several software forges can be
+set up on one computer system based on the `cubicweb-forge`_ cube.
+
+.. _`cubicweb-forge`: http://www.cubicweb.org/project/cubicweb-forge
+
+Instances can be of three different types: all-in-one, web engine or data
+repository. For applications that support high traffic, several web (front-end)
+and data (back-end) instances can be set-up to share the load.
+
+.. image:: ../images/archi_globale_en.png
+
+The command :command:`cubicweb-ctl list` also displays the list of instances
+installed on your system.
+
+On a Unix system, the instances are usually stored in the directory
+:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/`
+directory is looked up, as well as the paths in :envvar:`CW_INSTANCES_DIR`
+environment variable.
+
+
+.. note::
+
+  The term application is used to refer to "something that should do something as
+  a whole", eg more like a project and so can refer to an instance or to a cube,
+  depending on the context. This book will try to use *application*, *cube* and
+  *instance* as appropriate.
+
+
+.. _RepositoryIntro:
+
+Data Repository
+---------------
+
+The data repository [1]_ provides access to one or more data sources (including
+SQL databases, LDAP repositories, other |cubicweb| instance repositories, GAE's
+DataStore, etc).
+
+All interactions with the repository are done using the Relation Query Language
+(:ref:`RQL`). The repository federates the data sources and hides them from the
+querier, which does not realize when a query spans accross several data sources
+and requires running sub-queries and merges to complete.
+
+It is common to run the web engine and the repository in the same process (see
+instances of type all-in-one above), but this is not a requirement. A repository
+can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and
+act as a server. However, it's important to know if code you're writing is
+executed on the repository side, on our client side (the web engine being a
+client for instance): you don't have the same abilities on both side. On the
+repository side, you can for instance by-pass security checks, which isn't
+possible from client code.
+
+Some logic can be attached to events that happen in the repository,
+like creation of entities, deletion of relations, etc. This is used
+for example to send email notifications when the state of an object
+changes. See :ref:`HookIntro` below.
+
+.. [1] not to be confused with a Mercurial repository or a Debian repository.
+.. _`Python Remote Objects`: http://pyro.sourceforge.net/
+
+.. _WebEngineIntro:
+
+Web Engine
+----------
+
+The web engine replies to http requests and runs the user interface
+and most of the application logic.
+
+By default the web engine provides a `CRUD`_ user interface based on
+the data model of the instance. Entities can be created, displayed,
+updated and deleted. As the default user interface is not very fancy,
+it is usually necessary to develop your own.
+
+.. _`CRUD`: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
+
+.. _SchemaIntro:
+
+Schema (Data Model)
+-------------------
+
+The data model of a cube is described as an entity-relationship schema using a
+comprehensive language made of Python classes imported from the yams_ library.
+
+.. _yams: http://www.logilab.org/project/yams/
+
+An `entity type` defines a set of attributes and is used in some relations.
+Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`,
+`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`.
+
+A `relation type` is used to define an oriented binary relation between two
+entity types.  The left-hand part of a relation is named the `subject` and the
+right-hand part is named the `object`.
+
+A `relation definition` is a triple (*subject entity type*, *relation type*, *object
+entity type*) associated with a set of properties such as cardinality,
+constraints, etc.
+
+Permissions can be set on entity types and relation definition to control who
+will be able to create, read, update or delete entities and relations. Permissions
+are granted to groups (to which users may belong) or using rql expression (if the
+rql expression returns some results, the permission is granted).
+
+Some meta-data necessary to the system is added to the data model. That includes
+entities like users and groups, the entities used to store the data model
+itself and attributes like unique identifier, creation date, creator, etc.
+
+When you create a new |cubicweb| instance, the schema is stored in the database.
+When the cubes the instance is based on evolve, they may change their data model
+and provide migration scripts that will be executed when the administrator will
+run the upgrade process for the instance.
+
+
+.. _VRegistryIntro:
+
+Registries and application objects
+----------------------------------
+
+Application objects
+~~~~~~~~~~~~~~~~~~~
+
+Beside a few core functionalities, almost every feature of the framework is
+achieved by dynamic objects (`application objects` or `appobjects`) stored in a
+two-levels registry (the `vregistry`). Each object is affected to a registry with
+an identifier in this registry. You may have more than one object sharing an
+identifier in the same registry, At runtime, appobjects are selected in a
+registry according to the context. Selection is done by comparing *score*
+returned by each appobject's *selector*.
+
+Application objects are stored in the vregistry using a two-level hierarchy :
+
+  object's `__registry__` : object's `__regid__` : [list of app objects]
+
+E.g. the `vregistry` contains several (sub-)registries which hold a
+list of appobjects associated to an identifier.
+
+The base class of appobjects is :class:`cubicweb.appobject.AppObject`.
+
+Selectors
+~~~~~~~~~
+
+Each appobject has a selector, that is used to compute how well the object fits a
+given context. The better the object fits the context, the higher the score. They
+are the glue that tie appobjects to the data model. Using them appropriately is
+an essential part of the construction of well behaved cubes.
+
+|cubicweb| provides a set of basic selectors that may be parametrized.  Also,
+selectors can be combined with the `~` unary operator (negation) and the binary
+operators `&` and `|` (respectivly 'and' and 'or') to build more complex
+selector. Of course complex selector may be combined too. Last but not least, you
+can write your own selectors.
+
+The `vregistry`
+~~~~~~~~~~~~~~~
+
+At startup, the `vregistry` inspects a number of directories looking for
+compatible classes definition. After a recording process, the objects are
+assigned to registries so that they can be selected dynamically while the
+instance is running.
+
+In a cube, application object classes are looked in the following modules or
+packages:
+
+- `entities`
+- `views`
+- `sobjects`
+
+
+Once initialized, there are three common ways to retrieve some application object
+from a registry:
+
+* get the most appropriate object by specifying an identifier. In that case, the
+  object with the greatest score is selected. There should always be a single
+  appobject with a greater score than others for a particular context.
+
+* get all objects applying to a context by specifying a registry. In that case, a
+  list of objects will be returned containing the object with the highest score
+  (> 0) for each identifier in that registry.
+
+* get the object within a particular registry/identifier. In that case no
+  selection process is involved, the vregistry will expect to find a single
+  object in that cell.
+
+
+.. _RQLIntro:
+
+The RQL query language
+----------------------
+
+No need for a complicated ORM when you have a powerful data
+manipulation language.
+
+All the persistent data in a |cubicweb| instance is retrieved and
+modified using RQL (see :ref:`rql_intro`).
+
+This query language is inspired by SQL but is on a higher level in order to
+emphasize browsing relations.
+
+
+DB-API
+~~~~~~
+
+The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
+
+.. _`db-api`: http://www.python.org/dev/peps/pep-0249/
+
+You basically get a connection using :func:`cubicweb.dbapi.connect` , then
+get a cursor to call its `execute` method which will return result set for the
+given rql query.
+
+You can also get additional information through the connection, such as the
+repository'schema, version configuration, etc.
+
+
+Result set
+~~~~~~~~~~
+
+Every request made (using RQL) to the data repository returns an object we call a
+Result Set. It enables easy use of the retrieved data, providing a translation
+layer between the backend's native datatypes and |cubicweb| schema's EntityTypes.
+
+Result sets provide access to the raw data, yielding either basic Python data
+types, or schema-defined high-level entities, in a straightforward way.
+
+
+.. _ViewIntro:
+
+Views
+-----
+
+**CubicWeb is data driven**
+
+The view system is loosely coupled to data through the selection system explained
+above. Views are application objects with a dedicated interface to 'render'
+something, eg producing some html, text, xml, pdf, or whatsover that can be
+displayed to a user.
+
+The two main entry points of a view are:
+
+* `call()`, used to render a view on a context with no result set, or on a whole
+  result set
+
+* `cell_call(row, col)`, used to render a view on a the cell with index `row` and
+  `col` of the context's result set (remember result set may be seen as a two
+  dimensions array).
+
+Then view may gets refined into different kind of objects such as `template`,
+`boxes`, `components`, which are more high-level abstraction useful to build
+the user interface in an object oriented way.
+
+
+.. _HookIntro:
+
+Hooks and operations
+--------------------
+
+**CubicWeb provides an extensible data repository**
+
+The data model defined using Yams types allows to express the data
+model in a comfortable way. However several aspects of the data model
+can not be expressed there. For instance:
+
+* managing computed attributes
+
+* enforcing complicated structural invariants
+
+* real-world side-effects linked to data events (email notification
+  being a prime example)
+
+The hook system is much like the triggers of an SQL database engine,
+except that:
+
+* it is not limited to one specific SQL backend (every one of them
+  having an idiomatic way to encode triggers), nor to SQL backends at
+  all (think about LDAP or a Subversion repository)
+
+* it is well-coupled to the rest of the framework
+
+Hooks are also application objects registered on events such as after/before
+add/update/delete on entities/relations, server startup or shutdown, etc. As all
+application objects, they have a selector defining when they should be called or
+not.
+
+`Operations` may be instantiated by hooks to do further processing at different
+steps of the transaction's commit / rollback, which usually can not be done
+safely at the hook execution time.
+
+Hooks and operation are an essential building block of any moderately complicated
+cubicweb application.
+
+.. note::
+   RQL queries executed in hooks and operations are *unsafe* by default, e.g. the
+   read and write security is deactivated unless explicitly asked.
--- a/doc/book/en/intro/concepts/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,317 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Concepts:
-
-The Core Concepts of |cubicweb|
-===============================
-
-This section defines some terms and core concepts of the |cubicweb|
-framework. To avoid confusion while reading this book, take time to go through
-the following definitions and use this section as a reference during your
-reading.
-
-.. _Cube:
-
-Cubes
------
-
-A cube is a software component made of three parts: its data model
-(:file:`schema`), its logic (:file:`entities`) and its user interface
-(:file:`views`).
-
-A cube can use other cubes as building blocks and assemble them to provide
-a whole with richer functionnalities than its parts. The cubes `cubicweb-blog`_
-and `cubicweb-comment`_ could be used to make a cube named *myblog* with
-commentable blog entries.
-
-The `|cubicweb| Forge`_ offers a large number of cubes developed by the community
-and available under a free software license.
-
-The command :command:`cubicweb-ctl list` displays the list of cubes installed on your
-system.
-
-On a Unix system, the available cubes are usually stored in the directory
-:file:`/usr/share/cubicweb/cubes`. If you're using the cubicweb forest
-(:ref:SourceInstallation), the cubes are searched in the directory
-:file:`/path/to/cubicweb_forest/cubes`. The environment variable
-:envvar:`CW_CUBES_PATH` gives additionnal locations where to search for cubes.
-
-.. _`|cubicweb| Forge`: http://www.cubicweb.org/project/
-.. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
-.. _`cubicweb-comment`: http://www.cubicweb.org/project/cubicweb-comment
-
-
-Instances
----------
-
-An instance is a runnable application installed on a computer and based on a
-cube.
-
-The instance directory contains the configuration files. Several instances can
-be created and based on the same cube. For exemple, several software forges can
-be set up on one computer system based on the `cubicweb-forge`_ cube.
-
-.. _`cubicweb-forge`: http://www.cubicweb.org/project/cubicweb-forge
-
-Instances can be of three different types: all-in-one, web engine or data
-repository. For applications that support high traffic, several web (front-end)
-and data (back-end) instances can be set-up to share the load.
-
-.. image:: ../../images/archi_globale.en.png
-
-The command :command:`cubicweb-ctl list` also displays the list of instances
-installed on your system.
-
-On a Unix system, the instances are usually stored in the directory
-:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/`
-directory is looked up, as well as the paths in :envvar:`CW_INSTANCES_DIR`
-environment variable.
-
-The term application is used to refer to "something that should do something as a
-whole", eg more like a project and so can refer to an instance or to a cube,
-depending on the context. This book will try to use *application*, *cube* and
-*instance* as appropriate.
-
-Data Repository
----------------
-
-The data repository [1]_ provides access to one or more data sources (including
-SQL databases, LDAP repositories, Mercurial or Subversion version control
-systems, other |cubicweb| instance repositories, GAE's DataStore, etc).
-
-All interactions with the repository are done using the Relation Query Language
-(RQL). The repository federates the data sources and hides them from the
-querier, which does not realize when a query spans accross several data sources
-and requires running sub-queries and merges to complete.
-
-It is common to run the web engine and the repository in the same process (see
-instances of type all-in-one above), but this is not a requirement. A repository
-can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and
-act as a server.
-
-Some logic can be attached to events that happen in the repository, like
-creation of entities, deletion of relations, etc. This is used for example to
-send email notifications when the state of an object changes. See `Hooks` below.
-
-.. [1] not to be confused with a Mercurial repository or a Debian repository.
-.. _`Python Remote Objects`: http://pyro.sourceforge.net/
-
-Web Engine
-----------
-
-The web engine replies to http requests and runs the user interface
-and most of the application logic.
-
-By default the web engine provides a default user interface based on
-the data model of the instance. Entities can be created, displayed,
-updated and deleted. As the default user interface is not very fancy,
-it is usually necessary to develop your own.
-
-Schema (Data Model)
--------------------
-
-The data model of a cube is described as an entity-relationship schema using a
-comprehensive language made of Python classes imported from the yams_ library.
-
-.. _yams: http://www.logilab.org/project/yams/
-
-An `entity type` defines a set of attributes and is used in some relations.
-Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`,
-`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See
-:ref:`yams.BASE_TYPES` for details.
-
-A `relation type` is used to define an oriented binary relation between two
-entity types.  The left-hand part of a relation is named the `subject` and the
-right-hand part is named the `object`.
-
-A `relation definition` is a triple (*subject entity type*, *relation type*, *object
-entity type*) associated with a set of properties such as cardinality,
-constraints, etc.
-
-Permissions can be set on entity types and relation types to control who will be
-able to create, read, update or delete entities and relations.
-
-Some meta-data necessary to the system is added to the data model. That includes
-entities like users and groups, the entities used to store the data model
-itself and attributes like unique identifier, creation date, creator, etc.
-
-When you create a new |cubicweb| instance, the schema is stored in the database.
-When the cubes the instance is based on evolve, they may change their data model
-and provide migration scripts that will be executed when the administrator will
-run the upgrade process for the instance.
-
-Registries and Objects
-----------------------
-
-Application objects
-~~~~~~~~~~~~~~~~~~~
-
-Beside a few core functionalities, almost every feature of the framework is
-achieved by dynamic objects (`application objects` or `appobjects`) stored in a
-two-levels registry (the `vregistry`). Each object is affected to a registry with
-an identifier in this registry. You may have more than one object sharing an
-identifier in the same registry, At runtime, appobjects are selected in the
-vregistry according to the context.
-
-Application objects are stored in the registry using a two-level hierarchy :
-
-  object's `__registry__` : object's `id` : [list of app objects]
-
-The base class of appobjects is `AppObject` (module `cubicweb.appobject`).
-
-The `vregistry`
-~~~~~~~~~~~~~~~
-
-At startup, the `registry` inspects a number of directories looking
-for compatible classes definition. After a recording process, the
-objects are assigned to registers so that they can be selected
-dynamically while the instance is running.
-
-Selectors
-~~~~~~~~~
-
-Each appobject has a selector, that is used to compute how well the object fits
-a given context. The better the object fits the context, the higher the score.
-
-|cubicweb| provides a set of basic selectors that may be parametrized. Selectors
-can be combined with the binary operators `&` and `|` to build more complex
-selector that can be combined too.
-
-There are three common ways to retrieve some appobject from the repository:
-
-* get the most appropriate objects by specifying a registry and an identifier. In
-  that case, the object with the greatest score is selected. There should always
-  be a single appobject with a greater score than others.
-
-* get all appobjects applying to a context by specifying a registry. In
-  that case, every object with the a postive score is selected.
-
-* get the object within a particular registry/identifier. In that case no
-  selection process is involved, the vregistry will expect to find a single
-  object in that cell.
-
-Selector sets are the glue that tie views to the data model. Using them
-appropriately is an essential part of the construction of well behaved cubes.
-
-When no score is higher than the others, an exception is raised in development
-mode to let you know that the engine was not able to identify the view to
-apply. This error is silenced in production mode and one of the objects with the
-higher score is picked.
-
-If no object has a positive score, ``NoSelectableObject`` exception is raised.
-
-If no object is found for a particular registry and identifier,
-``ObjectNotFound`` exception is raised.
-
-In such cases you would need to review your design and make sure your views are
-properly defined.
-
-
-
-The RQL query language
-----------------------
-
-**No need for a complicated ORM when you have a powerful query language**
-
-All the persistent data in a |cubicweb| instance is retrieved and modified by using the
-Relation Query Language.
-
-This query language is inspired by SQL but is on a higher level in order to
-emphasize browsing relations.
-
-db-api
-~~~~~~
-
-The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
-XXX feed me
-
-Result set
-~~~~~~~~~~
-
-Every request made (using RQL) to the data repository returns an
-object we call a Result Set. It enables easy use of the retrieved
-data, providing a translation layer between the backend's native
-datatypes and |cubicweb| schema's EntityTypes.
-
-Result sets provide access to the raw data, yielding either basic
-Python data types, or schema-defined high-level entities, in a
-straightforward way.
-
-
-Views
------
-
-**CubicWeb| is data driven**
-
-The view system is loosely coupled to data through a selection
-system. Views are, in essence, defined by an id, a selection predicate
-and an entry point (generaly producing html).
-
-XXX feed me.
-
-
-Hooks
------
-
-**CubicWeb provides an extensible data repository**
-
-The data model defined using Yams types allows to express the data
-model in a comfortable way. However several aspects of the data model
-can not be expressed there. For instance:
-
-* managing computed attributes
-
-* enforcing complicated structural invariants
-
-* real-world side-effects linked to data events (email notification
-  being a prime example)
-
-The hook system is much like the triggers of an SQL database engine,
-except that:
-
-* it is not limited to one specific SQL backend (every one of them
-  having an idiomatic way to encode triggers), nor to SQL backends at
-  all (think about LDAP or a Subversion repository)
-
-* it is well-coupled to the rest of the framework
-
-Hooks are basically functions that dispatch on both:
-
-* events : after/before add/update/delete on entities/relations
-
-* entity or relation types
-
-They are an essential building block of any moderately complicated
-cubicweb application.
-
-
-.. _RunMode:
-
-Running mode
-------------
-
-A running mode is a predifined set of configuration telling where it should look
-for various resources, such as cubes, instances, etc. To ease development with
-the framework, there are two running modes with |cubicweb|:
-
-* 'user', resources are searched / created in the user home directory:
-  - instances are stored in :file:`~/etc/cubicweb.d`
-  - temporary files (such as pid file) in :file:`/tmp`
-
-* 'system', resources are searched / created in the system directories (eg usually requiring root access):
-  - instances are stored in :file:`/etc/cubicweb.d`
-  - temporary files (such as pid file) in :file:`/var/run/cubicweb`
-
-Cubes search path is also affected, see the :ref:Cube section.
-
-By default, the mode automatically set to 'user' if a :file:`.hg` directory is found
-in the cubicweb package, else it's set to 'system'. You can force this by setting
-the :envvar:`CW_MODE` environment variable to either 'user' or 'system'.
-
-If you've a doubt about the mode you're currently running, check the first line
-outputed by the :command:`cubicweb-ctl list` command.
-
-Notice that each resource path may be explicitly set using an environment
-variable if the default doesn't suit your needs.
-
-.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
--- a/doc/book/en/intro/history.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/intro/history.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -7,8 +7,11 @@
 developing in 2001 as an offspring of its Narval_ research project. *CubicWeb*
 is written in Python and includes a data server and a web engine.
 
-Its data server publishes data federated from different sources like SQL
-databases, LDAP directories or even from other CubicWeb data servers.
+Its data server publishes data federated from different sources like
+SQL databases, LDAP directories, `VCS`_ repositories or even from other
+CubicWeb data servers.
+
+.. _`VCS`: http://en.wikipedia.org/wiki/Revision_control
 
 Its web engine was designed to let the final user control what content to select
 and how to display it. It allows one to browse the federated data sources and
@@ -19,10 +22,10 @@
 *CubicWeb* has been developed by Logilab_ and used in-house for many years
 before it was first installed for its clients in 2006 as version 2.
 
-In 2008, *CubicWeb* version 3 became downloadable for free under the terms of
-the LGPL license. Its community is now steadily growing as changes can occur
-rapidly thanks to the time and energy originally put in the design of the
-framework.
+In 2008, *CubicWeb* version 3 became downloadable for free under the
+terms of the LGPL license. Its community is now steadily growing
+without hampering the fast-paced stream of changes thanks to the time
+and energy originally put in the design of the framework.
 
 
 .. _Narval: http://www.logilab.org/project/narval
--- a/doc/book/en/intro/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/en/intro/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -2,9 +2,9 @@
 
 .. _Part1:
 
------------------------------------
-Part I - Introduction to *CubicWeb*
------------------------------------
+--------------------------
+Introduction to *CubicWeb*
+--------------------------
 
 This first part of the book offers different reading path to
 discover the *CubicWeb* framework, provides a tutorial to get a quick
@@ -15,7 +15,5 @@
    :maxdepth: 2
    :numbered:
 
-   book-map
    history
-   concepts/index
-   tutorial/index
+   concepts.rst
--- a/doc/book/en/intro/tutorial/blog-in-five-minutes.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _BlogFiveMinutes:
-
-Get a blog running in five minutes!
------------------------------------
-
-For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`)::
-
-    cubicweb, cubicweb-dev, cubicweb-blog
-
-For Windows or Mac OS X users, you must install cubicweb from source (see :ref:`SourceInstallation` and  :ref:`WindowsInstallation`).
-
-Then create and initialize your instance::
-
-    cubicweb-ctl create blog myblog
-
-And start it::
-
-    cubicweb-ctl start -D myblog
-
-The -D option is the debugging mode of cubicweb, removing it will lauch the instance in the background.
-
-Permission
-~~~~~~~~~~
-
-This command assumes that you have root access to the /etc/ path. In order to initialize your instance as a `user` (from scratch), please check your current PYTHONPATH then create the ~/etc/cubicweb.d directory.
-
-Instance parameters
-~~~~~~~~~~~~~~~~~~~
-
-If the database installation failed, you'd like to change some instance parameters, for example, the database host or the user name. These informations can be edited in the `source` file located in the /etc/cubicweb.d/myblog directory.
-
-Then relaunch the database creation:
-
-     cubicweb-ctl db-create myblog
-
-Other paramaters, like web server or emails parameters, can be modified in the `all-in-one.conf` file.
-
-This is it. Your blog is running. Visit http://localhost:8080 and enjoy it! This blog is fully functionnal. The next section section will present the way to develop new cubes and customizing the look of your instance.
-
-
--- a/doc/book/en/intro/tutorial/components.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubes:
-
-Cubes
------
-
-Standard library
-~~~~~~~~~~~~~~~~
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities in standard cubes are:
-
-* addressbook: PhoneNumber and PostalAddress
-
-* basket: Basket (like a shopping cart)
-
-* blog: Blog (a *very* basic blog)
-
-* classfolder: Folder (to organize things but grouping them in folders)
-
-* classtags: Tag (to tag anything)
-
-* comment: Comment (to attach comment threads to entities)
-
-* file: File (to allow users to upload and store binary or text files)
-
-* link: Link (to collect links to web resources)
-
-* mailinglist: MailingList (to reference a mailing-list and the URLs
-  for its archives and its admin interface)
-
-* person: Person (easily mixed with addressbook)
-
-* task: Task (something to be done between start and stop date)
-
-* zone: Zone (to define places within larger places, for example a
-  city in a state in a country)
-
-.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
-
-Adding comments to BlogDemo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To import a cube in your instance just change the line in the
-``__pkginfo__.py`` file and verify that the cube you are planning
-to use is listed by the command ``cubicweb-ctl list``.
-For example::
-
-    __use__ = ('comment',)
-
-will make the ``Comment`` entity available in your ``BlogDemo``
-cube.
-
-Change the schema to add a relationship between ``BlogEntry`` and
-``Comment`` and you are done. Since the comment cube defines the
-``comments`` relationship, adding the line::
-
-    comments = ObjectRelation('Comment', cardinality='1*', composite='object')
-
-to the definition of a ``BlogEntry`` will be enough.
-
-Synchronize the data model
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once you modified your data model, you need to synchronize the
-database with your model. For this purpose, *CubicWeb* provides
-a very useful command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive shell where you can enter migration
-commands (see :ref:`cubicweb-ctl` for more details)).
-As you added the cube named `comment`, you need to run:
-
-::
-
-  add_cube('comment')
-
-You can now start your instance and comment your blog entries.
--- a/doc/book/en/intro/tutorial/conclusion.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-What's next?
-------------
-
-We demonstrated how from a straight out of the box *CubicWeb* installation, you
-can build your web application based on a data model. It's all already there:
-views, templates, permissions, etc. The step forward is now for you to customize
-according to your needs.
-
-Many features are available to extend your application, for example: RSS channel
-integration (:ref:`XmlAndRss`), hooks (:ref:`hooks`), support of sources such as
-Google App Engine (:ref:`GoogleAppEngineSource`) and lots of others to discover
-through our book.
-
--- a/doc/book/en/intro/tutorial/create-cube.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,430 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-.. _Steps:
-
-Steps for creating your cube
-----------------------------
-
-The following steps will help you to create and customize a new cube.
-
-1. :ref:`CreateYourCube`
-
-Create the directory to hold the code of your cube. The most important files that will be useful to customize your newly created cube are:
-  * schema.py: contains the data model
-  * views.py: contains your custom views
-  * entities.py: contains XXX 
-
-The detailed structure of the code directory is described in :ref:`cubelayout`.
-
-2. :ref:`DefineDataModel`
-
-Define the data model of your application.
-
-3. :ref:`ExploreYourInstance`
-
-Create, run, and explore an instance of your cube.
-
-4. :ref:`DefineViews`
-
-Customize the views of your data: how and which part of your data are showed. 
-
-Note: views don't concern the look'n'feel or design of the site. For that, you should use CSS instead, and default CSS or your new cube are located in 'blog/data/'.
-
-
-5. :ref:`DefineEntities`
-
-Define your own entities to add useful functions when you manipulate your data, especially when you write view.
-
-
-.. _CreateYourCube:
-
-Create your cube
-----------------
-
-The packages ``cubicweb`` and ``cubicweb-dev`` install a command line
-tool for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide
-range of commands described in details in :ref:`cubicweb-ctl`.
-
-Once your *CubicWeb* development environment is set up, you can create
-a new cube::
-
-  cubicweb-ctl newcube blog
-
-This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
-installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
-a directory named ``blog`` reflecting the structure described in :ref:`Concepts`.
-
-
-For packages installation, you can still create new cubes in your home directory using the following configuration. Let's say you want to develop your new cubes in `~src/cubes`, then set the following environment variables:
-::
-
-  CW_CUBES_PATH=~/src/cubes
-  CW_MODE=user
-
-and then create your new cube using:
-::
-
-  cubicweb-ctl newcube --directory=~/src/cubes blog
-
-
-
-
-
-
-.. _DefineDataModel:
-
-Define your data model
-----------------------
-
-The data model or schema is the core of your *CubicWeb* application.
-It defines the type of content your application will handle.
-
-The data model of your cube ``blog`` is defined in the file ``schema.py``:
-
-.. sourcecode:: python
-
-  from yams.buildobjs import EntityType, String, SubjectRelation, Date
-
-  class Blog(EntityType):
-    title = String(maxsize=50, required=True)
-    description = String()
-
-  class BlogEntry(EntityType):
-    title = String(required=True, fulltextindexed=True, maxsize=256)
-    publish_date = Date(default='TODAY')
-    content = String(required=True, fulltextindexed=True)
-    entry_of = SubjectRelation('Blog', cardinality='?*')
-
-The first step is the import of the EntityType (generic class for entity and 
-attributes that will be used in both Blog and BlogEntry entities. 
-
-A Blog has a title and a description. The title is a string that is
-required and must be less than 50 characters.  The
-description is a string that is not constrained.
-
-A BlogEntry has a title, a publish_date and a content. The title is a
-string that is required and must be less than 100 characters. The
-publish_date is a Date with a default value of TODAY, meaning that
-when a BlogEntry is created, its publish_date will be the current day
-unless it is modified. The content is a string that will be indexed in
-the database full-text index and has no constraint.
-
-A BlogEntry also has a relationship ``entry_of`` that links it to a
-Blog. The cardinality ``?*`` means that a BlogEntry can be part of
-zero or one Blog (``?`` means `zero or one`) and that a Blog can
-have any number of BlogEntry (``*`` means `any number including
-zero`). For completeness, remember that ``+`` means `one or more`.
-
-
-.. _ExploreYourInstance:
-
-Create and explore your instance
---------------------------------
-.. _CreateYourInstance:
-
-Create your instance
-~~~~~~~~~~~~~~~~~~~~
-
-To use this cube as an instance and create a new instance named ``blogdemo``, do::
-
-  cubicweb-ctl create blog blogdemo
-
-This command will create the corresponding database and initialize it.
-
-
-.. _WelcomeToYourWebInstance:
-
-Welcome to your web instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Start your instance in debug mode with the following command: ::
-
-  cubicweb-ctl start -D blogdemo
-
-
-You can now access your web instance to create blogs and post messages
-by visiting the URL http://localhost:8080/.
-
-A login form will appear. By default, the instance will not allow anonymous
-users to enter the instance. To login, you need then use the admin account
-you created at the time you initialized the database with ``cubicweb-ctl
-create``.
-
-.. image:: ../../images/login-form.png
-
-
-Once authenticated, you can start playing with your instance
-and create entities.
-
-.. image:: ../../images/blog-demo-first-page.png
-
-Please notice that so far, the *CubicWeb* framework managed all aspects of
-the web application based on the schema provided at the beginning.
-
-.. _AddEntities:
-
-Add entities
-~~~~~~~~~~~~
-
-We will now add entities in our web application.
-
-Add a Blog
-**********
-
-Let us create a few of these entities. Click on the `[+]` at the left of the
-link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
-``everything about technology`` as the description, then validate the form by
-clicking on ``Validate``.
-
-.. image:: ../../images/cbw-create-blog.en.png
-   :alt: from to create blog
-
-Click on the logo at top left to get back to the home page, then
-follow the Blog link that will list for you all the existing Blog.
-You should be seeing a list with a single item ``Tech-blog`` you
-just created.
-
-.. image:: ../../images/cbw-list-one-blog.en.png
-   :alt: displaying a list of a single blog
-
-Clicking on this item will get you to its detailed description except
-that in this case, there is not much to display besides the name and
-the phrase ``everything about technology``.
-
-Now get back to the home page by clicking on the top-left logo, then
-create a new Blog called ``MyLife`` and get back to the home page
-again to follow the Blog link for the second time. The list now
-has two items.
-
-.. image:: ../../images/cbw-list-two-blog.en.png
-   :alt: displaying a list of two blogs
-
-Add a BlogEntry
-***************
-
-Get back to the home page and click on [+] at the left of the link
-BlogEntry. Call this new entry ``Hello World`` and type in some text
-before clicking on ``Validate``. You added a new blog entry without
-saying to what blog it belongs. There is a box on the left entitled
-``actions``, click on the menu item ``modify``. You are back to the form
-to edit the blog entry you just created, except that the form now has
-another section with a combobox titled ``add relation``. Chose
-``entry_of`` in this menu and a second combobox appears where you pick
-``MyLife``.
-
-You could also have, at the time you started to fill the form for a
-new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
-combobox titled ``add relation`` would have showed up.
-
-
-.. image:: ../../images/cbw-add-relation-entryof.en.png
-   :alt: editing a blog entry to add a relation to a blog
-
-Validate the changes by clicking ``Validate``. The entity BlogEntry
-that is displayed now includes a link to the entity Blog named
-``MyLife``.
-
-.. image:: ../../images/cbw-detail-one-blogentry.en.png
-   :alt: displaying the detailed view of a blogentry
-
-Note that all of this was handled by the framework and that the only input
-that was provided so far is the schema. To get a graphical view of the schema,
-point your browser to the URL http://localhost:8080/schema
-
-.. image:: ../../images/cbw-schema.en.png
-   :alt: graphical view of the schema (aka data-model)
-
-
-.. _DefineViews:
-
-Define your entity views
-------------------------
-
-Each entity defined in a model is associated with default views
-allowing different rendering of the data. You can redefine each of
-them according to your needs and preferences. So let's see how the
-views are defined.
-
-
-The view selection principle
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A view is defined by a Python class which includes:
-
-  - an identifier (all objects in *CubicWeb* are recorded in a
-    registry and this identifier will be used as a key)
-
-  - a filter to select the result sets it can be applied to
-
-A view has a set of methods complying with the `View` class interface
-(`cubicweb.common.view`).
-
-*CubicWeb* provides a lot of standard views for the type `EntityView`;
-for a complete list, read the code in directory ``cubicweb/web/views/``.
-
-A view is applied on a `result set` which contains a set of entities
-we are trying to display. *CubicWeb* uses a selector mechanism which
-computes for each available view a score: the view with the highest
-score is then used to display the given `result set`.  The standard
-library of selectors is in ``cubicweb.selector``.
-
-It is possible to define multiple views for the same identifier
-and to associate selectors and filters to allow the application
-to find the most appropriate way to render the data.
-
-For example, the view named ``primary`` is the one used to display a
-single entity. We will now show you how to create a primary view for
-BlogEntry.
-
-
-Primary view customization
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you wish to modify the way a `BlogEntry` is rendered, you will have
-to subclass the `primary` view, for instance in the module ``views``
-of the cube ``cubes/blog/views.py``.
-
-The standard primary view is the most sophisticated view of all. It
-has more than a call() method. It is a template. Actually the entry
-point calls the following sequence of (redefinable) methods:
-
- * render_entity_title
-
- * render_entity_metadata
-
- * render_entity_attributes
-
- * render_entity_relations
-
- * render_side_boxes
-
-Excepted side boxes, we can see all of them already in action in the
-blog entry view. This is all described in more details in
-:ref:`primary`.
-
-We can for example add in front of the publication date a prefix
-specifying that the date we see is the publication date.
-
-To do so, please apply the following changes:
-
-.. sourcecode:: python
-
-  from cubicweb.selectors import implements
-  from cubicweb.web.views import primary
-
-  class BlogEntryPrimaryView(primary.PrimaryView):
-      __select__ = implements('BlogEntry')
-
-      def render_entity_attributes(self, entity):
-          self.w(u'<p>published on %s</p>' %
-                 entity.publish_date.strftime('%Y-%m-%d'))
-          super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
-
-.. note::
-  When a view is modified, it is not required to restart the instance
-  server. Save the Python file and reload the page in your web browser
-  to view the changes.
-
-You can now see that the publication date has a prefix.
-
-.. image:: ../../images/cbw-update-primary-view.en.png
-   :alt: modified primary view
-
-
-The above source code defines a new primary view for ``BlogEntry``.
-
-Since views are applied to result sets and result sets can be tables of
-data, we have to recover the entity from its (row,col)-coordinates.
-The view has a ``self.w()`` method that is used to output data, in our
-example HTML output.
-
-.. note::
-   You can find more details about views and selectors in :ref:`Views`.
-
-
-.. _DefineEntities:
-
-Write entities to add logic in your data
-----------------------------------------
-
-By default, CubicWeb provides a default entity for each data type defined in the schema. 
-A default entity mainly contains the attributes defined in the data model. 
-
-You can redefine each entity to provide additional functions to help you write your views. 
-
-.. sourcecode:: python
-
-    from cubicweb.entities import AnyEntity
-
-    class BlogEntry(AnyEntity):
-        """customized class for BlogEntry entities"""
-    	__regid__ = 'BlogEntry'
-    	__implements__ = AnyEntity.__implements__ 
-
-        def display_cw_logo(self):
-            if 'CW' in self.title:
-                return True
-            else:	
-                return False
-
-Customizing an entity requires that your entity:
- - inherits from ``cubicweb.entities`` or any subclass
- - defines a ``__regid__`` linked to the corresponding data type of your schema
- - implements the base class by explicitly using ``__implements__``.
-
-We implemented here a function ``display_cw_logo`` which tests if the blog entry title contains 'CW'.
-This function can then be used when you customize your views. For instance, you can modify your previous ``views.py`` as follows:
-
-.. sourcecode:: python
-
- class BlogEntryPrimaryView(primary.PrimaryView):
-     __select__ = implements('BlogEntry')
-
-     ...
-
-     def render_entity_title(self, entity):
-	 if entity.display_cw_logo():
-	     self.w(u'<image src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
-	 super(BlogEntryPrimaryView, self).render_entity_title(entity)
-
-Then each blog entry whose title contains 'CW' is shown with the CubicWeb logo in front of it.
-
-.. _UpdatingSchemaAndSynchronisingInstance:
-
-Updating the schema and synchronising the instance
---------------------------------------------------
-
-While developping your cube, you may want to update your data model. Let's say you
-want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
-
-The required steps are:
-1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
-
-.. sourcecode:: python
-
- class Blog(EntityType):
-   title = String(maxsize=50, required=True)
-   description = String()
-   category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
-
-2. stop your ``blogdemo`` instance
-
-3. start the cubicweb shell for your instance by running the following command:
-
-.. sourcecode:: bash
-
-  cubicweb-ctl shell blogdemo
-
-4. in the shell, execute:
-
-.. sourcecode:: python
-
- add_attribute('Blog', 'category')
-
-5. you can restart your instance, modify a blog entity and check that the new attribute 
-``category`` has been added.
-
-Of course, you may also want to add relations, entity types, ... See :ref:`migration`
-for a list of all available migration commands.
-
--- a/doc/book/en/intro/tutorial/index.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Tutorial:
-
-Tutorial
-========
-
-*CubicWeb* is a semantic web application framework that favors reuse and
-object-oriented design.
-
-A `cube` is a component that includes a model defining the data types and a set of
-views to display the data. A cube can be built by assembling other cubes.
-
-An `instance` is a specific installation of a cube and includes configuration
-files.
-
-
-This tutorial will show how to create a `cube` and how to use it as an
-application to run an `instance`.
-
-.. toctree::
-   :maxdepth: 2
-
-   blog-in-five-minutes
-   create-cube
-   components
-   maintemplate
-   conclusion
--- a/doc/book/en/intro/tutorial/maintemplate.rst	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Templates
----------
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below:
-
-.. image:: ../../images/lax-book.06-main-template-layout.en.png
-
-In this section we will demonstrate a change in one of the main
-interesting template from the three you will look for,
-that is to say, the HTMLPageHeader, the HTMLPageFooter
-and the TheMainTemplate.
-
-
-Customize a template
-~~~~~~~~~~~~~~~~~~~~
-
-Based on the diagram below, each template can be overriden
-by your customized template. To do so, we recommand you create
-a Python module ``blog.views.templates`` to keep it organized.
-In this module you will have to import the parent class you are
-interested as follows: ::
-
-  from cubicweb.web.views.basetemplates import HTMLPageHeader, \
-                                    HTMLPageFooter, TheMainTemplate
-
-and then create your sub-class::
-
-  class MyBlogHTMLPageHeader(HTMLPageHeader):
-      ...
-
-Customize header
-`````````````````
-
-Let's now move the search box in the header and remove the login form from the
-header. We'll show how to move it to the left column of the user interface.
-
-Let's say we do not want anymore the login menu to be in the header
-
-First, to remove the login menu, we just need to comment out the display of the
-login graphic component such as follows:
-
-.. sourcecode:: python
-
-  class MyBlogHTMLPageHeader(HTMLPageHeader):
-
-      def main_header(self, view):
-          """build the top menu with authentification info and the rql box"""
-          self.w(u'<table id="header"><tr>\n')
-          self.w(u'<td id="firstcolumn">')
-          self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
-          self.w(u'</td>\n')
-          # appliname and breadcrumbs
-          self.w(u'<td id="headtext">')
-          comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w)
-          comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
-          if comp and comp.propval('visible'):
-              comp.dispatch(w=self.w, view=view)
-          self.w(u'</td>')
-          # logged user and help
-          #self.w(u'<td>\n')
-          #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
-          #comp.dispatch(w=self.w)
-          #self.w(u'</td><td>')
-
-          self.w(u'<td>')
-          helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
-          if helpcomp: # may not be available if Card is not defined in the schema
-              helpcomp.dispatch(w=self.w)
-          self.w(u'</td>')
-          # lastcolumn
-          self.w(u'<td id="lastcolumn">')
-          self.w(u'</td>\n')
-          self.w(u'</tr></table>\n')
-          self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
-                        title=False, message=False)
-
-
-
-.. image:: ../../images/lax-book.06-header-no-login.en.png
-
-Customize footer
-````````````````
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in:
-
-.. sourcecode:: python
-
-  from cubicweb.web.views.basetemplates import HTMLPageFooter
-
-  class MyHTMLPageFooter(HTMLPageFooter):
-
-      def call(self, **kwargs):
-          self.w(u'<div class="footer">')
-          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
-          self.w(u'</div>')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
-```````````````
-
-.. _TheMainTemplate:
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
-
-TheMainTemplate is responsible for the general layout of the entire application.
-It defines the template of ``__regid__ = main`` that is used by the application. Is
-also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
-be used based on TheMainTemplate called SimpleMainTemplate which does not have
-a top section.
-
-.. image:: ../../images/lax-book.06-simple-main-template.en.png
-
-XXX
-[WRITE ME]
-
-* customize MainTemplate and show that everything in the user
-  interface can be changed
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/standard_theme/layout.html	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,13 @@
+{% extends "basic/layout.html" %}
+
+{% block header %}
+<div class="header">
+ <a href="http://www.cubicweb.org">
+  <img alt="cubicweb logo" src="{{ pathto('_static/cubicweb.png', 1) }}"/>
+ </a>
+</div>
+{% endblock %}
+
+{# puts the sidebar into "sidebar1" block i.e. before the document body #}
+{% block sidebar1 %}{{ sidebar() }}{% endblock %}
+{% block sidebar2 %}{% endblock %}
Binary file doc/book/en/standard_theme/static/contents.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/standard_theme/static/lglb-sphinx-doc.css	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,359 @@
+/**
+ * Sphinx stylesheet -- CubicWeb theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Inspired from sphinxdoc original theme and logilab theme.
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+    font-family: 'Bitstream Vera Sans', 'Lucida Grande', 'Lucida Sans Unicode',
+    'Geneva', 'Verdana', sans-serif;
+    font-size: 14px;
+    line-height: 150%;
+    text-align: center;
+    padding: 0;
+}
+
+div.document {
+    text-align: left;
+}
+
+div.bodywrapper {
+    margin: 0 0 0 230px;
+    border-left: 1px solid #CCBCA7;
+}
+
+div.body {
+    margin: 0;
+    padding: 0.5em 20px 20px 20px;
+}
+
+div.header {
+    text-align: left;
+ }
+
+div.related {
+    background-color: #FF7700;
+    color: white;
+    font-weight: bolder;
+    font-size: 1em;
+}
+
+div.related a {
+    color: white;
+}
+
+div.related ul {
+    height: 2em;
+    border-top: 1px solid #CCBCA7;
+    border-bottom: 1px solid #CCBCA7;
+}
+
+div.related ul li {
+    margin: 0;
+    padding: 0;
+    height: 2em;
+    float: left;
+}
+
+div.related ul li.right {
+    float: right;
+    margin-right: 5px;
+}
+
+div.related ul li a {
+    margin: 0;
+    padding: 0 5px 0 5px;
+    line-height: 1.75em;
+}
+
+div.sphinxsidebarwrapper {
+    padding: 0;
+}
+
+div.sphinxsidebar {
+    margin: 0;
+    padding: 5px 10px 5px 10px;
+    width: 210px;
+    float: left;
+    font-size: 1em;
+    text-align: left;
+}
+
+div.sphinxsidebar h3, div.sphinxsidebar h4 {
+    font-size: 1.2em;
+    font-style: italic;
+}
+
+div.sphinxsidebar ul {
+    padding-left: 1.5em;
+    margin-top: 15px;
+    padding: 0;
+    line-height: 130%;
+    font-weight: bold;
+}
+
+div.sphinxsidebar ul ul {
+    margin-left: 20px;
+    font-weight: normal;
+}
+
+div.sphinxsidebar li {
+    margin: 0;
+}
+
+div.sphinxsidebar input {
+    border: 1px solid #CCBCA7;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+div.footer {
+    color: orangered;
+    padding: 3px 8px 3px 0;
+    clear: both;
+    font-size: 0.8em;
+    text-align: center;
+}
+
+div.footer a {
+    text-decoration: underline;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+p {
+    margin: 0.8em 0 0 0;
+}
+
+ul, ol {
+    margin: 0;
+}
+
+li {
+    margin: 0.2em 0 0 0;
+}
+
+a {
+    color: orangered;
+    text-decoration: none;
+}
+
+div.sphinxsidebar a {
+    color: black;
+    text-decoration: none;
+}
+
+a:hover {
+    text-decoration: underline;
+}
+
+h1 {
+    margin: 0;
+    padding: 0.7em 0 0.3em 0;
+    font-size: 1.5em;
+    border-bottom: 1px dotted;
+}
+
+h2 {
+    margin: 1.3em 0 0.2em 0;
+    font-size: 1.35em;
+    padding: 0;
+    color: #303030;
+}
+
+h3 {
+    margin: 1em 0 -0.3em 0;
+    font-size: 1.2em;
+    color: #202020;
+}
+
+div.body h1 a {
+    color: #404040!important;
+}
+
+div.body h2 a {
+    color: #303030!important;
+}
+
+div.body h3 a {
+    color: #202020!important;
+}
+
+div.body h4 a, div.body h5 a, div.body h6 a {
+    color: #000000!important;
+}
+
+h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
+    display: none;
+    margin: 0 0 0 0.3em;
+    padding: 0 0.2em 0 0.2em;
+    color: #AAA!important;
+}
+
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
+h5:hover a.anchor, h6:hover a.anchor {
+    display: inline;
+}
+
+h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
+h5 a.anchor:hover, h6 a.anchor:hover {
+    color: #777;
+    background-color: #EEE;
+}
+
+a.headerlink {
+    color: #C60F0F!important;
+    font-size: 1em;
+    margin-left: 6px;
+    padding: 0 4px 0 4px;
+    text-decoration: none!important;
+}
+
+a.headerlink:hover {
+    background-color: #C0C0C0;
+    color: #FFFFFF!important;
+}
+
+cite, code, tt {
+    font-family: 'Consolas', 'Deja Vu Sans Mono',
+                 'Bitstream Vera Sans Mono', monospace;
+    font-size: 0.95em;
+    letter-spacing: 0.01em;
+}
+
+tt {
+    background-color: #F0F0F0;
+    border-bottom: 1px solid #D0D0D0;
+}
+
+tt.descname, tt.descclassname, tt.xref {
+    background-color: #F0F0F0;
+    font-weight: normal;
+    font-size: 1em;
+    border: 1px solid #D0D0D0;
+    border: 0;
+}
+
+hr {
+    border: 1px solid #CC8B00;
+    margin: 2em;
+}
+
+a tt {
+    border: 0;
+    color: #B45300;
+}
+
+a:hover tt {
+    color: #4BACFF;
+}
+
+pre {
+    font-family: 'Consolas', 'Deja Vu Sans Mono',
+                 'Bitstream Vera Sans Mono', monospace;
+    font-size: 0.95em;
+    letter-spacing: 0.015em;
+    line-height: 120%;
+    padding: 0.5em;
+    border: 1px solid #CCBCA7;
+    background-color: #F0F0F0;
+}
+
+pre a {
+    color: inherit;
+    text-decoration: underline;
+}
+
+td.linenos pre {
+    padding: 0.5em 0;
+}
+
+div.quotebar {
+    background-color: #F8F8F8;
+    max-width: 250px;
+    float: right;
+    padding: 2px 7px;
+    border: 1px solid #C0C0C0;
+}
+
+div.topic {
+    background-color: #F8F8F8;
+}
+
+table {
+    border-collapse: collapse;
+    margin: 0.8em -0.5em 0em -0.5em;
+}
+
+table td, table th {
+    padding: 0.2em 0.5em 0.2em 0.5em;
+}
+
+div.admonition, div.warning {
+    font-size: 0.9em;
+    margin: 1em 0 1em 0;
+    padding: 0;
+}
+
+div.admonition {
+    border: 1px solid #86989B;
+    background-color: #EBEBFF;
+}
+
+div.warning {
+    border: 1px solid #940000;
+    background-color: #FFEBEB;
+}
+
+div.admonition p, div.warning p {
+    margin: 0.5em 1em 0.5em 1em;
+    padding: 0;
+}
+
+div.admonition pre, div.warning pre {
+    margin: 0.4em 1em 0.4em 1em;
+}
+
+div.admonition p.admonition-title,
+div.warning p.admonition-title {
+    margin: 0;
+    padding: 0.1em 0 0.1em 0.5em;
+    color: #FFFFFF;
+    font-weight: bold;
+}
+
+div.admonition p.admonition-title {
+    border-bottom: 1px solid #86989B;
+    background-color: #8C88B5;
+}
+
+div.warning p.admonition-title {
+    background-color: #CF0000;
+    border-bottom: 1px solid #940000;
+}
+
+div.admonition ul, div.admonition ol,
+div.warning ul, div.warning ol {
+    margin: 0.1em 0.5em 0.5em 3em;
+    padding: 0;
+}
+
+div.versioninfo {
+    margin: 1em 0 0 0;
+    border: 1px solid #C0C0C0;
+    background-color: #DDEAF0;
+    padding: 8px;
+    line-height: 1.3em;
+    font-size: 0.9em;
+}
+
+/* TOC trees */
+
+li.toctree-l1 {
+    margin-top: 0.4em;
+ }
\ No newline at end of file
Binary file doc/book/en/standard_theme/static/logilab_logo.png has changed
Binary file doc/book/en/standard_theme/static/navigation.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/standard_theme/theme.conf	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,4 @@
+[theme]
+inherit = basic
+stylesheet = lglb-sphinx-doc.css
+pygments_style = friendly
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,586 @@
+.. _advanced_tutorial:
+
+Building a photo gallery with CubicWeb
+======================================
+
+Desired features
+----------------
+
+* basically a photo gallery
+
+* photo stored onto the fs and displayed dynamically through a web interface
+
+* navigation through folder (album), tags, geographical zone, people on the
+  picture... using facets
+
+* advanced security (eg 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 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 represent in my web-site is somewhat already modelized in
+some cube that I'll extend for my need. So I'll pick the following cubes:
+
+* `folder`, containing `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 a 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 cubes/sytweb/__pkginfo__.py:
+
+  .. sourcecode:: python
+
+    __depends_cubes__ = {'file': '>= 1.2.0',
+			 'folder': '>= 1.1.0',
+			 'person': '>= 1.2.0',
+			 'comment': '>= 1.2.0',
+			 'tag': '>= 1.2.0',
+			 'zone': None,
+			 }
+    __depends__ = {'cubicweb': '>= 3.5.10',
+		   }
+    for key,value in __depends_cubes__.items():
+	__depends__['cubicweb-'+key] = value
+    __use__ = tuple(__depends_cubes__)
+
+Notice that you can express minimal version of the cube that should be used,
+`None` meaning whatever version available.
+
+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 make schema evolving is one of CubicWeb
+feature (and goal), we won't worry and see that later when needed.
+
+
+Step 4: creating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now that I've a schema, I want to create an instance so I can start To
+create an instance 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 reanswer to questions by runing ::
+
+   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...
+
+
+Security, testing and migration
+-------------------------------
+
+This post 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 user can see people
+* everyone can  see classifier entities, eg tag and zone
+
+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. 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 it the expression return some results. To implements the read
+security defined earlier, groups are not enough, we'll need 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'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):
+	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
+
+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 trick part of the requirement is in *unless explicitly specified*, notably
+because when the entity addition hook is added, we don't know yet its 'parent'
+entity (eg 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 implements
+    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__ & implements('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: ::
+
+    [syt@scorpius test]$ 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: ::
+
+    [syt@scorpius test]$ 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: ::
+
+    [syt@scorpius test]$ rm tmpdb*
+
+
+.. Note::
+  pytest is a very convenient utilities to control test execution, 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, Iv'e 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 later instruction)
+
+
+To migrate my instance I simply type::
+
+   [syt@scorpius ~]$ 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/base/blog-in-five-minutes.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,42 @@
+.. -*- coding: utf-8 -*-
+
+.. _BlogFiveMinutes:
+
+Get a blog running in five minutes!
+-----------------------------------
+
+For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`)::
+
+    cubicweb, cubicweb-dev, cubicweb-blog
+
+For Windows or Mac OS X users, you must install cubicweb from source (see :ref:`SourceInstallation` and  :ref:`WindowsInstallation`).
+
+Then create and initialize your instance::
+
+    cubicweb-ctl create blog myblog
+
+And start it::
+
+    cubicweb-ctl start -D myblog
+
+The -D option is the debugging mode of cubicweb, removing it will lauch the instance in the background.
+
+Permission
+~~~~~~~~~~
+
+This command assumes that you have root access to the /etc/ path. In order to initialize your instance as a `user` (from scratch), please check your current PYTHONPATH then create the ~/etc/cubicweb.d directory.
+
+Instance parameters
+~~~~~~~~~~~~~~~~~~~
+
+If the database installation failed, you'd like to change some instance parameters, for example, the database host or the user name. These informations can be edited in the `source` file located in the /etc/cubicweb.d/myblog directory.
+
+Then relaunch the database creation:
+
+     cubicweb-ctl db-create myblog
+
+Other paramaters, like web server or emails parameters, can be modified in the `all-in-one.conf` file.
+
+This is it. Your blog is running. Visit http://localhost:8080 and enjoy it! This blog is fully functionnal. The next section section will present the way to develop new cubes and customizing the look of your instance.
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/components.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,79 @@
+.. -*- coding: utf-8 -*-
+
+.. _cubes:
+
+Cubes
+-----
+
+Standard library
+~~~~~~~~~~~~~~~~
+
+A library of standard cubes are available from `CubicWeb Forge`_
+Cubes provide entities and views.
+
+The available application entities in standard cubes are:
+
+* addressbook: PhoneNumber and PostalAddress
+
+* basket: Basket (like a shopping cart)
+
+* blog: Blog (a *very* basic blog)
+
+* classfolder: Folder (to organize things but grouping them in folders)
+
+* classtags: Tag (to tag anything)
+
+* comment: Comment (to attach comment threads to entities)
+
+* file: File (to allow users to upload and store binary or text files)
+
+* link: Link (to collect links to web resources)
+
+* mailinglist: MailingList (to reference a mailing-list and the URLs
+  for its archives and its admin interface)
+
+* person: Person (easily mixed with addressbook)
+
+* task: Task (something to be done between start and stop date)
+
+* zone: Zone (to define places within larger places, for example a
+  city in a state in a country)
+
+.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
+
+Adding comments to BlogDemo
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To import a cube in your instance just change the line in the
+``__pkginfo__.py`` file and verify that the cube you are planning
+to use is listed by the command ``cubicweb-ctl list``.
+For example::
+
+    __use__ = ('comment',)
+
+will make the ``Comment`` entity available in your ``BlogDemo``
+cube.
+
+Change the schema to add a relationship between ``BlogEntry`` and
+``Comment`` and you are done. Since the comment cube defines the
+``comments`` relationship, adding the line::
+
+    comments = ObjectRelation('Comment', cardinality='1*', composite='object')
+
+to the definition of a ``BlogEntry`` will be enough.
+
+Synchronize the data model
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you modified your data model, you need to synchronize the
+database with your model. For this purpose, *CubicWeb* provides
+a very useful command ``cubicweb-ctl shell blogdemo`` which
+launches an interactive shell where you can enter migration
+commands (see :ref:`cubicweb-ctl` for more details)).
+As you added the cube named `comment`, you need to run:
+
+::
+
+  add_cube('comment')
+
+You can now start your instance and comment your blog entries.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/conclusion.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,15 @@
+.. -*- coding: utf-8 -*-
+
+What's next?
+------------
+
+We demonstrated how from a straight out of the box *CubicWeb* installation, you
+can build your web application based on a data model. It's all already there:
+views, templates, permissions, etc. The step forward is now for you to customize
+according to your needs.
+
+Many features are available to extend your application, for example: RSS channel
+integration (:ref:`XmlAndRss`), hooks (:ref:`hooks`), support of sources such as
+Google App Engine (:ref:`GoogleAppEngineSource`) and lots of others to discover
+through our book.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/create-cube.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,427 @@
+.. -*- coding: utf-8 -*-
+
+.. _Steps:
+
+Steps for creating your cube
+----------------------------
+
+The following steps will help you to create and customize a new cube.
+
+1. :ref:`CreateYourCube`
+
+Create the directory to hold the code of your cube. The most important
+files that will be useful to customize your newly created cube are:
+
+  * schema.py: contains the data model
+  * views.py: contains your custom views
+  * entities.py: contains XXX
+
+The detailed structure of the code directory is described in :ref:`cubelayout`.
+
+2. :ref:`DefineDataModel`
+
+Define the data model of your application.
+
+3. :ref:`ExploreYourInstance`
+
+Create, run, and explore an instance of your cube.
+
+4. :ref:`DefineViews`
+
+Customize the views of your data: how and which part of your data are showed.
+
+Note: views don't concern the look'n'feel or design of the site. For that, you should use CSS instead, and default CSS or your new cube are located in 'blog/data/'.
+
+
+5. :ref:`DefineEntities`
+
+Define your own entities to add useful functions when you manipulate your data, especially when you write view.
+
+
+.. _CreateYourCube:
+
+Create your cube
+----------------
+
+The packages ``cubicweb`` and ``cubicweb-dev`` install a command line
+tool for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide
+range of commands described in details in :ref:`cubicweb-ctl`.
+
+Once your *CubicWeb* development environment is set up, you can create
+a new cube::
+
+  cubicweb-ctl newcube blog
+
+This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
+installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
+a directory named ``blog`` reflecting the structure described in :ref:`Concepts`.
+
+
+For packages installation, you can still create new cubes in your home directory using the following configuration. Let's say you want to develop your new cubes in `~src/cubes`, then set the following environment variables:
+::
+
+  CW_CUBES_PATH=~/src/cubes
+  CW_MODE=user
+
+and then create your new cube using:
+::
+
+  cubicweb-ctl newcube --directory=~/src/cubes blog
+
+
+.. _DefineDataModel:
+
+Define your data model
+----------------------
+
+The data model or schema is the core of your *CubicWeb* application.
+It defines the type of content your application will handle.
+
+The data model of your cube ``blog`` is defined in the file ``schema.py``:
+
+.. sourcecode:: python
+
+  from yams.buildobjs import EntityType, String, SubjectRelation, Date
+
+  class Blog(EntityType):
+    title = String(maxsize=50, required=True)
+    description = String()
+
+  class BlogEntry(EntityType):
+    title = String(required=True, fulltextindexed=True, maxsize=256)
+    publish_date = Date(default='TODAY')
+    content = String(required=True, fulltextindexed=True)
+    entry_of = SubjectRelation('Blog', cardinality='?*')
+
+The first step is the import of the EntityType (generic class for entity and
+attributes that will be used in both Blog and BlogEntry entities.
+
+A Blog has a title and a description. The title is a string that is
+required and must be less than 50 characters.  The
+description is a string that is not constrained.
+
+A BlogEntry has a title, a publish_date and a content. The title is a
+string that is required and must be less than 100 characters. The
+publish_date is a Date with a default value of TODAY, meaning that
+when a BlogEntry is created, its publish_date will be the current day
+unless it is modified. The content is a string that will be indexed in
+the database full-text index and has no constraint.
+
+A BlogEntry also has a relationship ``entry_of`` that links it to a
+Blog. The cardinality ``?*`` means that a BlogEntry can be part of
+zero or one Blog (``?`` means `zero or one`) and that a Blog can
+have any number of BlogEntry (``*`` means `any number including
+zero`). For completeness, remember that ``+`` means `one or more`.
+
+
+.. _ExploreYourInstance:
+
+Create and explore your instance
+--------------------------------
+.. _CreateYourInstance:
+
+Create your instance
+~~~~~~~~~~~~~~~~~~~~
+
+To use this cube as an instance and create a new instance named ``blogdemo``, do::
+
+  cubicweb-ctl create blog blogdemo
+
+This command will create the corresponding database and initialize it.
+
+
+.. _WelcomeToYourWebInstance:
+
+Welcome to your web instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Start your instance in debug mode with the following command: ::
+
+  cubicweb-ctl start -D blogdemo
+
+
+You can now access your web instance to create blogs and post messages
+by visiting the URL http://localhost:8080/.
+
+A login form will appear. By default, the instance will not allow anonymous
+users to enter the instance. To login, you need then use the admin account
+you created at the time you initialized the database with ``cubicweb-ctl
+create``.
+
+.. image:: ../../images/login-form.png
+
+
+Once authenticated, you can start playing with your instance
+and create entities.
+
+.. image:: ../../images/blog-demo-first-page.png
+
+Please notice that so far, the *CubicWeb* framework managed all aspects of
+the web application based on the schema provided at the beginning.
+
+.. _AddEntities:
+
+Add entities
+~~~~~~~~~~~~
+
+We will now add entities in our web application.
+
+Add a Blog
+**********
+
+Let us create a few of these entities. Click on the `[+]` at the left of the
+link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
+``everything about technology`` as the description, then validate the form by
+clicking on ``Validate``.
+
+.. image:: ../../images/cbw-create-blog_en.png
+   :alt: from to create blog
+
+Click on the logo at top left to get back to the home page, then
+follow the Blog link that will list for you all the existing Blog.
+You should be seeing a list with a single item ``Tech-blog`` you
+just created.
+
+.. image:: ../../images/cbw-list-one-blog_en.png
+   :alt: displaying a list of a single blog
+
+Clicking on this item will get you to its detailed description except
+that in this case, there is not much to display besides the name and
+the phrase ``everything about technology``.
+
+Now get back to the home page by clicking on the top-left logo, then
+create a new Blog called ``MyLife`` and get back to the home page
+again to follow the Blog link for the second time. The list now
+has two items.
+
+.. image:: ../../images/cbw-list-two-blog_en.png
+   :alt: displaying a list of two blogs
+
+Add a BlogEntry
+***************
+
+Get back to the home page and click on [+] at the left of the link
+BlogEntry. Call this new entry ``Hello World`` and type in some text
+before clicking on ``Validate``. You added a new blog entry without
+saying to what blog it belongs. There is a box on the left entitled
+``actions``, click on the menu item ``modify``. You are back to the form
+to edit the blog entry you just created, except that the form now has
+another section with a combobox titled ``add relation``. Chose
+``entry_of`` in this menu and a second combobox appears where you pick
+``MyLife``.
+
+You could also have, at the time you started to fill the form for a
+new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
+combobox titled ``add relation`` would have showed up.
+
+
+.. image:: ../../images/cbw-add-relation-entryof_en.png
+   :alt: editing a blog entry to add a relation to a blog
+
+Validate the changes by clicking ``Validate``. The entity BlogEntry
+that is displayed now includes a link to the entity Blog named
+``MyLife``.
+
+.. image:: ../../images/cbw-detail-one-blogentry_en.png
+   :alt: displaying the detailed view of a blogentry
+
+Note that all of this was handled by the framework and that the only input
+that was provided so far is the schema. To get a graphical view of the schema,
+point your browser to the URL http://localhost:8080/schema
+
+.. image:: ../../images/cbw-schema_en.png
+   :alt: graphical view of the schema (aka data-model)
+
+
+.. _DefineViews:
+
+Define your entity views
+------------------------
+
+Each entity defined in a model is associated with default views
+allowing different renderings of the data. You can redefine each of
+them according to your needs and preferences. So let's see how the
+views are defined.
+
+
+The view selection principle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A view is defined by a Python class which includes:
+
+  - an identifier (all objects in *CubicWeb* are recorded in a
+    registry and this identifier will be used as a key)
+
+  - a filter to select the result sets it can be applied to
+
+A view has a set of methods complying with the `View` class interface
+(`cubicweb.common.view`).
+
+*CubicWeb* provides a lot of standard views for the type `EntityView`;
+for a complete list, read the code in directory ``cubicweb/web/views/``.
+
+A view is applied on a `result set` which contains a set of entities
+we are trying to display. *CubicWeb* uses a selector mechanism which
+computes for each available view a score: the view with the highest
+score is then used to display the given `result set`.  The standard
+library of selectors is in ``cubicweb.selector``.
+
+It is possible to define multiple views for the same identifier
+and to associate selectors and filters to allow the application
+to find the most appropriate way to render the data.
+
+For example, the view named ``primary`` is the one used to display a
+single entity. We will now show you how to create a primary view for
+BlogEntry.
+
+
+Primary view customization
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you wish to modify the way a `BlogEntry` is rendered, you will have
+to subclass the `primary` view, for instance in the module ``views``
+of the cube ``cubes/blog/views.py``.
+
+The standard primary view is the most sophisticated view of all. It
+has more than a call() method. It is a template. Actually the entry
+point calls the following sequence of (redefinable) methods:
+
+ * render_entity_title
+
+ * render_entity_metadata
+
+ * render_entity_attributes
+
+ * render_entity_relations
+
+ * render_side_boxes
+
+Excepted side boxes, we can see all of them already in action in the
+blog entry view. This is all described in more details in
+:ref:`primary_view`.
+
+We can for example add in front of the publication date a prefix
+specifying that the date we see is the publication date.
+
+To do so, please apply the following changes:
+
+.. sourcecode:: python
+
+  from cubicweb.selectors import implements
+  from cubicweb.web.views import primary
+
+  class BlogEntryPrimaryView(primary.PrimaryView):
+      __select__ = implements('BlogEntry')
+
+      def render_entity_attributes(self, entity):
+          self.w(u'<p>published on %s</p>' %
+                 entity.publish_date.strftime('%Y-%m-%d'))
+          super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
+
+.. note::
+  When a view is modified, it is not required to restart the instance
+  server. Save the Python file and reload the page in your web browser
+  to view the changes.
+
+You can now see that the publication date has a prefix.
+
+.. image:: ../../images/cbw-update-primary-view_en.png
+   :alt: modified primary view
+
+
+The above source code defines a new primary view for ``BlogEntry``.
+
+Since views are applied to result sets and result sets can be tables of
+data, we have to recover the entity from its (row,col)-coordinates.
+The view has a ``self.w()`` method that is used to output data, in our
+example HTML output.
+
+.. note::
+   You can find more details about views and selectors in :ref:`Views`.
+
+
+.. _DefineEntities:
+
+Write entities to add logic in your data
+----------------------------------------
+
+By default, CubicWeb provides a default entity for each data type defined in the schema.
+A default entity mainly contains the attributes defined in the data model.
+
+You can redefine each entity to provide additional functions to help you write your views.
+
+.. sourcecode:: python
+
+    from cubicweb.entities import AnyEntity
+
+    class BlogEntry(AnyEntity):
+        """customized class for BlogEntry entities"""
+    	__regid__ = 'BlogEntry'
+    	__implements__ = AnyEntity.__implements__
+
+        def display_cw_logo(self):
+            if 'CW' in self.title:
+                return True
+            else:
+                return False
+
+Customizing an entity requires that your entity:
+ - inherits from ``cubicweb.entities`` or any subclass
+ - defines a ``__regid__`` linked to the corresponding data type of your schema
+ - implements the base class by explicitly using ``__implements__``.
+
+We implemented here a function ``display_cw_logo`` which tests if the blog entry title contains 'CW'.
+This function can then be used when you customize your views. For instance, you can modify your previous ``views.py`` as follows:
+
+.. sourcecode:: python
+
+ class BlogEntryPrimaryView(primary.PrimaryView):
+     __select__ = implements('BlogEntry')
+
+     ...
+
+     def render_entity_title(self, entity):
+	 if entity.display_cw_logo():
+	     self.w(u'<image src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
+	 super(BlogEntryPrimaryView, self).render_entity_title(entity)
+
+Then each blog entry whose title contains 'CW' is shown with the CubicWeb logo in front of it.
+
+.. _UpdatingSchemaAndSynchronisingInstance:
+
+Updating the schema and synchronising the instance
+--------------------------------------------------
+
+While developping your cube, you may want to update your data model. Let's say you
+want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
+
+The required steps are:
+1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
+
+.. sourcecode:: python
+
+ class Blog(EntityType):
+   title = String(maxsize=50, required=True)
+   description = String()
+   category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
+
+2. stop your ``blogdemo`` instance
+
+3. start the cubicweb shell for your instance by running the following command:
+
+.. sourcecode:: bash
+
+  cubicweb-ctl shell blogdemo
+
+4. in the shell, execute:
+
+.. sourcecode:: python
+
+ add_attribute('Blog', 'category')
+
+5. you can restart your instance, modify a blog entity and check that the new attribute
+``category`` has been added.
+
+Of course, you may also want to add relations, entity types, ... See :ref:`migration`
+for a list of all available migration commands.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,30 @@
+.. -*- coding: utf-8 -*-
+
+.. _Tutorial:
+
+.. _tuto_blog:
+
+Building a simple blog
+======================
+
+*CubicWeb* is a semantic web application framework that favors reuse and
+object-oriented design.
+
+A `cube` is a component that includes a model defining the data types and a set of
+views to display the data. A cube can be built by assembling other cubes.
+
+An `instance` is a specific installation of a cube and includes configuration
+files.
+
+
+This tutorial will show how to create a `cube` and how to use it as an
+application to run an `instance`.
+
+.. toctree::
+   :maxdepth: 2
+
+   blog-in-five-minutes
+   create-cube
+   components
+   maintemplate
+   conclusion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/maintemplate.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,131 @@
+.. -*- coding: utf-8 -*-
+
+Templates
+---------
+
+Look at ``cubicweb/web/views/basetemplates.py`` and you will
+find the base templates used to generate HTML for your application.
+
+A page is composed as indicated on the schema below:
+
+.. image:: ../../images/lax-book_06-main-template-layout_en.png
+
+In this section we will demonstrate a change in one of the main
+interesting template from the three you will look for,
+that is to say, the HTMLPageHeader, the HTMLPageFooter
+and the TheMainTemplate.
+
+
+Customize a template
+~~~~~~~~~~~~~~~~~~~~
+
+Based on the diagram below, each template can be overriden
+by your customized template. To do so, we recommand you create
+a Python module ``blog.views.templates`` to keep it organized.
+In this module you will have to import the parent class you are
+interested as follows: ::
+
+  from cubicweb.web.views.basetemplates import HTMLPageHeader, \
+                                    HTMLPageFooter, TheMainTemplate
+
+and then create your sub-class::
+
+  class MyBlogHTMLPageHeader(HTMLPageHeader):
+      ...
+
+Customize header
+`````````````````
+
+Let's now move the search box in the header and remove the login form from the
+header. We'll show how to move it to the left column of the user interface.
+
+Let's say we do not want anymore the login menu to be in the header
+
+First, to remove the login menu, we just need to comment out the display of the
+login graphic component such as follows:
+
+.. sourcecode:: python
+
+  class MyBlogHTMLPageHeader(HTMLPageHeader):
+
+      def main_header(self, view):
+          """build the top menu with authentification info and the rql box"""
+          self.w(u'<table id="header"><tr>\n')
+          self.w(u'<td id="firstcolumn">')
+          self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
+          self.w(u'</td>\n')
+          # appliname and breadcrumbs
+          self.w(u'<td id="headtext">')
+          comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w)
+          comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
+          if comp and comp.propval('visible'):
+              comp.dispatch(w=self.w, view=view)
+          self.w(u'</td>')
+          # logged user and help
+          #self.w(u'<td>\n')
+          #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
+          #comp.dispatch(w=self.w)
+          #self.w(u'</td><td>')
+
+          self.w(u'<td>')
+          helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
+          if helpcomp: # may not be available if Card is not defined in the schema
+              helpcomp.dispatch(w=self.w)
+          self.w(u'</td>')
+          # lastcolumn
+          self.w(u'<td id="lastcolumn">')
+          self.w(u'</td>\n')
+          self.w(u'</tr></table>\n')
+          self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
+                        title=False, message=False)
+
+
+
+.. image:: ../../images/lax-book_06-header-no-login_en.png
+
+Customize footer
+````````````````
+
+If you want to change the footer for example, look
+for HTMLPageFooter and override it in your views file as in:
+
+.. sourcecode:: python
+
+  from cubicweb.web.views.basetemplates import HTMLPageFooter
+
+  class MyHTMLPageFooter(HTMLPageFooter):
+
+      def call(self, **kwargs):
+          self.w(u'<div class="footer">')
+          self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
+          self.w(u'</div>')
+
+Updating a view does not require any restart of the server. By reloading
+the page you can see your new page footer.
+
+
+TheMainTemplate
+```````````````
+
+.. _TheMainTemplate:
+
+The MainTemplate is a bit complex as it tries to accomodate many
+different cases. We are now about to go through it and cutomize entirely
+our application.
+
+TheMainTemplate is responsible for the general layout of the entire application.
+It defines the template of ``__regid__ = main`` that is used by the application. Is
+also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
+be used based on TheMainTemplate called SimpleMainTemplate which does not have
+a top section.
+
+.. image:: ../../images/lax-book_06-simple-main-template_en.png
+
+XXX
+[WRITE ME]
+
+* customize MainTemplate and show that everything in the user
+  interface can be changed
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/index.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,19 @@
+.. _Tutorials:
+
+---------
+Tutorials
+---------
+
+We present two tutorials of different levels. The blog building
+tutorial introduces one smoothly to the basic concepts.
+
+Then there is a photo gallery construction tutorial which highlights
+more advanced concepts such as unit tests, security settings,
+migration scripts.
+
+.. toctree::
+   :maxdepth: 1
+   :numbered:
+
+   base/index
+   advanced/index
--- a/doc/book/fr/conf.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/fr/conf.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 #
 # Cubicweb documentation build configuration file, created by
--- a/doc/book/mode_plan.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/book/mode_plan.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 >>> from mode_plan import *
 >>> ls()
@@ -5,7 +22,6 @@
 >>> ren('A01','A03')
 rename A010-joe.en.txt to A030-joe.en.txt
 accept [y/N]?
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 def ren(a,b):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/features_list.rst	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,224 @@
+=================
+CubicWeb features
+=================
+
+This page  tries to resume features found in the bare cubicweb framework,
+how mature and documented they are.
+
+:code maturity (CM):
+
+  - 0: experimental, not ready at all for production, may be killed
+
+  - 1: draft / unsatisfying, api may change in a near future, much probably in long
+       term
+
+  - 2: good enough, api sounds good but will probably evolve a bit with more
+    hindsight
+
+  - 3: mature, backward incompatible changes unexpected (may still evolve though,
+    of course)
+
+
+:documentation level (DL):
+
+  - 0: no documentation
+
+  - 1: poor documentation
+
+  - 2: some valuable documentation but some parts keep uncovered
+
+  - 3: good / complete documentation
+
+
+Instance configuration and maintainance
+=======================================
+
++====================================================================+====+====+
+|  FEATURE                                                           | CM | DL |
++====================================================================+====+====+
+| setup - installation                                               | 2  | 3  |
+| setup - environment variables                                      | 3  | 2  |
+| setup - running modes                                              | 2  | 2  |
+| setup - administration tasks                                       | 2  | 2  |
+| setup - configuration file                                         | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| configuration - user / groups handling                             | 3  | 1  |
+| configuration - site configuration                                 | 3  | 1  |
+| configuration - distributed configuration                          | 2  | 1  |
+| configuration - pyro                                               | 2  | 2  |
++--------------------------------------------------------------------+----+----+
+| multi-sources - capabilities                                       | NA | 0  |
+| multi-sources - configuration                                      | 2  | 0  |
+| multi-sources - ldap integration                                   | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| usage - custom ReST markup                                         | 2  | 0  |
+| usage - personal preferences                                       | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+
+
+Core development
+================
+
++====================================================================+====+====+
+|  FEATURE                                                           | CM | DL |
++====================================================================+====+====+
+| base - concepts                                                    | NA | 3  |
+| base - security model                                              | NA | 2  |
+| base - database initialization                                     | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| rql - base                                                         | 2  | 2  |
+| rql - write                                                        | 2  | 2  |
+| rql - function                                                     | 2  | 0  |
+| rql - outer joins                                                  | 2  | 1  |
+| rql - aggregates                                                   | 2  | 1  |
+| rql - subqueries                                                   | 2  | 0  |
++--------------------------------------------------------------------+----+----+
+| schema - base                                                      | 2  | 3  |
+| schema - constraints                                               | 3  | 2  |
+| schema - security                                                  | 2  | 2  |
+| schema - inheritance                                               | 1  | 1  |
+| schema - customization                                             | 1  | 1  |
+| schema - introspection                                             | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| vregistry - appobject                                              | 2  | 2  |
+| vregistry - registration                                           | 2  | 2  |
+| vregistry - selection                                              | 3  | 2  |
+| vregistry - core selectors                                         | 3  | 3  |
+| vregistry - custom selectors                                       | 2  | 1  |
+| vregistry - debugging selection                                    | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| entities - interfaces                                              | 2  | ?  |
+| entities - customization (dc_,...)                                 | 2  | ?  |
+| entities - app logic                                               | 2  | 2  |
+| entities - orm configuration                                       | 2  | 1  |
+| entities - pluggable mixins                                        | 1  | 0  |
+| entities - workflow                                                | 3  | 2  |
++--------------------------------------------------------------------+----+----+
+| dbapi - connection                                                 | 3  | 1  |
+| dbapi - data management                                            | 1  | 1  |
+| dbapi - result set                                                 | 3  | 1  |
+| dbapi - transaction, undo                                          | 2  | 0  |
++--------------------------------------------------------------------+----+----+
+| cube - layout                                                      | 2  | 3  |
+| cube - new cube                                                    | 2  | 2  |
++--------------------------------------------------------------------+----+----+
+| migration - context                                                | 2  | 1  |
+| migration - commands                                               | 2  | 2  |
++--------------------------------------------------------------------+----+----+
+| testlib - CubicWebTC                                               | 2  | 1  |
+| testlib - automatic tests                                          | 2  | 2  |
++--------------------------------------------------------------------+----+----+
+| i18n - mark string                                                 | 3  | 2  |
+| i18n - customize strings from other cubes / cubicweb               | 3  | 1  |
+| i18n - update catalog                                              | 3  | 2  |
++--------------------------------------------------------------------+----+----+
+| more - reloading tips                                              | NA | 0  |
+| more - site_cubicweb                                               | 2  | ?  |
+| more - adding options in configuration file                        | 3  | 0  |
+| more - adding options in site configuration / preferences          | 3  | ?  |
+| more - optimizing / profiling                                      | 2  | 1  |
+| more - c-c plugins                                                 | 3  | 0  |
+| more - crypto services                                             | 0  | 0  |
+| more - massive import                                              | 2  | 0  |
+| more - mime type based conversion                                  | 2  | 0  |
+| more - CWCache                                                     | 1  | 0  |
++--------------------------------------------------------------------+----+----+
+
+
+Web UI development
+==================
+
++====================================================================+====+====+
+|  FEATURE                                                           | CM | DL |
++====================================================================+====+====+
+| base - web request                                                 | 2  | 2  |
+| base - exceptions                                                  | 2  | 0  |
+| base - session, authentication                                     | 1  | 0  |
+| base - http caching                                                | 2  | 1  |
+| base - external resources                                          | 2  | 2  |
+| base - static files                                                | 2  | ?  |
+| base - data sharing                                                | 2  | 2  |
+| base - graphical chart customization                               | 1  | 1  |
++--------------------------------------------------------------------+----+----+
+| publishing - cycle                                                 | 2  | 2  |
+| publishing - error handling                                        | 2  | 1  |
+| publishing - transactions                                          | NA | ?  |
++--------------------------------------------------------------------+----+----+
+| controller - base                                                  | 2  | 2  |
+| controller - view                                                  | 2  | 1  |
+| controller - edit                                                  | 2  | 1  |
+| controller - json                                                  | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| views - base                                                       | 2  | 2  |
+| views - templates                                                  | 2  | 2  |
+| views - boxes                                                      | 2  | 1  |
+| views - components                                                 | 2  | 1  |
+| views - primary                                                    | 2  | 1  |
+| views - tabs                                                       | 2  | 1  |
+| views - xml                                                        | 2  | 0  |
+| views - text                                                       | 2  | 1  |
+| views - table                                                      | 2  | 1  |
+| views - plot                                                       | 2  | 0  |
+| views - navigation                                                 | 2  | 0  |
+| views - calendar, timeline                                         | 2  | 0  |
+| views - index                                                      | 2  | 2  |
+| views - breadcrumbs                                                | 2  | 1  |
+| views - actions                                                    | 2  | 1  |
+| views - debugging                                                  | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+| form - base                                                        | 2  | 1  |
+| form - fields                                                      | 2  | 1  |
+| form - widgets                                                     | 2  | 1  |
+| form - captcha                                                     | 2  | 0  |
+| form - renderers                                                   | 2  | 0  |
+| form - validation error handling                                   | 2  | 0  |
+| form - autoform                                                    | 2  | 2  |
+| form - reledit                                                     | 2  | 0  |
++--------------------------------------------------------------------+----+----+
+| facets - base                                                      | 2  | ?  |
+| facets - configuration                                             | 2  | 1  |
+| facets - custom facets                                             | 2  | 0  |
++--------------------------------------------------------------------+----+----+
+| css - base                                                         | 1  | 1  |
+| css - customization                                                | 1  | 1  |
++--------------------------------------------------------------------+----+----+
+| js - base                                                          | 1  | 1  |
+| js - jquery                                                        | 1  | 1  |
+| js - base functions                                                | 1  | 0  |
+| js - ajax                                                          | 1  | 0  |
+| js - widgets                                                       | 1  | 1  |
++--------------------------------------------------------------------+----+----+
+| other - page template                                              | 0  | 0  |
+| other - inline doc (wdoc)                                          | 2  | 0  |
+| other - magic search                                               | 2  | 0  |
+| other - url mapping                                                | 1  | 1  |
+| other - apache style url rewrite                                   | 1  | 1  |
+| other - sparql                                                     | 1  | 0  |
+| other - bookmarks                                                  | 2  | 1  |
++--------------------------------------------------------------------+----+----+
+
+
+Repository development
+======================
+
++====================================================================+====+====+
+|  FEATURE                                                           | CM | DL |
++====================================================================+====+====+
+| base - session                                                     | 2  | 2  |
+| base - more security control                                       | 2  | 0  |
+| base - debugging                                                   | 2  | 0  |
++--------------------------------------------------------------------+----+----+
+| hooks - development                                                | 2  | 2  |
+| hooks - abstract hooks                                             | 2  | 0  |
+| hooks - core hooks                                                 | 2  | 0  |
+| hooks - control                                                    | 2  | 0  |
+| hooks - operation                                                  | 2  | 2  |
++--------------------------------------------------------------------+----+----+
+| notification - sending email                                       | 2  | ?  |
+| notification - base views                                          | 1  | ?  |
+| notification - supervisions                                        | 1  | 0  |
++--------------------------------------------------------------------+----+----+
+| source - storages                                                  | 2  | 0  |
+| source - authentication plugins                                    | 2  | 0  |
+| source - custom sources                                            | 2  | 0  |
++--------------------------------------------------------------------+----+----+
--- a/doc/tools/generate_modules.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/doc/tools/generate_modules.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """generate list of modules for sphinx doc
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import sys
@@ -16,7 +29,7 @@
     cw_gen = ModuleGenerator('cubicweb', '../..')
     cw_gen.generate("../book/en/annexes/api_cubicweb.rst",
                     EXCLUDE_DIRS + ('cwdesklets', 'misc', 'skel', 'skeleton'))
-    for modname in ('indexer', 'logilab', 'rql', 'yams'):
+    for modname in ('logilab', 'rql', 'yams'):
         cw_gen = ModuleGenerator(modname, '../../../' + modname)
         cw_gen.generate("../book/en/annexes/api_%s.rst" % modname,
                         EXCLUDE_DIRS + ('tools',))
--- a/entities/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """base application's entities class implementation: `AnyEntity`
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/entities/authobjs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/authobjs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """entity classes user and group entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -93,15 +106,10 @@
         return self.groups == frozenset(('guests', ))
 
     def owns(self, eid):
-        if hasattr(self._cw, 'unsafe_execute'):
-            # use unsafe_execute on the repository side, in case
-            # session's user doesn't have access to CWUser
-            execute = self._cw.unsafe_execute
-        else:
-            execute = self._cw.execute
         try:
-            return execute('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
-                           {'x': eid, 'u': self.eid}, 'x')
+            return self._cw.execute(
+                'Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
+                {'x': eid, 'u': self.eid}, 'x')
         except Unauthorized:
             return False
     owns = cached(owns, keyarg=1)
--- a/entities/lib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/lib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """entity classes for optional library entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/entities/schemaobjs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/schemaobjs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,14 +1,29 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """schema definition related entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from logilab.common.decorators import cached
 
+from yams.schema import role_name
+
 from cubicweb import ValidationError
 from cubicweb.schema import ERQLExpression, RRQLExpression
 
@@ -64,13 +79,14 @@
         for rdef in self.reverse_relation_type:
             card = rdef.cardinality[0]
             if not card in '?1':
+                qname = role_name('inlined', 'subject')
                 rtype = self.name
                 stype = rdef.stype
                 otype = rdef.otype
                 msg = self._cw._("can't set inlined=%(inlined)s, "
                                  "%(stype)s %(rtype)s %(otype)s "
                                  "has cardinality=%(card)s")
-                raise ValidationError(self.eid, {'inlined': msg % locals()})
+                raise ValidationError(self.eid, {qname: msg % locals()})
 
     def db_key_name(self):
         """XXX goa specific"""
--- a/entities/test/data/migration/postcreate.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/test/data/migration/postcreate.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,2 +1,19 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 wf = add_workflow(u'bmk wf', 'Bookmark')
 wf.add_state(u'hop', initial=True)
--- a/entities/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """entities tests schema
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from yams.buildobjs import EntityType, String
--- a/entities/test/unittest_base.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/test/unittest_base.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.entities.base module
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import unittest_main
--- a/entities/test/unittest_wfobjs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/test/unittest_wfobjs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,5 +1,24 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import with_statement
 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:
@@ -37,7 +56,7 @@
         self.commit()
         wf.add_state(u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf2.add_state(u'foo', initial=True)
@@ -47,7 +66,7 @@
         self.commit()
         bar.set_attributes(name=u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
 
     def test_duplicated_transition(self):
         wf = add_wf(self, 'Company')
@@ -56,7 +75,7 @@
         wf.add_transition(u'baz', (foo,), bar, ('managers',))
         wf.add_transition(u'baz', (bar,), foo)
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf.add_state(u'foo', initial=True)
@@ -68,7 +87,7 @@
         self.commit()
         biz.set_attributes(name=u'baz')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
 
 
 class WorkflowTC(CubicWebTC):
@@ -126,10 +145,11 @@
         wf = add_wf(self, 'CWUser')
         s = wf.add_state(u'foo', initial=True)
         self.commit()
-        ex = self.assertRaises(ValidationError, self.session.unsafe_execute,
+        with security_enabled(self.session, write=False):
+            ex = self.assertRaises(ValidationError, self.session.execute,
                                'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                                {'x': self.user().eid, 's': s.eid}, 'x')
-        self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
+            self.assertEquals(ex.errors, {'in_state-subject': "state doesn't belong to entity's workflow. "
                                       "You may want to set a custom workflow for this entity first."})
 
     def test_fire_transition(self):
@@ -172,7 +192,7 @@
         member = req.entity_from_eid(self.member.eid)
         ex = self.assertRaises(ValidationError,
                                member.fire_transition, 'deactivate')
-        self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+        self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
         cnx.close()
         cnx = self.login('member')
         req = self.request()
@@ -181,7 +201,7 @@
         cnx.commit()
         ex = self.assertRaises(ValidationError,
                                member.fire_transition, 'activate')
-        self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+        self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
 
     def test_fire_transition_owned_by(self):
         self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
@@ -254,7 +274,7 @@
         # subworkflow input transition
         ex = self.assertRaises(ValidationError,
                                self.group.change_state, swfstate1, u'gadget')
-        self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's workflow"})
+        self.assertEquals(ex.errors, {'to_state-subject': "state doesn't belong to entity's workflow"})
         self.rollback()
         # force back to state1
         self.group.change_state('state1', u'gadget')
@@ -290,7 +310,7 @@
         mwf.add_wftransition(u'swftr1', swf, state1,
                              [(swfstate2, state2), (swfstate2, state3)])
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
+        self.assertEquals(ex.errors, {'subworkflow_exit-subject': u"can't have multiple exits on the same state"})
 
     def test_swf_fire_in_a_row(self):
         # sub-workflow
@@ -403,7 +423,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid})
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow': u'workflow has no initial state'})
+        self.assertEquals(ex.errors, {'custom_workflow-subject': u'workflow has no initial state'})
 
     def test_custom_wf_bad_etype(self):
         """try to set a custom workflow which doesn't apply to entity type"""
@@ -412,7 +432,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid}, 'x')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow': 'workflow isn\'t a workflow for this type'})
+        self.assertEquals(ex.errors, {'custom_workflow-subject': 'workflow isn\'t a workflow for this type'})
 
     def test_del_custom_wf(self):
         """member in some state shared by the new workflow, nothing has to be
@@ -440,19 +460,21 @@
 
 class AutoTransitionTC(CubicWebTC):
 
-    def setup_database(self):
-        self.wf = add_wf(self, 'CWUser')
-        asleep = self.wf.add_state('asleep', initial=True)
-        dead = self.wf.add_state('dead')
-        self.wf.add_transition('rest', asleep, asleep)
-        self.wf.add_transition('sick', asleep, dead, type=u'auto',
-                               conditions=({'expr': u'U surname "toto"',
-                                            'mainvars': u'U'},))
+    def setup_custom_wf(self):
+        wf = add_wf(self, 'CWUser')
+        asleep = wf.add_state('asleep', initial=True)
+        dead = wf.add_state('dead')
+        wf.add_transition('rest', asleep, asleep)
+        wf.add_transition('sick', asleep, dead, type=u'auto',
+                          conditions=({'expr': u'X surname "toto"',
+                                       'mainvars': u'X'},))
+        return wf
 
     def test_auto_transition_fired(self):
+        wf = self.setup_custom_wf()
         user = self.create_user('member')
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
-                     {'wf': self.wf.eid, 'x': user.eid})
+                     {'wf': wf.eid, 'x': user.eid})
         self.commit()
         user.clear_all_caches()
         self.assertEquals(user.state, 'asleep')
@@ -466,7 +488,7 @@
                           ['rest'])
         self.assertEquals(parse_hist(user.workflow_history),
                           [('asleep', 'asleep', 'rest', None)])
-        self.request().user.set_attributes(surname=u'toto') # fulfill condition
+        user.set_attributes(surname=u'toto') # fulfill condition
         self.commit()
         user.fire_transition('rest')
         self.commit()
@@ -477,6 +499,26 @@
                            ('asleep', 'asleep', 'rest', None),
                            ('asleep', 'dead', 'sick', None),])
 
+    def test_auto_transition_custom_initial_state_fired(self):
+        wf = self.setup_custom_wf()
+        user = self.create_user('member', surname=u'toto')
+        self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
+                     {'wf': wf.eid, 'x': user.eid})
+        self.commit()
+        self.assertEquals(user.state, 'dead')
+
+    def test_auto_transition_initial_state_fired(self):
+        wf = self.execute('Any WF WHERE ET default_workflow WF, '
+                          'ET name %(et)s', {'et': 'CWUser'}).get_entity(0, 0)
+        dead = wf.add_state('dead')
+        wf.add_transition('sick', wf.state_by_name('activated'), dead,
+                          type=u'auto', conditions=({'expr': u'X surname "toto"',
+                                                     'mainvars': u'X'},))
+        self.commit()
+        user = self.create_user('member', surname=u'toto')
+        self.commit()
+        self.assertEquals(user.state, 'dead')
+
 
 class WorkflowHooksTC(CubicWebTC):
 
@@ -505,7 +547,7 @@
                      {'wf': self.wf.eid})
         self.commit()
 
-    # XXX currently, we've to rely on hooks to set initial state, or to use unsafe_execute
+    # XXX currently, we've to rely on hooks to set initial state, or to use execute
     # def test_initial_state(self):
     #     cnx = self.login('stduser')
     #     cu = cnx.cursor()
@@ -532,7 +574,7 @@
         user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'activate')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                           u"transition isn't allowed from")
         cnx.close()
 
@@ -541,7 +583,7 @@
         user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'dummy')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                           u"transition isn't allowed from")
         cnx.close()
 
@@ -554,7 +596,7 @@
         session.set_pool()
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'deactivate')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                                             u"transition isn't allowed from")
         # get back now
         user.fire_transition('activate')
--- a/entities/wfobjs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entities/wfobjs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """workflow definition and history related entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -158,7 +171,7 @@
             todelstate = self.state_by_name(todelstate)
         if not hasattr(replacement, 'eid'):
             replacement = self.state_by_name(replacement)
-        execute = self._cw.unsafe_execute
+        execute = self._cw.execute
         execute('SET X in_state S WHERE S eid %(s)s', {'s': todelstate.eid}, 's')
         execute('SET X from_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s',
                 {'os': todelstate.eid, 'ns': replacement.eid}, 's')
--- a/entity.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/entity.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Base class for entity objects manipulated in clients
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -20,6 +33,7 @@
 from cubicweb.rset import ResultSet
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
+from cubicweb.req import _check_cw_unsafe
 from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint
 from cubicweb.rqlrewrite import RQLRewriter
 
@@ -59,7 +73,7 @@
     :cvar skip_copy_for: a list of relations that should be skipped when copying
                          this kind of entity. Note that some relations such
                          as composite relations or relations that have '?1' as object
-                         cardinality are always skipped. 
+                         cardinality are always skipped.
     """
     __registry__ = 'etypes'
     __select__ = yes()
@@ -201,6 +215,59 @@
             needcheck = False
         return mainattr, needcheck
 
+    @classmethod
+    def cw_instantiate(cls, execute, **kwargs):
+        """add a new entity of this given type
+
+        Example (in a shell session):
+
+        >>> companycls = vreg['etypes'].etype_class(('Company')
+        >>> personcls = vreg['etypes'].etype_class(('Person')
+        >>> c = companycls.cw_instantiate(req.execute, name=u'Logilab')
+        >>> personcls.cw_instantiate(req.execute, firstname=u'John', lastname=u'Doe',
+        ...                          works_for=c)
+
+        """
+        rql = 'INSERT %s X' % cls.__regid__
+        relations = []
+        restrictions = set()
+        pending_relations = []
+        for attr, value in kwargs.items():
+            if isinstance(value, (tuple, list, set, frozenset)):
+                if len(value) == 1:
+                    value = iter(value).next()
+                else:
+                    del kwargs[attr]
+                    pending_relations.append( (attr, value) )
+                    continue
+            if hasattr(value, 'eid'): # non final relation
+                rvar = attr.upper()
+                # XXX safer detection of object relation
+                if attr.startswith('reverse_'):
+                    relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
+                else:
+                    relations.append('X %s %s' % (attr, rvar))
+                restriction = '%s eid %%(%s)s' % (rvar, attr)
+                if not restriction in restrictions:
+                    restrictions.add(restriction)
+                kwargs[attr] = value.eid
+            else: # attribute
+                relations.append('X %s %%(%s)s' % (attr, attr))
+        if relations:
+            rql = '%s: %s' % (rql, ', '.join(relations))
+        if restrictions:
+            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
+        created = execute(rql, kwargs).get_entity(0, 0)
+        for attr, values in pending_relations:
+            if attr.startswith('reverse_'):
+                restr = 'Y %s X' % attr[len('reverse_'):]
+            else:
+                restr = 'X %s Y' % attr
+            execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
+                restr, ','.join(str(r.eid) for r in values)),
+                    {'x': created.eid}, build_descr=False)
+        return created
+
     def __init__(self, req, rset=None, row=None, col=0):
         AppObject.__init__(self, req, rset=rset, row=row, col=col)
         dict.__init__(self)
@@ -215,6 +282,14 @@
         return '<Entity %s %s %s at %s>' % (
             self.e_schema, self.eid, self.keys(), id(self))
 
+    def __json_encode__(self):
+        """custom json dumps hook to dump the entity's eid
+        which is not part of dict structure itself
+        """
+        dumpable = dict(self)
+        dumpable['eid'] = self.eid
+        return dumpable
+
     def __nonzero__(self):
         return True
 
@@ -224,6 +299,93 @@
     def __cmp__(self, other):
         raise NotImplementedError('comparison not implemented for %s' % self.__class__)
 
+    def __getitem__(self, key):
+        if key == 'eid':
+            warn('[3.7] entity["eid"] is deprecated, use entity.eid instead',
+                 DeprecationWarning, stacklevel=2)
+            return self.eid
+        return super(Entity, self).__getitem__(key)
+
+    def __setitem__(self, attr, value):
+        """override __setitem__ to update self.edited_attributes.
+
+        Typically, a before_[update|add]_hook could do::
+
+            entity['generated_attr'] = generated_value
+
+        and this way, edited_attributes will be updated accordingly. Also, add
+        the attribute to skip_security since we don't want to check security
+        for such attributes set by hooks.
+        """
+        if attr == 'eid':
+            warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead',
+                 DeprecationWarning, stacklevel=2)
+            self.eid = value
+        else:
+            super(Entity, self).__setitem__(attr, value)
+            # don't add attribute into skip_security if already in edited
+            # attributes, else we may accidentaly skip a desired security check
+            if hasattr(self, 'edited_attributes') and \
+                   attr not in self.edited_attributes:
+                self.edited_attributes.add(attr)
+                self.skip_security_attributes.add(attr)
+
+    def __delitem__(self, attr):
+        """override __delitem__ to update self.edited_attributes on cleanup of
+        undesired changes introduced in the entity's dict. For example, see the
+        code snippet below from the `forge` cube:
+
+        .. sourcecode:: python
+
+            edited = self.entity.edited_attributes
+            has_load_left = 'load_left' in edited
+            if 'load' in edited and self.entity.load_left is None:
+                self.entity.load_left = self.entity['load']
+            elif not has_load_left and edited:
+                # cleanup, this may cause undesired changes
+                del self.entity['load_left']
+
+        """
+        super(Entity, self).__delitem__(attr)
+        if hasattr(self, 'edited_attributes'):
+            self.edited_attributes.remove(attr)
+
+    def setdefault(self, attr, default):
+        """override setdefault to update self.edited_attributes"""
+        super(Entity, self).setdefault(attr, default)
+        # don't add attribute into skip_security if already in edited
+        # attributes, else we may accidentaly skip a desired security check
+        if hasattr(self, 'edited_attributes') and \
+               attr not in self.edited_attributes:
+            self.edited_attributes.add(attr)
+            self.skip_security_attributes.add(attr)
+
+    def pop(self, attr, default=_marker):
+        """override pop to update self.edited_attributes on cleanup of
+        undesired changes introduced in the entity's dict. See `__delitem__`
+        """
+        if default is _marker:
+            value = super(Entity, self).pop(attr)
+        else:
+            value = super(Entity, self).pop(attr, default)
+        if hasattr(self, 'edited_attributes') and attr in self.edited_attributes:
+            self.edited_attributes.remove(attr)
+        return value
+
+    def update(self, values):
+        """override update to update self.edited_attributes. See `__setitem__`
+        """
+        for attr, value in values.items():
+            self[attr] = value # use self.__setitem__ implementation
+
+    def rql_set_value(self, attr, value):
+        """call by rql execution plan when some attribute is modified
+
+        don't use dict api in such case since we don't want attribute to be
+        added to skip_security_attributes.
+        """
+        super(Entity, self).__setitem__(attr, value)
+
     def pre_add_hook(self):
         """hook called by the repository before doing anything to add the entity
         (before_add entity hooks have not been called yet). This give the
@@ -234,7 +396,7 @@
         return self
 
     def set_eid(self, eid):
-        self.eid = self['eid'] = eid
+        self.eid = eid
 
     def has_eid(self):
         """return True if the entity has an attributed eid (False
@@ -440,7 +602,8 @@
         """returns a resultset containing `self` information"""
         rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
                          {'x': self.eid}, [(self.__regid__,)])
-        return self._cw.decorate_rset(rset)
+        rset.req = self._cw
+        return rset
 
     def to_complete_relations(self):
         """by default complete final relations to when calling .complete()"""
@@ -459,7 +622,7 @@
                    all(matching_groups(e.get_groups('read')) for e in targets):
                     yield rschema, 'subject'
 
-    def to_complete_attributes(self, skip_bytes=True):
+    def to_complete_attributes(self, skip_bytes=True, skip_pwd=True):
         for rschema, attrschema in self.e_schema.attribute_definitions():
             # skip binary data by default
             if skip_bytes and attrschema.type == 'Bytes':
@@ -470,13 +633,13 @@
             # password retreival is blocked at the repository server level
             rdef = rschema.rdef(self.e_schema, attrschema)
             if not self._cw.user.matching_groups(rdef.get_groups('read')) \
-                   or attrschema.type == 'Password':
+                   or (attrschema.type == 'Password' and skip_pwd):
                 self[attr] = None
                 continue
             yield attr
 
     _cw_completed = False
-    def complete(self, attributes=None, skip_bytes=True):
+    def complete(self, attributes=None, skip_bytes=True, skip_pwd=True):
         """complete this entity by adding missing attributes (i.e. query the
         repository to fill the entity)
 
@@ -493,7 +656,7 @@
         V = varmaker.next()
         rql = ['WHERE %s eid %%(x)s' % V]
         selected = []
-        for attr in (attributes or self.to_complete_attributes(skip_bytes)):
+        for attr in (attributes or self.to_complete_attributes(skip_bytes, skip_pwd)):
             # if attribute already in entity, nothing to do
             if self.has_key(attr):
                 continue
@@ -531,8 +694,8 @@
             # if some outer join are included to fetch inlined relations
             rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected),
                                     ','.join(rql))
-            execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
-            rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
+            rset = self._cw.execute(rql, {'x': self.eid}, 'x',
+                                    build_descr=False)[0]
             # handle attributes
             for i in xrange(1, lastattr):
                 self[str(selected[i-1][0])] = rset[i]
@@ -542,7 +705,7 @@
                 value = rset[i]
                 if value is None:
                     rrset = ResultSet([], rql, {'x': self.eid})
-                    self._cw.decorate_rset(rrset)
+                    rrset.req = self._cw
                 else:
                     rrset = self._cw.eid_rset(value)
                 self.set_related_cache(rtype, role, rrset)
@@ -560,11 +723,8 @@
             if not self.is_saved():
                 return None
             rql = "Any A WHERE X eid %%(x)s, X %s A" % name
-            # XXX should we really use unsafe_execute here? I think so (syt),
-            # see #344874
-            execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
             try:
-                rset = execute(rql, {'x': self.eid}, 'x')
+                rset = self._cw.execute(rql, {'x': self.eid}, 'x')
             except Unauthorized:
                 self[name] = value = None
             else:
@@ -595,10 +755,7 @@
             pass
         assert self.has_eid()
         rql = self.related_rql(rtype, role)
-        # XXX should we really use unsafe_execute here? I think so (syt),
-        # see #344874
-        execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
-        rset = execute(rql, {'x': self.eid}, 'x')
+        rset = self._cw.execute(rql, {'x': self.eid}, 'x')
         self.set_related_cache(rtype, role, rset)
         return self.related(rtype, role, limit, entities)
 
@@ -785,10 +942,6 @@
         haseid = 'eid' in self
         self._cw_completed = False
         self.clear()
-        # set eid if it was in, else we may get nasty error while editing this
-        # entity if it's bound to a repo session
-        if haseid:
-            self['eid'] = self.eid
         # clear relations cache
         for rschema, _, role in self.e_schema.relation_definitions():
             self.clear_related_cache(rschema.type, role)
@@ -800,63 +953,68 @@
 
     # raw edition utilities ###################################################
 
-    def set_attributes(self, _cw_unsafe=False, **kwargs):
+    def set_attributes(self, **kwargs):
+        _check_cw_unsafe(kwargs)
         assert kwargs
+        assert self._is_saved, "should not call set_attributes while entity "\
+               "hasn't been saved yet"
         relations = []
         for key in kwargs:
             relations.append('X %s %%(%s)s' % (key, key))
-        # update current local object
-        self.update(kwargs)
         # and now update the database
         kwargs['x'] = self.eid
-        if _cw_unsafe:
-            self._cw.unsafe_execute(
-                'SET %s WHERE X eid %%(x)s' % ','.join(relations), kwargs, 'x')
-        else:
-            self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
-                             kwargs, 'x')
+        self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
+                         kwargs, 'x')
+        kwargs.pop('x')
+        # update current local object _after_ the rql query to avoid
+        # interferences between the query execution itself and the
+        # edited_attributes / skip_security_attributes machinery
+        self.update(kwargs)
 
-    def set_relations(self, _cw_unsafe=False, **kwargs):
+    def set_relations(self, **kwargs):
         """add relations to the given object. To set a relation where this entity
         is the object of the relation, use 'reverse_'<relation> as argument name.
 
-        Values may be an entity, a list of entity, or None (meaning that all
+        Values may be an entity, a list of entities, or None (meaning that all
         relations of the given type from or to this object should be deleted).
         """
-        if _cw_unsafe:
-            execute = self._cw.unsafe_execute
-        else:
-            execute = self._cw.execute
         # XXX update cache
+        _check_cw_unsafe(kwargs)
         for attr, values in kwargs.iteritems():
             if attr.startswith('reverse_'):
                 restr = 'Y %s X' % attr[len('reverse_'):]
             else:
                 restr = 'X %s Y' % attr
             if values is None:
-                execute('DELETE %s WHERE X eid %%(x)s' % restr,
-                        {'x': self.eid}, 'x')
+                self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
+                                 {'x': self.eid}, 'x')
                 continue
             if not isinstance(values, (tuple, list, set, frozenset)):
                 values = (values,)
-            execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
+            self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
                 restr, ','.join(str(r.eid) for r in values)),
-                    {'x': self.eid}, 'x')
+                             {'x': self.eid}, 'x')
 
-    def delete(self):
+    def delete(self, **kwargs):
         assert self.has_eid(), self.eid
         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
-                         {'x': self.eid})
+                         {'x': self.eid}, **kwargs)
 
     # server side utilities ###################################################
 
+    @property
+    def skip_security_attributes(self):
+        try:
+            return self._skip_security_attributes
+        except:
+            self._skip_security_attributes = set()
+            return self._skip_security_attributes
+
     def set_defaults(self):
         """set default values according to the schema"""
-        self._default_set = set()
         for attr, value in self.e_schema.defaults():
             if not self.has_key(attr):
                 self[str(attr)] = value
-                self._default_set.add(attr)
 
     def check(self, creation=False):
         """check this entity against its schema. Only final relation
@@ -868,7 +1026,18 @@
             _ = unicode
         else:
             _ = self._cw._
-        self.e_schema.check(self, creation=creation, _=_)
+        if creation:
+            # on creations, we want to check all relations, especially
+            # required attributes
+            relations = [rschema for rschema in self.e_schema.subject_relations()
+                         if rschema.final and rschema.type != 'eid']
+        elif hasattr(self, 'edited_attributes'):
+            relations = [self._cw.vreg.schema.rschema(rtype)
+                         for rtype in self.edited_attributes]
+        else:
+            relations = None
+        self.e_schema.check(self, creation=creation, _=_,
+                            relations=relations)
 
     def fti_containers(self, _done=None):
         if _done is None:
@@ -894,12 +1063,12 @@
         """used by the full text indexer to get words to index
 
         this method should only be used on the repository side since it depends
-        on the indexer package
+        on the logilab.database package
 
         :rtype: list
         :return: the list of indexable word of this entity
         """
-        from indexer.query_objects import tokenize
+        from logilab.database.fti import tokenize
         # take care to cases where we're modyfying the schema
         pending = self._cw.transaction_data.setdefault('pendingrdefs', set())
         words = []
@@ -942,8 +1111,6 @@
 
     def __set__(self, eobj, value):
         eobj[self._attrname] = value
-        if hasattr(eobj, 'edited_attributes'):
-            eobj.edited_attributes.add(self._attrname)
 
 class Relation(object):
     """descriptor that controls schema relation access"""
--- a/etwist/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """ CW - nevow/twisted client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
--- a/etwist/request.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/request.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Twisted request handler for CubicWeb
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/etwist/server.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/server.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """twisted server for CubicWeb web instances
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -11,7 +24,6 @@
 import os
 import select
 import errno
-import hotshot
 from time import mktime
 from datetime import date, timedelta
 from urlparse import urlsplit, urlunsplit
@@ -113,8 +125,6 @@
         if config.repo_method == 'inmemory':
             reactor.addSystemEventTrigger('before', 'shutdown',
                                           self.shutdown_event)
-            # monkey patch start_looping_task to get proper reactor integration
-            #self.appli.repo.__class__.start_looping_tasks = start_looping_tasks
             if config.pyro_enabled():
                 # if pyro is enabled, we have to register to the pyro name
                 # server, create a pyro daemon, and create a task to handle pyro
@@ -127,10 +137,8 @@
         CW_EVENT_MANAGER.bind('after-registry-reload', self.set_url_rewriter)
 
     def start_service(self):
-        config = self.config
-        interval = min(config['cleanup-session-time'] or 120,
-                       config['cleanup-anonymous-session-time'] or 720) / 2.
-        start_task(interval, self.appli.session_handler.clean_sessions)
+        start_task(self.appli.session_handler.clean_sessions_interval,
+                   self.appli.session_handler.clean_sessions)
 
     def set_url_rewriter(self):
         self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
@@ -167,7 +175,7 @@
                         datadir = self.config.locate_resource(segments[1])
                         if datadir is None:
                             return None, []
-                    self.info('static file %s from %s', segments[-1], datadir)
+                    self.debug('static file %s from %s', segments[-1], datadir)
                     if segments[0] == 'data':
                         return static.File(str(datadir)), segments[1:]
                     else:
@@ -182,7 +190,7 @@
         """Render a page from the root resource"""
         # reload modified files in debug mode
         if self.debugmode:
-            self.appli.vreg.register_objects(self.config.vregistry_path())
+            self.appli.vreg.reload_if_needed()
         if self.config['profile']: # default profiler don't trace threads
             return self.render_request(request)
         else:
@@ -286,11 +294,11 @@
 from twisted.internet import defer
 from twisted.web2 import fileupload
 
-# XXX set max file size to 100Mo: put max upload size in the configuration
+# XXX set max file size to 200MB: put max upload size in the configuration
 # line below for twisted >= 8.0, default param value for earlier version
-resource.PostableResource.maxSize = 100*1024*1024
+resource.PostableResource.maxSize = 200*1024*1024
 def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
-                  maxSize=100*1024*1024):
+                  maxSize=200*1024*1024):
     if request.stream.length == 0:
         return defer.succeed(None)
 
@@ -337,32 +345,131 @@
 set_log_methods(CubicWebRootResource, getLogger('cubicweb.twisted'))
 
 
+listiterator = type(iter([]))
 
-def _gc_debug():
+def _gc_debug(all=True):
     import gc
     from pprint import pprint
     from cubicweb.appobject import AppObject
     gc.collect()
     count = 0
     acount = 0
+    fcount = 0
+    rcount = 0
+    ccount = 0
+    scount = 0
     ocount = {}
+    from rql.stmts import Union
+    from cubicweb.schema import CubicWebSchema
+    from cubicweb.rset import ResultSet
+    from cubicweb.dbapi import Connection, Cursor
+    from cubicweb.req import RequestSessionBase
+    from cubicweb.server.repository import Repository
+    from cubicweb.server.sources.native import NativeSQLSource
+    from cubicweb.server.session import Session
+    from cubicweb.devtools.testlib import CubicWebTC
+    from logilab.common.testlib import TestSuite
+    from optparse import Values
+    import types, weakref
     for obj in gc.get_objects():
-        if isinstance(obj, CubicWebTwistedRequestAdapter):
+        if isinstance(obj, RequestSessionBase):
             count += 1
+            if isinstance(obj, Session):
+                print '   session', obj, referrers(obj, True)
         elif isinstance(obj, AppObject):
             acount += 1
-        else:
+        elif isinstance(obj, ResultSet):
+            rcount += 1
+            #print '   rset', obj, referrers(obj)
+        elif isinstance(obj, Repository):
+            print '   REPO', obj, referrers(obj, True)
+        #elif isinstance(obj, NativeSQLSource):
+        #    print '   SOURCe', obj, referrers(obj)
+        elif isinstance(obj, CubicWebTC):
+            print '   TC', obj, referrers(obj)
+        elif isinstance(obj, TestSuite):
+            print '   SUITE', obj, referrers(obj)
+        #elif isinstance(obj, Values):
+        #    print '   values', '%#x' % id(obj), referrers(obj, True)
+        elif isinstance(obj, Connection):
+            ccount += 1
+            #print '   cnx', obj, referrers(obj)
+        #elif isinstance(obj, Cursor):
+        #    ccount += 1
+        #    print '   cursor', obj, referrers(obj)
+        elif isinstance(obj, file):
+            fcount += 1
+        #    print '   open file', file.name, file.fileno
+        elif isinstance(obj, CubicWebSchema):
+            scount += 1
+            print '   schema', obj, referrers(obj)
+        elif not isinstance(obj, (type, tuple, dict, list, set, frozenset,
+                                  weakref.ref, weakref.WeakKeyDictionary,
+                                  listiterator,
+                                  property, classmethod,
+                                  types.ModuleType, types.MemberDescriptorType,
+                                  types.FunctionType, types.MethodType)):
             try:
                 ocount[obj.__class__] += 1
             except KeyError:
                 ocount[obj.__class__] = 1
             except AttributeError:
                 pass
-    print 'IN MEM REQUESTS', count
-    print 'IN MEM APPOBJECTS', acount
-    ocount = sorted(ocount.items(), key=lambda x: x[1], reverse=True)[:20]
-    pprint(ocount)
-    print 'UNREACHABLE', gc.garbage
+    if count:
+        print ' NB REQUESTS/SESSIONS', count
+    if acount:
+        print ' NB APPOBJECTS', acount
+    if ccount:
+        print ' NB CONNECTIONS', ccount
+    if rcount:
+        print ' NB RSETS', rcount
+    if scount:
+        print ' NB SCHEMAS', scount
+    if fcount:
+        print ' NB FILES', fcount
+    if all:
+        ocount = sorted(ocount.items(), key=lambda x: x[1], reverse=True)[:20]
+        pprint(ocount)
+    if gc.garbage:
+        print 'UNREACHABLE', gc.garbage
+
+def referrers(obj, showobj=False):
+    try:
+        return sorted(set((type(x), showobj and x or getattr(x, '__name__', '%#x' % id(x)))
+                          for x in _referrers(obj)))
+    except TypeError:
+        s = set()
+        unhashable = []
+        for x in _referrers(obj):
+            try:
+                s.add(x)
+            except TypeError:
+                unhashable.append(x)
+        return sorted(s) + unhashable
+
+def _referrers(obj, seen=None, level=0):
+    import gc, types
+    from cubicweb.schema import CubicWebRelationSchema, CubicWebEntitySchema
+    interesting = []
+    if seen is None:
+        seen = set()
+    for x in gc.get_referrers(obj):
+        if id(x) in seen:
+            continue
+        seen.add(id(x))
+        if isinstance(x, types.FrameType):
+            continue
+        if isinstance(x, (CubicWebRelationSchema, CubicWebEntitySchema)):
+            continue
+        if isinstance(x, (list, tuple, set, dict, listiterator)):
+            if level >= 5:
+                pass
+                #interesting.append(x)
+            else:
+                interesting += _referrers(x, seen, level+1)
+        else:
+            interesting.append(x)
+    return interesting
 
 def run(config, debug):
     # create the site
@@ -397,7 +504,7 @@
     root_resource.start_service()
     logger.info('instance started on %s', root_resource.base_url)
     if config['profile']:
-        prof = hotshot.Profile(config['profile'])
-        prof.runcall(reactor.run)
+        import cProfile
+        cProfile.runctx('reactor.run()', globals(), locals(), config['profile'])
     else:
         reactor.run()
--- a/etwist/service.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/service.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,61 +1,90 @@
-import os
-
-import win32serviceutil
-import win32service
-import win32event
-
-from cubicweb.etwist.server import (CubicWebRootResource, reactor, server,
-                                    parsePOSTData, channel)
-
-from logging import getLogger, handlers
-from cubicweb import set_log_methods
-from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
-
-logger = getLogger('cubicweb.twisted')
-logger.handlers = [handlers.NTEventLogHandler('cubicweb')]
-
-os.environ['CW_INSTANCES_DIR'] = r'C:\etc\cubicweb.d'
-os.environ['USERNAME'] = 'cubicweb'
-
-
-class CWService(object, win32serviceutil.ServiceFramework):
-    _svc_name_ = None
-    _svc_display_name_ = None
-    instance = None
-
-    def __init__(self, *args, **kwargs):
-        win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs)
-        self._stop_event = win32event.CreateEvent(None, 0, 0, None)
-        cwcfg.load_cwctl_plugins()
-        set_log_methods(CubicWebRootResource, logger)
-        server.parsePOSTData = parsePOSTData
-
-    def SvcStop(self):
-        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-        logger.info('stopping %s service' % self.instance)
-        win32event.SetEvent(self._stop_event)
-        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
-
-    def SvcDoRun(self):
-        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
-        logger = getLogger('cubicweb.twisted')
-        logger.info('starting %s service' % self.instance)
-        try:
-            # create the site
-            config = cwcfg.config_for(self.instance)
-            root_resource = CubicWebRootResource(config, False)
-            website = server.Site(root_resource)
-            # serve it via standard HTTP on port set in the configuration
-            port = config['port'] or 8080
-            logger.info('listening on port %s' % port)
-            reactor.listenTCP(port, channel.HTTPFactory(website))
-            root_resource.init_publisher()
-            root_resource.start_service()
-            logger.info('instance started on %s', root_resource.base_url)
-            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
-            reactor.run()
-        except Exception, e:
-            logger.error('service %s stopped (cause: %s)' % (self.instance, e))
-            logger.exception('what happened ...')
-            self.SvcStop()
-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+import os
+import sys
+
+try:
+    import win32serviceutil
+    import win32service
+except ImportError:
+    print 'Win32 extensions for Python are likely not installed.'
+    sys.exit(3)
+
+
+from cubicweb.etwist.server import (CubicWebRootResource, reactor, server,
+                                    parsePOSTData, channel)
+
+import logging
+from logging import getLogger, handlers
+from cubicweb import set_log_methods
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
+
+def _check_env(env):
+    env_vars = ('CW_INSTANCES_DIR', 'CW_INSTANCES_DATA_DIR', 'CW_RUNTIME_DIR')
+    for var in env_vars:
+        if var not in env:
+            raise Exception('The environment variables %s must be set.' % \
+                            ', '.join(env_vars))            
+    if not env.get('USERNAME'):
+        env['USERNAME'] = 'cubicweb'
+
+class CWService(object, win32serviceutil.ServiceFramework):
+    _svc_name_ = None
+    _svc_display_name_ = None
+    instance = None
+
+    def __init__(self, *args, **kwargs):
+        win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs)
+        cwcfg.load_cwctl_plugins()
+        logger = getLogger('cubicweb')
+        set_log_methods(CubicWebRootResource, logger)
+        server.parsePOSTData = parsePOSTData
+
+    def SvcStop(self):
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        logger = getLogger('cubicweb.twisted')
+        logger.info('stopping %s service' % self.instance)
+        reactor.stop()
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+
+    def SvcDoRun(self):
+        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
+        logger = getLogger('cubicweb.twisted')
+        handler = handlers.NTEventLogHandler('cubicweb')
+        handler.setLevel(logging.INFO)
+        logger.addHandler(handler)
+        logger.info('starting %s service' % self.instance)
+        try:
+            _check_env(os.environ)
+            # create the site
+            config = cwcfg.config_for(self.instance)
+            root_resource = CubicWebRootResource(config, False)
+            website = server.Site(root_resource)
+            # serve it via standard HTTP on port set in the configuration
+            port = config['port'] or 8080
+            logger.info('listening on port %s' % port)
+            reactor.listenTCP(port, channel.HTTPFactory(website))
+            root_resource.init_publisher()
+            root_resource.start_service()
+            logger.info('instance started on %s', root_resource.base_url)
+            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
+            reactor.run()
+        except Exception, e:
+            logger.error('service %s stopped (cause: %s)' % (self.instance, e))
+            logger.exception('what happened ...')
+        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
--- a/etwist/test/unittest_server.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/test/unittest_server.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.etwist.server import host_prefixed_baseurl
--- a/etwist/twconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/twconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """twisted server configurations:
 
 * the "twisted" configuration to get a web instance running in a standalone
@@ -7,10 +24,6 @@
   web server integrating a repository server in the same process (only available
   if the repository part of the software is installed
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -52,8 +65,8 @@
           'group': 'main', 'inputlevel': WebConfiguration.mode == 'system'
           }),
         ('session-time',
-         {'type' : 'int',
-          'default': 30*60,
+         {'type' : 'time',
+          'default': '30min',
           'help': 'session expiration time, default to 30 minutes',
           'group': 'main', 'inputlevel': 1,
           }),
--- a/etwist/twctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/etwist/twctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-clt handlers for twisted
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cubicweb.toolsutils import CommandHandler
--- a/ext/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- a/ext/html4zope.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/html4zope.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # Author: David Goodger
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 # Contact: goodger@users.sourceforge.net
 # Revision: $Revision: 1.2 $
@@ -24,12 +37,13 @@
 
 __docformat__ = 'reStructuredText'
 
+import os
+
 from logilab.mtconverter import xml_escape
 
 from docutils import nodes
 from docutils.writers.html4css1 import Writer as CSS1Writer
 from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
 
 default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
 
--- a/ext/rest.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/rest.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """rest publishing functions
 
 contains some functions and setup of docutils for cubicweb. Provides the
@@ -12,10 +29,6 @@
 
 * `sourcecode` (if pygments is installed), source code colorization
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -25,7 +38,7 @@
 from os.path import join
 
 from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
+from docutils.core import Publisher
 from docutils.parsers.rst import Parser, states, directives
 from docutils.parsers.rst.roles import register_canonical_role, set_classes
 
@@ -92,14 +105,15 @@
     in `docutils.parsers.rst.directives.misc`
     """
     context = state.document.settings.context
+    cw = context._cw
     source = state_machine.input_lines.source(
         lineno - state_machine.input_offset - 1)
     #source_dir = os.path.dirname(os.path.abspath(source))
     fid = arguments[0]
-    for lang in chain((context._cw.lang, context.vreg.property_value('ui.language')),
-                      context.config.available_languages()):
+    for lang in chain((cw.lang, cw.vreg.property_value('ui.language')),
+                      cw.vreg.config.available_languages()):
         rid = '%s_%s.rst' % (fid, lang)
-        resourcedir = context.config.locate_doc_file(rid)
+        resourcedir = cw.vreg.config.locate_doc_file(rid)
         if resourcedir:
             break
     else:
@@ -196,6 +210,15 @@
         self.finish_parse()
 
 
+# XXX docutils keep a ref on context, can't find a correct way to remove it
+class CWReSTPublisher(Publisher):
+    def __init__(self, context, settings, **kwargs):
+        Publisher.__init__(self, **kwargs)
+        self.set_components('standalone', 'restructuredtext', 'pseudoxml')
+        self.process_programmatic_settings(None, settings, None)
+        self.settings.context = context
+
+
 def rest_publish(context, data):
     """publish a string formatted as ReStructured Text to HTML
 
@@ -218,7 +241,7 @@
         # remove unprintable characters unauthorized in xml
         data = data.translate(ESC_CAR_TABLE)
     settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
-                'warning_stream': StringIO(), 'context': context,
+                'warning_stream': StringIO(),
                 # dunno what's the max, severe is 4, and we never want a crash
                 # (though try/except may be a better option...)
                 'halt_level': 10,
@@ -233,9 +256,17 @@
     else:
         base_url = None
     try:
-        return publish_string(writer=Writer(base_url=base_url),
-                              parser=CubicWebReSTParser(), source=data,
-                              settings_overrides=settings)
+        pub = CWReSTPublisher(context, settings,
+                              parser=CubicWebReSTParser(),
+                              writer=Writer(base_url=base_url),
+                              source_class=io.StringInput,
+                              destination_class=io.StringOutput)
+        pub.set_source(data)
+        pub.set_destination()
+        res = pub.publish(enable_exit_status=None)
+        # necessary for proper garbage collection, else a ref is kept somewhere in docutils...
+        del pub.settings.context
+        return res
     except Exception:
         LOGGER.exception('error while publishing ReST text')
         if not isinstance(data, unicode):
--- a/ext/tal.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/tal.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provides simpleTAL extensions for CubicWeb
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/ext/test/unittest_rest.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/test/unittest_rest.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.testlib import CubicWebTC
--- a/ext/xhtml2fo.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/ext/xhtml2fo.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from xml.etree.ElementTree import QName
 from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer
 from pysixt.utils.xslfo.standard import cm
--- a/gettext.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/gettext.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Internationalization and localization support.
 
 This module provides internationalization (I18N) and localization (L10N)
--- a/goa/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb on google appengine
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/appobjects/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/appobjects/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- a/goa/appobjects/components.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/appobjects/components.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """overrides some base views for cubicweb on google appengine
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/appobjects/dbmgmt.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/appobjects/dbmgmt.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """special management views to manage repository content (initialization and
 restoration).
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -172,7 +185,7 @@
     skip_etypes = ('CWGroup', 'CWUser')
 
     def call(self):
-        # XXX should use unsafe_execute with all hooks deactivated
+        # XXX should use unsafe execute with all hooks deactivated
         # XXX step by catching datastore errors?
         for eschema in self.schema.entities():
             if eschema.final or eschema in self.skip_etypes:
--- a/goa/appobjects/gauthservice.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/appobjects/gauthservice.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """authentication using google authentication service
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/appobjects/sessions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/appobjects/sessions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """persistent sessions stored in big table
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 XXX TODO:
 * cleanup persistent session
 * use user as ancestor?
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/db.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/db.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provide replacement classes for gae db module, so that a gae model can be
 used as base for a cubicweb application by simply replacing ::
 
@@ -24,10 +41,6 @@
 * XXX name/collection_name argument of properties constructor are ignored
 * XXX ListProperty
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -86,7 +99,7 @@
                 entity = vreg.etype_class(eschema.type)(req, rset, i, j)
                 rset._get_entity_cache_ = {(i, j): entity}
     rset.rowcount = len(rows)
-    req.decorate_rset(rset)
+    rset.req = req
     return rset
 
 
--- a/goa/dbinit.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/dbinit.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some utility functions for datastore initialization.
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -84,7 +97,7 @@
             Put(gaeentity)
 
 def init_persistent_schema(ssession, schema):
-    execute = ssession.unsafe_execute
+    execute = ssession.execute
     rql = ('INSERT CWEType X: X name %(name)s, X description %(descr)s,'
            'X final FALSE')
     eschema = schema.eschema('CWEType')
@@ -96,7 +109,7 @@
                       'descr': unicode(eschema.description)})
 
 def insert_versions(ssession, config):
-    execute = ssession.unsafe_execute
+    execute = ssession.execute
     # insert versions
     execute('INSERT CWProperty X: X pkey %(pk)s, X value%(v)s',
             {'pk': u'system.version.cubicweb',
--- a/goa/dbmyams.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/dbmyams.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """extends yams to be able to load google appengine's schemas
 
 MISSING FEATURES:
@@ -5,10 +22,6 @@
  - ReferenceProperty.verbose_name, collection_name, etc. (XXX)
 
 XXX proprify this knowing we'll use goa.db
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from os.path import join
--- a/goa/gaesource.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/gaesource.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Adapter for google appengine source.
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -255,10 +268,11 @@
                 if asession.user.eid == entity.eid:
                     asession.user.update(dict(gaeentity))
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source"""
         # do not delay delete_entity as other modifications to ensure
         # consistency
+        eid = entity.eid
         key = Key(eid)
         Delete(key)
         session.clear_datastore_cache(key)
--- a/goa/goaconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/goaconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """google appengine configuration
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -86,7 +99,7 @@
     cube_appobject_path = WebConfiguration.cube_appobject_path | ServerConfiguration.cube_appobject_path
 
     # use file system schema
-    bootstrap_schema = read_instance_schema = False
+    read_instance_schema = False
     # schema is not persistent, don't load schema hooks (unavailable)
     schema_hooks = False
     # no user workflow for now
--- a/goa/goactl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/goactl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb on appengine plugins for cubicweb-ctl
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -17,7 +30,11 @@
 
 
 def slink_directories():
-    import rql, yams, yapps, simplejson, docutils, roman
+    import rql, yams, yapps, docutils, roman
+    try:
+        import json as simplejson
+    except ImportError:
+        import simplejson
     from logilab import common as lgc
     from logilab import constraint as lgcstr
     from logilab import mtconverter as lgmtc
@@ -195,7 +212,7 @@
         # goa instance'skeleton
         copy_skeleton(join(CW_SOFTWARE_ROOT, 'goa', 'skel'),
                       appldir, context, askconfirm=True)
-        # cubicweb core dependancies
+        # cubicweb core dependencies
         for directory, subdirectory in slink_directories():
             subdirectory = join(appldir, subdirectory)
             if not exists(split(subdirectory)[0]):
--- a/goa/goavreg.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/goavreg.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """goa specific registry
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/overrides/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
 # server.__init__
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/goa/overrides/mttransforms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/mttransforms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """mime type transformation engine for cubicweb, based on mtconverter
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/overrides/rqlannotation.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/rqlannotation.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/overrides/server__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/server__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # server debugging flag
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 DEBUG = False
 
--- a/goa/overrides/server_utils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/server_utils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 class RepoThread(object):
--- a/goa/overrides/toolsutils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/overrides/toolsutils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 from cubicweb import warning
--- a/goa/rqlinterpreter.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/rqlinterpreter.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provide a minimal RQL support for google appengine dbmodel
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/skel/custom.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/skel/custom.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 def postinit(vreg):
     """this callback is called at the end of initialization process
--- a/goa/skel/loader.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/skel/loader.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 if __name__ == '__main__':
 
--- a/goa/skel/main.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/skel/main.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """module defining the root handler for a lax instance. You should not have
 to change anything here.
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/skel/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/skel/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 class Blog(EntityType):
--- a/goa/skel/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/skel/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # custom application views
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from datetime import date
 
--- a/goa/test/data/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/data/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """zou
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/goa/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 
--- a/goa/test/data/settings.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/data/settings.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 TEMPLATE_DEBUG = False
--- a/goa/test/data/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/data/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import os
 os.environ["DJANGO_SETTINGS_MODULE"] = 'data.settings'
--- a/goa/test/unittest_db.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_db.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/test/unittest_editcontroller.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_editcontroller.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/test/unittest_metadata.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_metadata.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/test/unittest_rql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_rql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/test/unittest_schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/test/unittest_views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/test/unittest_views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.goa.testlib import *
 
--- a/goa/testlib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/testlib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/goa/tools/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/tools/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """lax tools cube
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/goa/tools/generate_schema_img.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/tools/generate_schema_img.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 from os.path import dirname, abspath, join
--- a/goa/tools/laxctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/goa/tools/laxctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provides all lax instances management commands into a single utility script
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/hooks/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,1 +1,49 @@
-"""core hooks"""
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""core hooks
+
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import timedelta, datetime
+from cubicweb.server import hook
+
+class ServerStartupHook(hook.Hook):
+    """task to cleanup expirated auth cookie entities"""
+    __regid__ = 'cw_cleanup_transactions'
+    events = ('server_startup',)
+
+    def __call__(self):
+        # XXX use named args and inner functions to avoid referencing globals
+        # which may cause reloading pb
+        lifetime = timedelta(days=self.repo.config['keep-transaction-lifetime'])
+        def cleanup_old_transactions(repo=self.repo, lifetime=lifetime):
+            mindate = datetime.now() - lifetime
+            session = repo.internal_session()
+            try:
+                session.system_sql(
+                    'DELETE FROM transactions WHERE tx_time < %(time)s',
+                    {'time': mindate})
+                # cleanup deleted entities
+                session.system_sql(
+                    'DELETE FROM deleted_entities WHERE dtime < %(time)s',
+                    {'time': mindate})
+                session.commit()
+            finally:
+                session.close()
+        self.repo.looping_task(60*60*24, cleanup_old_transactions, self.repo)
--- a/hooks/bookmark.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/bookmark.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """bookmark related hooks
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/hooks/email.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/email.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """hooks to ensure use_email / primary_email relations consistency
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -26,7 +39,7 @@
 
     def precommit_event(self):
         if self.condition():
-            self.session.unsafe_execute(
+            self.session.execute(
                 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
                 {'x': self.entity.eid, 'y': self.email.eid}, 'x')
 
--- a/hooks/integrity.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/integrity.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,20 +1,36 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Core hooks: check for data integrity according to the instance'schema
 validity
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from threading import Lock
 
+from yams.schema import role_name
+
 from cubicweb import ValidationError
 from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
 from cubicweb.selectors import implements
 from cubicweb.uilib import soup2xhtml
 from cubicweb.server import hook
+from cubicweb.server.hook import set_operation
 
 # special relations that don't have to be checked for integrity, usually
 # because they are handled internally by hooks (so we trust ourselves)
@@ -35,13 +51,12 @@
     RQLUniqueConstraint in two different transactions, as explained in
     http://intranet.logilab.fr/jpl/ticket/36564
     """
-    asession = session.actual_session()
-    if 'uniquecstrholder' in asession.transaction_data:
+    if 'uniquecstrholder' in session.transaction_data:
         return
     _UNIQUE_CONSTRAINTS_LOCK.acquire()
-    asession.transaction_data['uniquecstrholder'] = True
+    session.transaction_data['uniquecstrholder'] = True
     # register operation responsible to release the lock on commit/rollback
-    _ReleaseUniqueConstraintsOperation(asession)
+    _ReleaseUniqueConstraintsOperation(session)
 
 def _release_unique_cstr_lock(session):
     if 'uniquecstrholder' in session.transaction_data:
@@ -61,50 +76,48 @@
     """checking relation cardinality has to be done after commit in
     case the relation is being replaced
     """
-    eid, rtype = None, None
+    role = key = base_rql = None
 
     def precommit_event(self):
-        # recheck pending eids
-        if self.session.deleted_in_transaction(self.eid):
-            return
-        if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
-            return
-        if self.session.unsafe_execute(*self._rql()).rowcount < 1:
-            etype = self.session.describe(self.eid)[0]
-            _ = self.session._
-            msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
-            msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
-            raise ValidationError(self.eid, {self.rtype: msg})
-
-    def commit_event(self):
-        pass
-
-    def _rql(self):
-        raise NotImplementedError()
+        session =self.session
+        pendingeids = session.transaction_data.get('pendingeids', ())
+        pendingrtypes = session.transaction_data.get('pendingrtypes', ())
+        # poping key is not optional: if further operation trigger new deletion
+        # of relation, we'll need a new operation
+        for eid, rtype in session.transaction_data.pop(self.key):
+            # recheck pending eids / relation types
+            if eid in pendingeids:
+                continue
+            if rtype in pendingrtypes:
+                continue
+            if not session.execute(self.base_rql % rtype, {'x': eid}, 'x'):
+                etype = session.describe(eid)[0]
+                _ = session._
+                msg = _('at least one relation %(rtype)s is required on '
+                        '%(etype)s (%(eid)s)')
+                msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
+                raise ValidationError(eid, {role_name(rtype, self.role): msg})
 
 
 class _CheckSRelationOp(_CheckRequiredRelationOperation):
     """check required subject relation"""
-    def _rql(self):
-        return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-
+    role = 'subject'
+    key = '_cwisrel'
+    base_rql = 'Any O WHERE S eid %%(x)s, S %s O'
 
 class _CheckORelationOp(_CheckRequiredRelationOperation):
     """check required object relation"""
-    def _rql(self):
-        return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+    role = 'object'
+    key = '_cwiorel'
+    base_rql = 'Any S WHERE O eid %%(x)s, S %s O'
 
 
 class IntegrityHook(hook.Hook):
     __abstract__ = True
     category = 'integrity'
 
-class UserIntegrityHook(IntegrityHook):
-    __abstract__ = True
-    __select__ = IntegrityHook.__select__ & hook.regular_session()
 
-
-class CheckCardinalityHook(UserIntegrityHook):
+class CheckCardinalityHook(IntegrityHook):
     """check cardinalities are satisfied"""
     __regid__ = 'checkcard'
     events = ('after_add_entity', 'before_delete_relation')
@@ -112,14 +125,6 @@
     def __call__(self):
         getattr(self, self.event)()
 
-    def checkrel_if_necessary(self, opcls, rtype, eid):
-        """check an equivalent operation has not already been added"""
-        for op in self._cw.pending_operations:
-            if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid:
-                break
-        else:
-            opcls(self._cw, rtype=rtype, eid=eid)
-
     def after_add_entity(self):
         eid = self.entity.eid
         eschema = self.entity.e_schema
@@ -127,10 +132,14 @@
             # skip automatically handled relations
             if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
                 continue
-            opcls = role == 'subject' and _CheckSRelationOp or _CheckORelationOp
             rdef = rschema.role_rdef(eschema, targetschemas[0], role)
             if rdef.role_cardinality(role) in '1+':
-                self.checkrel_if_necessary(opcls, rschema.type, eid)
+                if role == 'subject':
+                    set_operation(self._cw, '_cwisrel', (eid, rschema.type),
+                                  _CheckSRelationOp)
+                else:
+                    set_operation(self._cw, '_cwiorel', (eid, rschema.type),
+                                  _CheckORelationOp)
 
     def before_delete_relation(self):
         rtype = self.rtype
@@ -138,14 +147,16 @@
             return
         session = self._cw
         eidfrom, eidto = self.eidfrom, self.eidto
-        card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
         pendingrdefs = session.transaction_data.get('pendingrdefs', ())
         if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
             return
+        card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
         if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
-            self.checkrel_if_necessary(_CheckSRelationOp, rtype, eidfrom)
+            set_operation(self._cw, '_cwisrel', (eidfrom, rtype),
+                          _CheckSRelationOp)
         if card[1] in '1+' and not session.deleted_in_transaction(eidto):
-            self.checkrel_if_necessary(_CheckORelationOp, rtype, eidto)
+            set_operation(self._cw, '_cwiorel', (eidto, rtype),
+                          _CheckORelationOp)
 
 
 class _CheckConstraintsOp(hook.LateOperation):
@@ -176,7 +187,7 @@
         pass
 
 
-class CheckConstraintHook(UserIntegrityHook):
+class CheckConstraintHook(IntegrityHook):
     """check the relation satisfy its constraints
 
     this is delayed to a precommit time operation since other relation which
@@ -194,7 +205,7 @@
                                rdef=(self.eidfrom, self.rtype, self.eidto))
 
 
-class CheckAttributeConstraintHook(UserIntegrityHook):
+class CheckAttributeConstraintHook(IntegrityHook):
     """check the attribute relation satisfy its constraints
 
     this is delayed to a precommit time operation since other relation which
@@ -214,7 +225,7 @@
                                         rdef=(self.entity.eid, attr, None))
 
 
-class CheckUniqueHook(UserIntegrityHook):
+class CheckUniqueHook(IntegrityHook):
     __regid__ = 'checkunique'
     events = ('before_add_entity', 'before_update_entity')
 
@@ -227,49 +238,11 @@
                 if val is None:
                     continue
                 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
-                rset = self._cw.unsafe_execute(rql, {'val': val})
+                rset = self._cw.execute(rql, {'val': val})
                 if rset and rset[0][0] != entity.eid:
                     msg = self._cw._('the value "%s" is already used, use another one')
-                    raise ValidationError(entity.eid, {attr: msg % val})
-
-
-class _DelayedDeleteOp(hook.Operation):
-    """delete the object of composite relation except if the relation
-    has actually been redirected to another composite
-    """
-
-    def precommit_event(self):
-        session = self.session
-        # don't do anything if the entity is being created or deleted
-        if not (session.deleted_in_transaction(self.eid) or
-                session.added_in_transaction(self.eid)):
-            etype = session.describe(self.eid)[0]
-            session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
-                                   % (etype, self.relation),
-                                   {'x': self.eid}, 'x')
-
-
-class DeleteCompositeOrphanHook(IntegrityHook):
-    """delete the composed of a composite relation when this relation is deleted
-    """
-    __regid__ = 'deletecomposite'
-    events = ('before_delete_relation',)
-
-    def __call__(self):
-        # if the relation is being delete, don't delete composite's components
-        # automatically
-        pendingrdefs = self._cw.transaction_data.get('pendingrdefs', ())
-        if (self._cw.describe(self.eidfrom)[0], self.rtype,
-            self._cw.describe(self.eidto)[0]) in pendingrdefs:
-            return
-        composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
-                                                 'composite')
-        if composite == 'subject':
-            _DelayedDeleteOp(self._cw, eid=self.eidto,
-                             relation='Y %s X' % self.rtype)
-        elif composite == 'object':
-            _DelayedDeleteOp(self._cw, eid=self.eidfrom,
-                             relation='X %s Y' % self.rtype)
+                    qname = role_name(attr, 'subject')
+                    raise ValidationError(entity.eid, {qname: msg % val})
 
 
 class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -281,16 +254,20 @@
 
     def __call__(self):
         if self.event == 'before_delete_entity' and self.entity.name == 'owners':
-            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
-        elif self.event == 'before_update_entity' and 'name' in self.entity.edited_attributes:
+            msg = self._cw._('can\'t be deleted')
+            raise ValidationError(self.entity.eid, {None: msg})
+        elif self.event == 'before_update_entity' and \
+                 'name' in self.entity.edited_attributes:
             newname = self.entity.pop('name')
             oldname = self.entity.name
             if oldname == 'owners' and newname != oldname:
-                raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
+                qname = role_name('name', 'subject')
+                msg = self._cw._('can\'t be changed')
+                raise ValidationError(self.entity.eid, {qname: msg})
             self.entity['name'] = newname
 
 
-class TidyHtmlFields(UserIntegrityHook):
+class TidyHtmlFields(IntegrityHook):
     """tidy HTML in rich text strings"""
     __regid__ = 'htmltidy'
     events = ('before_add_entity', 'before_update_entity')
@@ -319,3 +296,59 @@
         user = self.entity
         if 'login' in user.edited_attributes and user.login:
             user.login = user.login.strip()
+
+
+# 'active' integrity hooks: you usually don't want to deactivate them, they are
+# not really integrity check, they maintain consistency on changes
+
+class _DelayedDeleteOp(hook.Operation):
+    """delete the object of composite relation except if the relation has
+    actually been redirected to another composite
+    """
+    key = base_rql = None
+
+    def precommit_event(self):
+        session = self.session
+        pendingeids = session.transaction_data.get('pendingeids', ())
+        neweids = session.transaction_data.get('neweids', ())
+        # poping key is not optional: if further operation trigger new deletion
+        # of composite relation, we'll need a new operation
+        for eid, rtype in session.transaction_data.pop(self.key):
+            # don't do anything if the entity is being created or deleted
+            if not (eid in pendingeids or eid in neweids):
+                etype = session.describe(eid)[0]
+                session.execute(self.base_rql % (etype, rtype), {'x': eid}, 'x')
+
+class _DelayedDeleteSEntityOp(_DelayedDeleteOp):
+    """delete orphan subject entity of a composite relation"""
+    key = '_cwiscomp'
+    base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT X %s Y'
+
+class _DelayedDeleteOEntityOp(_DelayedDeleteOp):
+    """check required object relation"""
+    key = '_cwiocomp'
+    base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT Y %s X'
+
+
+class DeleteCompositeOrphanHook(hook.Hook):
+    """delete the composed of a composite relation when this relation is deleted
+    """
+    __regid__ = 'deletecomposite'
+    events = ('before_delete_relation',)
+    category = 'activeintegrity'
+
+    def __call__(self):
+        # if the relation is being delete, don't delete composite's components
+        # automatically
+        pendingrdefs = self._cw.transaction_data.get('pendingrdefs', ())
+        if (self._cw.describe(self.eidfrom)[0], self.rtype,
+            self._cw.describe(self.eidto)[0]) in pendingrdefs:
+            return
+        composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+                                              'composite')
+        if composite == 'subject':
+            set_operation(self._cw, '_cwiocomp', (self.eidto, self.rtype),
+                          _DelayedDeleteOEntityOp)
+        elif composite == 'object':
+            set_operation(self._cw, '_cwiscomp', (self.eidfrom, self.rtype),
+                          _DelayedDeleteSEntityOp)
--- a/hooks/metadata.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/metadata.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Core hooks: set generic metadata
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -12,17 +25,7 @@
 
 from cubicweb.selectors import implements
 from cubicweb.server import hook
-
-
-def eschema_eid(session, eschema):
-    """get eid of the CWEType entity for the given yams type"""
-    # eschema.eid is None if schema has been readen from the filesystem, not
-    # from the database (eg during tests)
-    if eschema.eid is None:
-        eschema.eid = session.unsafe_execute(
-            'Any X WHERE X is CWEType, X name %(name)s',
-            {'name': str(eschema)})[0][0]
-    return eschema.eid
+from cubicweb.server.utils import eschema_eid
 
 
 class MetaDataHook(hook.Hook):
@@ -103,18 +106,17 @@
     events = ('after_add_entity',)
 
     def __call__(self):
-        asession = self._cw.actual_session()
-        if not asession.is_internal_session:
-            self._cw.add_relation(self.entity.eid, 'owned_by', asession.user.eid)
-            _SetCreatorOp(asession, entity=self.entity)
+        if not self._cw.is_internal_session:
+            self._cw.add_relation(self.entity.eid, 'owned_by', self._cw.user.eid)
+            _SetCreatorOp(self._cw, entity=self.entity)
 
 
 class _SyncOwnersOp(hook.Operation):
     def precommit_event(self):
-        self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
-                                    'NOT EXISTS(X owned_by U, X eid %(x)s)',
-                                    {'c': self.compositeeid, 'x': self.composedeid},
-                                    ('c', 'x'))
+        self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
+                             'NOT EXISTS(X owned_by U, X eid %(x)s)',
+                             {'c': self.compositeeid, 'x': self.composedeid},
+                             ('c', 'x'))
 
 
 class SyncCompositeOwner(MetaDataHook):
--- a/hooks/notification.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/notification.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some hooks to handle notification on entity's changes
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -103,29 +116,28 @@
 class EntityUpdateHook(NotificationHook):
     __regid__ = 'notifentityupdated'
     __abstract__ = True # do not register by default
-
+    __select__ = NotificationHook.__select__ & hook.from_dbapi_query()
     events = ('before_update_entity',)
     skip_attrs = set()
 
     def __call__(self):
         session = self._cw
-        if self.entity.eid in session.transaction_data.get('neweids', ()):
+        if session.added_in_transaction(self.entity.eid):
             return # entity is being created
-        if session.is_super_session:
-            return # ignore changes triggered by hooks
         # then compute changes
+        attrs = [k for k in self.entity.edited_attributes
+                 if not k in self.skip_attrs]
+        if not attrs:
+            return
         changes = session.transaction_data.setdefault('changes', {})
         thisentitychanges = changes.setdefault(self.entity.eid, set())
-        attrs = [k for k in self.entity.edited_attributes if not k in self.skip_attrs]
-        if not attrs:
-            return
         rqlsel, rqlrestr = [], ['X eid %(x)s']
         for i, attr in enumerate(attrs):
             var = chr(65+i)
             rqlsel.append(var)
             rqlrestr.append('X %s %s' % (attr, var))
         rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
-        rset = session.unsafe_execute(rql, {'x': self.entity.eid}, 'x')
+        rset = session.execute(rql, {'x': self.entity.eid}, 'x')
         for i, attr in enumerate(attrs):
             oldvalue = rset[0][i]
             newvalue = self.entity[attr]
@@ -139,13 +151,11 @@
 
 class SomethingChangedHook(NotificationHook):
     __regid__ = 'supervising'
+    __select__ = NotificationHook.__select__ & hook.from_dbapi_query()
     events = ('before_add_relation', 'before_delete_relation',
               'after_add_entity', 'before_update_entity')
 
     def __call__(self):
-        # XXX use proper selectors
-        if self._cw.is_super_session or self._cw.repo.config.repairing:
-            return # ignore changes triggered by hooks or maintainance shell
         dest = self._cw.vreg.config['supervising-addrs']
         if not dest: # no supervisors, don't do this for nothing...
             return
--- a/hooks/security.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/security.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,35 +1,53 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Security hooks: check permissions to add/delete/update entities according to
 the user connected to a session
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from cubicweb import Unauthorized
+from cubicweb.selectors import objectify_selector, lltrace
 from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
 
 
 def check_entity_attributes(session, entity, editedattrs=None):
     eid = entity.eid
     eschema = entity.e_schema
-    # ._default_set is only there on entity creation to indicate unspecified
-    # attributes which has been set to a default value defined in the schema
-    defaults = getattr(entity, '_default_set', ())
+    # .skip_security_attributes is there to bypass security for attributes
+    # set by hooks by modifying the entity's dictionnary
+    dontcheck = entity.skip_security_attributes
     if editedattrs is None:
         try:
             editedattrs = entity.edited_attributes
         except AttributeError:
-            editedattrs = entity
+            editedattrs = entity # XXX unexpected
     for attr in editedattrs:
-        if attr in defaults:
+        if attr in dontcheck:
             continue
         rdef = eschema.rdef(attr)
         if rdef.final: # non final relation are checked by other hooks
             # add/delete should be equivalent (XXX: unify them into 'update' ?)
             rdef.check_perm(session, 'update', eid=eid)
+    # don't update dontcheck until everything went fine: see usage in
+    # after_update_entity, where if we got an Unauthorized at hook time, we will
+    # retry and commit time
+    dontcheck |= frozenset(editedattrs)
 
 
 class _CheckEntityPermissionOp(hook.LateOperation):
@@ -53,10 +71,17 @@
         pass
 
 
+@objectify_selector
+@lltrace
+def write_security_enabled(cls, req, **kwargs):
+    if req is None or not req.write_security:
+        return 0
+    return 1
+
 class SecurityHook(hook.Hook):
     __abstract__ = True
     category = 'security'
-    __select__ = hook.Hook.__select__ & hook.regular_session()
+    __select__ = hook.Hook.__select__ & write_security_enabled()
 
 
 class AfterAddEntitySecurityHook(SecurityHook):
--- a/hooks/storages.py	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-"""hooks to handle attributes mapped to a custom storage
-"""
-from cubicweb.server.hook import Hook
-from cubicweb.server.sources.storages import ETYPE_ATTR_STORAGE
-
-
-class BFSSHook(Hook):
-    """abstract class for bytes file-system storage hooks"""
-    __abstract__ = True
-    category = 'bfss'
-
-
-class PreAddEntityHook(BFSSHook):
-    """"""
-    __regid__ = 'bfss_add_entity'
-    events = ('before_add_entity', )
-
-    def __call__(self):
-        etype = self.entity.__regid__
-        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
-            ETYPE_ATTR_STORAGE[etype][attr].entity_added(self.entity, attr)
-
-class PreUpdateEntityHook(BFSSHook):
-    """"""
-    __regid__ = 'bfss_update_entity'
-    events = ('before_update_entity', )
-
-    def __call__(self):
-        etype = self.entity.__regid__
-        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
-            ETYPE_ATTR_STORAGE[etype][attr].entity_updated(self.entity, attr)
-
-class PreDeleteEntityHook(BFSSHook):
-    """"""
-    __regid__ = 'bfss_delete_entity'
-    events = ('before_delete_entity', )
-
-    def __call__(self):
-        etype = self.entity.__regid__
-        for attr in ETYPE_ATTR_STORAGE.get(etype, ()):
-            ETYPE_ATTR_STORAGE[etype][attr].entity_deleted(self.entity, attr)
--- a/hooks/syncschema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/syncschema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """schema hooks:
 
 - synchronize the living schema object with the persistent schema
@@ -5,18 +22,15 @@
 
 checking for schema consistency is done in hooks.py
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
+from copy import copy
 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
-from yams.buildobjs import EntityType, RelationType, RelationDefinition
-from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
+from yams import buildobjs as ybo, schema2sql as y2sql
 
 from logilab.common.decorators import clear_cache
+from logilab.common.testlib import mock_object
 
 from cubicweb import ValidationError
 from cubicweb.selectors import implements
@@ -255,7 +269,7 @@
             # need to create the relation if it has not been already done by
             # another event of the same transaction
             if not rschema.type in session.transaction_data.get('createdtables', ()):
-                tablesql = rschema2sql(rschema)
+                tablesql = y2sql.rschema2sql(rschema)
                 # create the necessary table
                 for sql in tablesql.split(';'):
                     if sql.strip():
@@ -323,13 +337,13 @@
         rtype = entity.rtype.name
         obj = str(entity.otype.name)
         constraints = get_constraints(self.session, entity)
-        rdef = RelationDefinition(subj, rtype, obj,
-                                  description=entity.description,
-                                  cardinality=entity.cardinality,
-                                  constraints=constraints,
-                                  order=entity.ordernum,
-                                  eid=entity.eid,
-                                  **kwargs)
+        rdef = ybo.RelationDefinition(subj, rtype, obj,
+                                      description=entity.description,
+                                      cardinality=entity.cardinality,
+                                      constraints=constraints,
+                                      order=entity.ordernum,
+                                      eid=entity.eid,
+                                      **kwargs)
         MemSchemaRDefAdd(self.session, rdef)
         return rdef
 
@@ -347,8 +361,8 @@
                  'internationalizable': entity.internationalizable}
         rdef = self.init_rdef(**props)
         sysource = session.pool.source('system')
-        attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
-                                         rdef.constraints)
+        attrtype = y2sql.type_from_constraints(
+            sysource.dbhelper, rdef.object, rdef.constraints)
         # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
         # add a new column with UNIQUE, it should be added after the ALTER TABLE
         # using ADD INDEX
@@ -379,12 +393,13 @@
                 self.error('error while creating index for %s.%s: %s',
                            table, column, ex)
         # final relations are not infered, propagate
+        schema = session.vreg.schema
         try:
-            eschema = session.vreg.schema.eschema(rdef.subject)
+            eschema = schema.eschema(rdef.subject)
         except KeyError:
             return # entity type currently being added
         # propagate attribute to children classes
-        rschema = session.vreg.schema.rschema(rdef.name)
+        rschema = schema.rschema(rdef.name)
         # if relation type has been inserted in the same transaction, its final
         # attribute is still set to False, so we've to ensure it's False
         rschema.final = True
@@ -394,15 +409,19 @@
                       'cardinality': rdef.cardinality,
                       'constraints': rdef.constraints,
                       'permissions': rdef.get_permissions(),
-                      'order': rdef.order})
+                      'order': rdef.order,
+                      'infered': False, 'eid': None
+                      })
+        cstrtypemap = ss.cstrtype_mapping(session)
         groupmap = group_mapping(session)
+        object = schema.eschema(rdef.object)
         for specialization in eschema.specialized_by(False):
             if (specialization, rdef.object) in rschema.rdefs:
                 continue
-            sperdef = RelationDefinitionSchema(specialization, rschema, rdef.object, props)
-            for rql, args in ss.rdef2rql(rschema, str(specialization),
-                                         rdef.object, sperdef, groupmap=groupmap):
-                session.execute(rql, args)
+            sperdef = RelationDefinitionSchema(specialization, rschema,
+                                               object, props)
+            ss.execschemarql(session.execute, sperdef,
+                             ss.rdef2rql(sperdef, cstrtypemap, groupmap))
         # set default value, using sql for performance and to avoid
         # modification_date update
         if default:
@@ -451,13 +470,13 @@
                     rtype in session.transaction_data.get('createdtables', ())):
                 try:
                     rschema = schema.rschema(rtype)
-                    tablesql = rschema2sql(rschema)
+                    tablesql = y2sql.rschema2sql(rschema)
                 except KeyError:
                     # fake we add it to the schema now to get a correctly
                     # initialized schema but remove it before doing anything
                     # more dangerous...
                     rschema = schema.add_relation_type(rdef)
-                    tablesql = rschema2sql(rschema)
+                    tablesql = y2sql.rschema2sql(rschema)
                     schema.del_relation_type(rtype)
                 # create the necessary table
                 for sql in tablesql.split(';'):
@@ -490,11 +509,11 @@
                 return
             atype = self.rschema.objects(etype)[0]
             constraints = self.rschema.rdef(etype, atype).constraints
-            coltype = type_from_constraints(adbh, atype, constraints,
-                                            creating=False)
+            coltype = y2sql.type_from_constraints(adbh, atype, constraints,
+                                                  creating=False)
             # XXX check self.values['cardinality'][0] actually changed?
-            sql = adbh.sql_set_null_allowed(table, column, coltype,
-                                            self.values['cardinality'][0] != '1')
+            notnull = self.values['cardinality'][0] != '1'
+            sql = adbh.sql_set_null_allowed(table, column, coltype, notnull)
             session.system_sql(sql)
         if 'fulltextindexed' in self.values:
             UpdateFTIndexOp(session)
@@ -527,8 +546,8 @@
             oldcstr is None or oldcstr.max != newcstr.max):
             adbh = self.session.pool.source('system').dbhelper
             card = rtype.rdef(subjtype, objtype).cardinality
-            coltype = type_from_constraints(adbh, objtype, [newcstr],
-                                            creating=False)
+            coltype = y2sql.type_from_constraints(adbh, objtype, [newcstr],
+                                                  creating=False)
             sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
             try:
                 session.system_sql(sql, rollback_on_failure=False)
@@ -796,7 +815,7 @@
         if name in CORE_ETYPES:
             raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
         # delete every entities of this type
-        self._cw.unsafe_execute('DELETE %s X' % name)
+        self._cw.execute('DELETE %s X' % name)
         DropTable(self._cw, table=SQL_PREFIX + name)
         MemSchemaCWETypeDel(self._cw, name)
 
@@ -828,23 +847,26 @@
             return
         schema = self._cw.vreg.schema
         name = entity['name']
-        etype = EntityType(name=name, description=entity.get('description'),
-                           meta=entity.get('meta')) # don't care about final
+        etype = ybo.EntityType(name=name, description=entity.get('description'),
+                               meta=entity.get('meta')) # don't care about final
         # fake we add it to the schema now to get a correctly initialized schema
         # but remove it before doing anything more dangerous...
         schema = self._cw.vreg.schema
         eschema = schema.add_entity_type(etype)
         # generate table sql and rql to add metadata
-        tablesql = eschema2sql(self._cw.pool.source('system').dbhelper, eschema,
-                               prefix=SQL_PREFIX)
-        relrqls = []
+        tablesql = y2sql.eschema2sql(self._cw.pool.source('system').dbhelper,
+                                     eschema, prefix=SQL_PREFIX)
+        rdefrqls = []
+        gmap = group_mapping(self._cw)
+        cmap = ss.cstrtype_mapping(self._cw)
         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
             rschema = schema[rtype]
             sampletype = rschema.subjects()[0]
             desttype = rschema.objects()[0]
-            props = rschema.rdef(sampletype, desttype)
-            relrqls += list(ss.rdef2rql(rschema, name, desttype, props,
-                                        groupmap=group_mapping(self._cw)))
+            rdef = copy(rschema.rdef(sampletype, desttype))
+            rdef.subject = mock_object(eid=entity.eid)
+            mock = mock_object(eid=None)
+            rdefrqls.append( (mock, tuple(ss.rdef2rql(rdef, cmap, gmap))) )
         # now remove it !
         schema.del_entity_type(name)
         # create the necessary table
@@ -857,8 +879,8 @@
         etype.eid = entity.eid
         MemSchemaCWETypeAdd(self._cw, etype)
         # add meta relations
-        for rql, kwargs in relrqls:
-            self._cw.execute(rql, kwargs)
+        for rdef, relrqls in rdefrqls:
+            ss.execschemarql(self._cw.execute, rdef, relrqls)
 
 
 class BeforeUpdateCWETypeHook(DelCWETypeHook):
@@ -915,12 +937,12 @@
 
     def __call__(self):
         entity = self.entity
-        rtype = RelationType(name=entity.name,
-                             description=entity.get('description'),
-                             meta=entity.get('meta', False),
-                             inlined=entity.get('inlined', False),
-                             symmetric=entity.get('symmetric', False),
-                             eid=entity.eid)
+        rtype = ybo.RelationType(name=entity.name,
+                                 description=entity.get('description'),
+                                 meta=entity.get('meta', False),
+                                 inlined=entity.get('inlined', False),
+                                 symmetric=entity.get('symmetric', False),
+                                 eid=entity.eid)
         MemSchemaCWRTypeAdd(self._cw, rtype)
 
 
@@ -974,7 +996,7 @@
             if not (subjschema.eid in pendings or objschema.eid in pendings):
                 session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
                                 % (rschema, subjschema, objschema))
-        execute = session.unsafe_execute
+        execute = session.execute
         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
                        'R eid %%(x)s' % rdeftype, {'x': self.eidto})
         lastrel = rset[0][0] == 0
--- a/hooks/syncsession.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/syncsession.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Core hooks: synchronize living session on persistent data changes
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
+from yams.schema import role_name
 from cubicweb import UnknownProperty, ValidationError, BadConnectionId
 from cubicweb.selectors import implements
 from cubicweb.server import hook
@@ -147,11 +161,13 @@
         try:
             value = session.vreg.typed_value(key, value)
         except UnknownProperty:
+            qname = role_name('pkey', 'subject')
             raise ValidationError(self.entity.eid,
-                                  {'pkey': session._('unknown property key')})
+                                  {qname: session._('unknown property key')})
         except ValueError, ex:
+            qname = role_name('value', 'subject')
             raise ValidationError(self.entity.eid,
-                                  {'value': session._(str(ex))})
+                                  {qname: session._(str(ex))})
         if not session.user.matching_groups('managers'):
             session.add_relation(self.entity.eid, 'for_user', session.user.eid)
         else:
@@ -174,7 +190,8 @@
         except UnknownProperty:
             return
         except ValueError, ex:
-            raise ValidationError(entity.eid, {'value': session._(str(ex))})
+            qname = role_name('value', 'subject')
+            raise ValidationError(entity.eid, {qname: session._(str(ex))})
         if entity.for_user:
             for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
                 _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
@@ -214,8 +231,10 @@
         key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
                                      {'x': eidfrom}, 'x')[0]
         if session.vreg.property_info(key)['sitewide']:
+            qname = role_name('for_user', 'subject')
+            msg = session._("site-wide property can't be set for user")
             raise ValidationError(eidfrom,
-                                  {'for_user': session._("site-wide property can't be set for user")})
+                                  {qname: msg})
         for session_ in get_user_sessions(session.repo, self.eidto):
             _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
                               key=key, value=value)
--- a/hooks/test/unittest_bookmarks.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/test/unittest_bookmarks.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.testlib import CubicWebTC
--- a/hooks/test/unittest_hooks.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/test/unittest_hooks.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,24 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functional tests for core hooks
 
 note: most schemahooks.py hooks are actually tested in unittest_migrations.py
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main
@@ -102,9 +118,9 @@
                               'WHERE FE name "CWUser", RT name "in_group", TE name "String"')[0][0]
         self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
                      {'x': releid}, 'x')
-        ex = self.assertRaises(ValidationError,
-                               self.commit)
-        self.assertEquals(ex.errors, {'to_entity': 'RQLConstraint O final FALSE failed'})
+        ex = self.assertRaises(ValidationError, self.commit)
+        self.assertEquals(ex.errors,
+                          {'to_entity-object': 'RQLConstraint O final FALSE failed'})
 
     def test_html_tidy_hook(self):
         req = self.request()
@@ -217,23 +233,23 @@
     def test_unexistant_eproperty(self):
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+        self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+        self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
 
     def test_site_wide_eproperty(self):
         ex = self.assertRaises(ValidationError,
                                self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
+        self.assertEquals(ex.errors, {'for_user-subject': "site-wide property can't be set for user"})
 
     def test_bad_type_eproperty(self):
         ex = self.assertRaises(ValidationError,
                                self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+        self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+        self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
 
 
 class SchemaHooksTC(CubicWebTC):
@@ -253,7 +269,7 @@
             self.execute('INSERT CWUser X: X login "admin"')
         except ValidationError, ex:
             self.assertIsInstance(ex.entity, int)
-            self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+            self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
 
 if __name__ == '__main__':
--- a/hooks/test/unittest_syncschema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/test/unittest_syncschema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,30 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server.sqlutils import SQL_PREFIX
-
+from cubicweb.devtools.repotest import schema_eids_idx, restore_schema_eids_idx
 
-SCHEMA_EIDS = {}
+def teardown_module(*args):
+    del SchemaModificationHooksTC.schema_eids
+
 class SchemaModificationHooksTC(CubicWebTC):
     reset_schema = True
 
@@ -15,29 +34,12 @@
         # we have to read schema from the database to get eid for schema entities
         config._cubes = None
         cls.repo.fill_schema()
-        # remember them so we can reread it from the fs instead of the db (too
-        # costly) between tests
-        for x in cls.repo.schema.entities():
-            SCHEMA_EIDS[x] = x.eid
-        for x in cls.repo.schema.relations():
-            SCHEMA_EIDS[x] = x.eid
-            for rdef in x.rdefs.itervalues():
-                SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+        cls.schema_eids = schema_eids_idx(cls.repo.schema)
 
     @classmethod
     def _refresh_repo(cls):
         super(SchemaModificationHooksTC, cls)._refresh_repo()
-        # rebuild schema eid index
-        schema = cls.repo.schema
-        for x in schema.entities():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-        for x in cls.repo.schema.relations():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-            for rdef in x.rdefs.itervalues():
-                rdef.eid = SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)]
-                schema._eid_index[rdef.eid] = rdef
+        restore_schema_eids_idx(cls.repo.schema, cls.schema_eids)
 
     def index_exists(self, etype, attr, unique=False):
         self.session.set_pool()
--- a/hooks/workflow.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/hooks/workflow.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,14 +1,29 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Core hooks: workflow related hooks
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from datetime import datetime
 
+from yams.schema import role_name
+
 from cubicweb import RepositoryError, ValidationError
 from cubicweb.interfaces import IWorkflowable
 from cubicweb.selectors import implements
@@ -19,8 +34,8 @@
     nocheck = session.transaction_data.setdefault('skip-security', set())
     nocheck.add((x, 'in_state', oldstate))
     nocheck.add((x, 'in_state', newstate))
-    # delete previous state first in case we're using a super session,
-    # unless in_state isn't stored in the system source
+    # delete previous state first unless in_state isn't stored in the system
+    # source
     fromsource = session.describe(x)[1]
     if fromsource == 'system' or \
            not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
@@ -42,10 +57,8 @@
                and entity.current_workflow:
             state = entity.current_workflow.initial
             if state:
-                # use super session to by-pass security checks
-                session.super_session.add_relation(entity.eid, 'in_state',
-                                                   state.eid)
-
+                session.add_relation(entity.eid, 'in_state', state.eid)
+                _FireAutotransitionOp(session, entity=entity)
 
 class _FireAutotransitionOp(hook.Operation):
     """try to fire auto transition after state changes"""
@@ -75,8 +88,9 @@
         if mainwf.eid == self.wfeid:
             deststate = mainwf.initial
             if not deststate:
+                qname = role_name('custom_workflow', 'subject')
                 msg = session._('workflow has no initial state')
-                raise ValidationError(entity.eid, {'custom_workflow': msg})
+                raise ValidationError(entity.eid, {qname: msg})
             if mainwf.state_by_eid(entity.current_state.eid):
                 # nothing to do
                 return
@@ -85,6 +99,7 @@
                 if entity.current_state.eid != deststate.eid:
                     _change_state(session, entity.eid,
                                   entity.current_state.eid, deststate.eid)
+                    _FireAutotransitionOp(session, entity=entity)
                 return
             msg = session._('workflow changed to "%s"')
             msg %= session._(mainwf.name)
@@ -99,8 +114,9 @@
         outputs = set()
         for ep in tr.subworkflow_exit:
             if ep.subwf_state.eid in outputs:
+                qname = role_name('subworkflow_exit', 'subject')
                 msg = self.session._("can't have multiple exits on the same state")
-                raise ValidationError(self.treid, {'subworkflow_exit': msg})
+                raise ValidationError(self.treid, {qname: msg})
             outputs.add(ep.subwf_state.eid)
 
 
@@ -114,6 +130,7 @@
         wftr = forentity.subworkflow_input_transition()
         if wftr is None:
             # inconsistency detected
+            qname = role_name('to_state', 'subject')
             msg = session._("state doesn't belong to entity's current workflow")
             raise ValidationError(self.trinfo.eid, {'to_state': msg})
         tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
@@ -122,14 +139,7 @@
             msg = session._('exiting from subworkflow %s')
             msg %= session._(forentity.current_workflow.name)
             session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
-            # XXX iirk
-            req = forentity._cw
-            forentity._cw = session.super_session
-            try:
-                trinfo = forentity.change_state(tostate, msg, u'text/plain',
-                                                tr=wftr)
-            finally:
-                forentity._cw = req
+            forentity.change_state(tostate, msg, u'text/plain', tr=wftr)
 
 
 # hooks ########################################################################
@@ -175,8 +185,9 @@
         try:
             foreid = entity['wf_info_for']
         except KeyError:
+            qname = role_name('wf_info_for', 'subject')
             msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {'wf_info_for': msg})
+            raise ValidationError(entity.eid, {qname: msg})
         forentity = session.entity_from_eid(foreid)
         # then check it has a workflow set, unless we're in the process of changing
         # entity's workflow
@@ -195,7 +206,8 @@
             raise ValidationError(entity.eid, {None: msg})
         # True if we are coming back from subworkflow
         swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
-        cowpowers = session.is_super_session or 'managers' in session.user.groups
+        cowpowers = (session.user.is_in_group('managers')
+                     or not session.write_security)
         # no investigate the requested state change...
         try:
             treid = entity['by_transition']
@@ -203,41 +215,47 @@
             # no transition set, check user is a manager and destination state
             # is specified (and valid)
             if not cowpowers:
+                qname = role_name('by_transition', 'subject')
                 msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {'by_transition': msg})
+                raise ValidationError(entity.eid, {qname: msg})
             deststateeid = entity.get('to_state')
             if not deststateeid:
+                qname = role_name('by_transition', 'subject')
                 msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {'by_transition': msg})
+                raise ValidationError(entity.eid, {qname: msg})
             deststate = wf.state_by_eid(deststateeid)
             if deststate is None:
+                qname = role_name('to_state', 'subject')
                 msg = session._("state doesn't belong to entity's workflow")
-                raise ValidationError(entity.eid, {'to_state': msg})
+                raise ValidationError(entity.eid, {qname: msg})
         else:
             # check transition is valid and allowed, unless we're coming back
             # from subworkflow
             tr = session.entity_from_eid(treid)
             if swtr is None:
+                qname = role_name('by_transition', 'subject')
                 if tr is None:
                     msg = session._("transition doesn't belong to entity's workflow")
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
                 if not tr.has_input_state(fromstate):
                     msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
                         'tr': session._(tr.name), 'st': session._(fromstate.name)}
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
                 if not tr.may_be_fired(foreid):
                     msg = session._("transition may not be fired")
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
             if entity.get('to_state'):
                 deststateeid = entity['to_state']
                 if not cowpowers and deststateeid != tr.destination(forentity).eid:
+                    qname = role_name('by_transition', 'subject')
                     msg = session._("transition isn't allowed")
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
                 if swtr is None:
                     deststate = session.entity_from_eid(deststateeid)
                     if not cowpowers and deststate is None:
+                        qname = role_name('to_state', 'subject')
                         msg = session._("state doesn't belong to entity's workflow")
-                        raise ValidationError(entity.eid, {'to_state': msg})
+                        raise ValidationError(entity.eid, {qname: msg})
             else:
                 deststateeid = tr.destination(forentity).eid
         # everything is ok, add missing information on the trinfo entity
@@ -266,7 +284,7 @@
 
 
 class CheckInStateChangeAllowed(WorkflowHook):
-    """check state apply, in case of direct in_state change using unsafe_execute
+    """check state apply, in case of direct in_state change using unsafe execute
     """
     __regid__ = 'wfcheckinstate'
     __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
@@ -287,12 +305,14 @@
             if wf.state_by_eid(self.eidto):
                 break
         else:
+            qname = role_name('in_state', 'subject')
             msg = session._("state doesn't belong to entity's workflow. You may "
                             "want to set a custom workflow for this entity first.")
-            raise ValidationError(self.eidfrom, {'in_state': msg})
+            raise ValidationError(self.eidfrom, {qname: msg})
         if entity.current_workflow and wf.eid != entity.current_workflow.eid:
+            qname = role_name('in_state', 'subject')
             msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.eidfrom, {'in_state': msg})
+            raise ValidationError(self.eidfrom, {qname: msg})
 
 
 class SetModificationDateOnStateChange(WorkflowHook):
@@ -307,8 +327,7 @@
             return
         entity = self._cw.entity_from_eid(self.eidfrom)
         try:
-            entity.set_attributes(modification_date=datetime.now(),
-                                  _cw_unsafe=True)
+            entity.set_attributes(modification_date=datetime.now())
         except RepositoryError, ex:
             # usually occurs if entity is coming from a read-only source
             # (eg ldap user)
--- a/i18n.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/i18n.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Some i18n/gettext utilities.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/i18n/en.po	Wed Mar 24 10:23:31 2010 +0100
+++ b/i18n/en.po	Wed Apr 28 11:54:13 2010 +0200
@@ -308,6 +308,42 @@
 msgid "CWUser_plural"
 msgstr "Users"
 
+#, python-format
+msgid ""
+"Can't restore %(role)s relation %(rtype)s to entity %(eid)s which is already "
+"linked using this relation."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s between %(subj)s and %(obj)s, that relation "
+"does not exists anymore in the schema."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
+"exists anymore in the schema."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s, %(role)s entity %(eid)s doesn't exist "
+"anymore."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
 msgid "Date"
 msgstr "Date"
 
@@ -2107,9 +2143,15 @@
 msgid "entity created"
 msgstr ""
 
+msgid "entity creation"
+msgstr ""
+
 msgid "entity deleted"
 msgstr ""
 
+msgid "entity deletion"
+msgstr ""
+
 msgid "entity edited"
 msgstr ""
 
@@ -2130,6 +2172,9 @@
 msgid "entity types which may use this workflow"
 msgstr ""
 
+msgid "entity update"
+msgstr ""
+
 msgid "error while embedding page"
 msgstr ""
 
@@ -2219,6 +2264,10 @@
 msgid "facets_in_state-facet_description"
 msgstr ""
 
+#, python-format
+msgid "failed to uniquify path (%s, %s)"
+msgstr ""
+
 msgid "february"
 msgstr ""
 
@@ -2481,6 +2530,9 @@
 msgid "incontext"
 msgstr "in-context"
 
+msgid "incorrect captcha value"
+msgstr ""
+
 #, python-format
 msgid "incorrect value (%(value)s) for type \"%(type)s\""
 msgstr ""
@@ -3099,6 +3151,12 @@
 msgid "relation %(relname)s of %(ent)s"
 msgstr ""
 
+msgid "relation add"
+msgstr ""
+
+msgid "relation removal"
+msgstr ""
+
 msgid "relation_type"
 msgstr "relation type"
 
@@ -3313,6 +3371,12 @@
 msgid "site-wide property can't be set for user"
 msgstr ""
 
+msgid "some errors occured:"
+msgstr ""
+
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
 msgid "sorry, the server is unable to handle this query"
 msgstr ""
 
@@ -3584,6 +3648,9 @@
 msgid "toggle check boxes"
 msgstr ""
 
+msgid "transaction undoed"
+msgstr ""
+
 #, python-format
 msgid "transition %(tr)s isn't allowed from %(st)s"
 msgstr ""
@@ -3676,12 +3743,18 @@
 msgid "ui.time-format"
 msgstr "time format"
 
+msgid "unable to check captcha, please try again"
+msgstr ""
+
 msgid "unaccessible"
 msgstr ""
 
 msgid "unauthorized value"
 msgstr ""
 
+msgid "undo"
+msgstr ""
+
 msgid "unique identifier used to connect to the application"
 msgstr ""
 
--- a/i18n/es.po	Wed Mar 24 10:23:31 2010 +0100
+++ b/i18n/es.po	Wed Apr 28 11:54:13 2010 +0200
@@ -316,6 +316,42 @@
 msgid "CWUser_plural"
 msgstr "Usuarios"
 
+#, python-format
+msgid ""
+"Can't restore %(role)s relation %(rtype)s to entity %(eid)s which is already "
+"linked using this relation."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s between %(subj)s and %(obj)s, that relation "
+"does not exists anymore in the schema."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
+"exists anymore in the schema."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s, %(role)s entity %(eid)s doesn't exist "
+"anymore."
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
 msgid "Date"
 msgstr "Fecha"
 
@@ -2152,9 +2188,15 @@
 msgid "entity created"
 msgstr "entidad creada"
 
+msgid "entity creation"
+msgstr ""
+
 msgid "entity deleted"
 msgstr "Entidad eliminada"
 
+msgid "entity deletion"
+msgstr ""
+
 msgid "entity edited"
 msgstr "entidad modificada"
 
@@ -2177,6 +2219,9 @@
 msgid "entity types which may use this workflow"
 msgstr ""
 
+msgid "entity update"
+msgstr ""
+
 msgid "error while embedding page"
 msgstr "Error durante la inclusión de la página"
 
@@ -2269,6 +2314,10 @@
 msgid "facets_in_state-facet_description"
 msgstr "faceta en el estado"
 
+#, python-format
+msgid "failed to uniquify path (%s, %s)"
+msgstr ""
+
 msgid "february"
 msgstr "Febrero"
 
@@ -2539,6 +2588,9 @@
 msgid "incontext"
 msgstr "En el contexto"
 
+msgid "incorrect captcha value"
+msgstr ""
+
 #, python-format
 msgid "incorrect value (%(value)s) for type \"%(type)s\""
 msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
@@ -3172,6 +3224,12 @@
 msgid "relation %(relname)s of %(ent)s"
 msgstr "relación %(relname)s de %(ent)s"
 
+msgid "relation add"
+msgstr ""
+
+msgid "relation removal"
+msgstr ""
+
 msgid "relation_type"
 msgstr "tipo de relación"
 
@@ -3394,6 +3452,12 @@
 msgstr ""
 "una propiedad especifica para el sitio no puede establecerse para el usuario"
 
+msgid "some errors occured:"
+msgstr ""
+
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
 msgid "sorry, the server is unable to handle this query"
 msgstr "lo sentimos, el servidor no puede manejar esta consulta"
 
@@ -3665,6 +3729,9 @@
 msgid "toggle check boxes"
 msgstr "cambiar valor"
 
+msgid "transaction undoed"
+msgstr ""
+
 #, python-format
 msgid "transition %(tr)s isn't allowed from %(st)s"
 msgstr ""
@@ -3757,12 +3824,18 @@
 msgid "ui.time-format"
 msgstr ""
 
+msgid "unable to check captcha, please try again"
+msgstr ""
+
 msgid "unaccessible"
 msgstr "inaccesible"
 
 msgid "unauthorized value"
 msgstr "valor no permitido"
 
+msgid "undo"
+msgstr ""
+
 msgid "unique identifier used to connect to the application"
 msgstr "identificador unico utilizado para conectar a la aplicación"
 
--- a/i18n/fr.po	Wed Mar 24 10:23:31 2010 +0100
+++ b/i18n/fr.po	Wed Apr 28 11:54:13 2010 +0200
@@ -315,6 +315,54 @@
 msgid "CWUser_plural"
 msgstr "Utilisateurs"
 
+#, python-format
+msgid ""
+"Can't restore %(role)s relation %(rtype)s to entity %(eid)s which is already "
+"linked using this relation."
+msgstr ""
+"Ne peut restaurer la relation %(role)s %(rtype)s vers l'entité %(eid)s qui "
+"est déja lié à une autre entité par cette relation."
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s between %(subj)s and %(obj)s, that relation "
+"does not exists anymore in the schema."
+msgstr ""
+"Ne peut restaurer la relation %(rtype)s entre %(subj)s et  %(obj)s, cette "
+"relation n'existe plus dans le schéma."
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
+"exists anymore in the schema."
+msgstr ""
+"Ne peut restaurer la relation %(rtype)s de l'entité %(eid)s, cette "
+"relationn'existe plus dans le schéma"
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s, %(role)s entity %(eid)s doesn't exist "
+"anymore."
+msgstr ""
+"Ne peut restaurer la relation %(rtype)s, l'entité %(role)s %(eid)s n'existe "
+"plus."
+
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+"Ne peut annuler l'ajout de relation %(rtype)s de %(subj)s vers %(obj)s, "
+"cette relation n'existe plus"
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+"Ne peut annuler la création de l'entité %(eid)s de type %(etype)s, ce type "
+"n'existe plus"
+
 msgid "Date"
 msgstr "Date"
 
@@ -2175,9 +2223,15 @@
 msgid "entity created"
 msgstr "entité créée"
 
+msgid "entity creation"
+msgstr "création d'entité"
+
 msgid "entity deleted"
 msgstr "entité supprimée"
 
+msgid "entity deletion"
+msgstr "suppression d'entité"
+
 msgid "entity edited"
 msgstr "entité éditée"
 
@@ -2199,6 +2253,9 @@
 msgid "entity types which may use this workflow"
 msgstr "types d'entité pouvant utiliser ce workflow"
 
+msgid "entity update"
+msgstr "mise à jour d'entité"
+
 msgid "error while embedding page"
 msgstr "erreur pendant l'inclusion de la page"
 
@@ -2291,6 +2348,10 @@
 msgid "facets_in_state-facet_description"
 msgstr ""
 
+#, python-format
+msgid "failed to uniquify path (%s, %s)"
+msgstr "ne peut obtenir un nom de fichier unique (%s, %s)"
+
 msgid "february"
 msgstr "février"
 
@@ -2564,6 +2625,9 @@
 msgid "incontext"
 msgstr "dans le contexte"
 
+msgid "incorrect captcha value"
+msgstr "valeur de captcha incorrecte"
+
 #, python-format
 msgid "incorrect value (%(value)s) for type \"%(type)s\""
 msgstr "valeur %(value)s incorrecte pour le type \"%(type)s\""
@@ -3196,6 +3260,12 @@
 msgid "relation %(relname)s of %(ent)s"
 msgstr "relation %(relname)s de %(ent)s"
 
+msgid "relation add"
+msgstr "ajout de relation"
+
+msgid "relation removal"
+msgstr "suppression de relation"
+
 msgid "relation_type"
 msgstr "type de relation"
 
@@ -3418,6 +3488,13 @@
 msgid "site-wide property can't be set for user"
 msgstr "une propriété spécifique au site ne peut être propre à un utilisateur"
 
+msgid "some errors occured:"
+msgstr "des erreurs sont survenues"
+
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+"des transactions plus récentes modifient cette entité, annulez les d'abord"
+
 msgid "sorry, the server is unable to handle this query"
 msgstr "désolé, le serveur ne peut traiter cette requête"
 
@@ -3694,6 +3771,9 @@
 msgid "toggle check boxes"
 msgstr "inverser les cases à cocher"
 
+msgid "transaction undoed"
+msgstr "transaction annulées"
+
 #, python-format
 msgid "transition %(tr)s isn't allowed from %(st)s"
 msgstr "la transition %(tr)s n'est pas autorisée depuis l'état %(st)s"
@@ -3786,12 +3866,18 @@
 msgid "ui.time-format"
 msgstr "format de l'heure"
 
+msgid "unable to check captcha, please try again"
+msgstr "impossible de vérifier le captcha, veuillez réessayer"
+
 msgid "unaccessible"
 msgstr "inaccessible"
 
 msgid "unauthorized value"
 msgstr "valeur non autorisée"
 
+msgid "undo"
+msgstr "annuler"
+
 msgid "unique identifier used to connect to the application"
 msgstr "identifiant unique utilisé pour se connecter à l'application"
 
--- a/interfaces.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/interfaces.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,29 @@
-"""Specific views for entities implementing IDownloadable
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Standard interfaces.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. note::
+
+  The `implements` selector matches not only entity classes but also
+  their interfaces. Writing __select__ = implements('IGeocodable') is
+  a perfectly fine thing to do.
 """
-
 __docformat__ = "restructuredtext en"
 
 from logilab.common.interface import Interface
@@ -159,6 +177,7 @@
 class IBreadCrumbs(Interface):
     """interface for entities which can be "located" on some path"""
 
+    # XXX fix recurs !
     def breadcrumbs(self, view, recurs=False):
         """return a list containing some:
 
--- a/mail.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/mail.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Common utilies to format / semd emails.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -215,16 +228,9 @@
         """return a list of either 2-uple (email, language) or user entity to
         who this email should be sent
         """
-        # use super_session when available, we don't want to consider security
-        # when selecting recipients_finder
-        try:
-            req = self._cw.super_session
-        except AttributeError:
-            req = self._cw
-        finder = self._cw.vreg['components'].select('recipients_finder', req,
-                                                    rset=self.cw_rset,
-                                                    row=self.cw_row or 0,
-                                                    col=self.cw_col or 0)
+        finder = self._cw.vreg['components'].select(
+            'recipients_finder', self._cw, rset=self.cw_rset,
+            row=self.cw_row or 0, col=self.cw_col or 0)
         return finder.recipients()
 
     def send_now(self, recipients, msg):
--- a/md5crypt.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/md5crypt.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,23 @@
 #########################################################
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 XXX clarify this header
-:organization: Logilab
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 # md5crypt.py
 #
--- a/migration.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/migration.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """utilities for instances migration
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/misc/cwdesklets/rqlsensor/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/cwdesklets/rqlsensor/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import webbrowser
 reload(webbrowser)
--- a/misc/cwfs/cwfs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/cwfs/cwfs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 class Schema :
 
--- a/misc/cwfs/cwfs_test.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/cwfs/cwfs_test.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/misc/cwzope/cwzope.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/cwzope/cwzope.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from AccessControl import getSecurityManager
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.7.0_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,40 @@
+typemap = repo.system_source.dbhelper.TYPE_MAPPING
+sqls = """
+CREATE TABLE transactions (
+  tx_uuid CHAR(32) PRIMARY KEY NOT NULL,
+  tx_user INTEGER NOT NULL,
+  tx_time %s NOT NULL
+);;
+CREATE INDEX transactions_tx_user_idx ON transactions(tx_user);;
+
+CREATE TABLE tx_entity_actions (
+  tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
+  txa_action CHAR(1) NOT NULL,
+  txa_public %s NOT NULL,
+  txa_order INTEGER,
+  eid INTEGER NOT NULL,
+  etype VARCHAR(64) NOT NULL,
+  changes %s
+);;
+CREATE INDEX tx_entity_actions_txa_action_idx ON tx_entity_actions(txa_action);;
+CREATE INDEX tx_entity_actions_txa_public_idx ON tx_entity_actions(txa_public);;
+CREATE INDEX tx_entity_actions_eid_idx ON tx_entity_actions(eid);;
+CREATE INDEX tx_entity_actions_etype_idx ON tx_entity_actions(etype);;
+
+CREATE TABLE tx_relation_actions (
+  tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
+  txa_action CHAR(1) NOT NULL,
+  txa_public %s NOT NULL,
+  txa_order INTEGER,
+  eid_from INTEGER NOT NULL,
+  eid_to INTEGER NOT NULL,
+  rtype VARCHAR(256) NOT NULL
+);;
+CREATE INDEX tx_relation_actions_txa_action_idx ON tx_relation_actions(txa_action);;
+CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);;
+CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);;
+CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to)
+""" % (typemap['Datetime'],
+       typemap['Boolean'], typemap['Bytes'], typemap['Boolean'])
+for statement in sqls.split(';;'):
+    sql(statement)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.7.2_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,2 @@
+sql('DROP FUNCTION IF EXISTS _fsopen(bytea)')
+sql('DROP FUNCTION IF EXISTS fspath(bigint, text, text)')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.7.4_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,1 @@
+sync_schema_props_perms('TrInfo', syncprops=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.7.5_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,4 @@
+if versions_map['cubicweb'][0] == (3, 7, 4):
+    config['http-session-time'] *= 60
+    config['cleanup-session-time'] *= 60
+    config['cleanup-anonymous-session-time'] *= 60
--- a/misc/migration/bootstrapmigration_repository.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,95 +1,112 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """allways executed before all others in server migration
 
 it should only include low level schema changes
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
+from cubicweb.server.session import hooks_control
+from cubicweb.server import schemaserial as ss
 
 applcubicwebversion, cubicwebversion = versions_map['cubicweb']
 
-from cubicweb.server import schemaserial as ss
 def _add_relation_definition_no_perms(subjtype, rtype, objtype):
     rschema = fsschema.rschema(rtype)
-    for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
-        rql(query, args, ask_confirm=False)
+    rdef = rschema.rdefs[(subjtype, objtype)]
+    rdef.rtype = schema.rschema(rtype)
+    rdef.subject = schema.eschema(subjtype)
+    rdef.object = schema.eschema(objtype)
+    ss.execschemarql(rql, rdef, ss.rdef2rql(rdef, CSTRMAP, groupmap=None))
     commit(ask_confirm=False)
 
 if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+    CSTRMAP = dict(rql('Any T, X WHERE X is CWConstraintType, X name T',
+                       ask_confirm=False))
     _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'CWGroup')
     _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'RQLExpression')
-    session.set_pool()
-    session.unsafe_execute('SET X update_permission Y WHERE X is CWAttribute, X add_permission Y')
+    rql('SET X update_permission Y WHERE X is CWAttribute, X add_permission Y')
     drop_relation_definition('CWAttribute', 'add_permission', 'CWGroup')
     drop_relation_definition('CWAttribute', 'add_permission', 'RQLExpression')
     drop_relation_definition('CWAttribute', 'delete_permission', 'CWGroup')
     drop_relation_definition('CWAttribute', 'delete_permission', 'RQLExpression')
 
 elif applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+    CSTRMAP = dict(rql('Any T, X WHERE X is CWConstraintType, X name T',
+                       ask_confirm=False))
     session.set_pool()
-    session.execute = session.unsafe_execute
     permsdict = ss.deserialize_ertype_permissions(session)
 
-    config.disabled_hooks_categories.add('integrity')
-    for rschema in repo.schema.relations():
-        rpermsdict = permsdict.get(rschema.eid, {})
-        for rdef in rschema.rdefs.values():
-            for action in rdef.ACTIONS:
-                actperms = []
-                for something in rpermsdict.get(action == 'update' and 'add' or action, ()):
-                    if isinstance(something, tuple):
-                        actperms.append(rdef.rql_expression(*something))
-                    else: # group name
-                        actperms.append(something)
-                rdef.set_action_permissions(action, actperms)
-    for action in ('read', 'add', 'delete'):
-        _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup')
-        _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression')
-    for action in ('read', 'update'):
-        _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup')
-        _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression')
-    for action in ('read', 'add', 'delete'):
-        rql('SET X %s_permission Y WHERE X is CWRelation, '
-            'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action))
+    with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+        for rschema in repo.schema.relations():
+            rpermsdict = permsdict.get(rschema.eid, {})
+            for rdef in rschema.rdefs.values():
+                for action in rdef.ACTIONS:
+                    actperms = []
+                    for something in rpermsdict.get(action == 'update' and 'add' or action, ()):
+                        if isinstance(something, tuple):
+                            actperms.append(rdef.rql_expression(*something))
+                        else: # group name
+                            actperms.append(something)
+                    rdef.set_action_permissions(action, actperms)
+        for action in ('read', 'add', 'delete'):
+            _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup')
+            _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression')
+        for action in ('read', 'update'):
+            _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup')
+            _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression')
+        for action in ('read', 'add', 'delete'):
+            rql('SET X %s_permission Y WHERE X is CWRelation, '
+                'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action))
+            rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+                'X %s_permission Y WHERE X is CWRelation, '
+                'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
+                'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
+        rql('SET X read_permission Y WHERE X is CWAttribute, '
+            'RT read_permission Y, X relation_type RT, Y is CWGroup')
         rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
-            'X %s_permission Y WHERE X is CWRelation, '
-            'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
-            'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
-    rql('SET X read_permission Y WHERE X is CWAttribute, '
-        'RT read_permission Y, X relation_type RT, Y is CWGroup')
-    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
-        'X read_permission Y WHERE X is CWAttribute, '
-        'X relation_type RT, RT read_permission Y2, Y2 exprtype YET, '
-        'Y2 mainvars YMV, Y2 expression YEX')
-    rql('SET X update_permission Y WHERE X is CWAttribute, '
-        'RT add_permission Y, X relation_type RT, Y is CWGroup')
-    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
-        'X update_permission Y WHERE X is CWAttribute, '
-        'X relation_type RT, RT add_permission Y2, Y2 exprtype YET, '
-        'Y2 mainvars YMV, Y2 expression YEX')
-    for action in ('read', 'add', 'delete'):
-        drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
-        drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
-    config.disabled_hooks_categories.remove('integrity')
+            'X read_permission Y WHERE X is CWAttribute, '
+            'X relation_type RT, RT read_permission Y2, Y2 exprtype YET, '
+            'Y2 mainvars YMV, Y2 expression YEX')
+        rql('SET X update_permission Y WHERE X is CWAttribute, '
+            'RT add_permission Y, X relation_type RT, Y is CWGroup')
+        rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+            'X update_permission Y WHERE X is CWAttribute, '
+            'X relation_type RT, RT add_permission Y2, Y2 exprtype YET, '
+            'Y2 mainvars YMV, Y2 expression YEX')
+        for action in ('read', 'add', 'delete'):
+            drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
+            drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
 
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
 
-    session.set_shared_data('do-not-insert-cwuri', True)
-    deactivate_verification_hooks()
-    add_relation_type('cwuri')
-    base_url = session.base_url()
-    # use an internal session since some entity might forbid modifications to admin
-    isession = repo.internal_session()
-    for eid, in rql('Any X', ask_confirm=False):
-        type, source, extid = session.describe(eid)
-        if source == 'system':
-            isession.execute('SET X cwuri %(u)s WHERE X eid %(x)s',
-                             {'x': eid, 'u': base_url + u'eid/%s' % eid})
-    isession.commit()
-    reactivate_verification_hooks()
-    session.set_shared_data('do-not-insert-cwuri', False)
+    with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+        session.set_shared_data('do-not-insert-cwuri', True)
+        add_relation_type('cwuri')
+        base_url = session.base_url()
+        for eid, in rql('Any X', ask_confirm=False):
+            type, source, extid = session.describe(eid)
+            if source == 'system':
+                rql('SET X cwuri %(u)s WHERE X eid %(x)s',
+                    {'x': eid, 'u': base_url + u'eid/%s' % eid})
+        isession.commit()
+        session.set_shared_data('do-not-insert-cwuri', False)
 
 if applcubicwebversion < (3, 5, 0) and cubicwebversion >= (3, 5, 0):
     # check that migration is not doomed
--- a/misc/migration/postcreate.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/misc/migration/postcreate.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb post creation script, set user's workflow
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 # insert versions
 create_entity('CWProperty', pkey=u'system.version.cubicweb',
@@ -42,8 +55,8 @@
 
 # need this since we already have at least one user in the database (the default admin)
 for user in rql('Any X WHERE X is CWUser').entities():
-    session.unsafe_execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
-                           {'x': user.eid, 's': activated.eid}, 'x')
+    rql('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
+        {'x': user.eid, 's': activated.eid}, 'x')
 
 # on interactive mode, ask for level 0 persistent options
 if interactive_mode:
--- a/mixins.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/mixins.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,21 @@
-"""mixins of entity/views organized somewhat in a graph or tree structure
-
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""mixins of entity/views organized somewhat in a graph or tree structure"""
 __docformat__ = "restructuredtext en"
 
 from itertools import chain
@@ -17,7 +27,7 @@
 
 
 class TreeMixIn(object):
-    """base tree-mixin providing the tree interface
+    """base tree-mixin implementing the tree interface
 
     This mixin has to be inherited explicitly and configured using the
     tree_attribute, parent_target and children_target class attribute to
@@ -275,8 +285,7 @@
 
 
 class ProgressMixIn(object):
-    """provide default implementations for IProgress interface methods"""
-    # This is an adapter isn't it ?
+    """provide a default implementations for IProgress interface methods"""
 
     @property
     def cost(self):
--- a/mttransforms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/mttransforms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """mime type transformation engine for cubicweb, based on mtconverter
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytestconf.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,51 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""pytest configuration file: we need this to properly remove ressources
+cached on test classes, at least until we've proper support for teardown_class
+"""
+import sys
+from os.path import split, splitext
+from logilab.common.pytest import PyTester
+
+from cubicweb.etwist.server import _gc_debug
+
+class CustomPyTester(PyTester):
+    def testfile(self, filename, batchmode=False):
+        try:
+            return super(CustomPyTester, self).testfile(filename, batchmode)
+        finally:
+            modname = splitext(split(filename)[1])[0]
+            try:
+                module = sys.modules[modname]
+            except KeyError:
+                # error during test module import
+                return
+            for cls in vars(module).values():
+                if getattr(cls, '__module__', None) != modname:
+                    continue
+                clean_repo_test_cls(cls)
+            #_gc_debug()
+
+def clean_repo_test_cls(cls):
+    if 'repo' in cls.__dict__:
+        if not cls.repo._shutting_down:
+            cls.repo.shutdown()
+        del cls.repo
+    for clsattr in ('cnx', '_orig_cnx', 'config', '_config', 'vreg', 'schema'):
+        if clsattr in cls.__dict__:
+            delattr(cls, clsattr)
--- a/req.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/req.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Base class for request/session
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: Library General Public License version 2 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
 from urlparse import urlsplit, urlunsplit
 from urllib import quote as urlquote, unquote as urlunquote
 from datetime import time, datetime, timedelta
@@ -23,6 +37,12 @@
 CACHE_REGISTRY = {}
 
 
+def _check_cw_unsafe(kwargs):
+    if kwargs.pop('_cw_unsafe', False):
+        warn('[3.7] _cw_unsafe argument is deprecated, now unsafe by '
+             'default, control it using cw_[read|write]_security.',
+             DeprecationWarning, stacklevel=3)
+
 class Cache(dict):
     def __init__(self):
         super(Cache, self).__init__()
@@ -36,12 +56,10 @@
 
     request/session is the main resources accessor, mainly through it's vreg
     attribute:
-    :vreg:
-      the instance's registry
-    :vreg.schema:
-      the instance's schema
-    :vreg.config:
-      the instance's configuration
+
+    :attribute vreg: the instance's registry
+    :attribute vreg.schema: the instance's schema
+    :attribute vreg.config: the instance's configuration
     """
     def __init__(self, vreg):
         self.vreg = vreg
@@ -71,7 +89,8 @@
         def get_entity(row, col=0, etype=etype, req=self, rset=rset):
             return req.vreg.etype_class(etype)(req, rset, row, col)
         rset.get_entity = get_entity
-        return self.decorate_rset(rset)
+        rset.req = self
+        return rset
 
     def eid_rset(self, eid, etype=None):
         """return a result set for the given eid without doing actual query
@@ -83,14 +102,17 @@
             etype = self.describe(eid)[0]
         rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
                          [(etype,)])
-        return self.decorate_rset(rset)
+        rset.req = self
+        return rset
 
     def empty_rset(self):
         """return a result set for the given eid without doing actual query
         (we have the eid, we can suppose it exists and user has access to the
         entity)
         """
-        return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
+        rset = ResultSet([], 'Any X WHERE X eid -1')
+        rset.req = self
+        return rset
 
     def entity_from_eid(self, eid, etype=None):
         """return an entity instance for the given eid. No query is done"""
@@ -108,63 +130,19 @@
     def set_entity_cache(self, entity):
         pass
 
-    # XXX move to CWEntityManager or even better as factory method (unclear
-    # where yet...)
-
-    def create_entity(self, etype, _cw_unsafe=False, **kwargs):
+    def create_entity(self, etype, **kwargs):
         """add a new entity of the given type
 
         Example (in a shell session):
 
-        c = create_entity('Company', name=u'Logilab')
-        create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe')
+        >>> c = create_entity('Company', name=u'Logilab')
+        >>> create_entity('Person', firstname=u'John', lastname=u'Doe',
+        ...               works_for=c)
 
         """
-        if _cw_unsafe:
-            execute = self.unsafe_execute
-        else:
-            execute = self.execute
-        rql = 'INSERT %s X' % etype
-        relations = []
-        restrictions = set()
-        cachekey = []
-        pending_relations = []
-        for attr, value in kwargs.items():
-            if isinstance(value, (tuple, list, set, frozenset)):
-                if len(value) == 1:
-                    value = iter(value).next()
-                else:
-                    del kwargs[attr]
-                    pending_relations.append( (attr, value) )
-                    continue
-            if hasattr(value, 'eid'): # non final relation
-                rvar = attr.upper()
-                # XXX safer detection of object relation
-                if attr.startswith('reverse_'):
-                    relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
-                else:
-                    relations.append('X %s %s' % (attr, rvar))
-                restriction = '%s eid %%(%s)s' % (rvar, attr)
-                if not restriction in restrictions:
-                    restrictions.add(restriction)
-                cachekey.append(attr)
-                kwargs[attr] = value.eid
-            else: # attribute
-                relations.append('X %s %%(%s)s' % (attr, attr))
-        if relations:
-            rql = '%s: %s' % (rql, ', '.join(relations))
-        if restrictions:
-            rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
-        created = execute(rql, kwargs, cachekey).get_entity(0, 0)
-        for attr, values in pending_relations:
-            if attr.startswith('reverse_'):
-                restr = 'Y %s X' % attr[len('reverse_'):]
-            else:
-                restr = 'X %s Y' % attr
-            execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
-                restr, ','.join(str(r.eid) for r in values)),
-                         {'x': created.eid}, 'x')
-        return created
+        _check_cw_unsafe(kwargs)
+        cls = self.vreg['etypes'].etype_class(etype)
+        return cls.cw_instantiate(self.execute, **kwargs)
 
     def ensure_ro_rql(self, rql):
         """raise an exception if the given rql is not a select query"""
@@ -173,8 +151,8 @@
             raise Unauthorized(self._('only select queries are authorized'))
 
     def get_cache(self, cachename):
-        """
-        NOTE: cachename should be dotted names as in :
+        """cachename should be dotted names as in :
+
         - cubicweb.mycache
         - cubes.blog.mycache
         - etc.
@@ -301,7 +279,7 @@
             userinfo['name'] = "cubicweb"
             userinfo['email'] = ""
             return userinfo
-        user = self.actual_session().user
+        user = self.user
         userinfo['login'] = user.login
         userinfo['name'] = user.name()
         userinfo['email'] = user.get_email()
@@ -402,10 +380,6 @@
         """return the root url of the instance"""
         raise NotImplementedError
 
-    def decorate_rset(self, rset):
-        """add vreg/req (at least) attributes to the given result set """
-        raise NotImplementedError
-
     def describe(self, eid):
         """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
         raise NotImplementedError
--- a/rqlrewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/rqlrewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,17 +1,30 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """RQL rewriting utilities : insert rql expression snippets into rql syntax
 tree.
 
 This is used for instance for read security checking in the repository.
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from rql import nodes as n, stmts, TypeResolverException
-
+from yams import BadSchemaDefinition
 from logilab.common.graph import has_path
 
 from cubicweb import Unauthorized, typed_eid
@@ -185,7 +198,17 @@
                 vi['const'] = typed_eid(selectvar) # XXX gae
                 vi['rhs_rels'] = vi['lhs_rels'] = {}
             except ValueError:
-                vi['stinfo'] = sti = self.select.defined_vars[selectvar].stinfo
+                try:
+                    vi['stinfo'] = sti = self.select.defined_vars[selectvar].stinfo
+                except KeyError:
+                    # variable has been moved to a newly inserted subquery
+                    # we should insert snippet in that subquery
+                    subquery = self.select.aliases[selectvar].query
+                    assert len(subquery.children) == 1
+                    subselect = subquery.children[0]
+                    RQLRewriter(self.session).rewrite(subselect, [(varmap, rqlexprs)],
+                                                      subselect.solutions, self.kwargs)
+                    continue
                 if varexistsmap is None:
                     vi['rhs_rels'] = dict( (r.r_type, r) for r in sti['rhsrelations'])
                     vi['lhs_rels'] = dict( (r.r_type, r) for r in sti['relations']
@@ -294,21 +317,40 @@
         """introduce the given snippet in a subquery"""
         subselect = stmts.Select()
         selectvar = varmap[0]
-        subselect.append_selected(n.VariableRef(
-            subselect.get_variable(selectvar)))
+        subselectvar = subselect.get_variable(selectvar)
+        subselect.append_selected(n.VariableRef(subselectvar))
+        snippetrqlst = n.Exists(transformedsnippet.copy(subselect))
         aliases = [selectvar]
-        subselect.add_restriction(transformedsnippet.copy(subselect))
         stinfo = self.varinfo['stinfo']
+        need_null_test = False
         for rel in stinfo['relations']:
             rschema = self.schema.rschema(rel.r_type)
             if rschema.final or (rschema.inlined and
-                                      not rel in stinfo['rhsrelations']):
-                self.select.remove_node(rel)
-                rel.children[0].name = selectvar
+                                 not rel in stinfo['rhsrelations']):
+                rel.children[0].name = selectvar # XXX explain why
                 subselect.add_restriction(rel.copy(subselect))
                 for vref in rel.children[1].iget_nodes(n.VariableRef):
+                    if isinstance(vref.variable, n.ColumnAlias):
+                        # XXX could probably be handled by generating the subquery
+                        # into the detected subquery
+                        raise BadSchemaDefinition(
+                            "cant insert security because of usage two inlined "
+                            "relations in this query. You should probably at "
+                            "least uninline %s" % rel.r_type)
                     subselect.append_selected(vref.copy(subselect))
                     aliases.append(vref.name)
+                self.select.remove_node(rel)
+                # when some inlined relation has to be copied in the subquery,
+                # we need to test that either value is NULL or that the snippet
+                # condition is satisfied
+                if rschema.inlined and rel.optional:
+                    need_null_test = True
+        if need_null_test:
+            snippetrqlst = n.Or(
+                n.make_relation(subselectvar, 'is', (None, None), n.Constant,
+                                operator='IS'),
+                snippetrqlst)
+        subselect.add_restriction(snippetrqlst)
         if self.u_varname:
             # generate an identifier for the substitution
             argname = subselect.allocate_varname()
--- a/rset.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/rset.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
-"""The `ResultSet` class which is returned as result of a rql query
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""The `ResultSet` class which is returned as result of an rql query
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -15,21 +28,22 @@
 
 
 class ResultSet(object):
-    """a result set wrap a RQL query result. This object implements a partial
-    list protocol to allow direct use as a list of result rows.
+    """A result set wraps a RQL query result. This object implements
+    partially the list protocol to allow direct use as a list of
+    result rows.
 
     :type rowcount: int
-    :ivar rowcount: number of rows in the result
+    :param rowcount: number of rows in the result
 
     :type rows: list
-    :ivar rows: list of rows of result
+    :param rows: list of rows of result
 
     :type description: list
-    :ivar description:
+    :param description:
       result's description, using the same structure as the result itself
 
     :type rql: str or unicode
-    :ivar rql: the original RQL query string
+    :param rql: the original RQL query string
     """
     def __init__(self, results, rql, args=None, description=(), cachekey=None,
                  rqlst=None):
@@ -50,7 +64,6 @@
         # .limit method
         self.limited = None
         # set by the cursor which returned this resultset
-        self.vreg = None
         self.req = None
         # actions cache
         self._rsetactions = None
@@ -83,7 +96,7 @@
         try:
             return self._rsetactions[key]
         except KeyError:
-            actions = self.vreg['actions'].poss_visible_objects(
+            actions = self.req.vreg['actions'].poss_visible_objects(
                 self.req, rset=self, **kwargs)
             self._rsetactions[key] = actions
             return actions
@@ -114,15 +127,17 @@
         # but I tend to think that since we have that, we should not need this
         # method anymore (syt)
         rset = ResultSet(self.rows+rset.rows, self.rql, self.args,
-                         self.description +rset.description)
-        return self.req.decorate_rset(rset)
+                         self.description + rset.description)
+        rset.req = self.req
+        return rset
 
     def copy(self, rows=None, descr=None):
         if rows is None:
             rows = self.rows[:]
             descr = self.description[:]
         rset = ResultSet(rows, self.rql, self.args, descr)
-        return self.req.decorate_rset(rset)
+        rset.req = self.req
+        return rset
 
     def transformed_rset(self, transformcb):
         """ the result set according to a given column types
@@ -203,7 +218,8 @@
         return rset
 
     def split_rset(self, keyfunc=None, col=0, return_dict=False):
-        """Splits the result set in multiple result set according to a given key
+        """splits the result set in multiple result sets according to
+        a given key
 
         :type keyfunc: callable(entity or FinalType)
         :param keyfunc:
@@ -251,15 +267,15 @@
             return result
 
     def limited_rql(self):
-        """return a printable rql for the result set associated to the object,
+        """returns a printable rql for the result set associated to the object,
         with limit/offset correctly set according to maximum page size and
         currently displayed page when necessary
         """
         # try to get page boundaries from the navigation component
         # XXX we should probably not have a ref to this component here (eg in
         #     cubicweb)
-        nav = self.vreg['components'].select_or_none('navigation', self.req,
-                                                     rset=self)
+        nav = self.req.vreg['components'].select_or_none('navigation', self.req,
+                                                         rset=self)
         if nav:
             start, stop = nav.page_boundaries()
             rql = self._limit_offset_rql(stop - start, start)
@@ -376,12 +392,14 @@
 
     @cached
     def get_entity(self, row, col):
-        """special method for query retreiving a single entity, returns a
+        """convenience method for query retrieving a single entity, returns a
         partially initialized Entity instance.
 
-        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
+        .. 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)
 
         :type row,col: int, int
         :param row,col:
@@ -391,7 +409,7 @@
         """
         etype = self.description[row][col]
         try:
-            eschema = self.vreg.schema.eschema(etype)
+            eschema = self.req.vreg.schema.eschema(etype)
             if eschema.final:
                 raise NotAnEntity(etype)
         except KeyError:
@@ -435,8 +453,8 @@
             return entity
         # build entity instance
         etype = self.description[row][col]
-        entity = self.vreg['etypes'].etype_class(etype)(req, rset=self,
-                                                        row=row, col=col)
+        entity = self.req.vreg['etypes'].etype_class(etype)(req, rset=self,
+                                                            row=row, col=col)
         entity.set_eid(eid)
         # cache entity
         req.set_entity_cache(entity)
@@ -472,7 +490,7 @@
                         else:
                             rql = 'Any Y WHERE Y %s X, X eid %s'
                         rrset = ResultSet([], rql % (attr, entity.eid))
-                        req.decorate_rset(rrset)
+                        rrset.req = req
                     else:
                         rrset = self._build_entity(row, outerselidx).as_rset()
                     entity.set_related_cache(attr, role, rrset)
@@ -489,10 +507,10 @@
             rqlst = self._rqlst.copy()
             # to avoid transport overhead when pyro is used, the schema has been
             # unset from the syntax tree
-            rqlst.schema = self.vreg.schema
-            self.vreg.rqlhelper.annotate(rqlst)
+            rqlst.schema = self.req.vreg.schema
+            self.req.vreg.rqlhelper.annotate(rqlst)
         else:
-            rqlst = self.vreg.parse(self.req, self.rql, self.args)
+            rqlst = self.req.vreg.parse(self.req, self.rql, self.args)
         return rqlst
 
     @cached
@@ -532,7 +550,7 @@
         etype = self.description[row][col]
         # final type, find a better one to locate the correct subquery
         # (ambiguous if possible)
-        eschema = self.vreg.schema.eschema
+        eschema = self.req.vreg.schema.eschema
         if eschema(etype).final:
             for select in rqlst.children:
                 try:
--- a/rtags.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/rtags.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,25 @@
-#:organization: Logilab
-#:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-#:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
-A RelationTag object is an object which allows to link a configuration information to a relation definition. For instance, the standard primary view uses a RelationTag object (uicfg.primaryview_section) to get the section to display relations.
+A RelationTag object is an object which allows to link a configuration
+information to a relation definition. For instance, the standard
+primary view uses a RelationTag object (uicfg.primaryview_section) to
+get the section to display relations.
 
 .. sourcecode:: python
 
--- a/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """classes to define schemas for CubicWeb
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -21,7 +34,7 @@
 
 from yams import BadSchemaDefinition, buildobjs as ybo
 from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
-     RelationDefinitionSchema, PermissionMixIn
+     RelationDefinitionSchema, PermissionMixIn, role_name
 from yams.constraints import BaseConstraint, FormatConstraint
 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
                          obsolete as yobsolete, cleanup_sys_modules)
@@ -34,14 +47,15 @@
 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
 
-#  set of meta-relations available for every entity types
+# set of meta-relations available for every entity types
 META_RTYPES = set((
     'owned_by', 'created_by', 'is', 'is_instance_of', 'identity',
     'eid', 'creation_date', 'modification_date', 'has_text', 'cwuri',
     ))
-SYSTEM_RTYPES = set(('require_permission', 'custom_workflow', 'in_state', 'wf_info_for'))
+SYSTEM_RTYPES = set(('require_permission', 'custom_workflow', 'in_state',
+                     'wf_info_for'))
 
-#  set of entity and relation types used to build the schema
+# set of entity and relation types used to build the schema
 SCHEMA_TYPES = set((
     'CWEType', 'CWRType', 'CWAttribute', 'CWRelation',
     'CWConstraint', 'CWConstraintType', 'RQLExpression',
@@ -399,7 +413,9 @@
                                           __permissions__=RO_ATTR_PERMS)
             self.schema.add_relation_def(rdef)
         elif not need_has_text and has_has_text:
-            self.schema.del_relation_def(self.type, 'has_text', 'String')
+            # use rschema.del_relation_def and not schema.del_relation_def to
+            # avoid deleting the relation type accidentally...
+            self.schema['has_text'].del_relation_def(self, self.schema['String'])
 
     def schema_entity(self):
         """return True if this entity type is used to build the schema"""
@@ -681,17 +697,22 @@
             # XXX at this point if both or neither of S and O are in mainvar we
             # dunno if the validation error `occured` on eidfrom or eidto (from
             # user interface point of view)
+            #
+            # possible enhancement: check entity being created, it's probably
+            # the main eid unless this is a composite relation
             if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
                 maineid = eidfrom
+                qname = role_name(rtype, 'subject')
             else:
                 maineid = eidto
+                qname = role_name(rtype, 'object')
             if self.msg:
                 msg = session._(self.msg)
             else:
                 msg = '%(constraint)s %(restriction)s failed' % {
                     'constraint':  session._(self.type()),
                     'restriction': self.restriction}
-            raise ValidationError(maineid, {rtype: msg})
+            raise ValidationError(maineid, {qname: msg})
 
     def exec_query(self, session, eidfrom, eidto):
         if eidto is None:
@@ -704,7 +725,7 @@
         rql = 'Any %s WHERE %s' % (self.mainvars,  restriction)
         if self.distinct_query:
             rql = 'DISTINCT ' + rql
-        return session.unsafe_execute(rql, args, ck, build_descr=False)
+        return session.execute(rql, args, ck, build_descr=False)
 
 
 class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
@@ -830,13 +851,10 @@
                 return True
             return False
         if keyarg is None:
-            # on the server side, use unsafe_execute, but this is not available
-            # on the client side (session is actually a request)
-            execute = getattr(session, 'unsafe_execute', session.execute)
             kwargs.setdefault('u', session.user.eid)
             cachekey = kwargs.keys()
             try:
-                rset = execute(rql, kwargs, cachekey, build_descr=True)
+                rset = session.execute(rql, kwargs, cachekey, build_descr=True)
             except NotImplementedError:
                 self.critical('cant check rql expression, unsupported rql %s', rql)
                 if self.eid is not None:
@@ -1084,10 +1102,10 @@
     elif form is not None:
         cw = form._cw
     if cw is not None:
-        if hasattr(cw, 'is_super_session'):
+        if hasattr(cw, 'write_security'): # test it's a session and not a request
             # cw is a server session
-            hasperm = cw.is_super_session or \
-                      not cw.vreg.config.is_hook_category_activated('integrity') or \
+            hasperm = not cw.write_security or \
+                      not cw.is_hook_category_activated('integrity') or \
                       cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
         else:
             hasperm = cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
--- a/schemas/Bookmark.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemas/Bookmark.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """the Bookmark entity type for internal links
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/schemas/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemas/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some utilities to define schema permissions
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
--- a/schemas/_regproc_bss.postgres.sql	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/* -*- sql -*-
-
-   postgres specific registered procedures for the Bytes File System storage,
-   require the plpythonu language installed
-
-*/
-
-
-CREATE OR REPLACE FUNCTION _fsopen(bytea) RETURNS bytea AS $$
-    fpath = args[0]
-    if fpath:
-        try:
-            data = file(fpath, 'rb').read()
-            #/* XXX due to plpython bug we have to replace some characters... */
-            return data.replace("\\", r"\134").replace("\000", r"\000").replace("'", r"\047") #'
-        except Exception, ex:
-            plpy.warning('failed to get content for %s: %s', fpath, ex)
-    return None
-$$ LANGUAGE plpythonu
-/* WITH(ISCACHABLE) XXX does postgres handle caching of large data nicely */
-;;
-
-/* fspath(eid, entity type, attribute) */
-CREATE OR REPLACE FUNCTION fspath(bigint, text, text) RETURNS bytea AS $$
-    pkey = 'plan%s%s' % (args[1], args[2])
-    try:
-        plan = SD[pkey]
-    except KeyError:
-        #/* then prepare and cache plan to get versioned file information from a
-        # version content eid */
-        plan = plpy.prepare(
-            'SELECT X.cw_%s FROM cw_%s as X WHERE X.cw_eid=$1' % (args[2], args[1]),
-            ['bigint'])
-        SD[pkey] = plan
-    return plpy.execute(plan, [args[0]])[0]['cw_' + args[2]]
-$$ LANGUAGE plpythonu
-/* WITH(ISCACHABLE) XXX does postgres handle caching of large data nicely */
-;;
--- a/schemas/base.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemas/base.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """core CubicWeb schema, but not necessary at bootstrap time
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/schemas/bootstrap.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemas/bootstrap.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """core CubicWeb schema necessary for bootstrapping the actual instance's schema
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -311,8 +324,9 @@
 def post_build_callback(schema):
     """set attributes permissions for schema/workflow entities"""
     from cubicweb.schema import SCHEMA_TYPES, WORKFLOW_TYPES, META_RTYPES
+    wftypes = WORKFLOW_TYPES - set(('TrInfo',))
     for eschema in schema.entities():
-        if eschema in SCHEMA_TYPES or eschema in WORKFLOW_TYPES:
+        if eschema in SCHEMA_TYPES or eschema in wftypes:
             for rschema in eschema.subject_relations():
                 if rschema.final and not rschema in META_RTYPES:
                     rdef = eschema.rdef(rschema)
--- a/schemas/workflow.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemas/workflow.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """workflow related schemas
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/schemaviewer.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/schemaviewer.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """an helper class to display CubicWeb schema using ureports
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/selectors.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/selectors.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,44 +1,195 @@
-"""This file contains some basic selectors required by application objects.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+""".. _Selectors:
+
+Selectors
+---------
+
+Using and combining existant selectors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can combine selectors using the `&`, `|` and `~` operators.
+
+When two selectors are combined using the `&` operator, it means that
+both should return a positive score. On success, the sum of scores is
+returned.
+
+When two selectors are combined using the `|` operator, it means that
+one of them should return a positive score. On success, the first
+positive score is returned.
+
+You can also "negate" a selector by precedeing it by the `~` unary operator.
+
+Of course you can use parenthesis to balance expressions.
+
+Example
+~~~~~~~
+
+The goal: when on a blog, one wants the RSS link to refer to blog entries, not to
+the blog entity itself.
 
-A selector is responsible to score how well an object may be used with a
-given context by returning a score.
+To do that, one defines a method on entity classes that returns the
+RSS stream url for a given entity. The default implementation on
+:class:`~cubicweb.entities.AnyEntity` (the generic entity class used
+as base for all others) and a specific implementation on `Blog` will
+do what we want.
+
+But when we have a result set containing several `Blog` entities (or
+different entities), we don't know on which entity to call the
+aforementioned method. In this case, we keep the generic behaviour.
+
+Hence we have two cases here, one for a single-entity rsets, the other for
+multi-entities rsets.
+
+In web/views/boxes.py lies the RSSIconBox class. Look at its selector:
+
+.. sourcecode:: python
+
+  class RSSIconBox(ExtResourcesBoxTemplate):
+    ''' just display the RSS icon on uniform result set '''
+    __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
+
+It takes into account:
+
+* the inherited selection criteria (one has to look them up in the class
+  hierarchy to know the details)
 
-In CubicWeb Usually the context consists for a request object, a result set
-or None, a specific row/col in the result set, etc...
+* :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets
+  containing non final entities (a 'final entity' being synonym for entity
+  attributes type, eg `String`, `Int`, etc)
+
+This matches our second case. Hence we have to provide a specific component for
+the first case:
+
+.. sourcecode:: python
+
+  class EntityRSSIconBox(RSSIconBox):
+    '''just display the RSS icon on uniform result set for a single entity'''
+    __select__ = RSSIconBox.__select__ & one_line_rset()
+
+Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which
+filters result sets of size 1. Thus, on a result set containing multiple
+entities, :class:`one_line_rset` makes the EntityRSSIconBox class non
+selectable. However for a result set with one entity, the `EntityRSSIconBox`
+class will have a higher score than `RSSIconBox`, which is what we wanted.
+
+Of course, once this is done, you have to:
+
+* fill in the call method of `EntityRSSIconBox`
+
+* provide the default implementation of the method returning the RSS stream url
+  on :class:`~cubicweb.entities.AnyEntity`
+
+* redefine this method on `Blog`.
 
 
-If you have trouble with selectors, especially if the objet (typically
-a view or a component) you want to use is not selected and you want to
-know which one(s) of its selectors fail (e.g. returns 0), you can use
-`traced_selection` or even direclty `TRACED_OIDS`.
+When to use selectors?
+~~~~~~~~~~~~~~~~~~~~~~
+
+Selectors are to be used whenever arises the need of dispatching on the shape or
+content of a result set or whatever else context (value in request form params,
+authenticated user groups, etc...). That is, almost all the time.
+
+Here is a quick example:
+
+.. sourcecode:: python
 
-`TRACED_OIDS` is a tuple of traced object ids. The special value
-'all' may be used to log selectors for all objects.
+    class UserLink(component.Component):
+	'''if the user is the anonymous user, build a link to login else a link
+	to the connected user object with a loggout link
+	'''
+	__regid__ = 'loggeduserlink'
 
-For instance, say that the following code yields a `NoSelectableObject`
-exception::
-
-    self.view('calendar', myrset)
+	def call(self):
+	    if self._cw.cnx.anonymous_connection:
+		# display login link
+		...
+	    else:
+		# display a link to the connected user object with a loggout link
+		...
 
-You can log the selectors involved for *calendar* by replacing the line
-above by::
+The proper way to implement this with |cubicweb| is two have two different
+classes sharing the same identifier but with different selectors so you'll get
+the correct one according to the context.
+
+.. sourcecode:: python
+
+    class UserLink(component.Component):
+	'''display a link to the connected user object with a loggout link'''
+	__regid__ = 'loggeduserlink'
+	__select__ = component.Component.__select__ & authenticated_user()
 
-    # in Python2.5
-    from cubicweb.selectors import traced_selection
-    with traced_selection():
-        self.view('calendar', myrset)
+	def call(self):
+            # display useractions and siteactions
+	    ...
 
-    # in Python2.4
-    from cubicweb import selectors
-    selectors.TRACED_OIDS = ('calendar',)
-    self.view('calendar', myrset)
-    selectors.TRACED_OIDS = ()
+    class AnonUserLink(component.Component):
+	'''build a link to login'''
+	__regid__ = 'loggeduserlink'
+	__select__ = component.Component.__select__ & anonymous_user()
+
+	def call(self):
+	    # display login link
+            ...
+
+The big advantage, aside readability once you're familiar with the
+system, is that your cube becomes much more easily customizable by
+improving componentization.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. _CustomSelectors:
+
+Defining your own selectors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autodocstring:: cubicweb.appobject::objectify_selector
+
+In other cases, you can take a look at the following abstract base classes:
+
+.. autoclass:: cubicweb.selectors.ExpectedValueSelector
+.. autoclass:: cubicweb.selectors.EClassSelector
+.. autoclass:: cubicweb.selectors.EntitySelector
+
+Also, think to use the :func:`lltrace` decorator on your selector class' :meth:`__call__` method
+or below the :func:`objectify_selector` decorator of your selector function so it gets
+traceable when :class:`traced_selection` is activated (see :ref:`DebuggingSelectors`).
+
+.. autofunction:: cubicweb.selectors.lltrace
+
+.. note::
+  Selectors __call__ should *always* return a positive integer, and shall never
+  return `None`.
+
+
+.. _DebuggingSelectors:
+
+Debugging selection
+~~~~~~~~~~~~~~~~~~~
+
+Once in a while, one needs to understand why a view (or any application object)
+is, or is not selected appropriately. Looking at which selectors fired (or did
+not) is the way. The :class:`cubicweb.selectors.traced_selection` context
+manager to help with that, *if you're running your instance in debug mode*.
+
+.. autoclass:: cubicweb.selectors.traced_selection
+
+
+.. |cubicweb| replace:: *CubicWeb*
 """
 __docformat__ = "restructuredtext en"
 
@@ -60,47 +211,68 @@
 
 # helpers for debugging selectors
 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
-TRACED_OIDS = ()
+TRACED_OIDS = None
+
+def _trace_selector(cls, selector, args, ret):
+    # /!\ lltrace decorates pure function or __call__ method, this
+    #     means argument order may be different
+    if isinstance(cls, Selector):
+        selname = str(cls)
+        vobj = args[0]
+    else:
+        selname = selector.__name__
+        vobj = cls
+    if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS:
+        #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
+        print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
 
 def lltrace(selector):
+    """use this decorator on your selectors so the becomes traceable with
+    :class:`traced_selection`
+    """
     # don't wrap selectors if not in development mode
     if CubicWebConfiguration.mode == 'system': # XXX config.debug
         return selector
     def traced(cls, *args, **kwargs):
-        # /!\ lltrace decorates pure function or __call__ method, this
-        #     means argument order may be different
-        if isinstance(cls, Selector):
-            selname = str(cls)
-            vobj = args[0]
-        else:
-            selname = selector.__name__
-            vobj = cls
-        oid = class_regid(vobj)
         ret = selector(cls, *args, **kwargs)
-        if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
-            #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
-            print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
+        if TRACED_OIDS is not None:
+            _trace_selector(cls, selector, args, ret)
         return ret
     traced.__name__ = selector.__name__
     traced.__doc__ = selector.__doc__
     return traced
 
 class traced_selection(object):
-    """selector debugging helper.
-
+    """
     Typical usage is :
 
-    >>> with traced_selection():
-    ...     # some code in which you want to debug selectors
-    ...     # for all objects
+    .. sourcecode:: python
+
+        >>> from cubicweb.selectors import traced_selection
+        >>> with traced_selection():
+        ...     # some code in which you want to debug selectors
+        ...     # for all objects
 
-    or
+    Don't forget the 'from __future__ import with_statement' at the module top-level
+    if you're using python prior to 2.6.
+
+    This will yield lines like this in the logs::
+
+        selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
 
-    >>> with traced_selection( ('oid1', 'oid2') ):
-    ...     # some code in which you want to debug selectors
-    ...     # for objects with id 'oid1' and 'oid2'
+    You can also give to :class:`traced_selection` the identifiers of objects on
+    which you want to debug selection ('oid1' and 'oid2' in the example above).
+
+    .. sourcecode:: python
 
+        >>> with traced_selection( ('regid1', 'regid2') ):
+        ...     # some code in which you want to debug selectors
+        ...     # for objects with __regid__ 'regid1' and 'regid2'
+
+    A potentially usefull point to set up such a tracing function is
+    the `cubicweb.vregistry.Registry.select` method body.
     """
+
     def __init__(self, traced='all'):
         self.traced = traced
 
@@ -110,7 +282,7 @@
 
     def __exit__(self, exctype, exc, traceback):
         global TRACED_OIDS
-        TRACED_OIDS = ()
+        TRACED_OIDS = None
         return traceback is None
 
 
@@ -260,12 +432,12 @@
       - `accept_none` is False and some cell in the column has a None value
         (this may occurs with outer join)
 
-    .. note::
-       using EntitySelector or EClassSelector as base selector class impacts
-       performance, since when no entity or row is specified the later works on
-       every different *entity class* found in the result set, while the former
-       works on each *entity* (eg each row of the result set), which may be much
-       more costly.
+    .. Note::
+       using :class:`EntitySelector` or :class:`EClassSelector` as base selector
+       class impacts performance, since when no entity or row is specified the
+       later works on every different *entity class* found in the result set,
+       while the former works on each *entity* (eg each row of the result set),
+       which may be much more costly.
     """
 
     @lltrace
@@ -306,8 +478,12 @@
 
 
 class ExpectedValueSelector(Selector):
-    """Take a list of expected values as initializer argument, check
-    _get_value method return one of these expected values.
+    """Take a list of expected values as initializer argument and store them
+    into the :attr:`expected` set attribute.
+
+    You should implements the :meth:`_get_value(cls, req, **kwargs)` method
+    which should return the value for the given context. The selector will then
+    return 1 if the value is expected, else 0.
     """
     def __init__(self, *expected):
         assert expected, self
@@ -345,10 +521,12 @@
 
 
 class appobject_selectable(Selector):
-    """return 1 if another appobject is selectable using the same input context.
+    """Return 1 if another appobject is selectable using the same input context.
 
     Initializer arguments:
+
     * `registry`, a registry name
+
     * `regid`, an object identifier in this registry
     """
     def __init__(self, registry, regid):
@@ -1080,6 +1258,24 @@
             return 1
         return 0
 
+class is_in_state(score_entity):
+    """return 1 if entity is in one of the states given as argument list
+
+    you should use this instead of your own score_entity x: x.state == 'bla'
+    selector to avoid some gotchas:
+
+    * possible views gives a fake entity with no state
+    * you must use the latest tr info, not entity.state for repository side
+      checking of the current state
+    """
+    def __init__(self, *states):
+        def score(entity, states=set(states)):
+            try:
+                return entity.latest_trinfo().new_state.name in states
+            except AttributeError:
+                return None
+        super(is_in_state, self).__init__(score)
+
 
 ## deprecated stuff ############################################################
 
--- a/server/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,28 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Server subcube of cubicweb : defines objects used only on the server
 (repository) side
 
 This module contains functions to initialize a new repository.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -113,11 +128,7 @@
     from cubicweb.server.sqlutils import sqlexec, sqlschema, sqldropschema
     # configuration to avoid db schema loading and user'state checking
     # on connection
-    read_instance_schema = config.read_instance_schema
-    bootstrap_schema = config.bootstrap_schema
-    config.read_instance_schema = False
     config.creating = True
-    config.bootstrap_schema = True
     config.consider_user_state = False
     config.set_language = False
     # only enable the system source at initialization time + admin which is not
@@ -148,7 +159,7 @@
     schemasql = sqlschema(schema, driver)
     #skip_entities=[str(e) for e in schema.entities()
     #               if not repo.system_source.support_entity(str(e))])
-    sqlexec(schemasql, execute, pbtitle=_title)
+    sqlexec(schemasql, execute, pbtitle=_title, delimiter=';;')
     sqlcursor.close()
     sqlcnx.commit()
     sqlcnx.close()
@@ -197,10 +208,9 @@
     cnx.commit()
     cnx.close()
     session.close()
+    repo.shutdown()
     # restore initial configuration
     config.creating = False
-    config.read_instance_schema = read_instance_schema
-    config.bootstrap_schema = bootstrap_schema
     config.consider_user_state = True
     config.set_language = True
     print '-> database for instance %s initialized.' % config.appid
@@ -208,33 +218,33 @@
 
 def initialize_schema(config, schema, mhandler, event='create'):
     from cubicweb.server.schemaserial import serialize_schema
-    # deactivate every hooks but those responsible to set metadata
-    # so, NO INTEGRITY CHECKS are done, to have quicker db creation
-    oldmode = config.set_hooks_mode(config.DENY_ALL)
-    changes = config.enable_hook_category('metadata')
+    from cubicweb.server.session import hooks_control
+    session = mhandler.session
     paths = [p for p in config.cubes_path() + [config.apphome]
              if exists(join(p, 'migration'))]
-    # execute cubicweb's pre<event> script
-    mhandler.exec_event_script('pre%s' % event)
-    # execute cubes pre<event> script if any
-    for path in reversed(paths):
-        mhandler.exec_event_script('pre%s' % event, path)
-    # enter instance'schema into the database
-    mhandler.session.set_pool()
-    serialize_schema(mhandler.session, schema)
-    # execute cubicweb's post<event> script
-    mhandler.exec_event_script('post%s' % event)
-    # execute cubes'post<event> script if any
-    for path in reversed(paths):
-        mhandler.exec_event_script('post%s' % event, path)
-    # restore hooks config
-    if changes:
-        config.disable_hook_category(changes)
-    config.set_hooks_mode(oldmode)
+    # deactivate every hooks but those responsible to set metadata
+    # so, NO INTEGRITY CHECKS are done, to have quicker db creation.
+    # Active integrity is kept else we may pb such as two default
+    # workflows for one entity type.
+    with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
+                       'activeintegrity'):
+        # execute cubicweb's pre<event> script
+        mhandler.exec_event_script('pre%s' % event)
+        # execute cubes pre<event> script if any
+        for path in reversed(paths):
+            mhandler.exec_event_script('pre%s' % event, path)
+        # enter instance'schema into the database
+        session.set_pool()
+        serialize_schema(session, schema)
+        # execute cubicweb's post<event> script
+        mhandler.exec_event_script('post%s' % event)
+        # execute cubes'post<event> script if any
+        for path in reversed(paths):
+            mhandler.exec_event_script('post%s' % event, path)
 
 
-# sqlite'stored procedures have to be registered at connexion opening time
-SQL_CONNECT_HOOKS = {}
+# sqlite'stored procedures have to be registered at connection opening time
+from logilab.database import SQL_CONNECT_HOOKS
 
 # add to this set relations which should have their add security checking done
 # *BEFORE* adding the actual relation (done after by default)
--- a/server/checkintegrity.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/checkintegrity.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Check integrity of a CubicWeb repository. Hum actually only the system database
 is checked.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -15,10 +30,11 @@
 
 from cubicweb.schema import PURE_VIRTUAL_RTYPES
 from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.session import security_enabled
 
-def has_eid(sqlcursor, eid, eids):
+def has_eid(session, sqlcursor, eid, eids):
     """return true if the eid is a valid eid"""
-    if eids.has_key(eid):
+    if eid in eids:
         return eids[eid]
     sqlcursor.execute('SELECT type, source FROM entities WHERE eid=%s' % eid)
     try:
@@ -27,9 +43,17 @@
         eids[eid] = False
         return False
     if source and source != 'system':
-        # XXX what to do...
-        eids[eid] = True
-        return True
+        try:
+            # insert eid *and* etype to attempt checking entity has not been
+            # replaced by another subsquently to a restore of an old dump
+            if session.execute('Any X WHERE X is %s, X eid %%(x)s' % etype,
+                               {'x': eid}):
+                eids[eid] = True
+                return True
+        except: # TypeResolverError, Unauthorized...
+            pass
+        eids[eid] = False
+        return False
     sqlcursor.execute('SELECT * FROM %s%s WHERE %seid=%s' % (SQL_PREFIX, etype,
                                                              SQL_PREFIX, eid))
     result = sqlcursor.fetchall()
@@ -70,15 +94,9 @@
     # to be updated due to the reindexation
     repo = session.repo
     cursor = session.pool['system']
-    if not repo.system_source.indexer.has_fti_table(cursor):
-        from indexer import get_indexer
+    if not repo.system_source.dbhelper.has_fti_table(cursor):
         print 'no text index table'
-        indexer = get_indexer(repo.system_source.dbdriver)
-        # XXX indexer.init_fti(cursor) once index 0.7 is out
-        indexer.init_extensions(cursor)
-        cursor.execute(indexer.sql_init_fti())
-    repo.config.disabled_hooks_categories.add('metadata')
-    repo.config.disabled_hooks_categories.add('integrity')
+        dbhelper.init_fti(cursor)
     repo.system_source.do_fti = True  # ensure full-text indexation is activated
     etypes = set()
     for eschema in schema.entities():
@@ -94,9 +112,6 @@
     if withpb:
         pb = ProgressBar(len(etypes) + 1)
     # first monkey patch Entity.check to disable validation
-    from cubicweb.entity import Entity
-    _check = Entity.check
-    Entity.check = lambda self, creation=False: True
     # clear fti table first
     session.system_sql('DELETE FROM %s' % session.repo.system_source.dbhelper.fti_table)
     if withpb:
@@ -106,14 +121,9 @@
     source = repo.system_source
     for eschema in etypes:
         for entity in session.execute('Any X WHERE X is %s' % eschema).entities():
-            source.fti_unindex_entity(session, entity.eid)
             source.fti_index_entity(session, entity)
         if withpb:
             pb.update()
-    # restore Entity.check
-    Entity.check = _check
-    repo.config.disabled_hooks_categories.remove('metadata')
-    repo.config.disabled_hooks_categories.remove('integrity')
 
 
 def check_schema(schema, session, eids, fix=1):
@@ -122,16 +132,16 @@
     unique_constraints = ('SizeConstraint', 'FormatConstraint',
                           'VocabularyConstraint', 'RQLConstraint',
                           'RQLVocabularyConstraint')
-    rql = ('Any COUNT(X),RN,EN,ECTN GROUPBY RN,EN,ECTN ORDERBY 1 '
+    rql = ('Any COUNT(X),RN,SN,ON,CTN GROUPBY RN,SN,ON,CTN ORDERBY 1 '
            'WHERE X is CWConstraint, R constrained_by X, '
-           'R relation_type RT, R from_entity ET, RT name RN, '
-           'ET name EN, X cstrtype ECT, ECT name ECTN')
-    for count, rn, en, cstrname in session.execute(rql):
+           'R relation_type RT, RT name RN, R from_entity ST, ST name SN, '
+           'R to_entity OT, OT name ON, X cstrtype CT, CT name CTN')
+    for count, rn, sn, on, cstrname in session.execute(rql):
         if count == 1:
             continue
         if cstrname in unique_constraints:
-            print "ERROR: got %s %r constraints on relation %s.%s" % (
-                count, cstrname, en, rn)
+            print "ERROR: got %s %r constraints on relation %s.%s.%s" % (
+                count, cstrname, sn, rn, on)
 
 
 
@@ -141,7 +151,7 @@
     cursor = session.system_sql('SELECT uid FROM appears;')
     for row in cursor.fetchall():
         eid = row[0]
-        if not has_eid(cursor, eid, eids):
+        if not has_eid(session, cursor, eid, eids):
             msg = '  Entity with eid %s exists in the text index but in no source'
             print >> sys.stderr, msg % eid,
             if fix:
@@ -157,7 +167,7 @@
     cursor = session.system_sql('SELECT eid FROM entities;')
     for row in cursor.fetchall():
         eid = row[0]
-        if not has_eid(cursor, eid, eids):
+        if not has_eid(session, cursor, eid, eids):
             msg = '  Entity with eid %s exists in the system table but in no source'
             print >> sys.stderr, msg % eid,
             if fix:
@@ -174,7 +184,7 @@
         cursor = session.system_sql('SELECT %s FROM %s;' % (column, table))
         for row in cursor.fetchall():
             eid = row[0]
-            # eids is full since we have fetched everyting from the entities table,
+            # eids is full since we have fetched everything from the entities table,
             # no need to call has_eid
             if not eid in eids or not eids[eid]:
                 msg = '  Entity with eid %s exists in the %s table but not in the system table'
@@ -210,7 +220,7 @@
                 cursor = session.system_sql(sql)
                 for row in cursor.fetchall():
                     eid = row[0]
-                    if not has_eid(cursor, eid, eids):
+                    if not has_eid(session, cursor, eid, eids):
                         bad_related_msg(rschema, 'object', eid, fix)
                         if fix:
                             sql = 'UPDATE %s SET %s=NULL WHERE %s=%s;' % (
@@ -220,7 +230,7 @@
         cursor = session.system_sql('SELECT eid_from FROM %s_relation;' % rschema)
         for row in cursor.fetchall():
             eid = row[0]
-            if not has_eid(cursor, eid, eids):
+            if not has_eid(session, cursor, eid, eids):
                 bad_related_msg(rschema, 'subject', eid, fix)
                 if fix:
                     sql = 'DELETE FROM %s_relation WHERE eid_from=%s;' % (
@@ -229,7 +239,7 @@
         cursor = session.system_sql('SELECT eid_to FROM %s_relation;' % rschema)
         for row in cursor.fetchall():
             eid = row[0]
-            if not has_eid(cursor, eid, eids):
+            if not has_eid(session, cursor, eid, eids):
                 bad_related_msg(rschema, 'object', eid, fix)
                 if fix:
                     sql = 'DELETE FROM %s_relation WHERE eid_to=%s;' % (
@@ -268,7 +278,7 @@
     assert default_user_eid is not None, 'no user defined !'
     for rel, default in ( ('owned_by', default_user_eid), ):
         cursor = session.system_sql("SELECT eid, type FROM entities "
-                                    "WHERE NOT EXISTS "
+                                    "WHERE source='system' AND NOT EXISTS "
                                     "(SELECT 1 FROM %s_relation WHERE eid_from=eid);"
                                     % rel)
         for eid, etype in cursor.fetchall():
@@ -291,9 +301,10 @@
     # yo, launch checks
     if checks:
         eids_cache = {}
-        for check in checks:
-            check_func = globals()['check_%s' % check]
-            check_func(repo.schema, session, eids_cache, fix=fix)
+        with security_enabled(session, read=False): # ensure no read security
+            for check in checks:
+                check_func = globals()['check_%s' % check]
+                check_func(repo.schema, session, eids_cache, fix=fix)
         if fix:
             cnx.commit()
         else:
--- a/server/hook.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/hook.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Hooks management
 
 This module defined the `Hook` class and registry and a set of abstract classes
@@ -19,20 +36,20 @@
 Relation (eg before_add_relation, after_add_relation, before_delete_relation,
 after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.
 
-Server start/stop hooks (eg server_startup, server_shutdown) have a `repo`
-attribute, but *their `_cw` attribute is None*.
+Server start/maintenance/stop hooks (eg server_startup, server_maintenance,
+server_shutdown) have a `repo` attribute, but *their `_cw` attribute is None*.
+The `server_startup` is called on regular startup, while `server_maintenance`
+is called on cubicweb-ctl upgrade or shell commands. `server_shutdown` is
+called anyway.
 
 Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a
 `timestamp` attributes, but *their `_cw` attribute is None*.
 
 Session hooks (eg session_open, session_close) have no special attribute.
 
+"""
+from __future__ import with_statement
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
@@ -43,11 +60,12 @@
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 
+from cubicweb import RegistryNotFound
 from cubicweb.cwvreg import CWRegistry, VRegistry
 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
                                 implements)
 from cubicweb.appobject import AppObject
-
+from cubicweb.server.session import security_enabled
 
 ENTITIES_HOOKS = set(('before_add_entity',    'after_add_entity',
                       'before_update_entity', 'after_update_entity',
@@ -55,36 +73,51 @@
 RELATIONS_HOOKS = set(('before_add_relation',   'after_add_relation' ,
                        'before_delete_relation','after_delete_relation'))
 SYSTEM_HOOKS = set(('server_backup', 'server_restore',
-                    'server_startup', 'server_shutdown',
+                    'server_startup', 'server_maintenance', 'server_shutdown',
                     'session_open', 'session_close'))
 ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
 
 
 class HooksRegistry(CWRegistry):
+    def initialization_completed(self):
+        for appobjects in self.values():
+            for cls in appobjects:
+                if not cls.enabled:
+                    warn('[3.6] %s: enabled is deprecated' % cls)
+                    self.unregister(cls)
 
     def register(self, obj, **kwargs):
-        try:
-            iter(obj.events)
-        except AttributeError:
-            raise
-        except:
-            raise Exception('bad .events attribute %s on %s.%s' % (
-                obj.events, obj.__module__, obj.__name__))
-        for event in obj.events:
-            if event not in ALL_HOOKS:
-                raise Exception('bad event %s on %s.%s' % (
-                    event, obj.__module__, obj.__name__))
+        obj.check_events()
         super(HooksRegistry, self).register(obj, **kwargs)
 
-    def call_hooks(self, event, req=None, **kwargs):
+    def call_hooks(self, event, session=None, **kwargs):
         kwargs['event'] = event
-        for hook in sorted(self.possible_objects(req, **kwargs), key=lambda x: x.order):
-            if hook.enabled:
+        if session is None:
+            for hook in sorted(self.possible_objects(session, **kwargs),
+                               key=lambda x: x.order):
                 hook()
-            else:
-                warn('[3.6] %s: enabled is deprecated' % hook.__class__)
+        else:
+            # by default, hooks are executed with security turned off
+            with security_enabled(session, read=False):
+                hooks = sorted(self.possible_objects(session, **kwargs),
+                               key=lambda x: x.order)
+                with security_enabled(session, write=False):
+                    for hook in hooks:
+                        hook()
 
-VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
+class HooksManager(object):
+    def __init__(self, vreg):
+        self.vreg = vreg
+
+    def call_hooks(self, event, session=None, **kwargs):
+        try:
+            self.vreg['%s_hooks' % event].call_hooks(event, session, **kwargs)
+        except RegistryNotFound:
+            pass # no hooks for this event
+
+
+for event in ALL_HOOKS:
+    VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
 
 _MARKER = object()
 def entity_oldnewvalue(entity, attr):
@@ -104,28 +137,17 @@
 
 @objectify_selector
 @lltrace
-def match_event(cls, req, **kwargs):
-    if kwargs.get('event') in cls.events:
-        return 1
-    return 0
+def enabled_category(cls, req, **kwargs):
+    if req is None:
+        return True # XXX how to deactivate server startup / shutdown event
+    return req.is_hook_activated(cls)
 
 @objectify_selector
 @lltrace
-def enabled_category(cls, req, **kwargs):
-    if req is None:
-        # server startup / shutdown event
-        config = kwargs['repo'].config
-    else:
-        config = req.vreg.config
-    return config.is_hook_activated(cls)
-
-@objectify_selector
-@lltrace
-def regular_session(cls, req, **kwargs):
-    if req is None or req.is_super_session:
-        return 0
-    return 1
-
+def from_dbapi_query(cls, req, **kwargs):
+    if req.running_dbapi_query:
+        return 1
+    return 0
 
 class rechain(object):
     def __init__(self, *iterators):
@@ -174,11 +196,11 @@
                 return 1
         return 0
 
+
 # base class for hook ##########################################################
 
 class Hook(AppObject):
-    __registry__ = 'hooks'
-    __select__ = match_event() & enabled_category()
+    __select__ = enabled_category()
     # set this in derivated classes
     events = None
     category = None
@@ -186,6 +208,24 @@
     # XXX deprecated
     enabled = True
 
+    @classmethod
+    def check_events(cls):
+        try:
+            for event in cls.events:
+                if event not in ALL_HOOKS:
+                    raise Exception('bad event %s on %s.%s' % (
+                        event, cls.__module__, cls.__name__))
+        except AttributeError:
+            raise
+        except TypeError:
+            raise Exception('bad .events attribute %s on %s.%s' % (
+                cls.events, cls.__module__, cls.__name__))
+
+    @classproperty
+    def __registries__(cls):
+        cls.check_events()
+        return ['%s_hooks' % ev for ev in cls.events]
+
     @classproperty
     def __regid__(cls):
         warn('[3.6] %s.%s: please specify an id for your hook'
@@ -263,7 +303,7 @@
         else:
             assert self.rtype in self.object_relations
             meid, seid = self.eidto, self.eidfrom
-        self._cw.unsafe_execute(
+        self._cw.execute(
             'SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\
             % (self.main_rtype, self.main_rtype, self.main_rtype),
             {'x': meid, 'e': seid}, ('x', 'e'))
@@ -281,7 +321,7 @@
 
     def __call__(self):
         eschema = self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])
-        execute = self._cw.unsafe_execute
+        execute = self._cw.execute
         for rel in self.subject_relations:
             if rel in eschema.subjrels:
                 execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
@@ -306,7 +346,7 @@
 
     def __call__(self):
         eschema = self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])
-        execute = self._cw.unsafe_execute
+        execute = self._cw.execute
         for rel in self.subject_relations:
             if rel in eschema.subjrels:
                 execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
@@ -342,14 +382,14 @@
       revert things (including the operation which made fail the commit)
 
     rollback:
-      the transaction has been either rollbacked either
-      * intentionaly
-      * a precommit event failed, all operations are rollbacked
-      * a commit event failed, all operations which are not been triggered for
-        commit are rollbacked
+      the transaction has been either rollbacked either:
+       * intentionaly
+       * a precommit event failed, all operations are rollbacked
+       * a commit event failed, all operations which are not been triggered for
+         commit are rollbacked
 
-    order of operations may be important, and is controlled according to:
-    * operation's class
+    order of operations may be important, and is controlled according to
+    the insert_index's method output
     """
 
     def __init__(self, session, **kwargs):
@@ -434,6 +474,24 @@
 set_log_methods(Operation, getLogger('cubicweb.session'))
 
 
+def set_operation(session, datakey, value, opcls, **opkwargs):
+    """Search for session.transaction_data[`datakey`] (expected to be a set):
+
+    * if found, simply append `value`
+
+    * else, initialize it to set([`value`]) and instantiate the given `opcls`
+      operation class with additional keyword arguments.
+
+    You should use this instead of creating on operation for each `value`,
+    since handling operations becomes coslty on massive data import.
+    """
+    try:
+        session.transaction_data[datakey].add(value)
+    except KeyError:
+        opcls(session, **opkwargs)
+        session.transaction_data[datakey] = set((value,))
+
+
 class LateOperation(Operation):
     """special operation which should be called after all possible (ie non late)
     operations
@@ -510,6 +568,43 @@
 
 class RQLPrecommitOperation(Operation):
     def precommit_event(self):
-        execute = self.session.unsafe_execute
+        execute = self.session.execute
         for rql in self.rqls:
             execute(*rql)
+
+
+class CleanupNewEidsCacheOp(SingleLastOperation):
+    """on rollback of a insert query we have to remove from repository's
+    type/source cache eids of entities added in that transaction.
+
+    NOTE: querier's rqlst/solutions cache may have been polluted too with
+    queries such as Any X WHERE X eid 32 if 32 has been rollbacked however
+    generated queries are unpredictable and analysing all the cache probably
+    too expensive. Notice that there is no pb when using args to specify eids
+    instead of giving them into the rql string.
+    """
+
+    def rollback_event(self):
+        """the observed connections pool has been rollbacked,
+        remove inserted eid from repository type/source cache
+        """
+        try:
+            self.session.repo.clear_caches(
+                self.session.transaction_data['neweids'])
+        except KeyError:
+            pass
+
+class CleanupDeletedEidsCacheOp(SingleLastOperation):
+    """on commit of delete query, we have to remove from repository's
+    type/source cache eids of entities deleted in that transaction.
+    """
+
+    def commit_event(self):
+        """the observed connections pool has been rollbacked,
+        remove inserted eid from repository type/source cache
+        """
+        try:
+            self.session.repo.clear_caches(
+                self.session.transaction_data['pendingeids'])
+        except KeyError:
+            pass
--- a/server/hookhelper.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/hookhelper.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """helper functions for application hooks
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/server/hooksmanager.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/hooksmanager.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.deprecation import class_renamed, class_moved
 from cubicweb.server import hook
 SystemHook = class_renamed('SystemHook', hook.Hook)
--- a/server/migractions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/migractions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """a class implementing basic actions used in migration scripts.
 
 The following schema actions are supported for now:
@@ -10,11 +27,9 @@
 * execute raw RQL queries
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -25,10 +40,12 @@
 import os.path as osp
 from datetime import datetime
 from glob import glob
+from copy import copy
 from warnings import warn
 
 from logilab.common.deprecation import deprecated
 from logilab.common.decorators import cached, clear_cache
+from logilab.common.testlib import mock_object
 
 from yams.constraints import SizeConstraint
 from yams.schema2sql import eschema2sql, rschema2sql
@@ -38,7 +55,7 @@
                              CubicWebRelationSchema, order_eschemas)
 from cubicweb.dbapi import get_repository, repo_connect
 from cubicweb.migration import MigrationHelper, yes
-
+from cubicweb.server.session import hooks_control
 try:
     from cubicweb.server import SOURCE_TYPES, schemaserial as ss
     from cubicweb.server.utils import manager_userpasswd, ask_source_config
@@ -55,7 +72,6 @@
     def __init__(self, config, schema, interactive=True,
                  repo=None, cnx=None, verbosity=1, connect=True):
         MigrationHelper.__init__(self, config, interactive, verbosity)
-        # no config on shell to a remote instance
         if not interactive:
             assert cnx
             assert repo
@@ -63,11 +79,13 @@
             assert repo
             self._cnx = cnx
             self.repo = repo
-            if config is not None:
-                self.session.data['rebuild-infered'] = False
         elif connect:
             self.repo_connect()
-        if not schema:
+        # no config on shell to a remote instance
+        if config is not None and (cnx or connect):
+            self.session.data['rebuild-infered'] = False
+            self.repo.hm.call_hooks('server_maintenance', repo=self.repo)
+        if not schema and not getattr(config, 'quick_start', False):
             schema = config.load_schema(expand_cubes=True)
         self.fs_schema = schema
         self._synchronized = set()
@@ -94,7 +112,9 @@
                 self.backup_database()
             elif options.backup_db:
                 self.backup_database(askconfirm=False)
-        super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
+        # disable notification during migration
+        with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, 'notification'):
+            super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
 
     def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
         """execute a migration script
@@ -109,10 +129,10 @@
                 return super(ServerMigrationHelper, self).cmd_process_script(
                     migrscript, funcname, *args, **kwargs)
             else:
-                print
-                print ('-> ignoring %s, only .py .sql and .txt scripts are considered' %
+                print >> sys.stderr
+                print >> sys.stderr, ('-> ignoring %s, only .py .sql and .txt scripts are considered' %
                        migrscript)
-                print
+                print >> sys.stderr
             self.commit()
         except:
             self.rollback()
@@ -240,21 +260,30 @@
     @property
     def session(self):
         if self.config is not None:
-            return self.repo._get_session(self.cnx.sessionid)
+            session = self.repo._get_session(self.cnx.sessionid)
+            if session.pool is None:
+                session.set_read_security(False)
+                session.set_write_security(False)
+            session.set_pool()
+            return session
         # no access to session on remote instance
         return None
 
     def commit(self):
         if hasattr(self, '_cnx'):
             self._cnx.commit()
+        if self.session:
+            self.session.set_pool()
 
     def rollback(self):
         if hasattr(self, '_cnx'):
             self._cnx.rollback()
+        if self.session:
+            self.session.set_pool()
 
     def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True):
         for rql, kwargs in rqliter:
-            self.rqlexec(rql, kwargs, cachekey, ask_confirm)
+            self.rqlexec(rql, kwargs, cachekey, ask_confirm=ask_confirm)
 
     @cached
     def _create_context(self):
@@ -282,6 +311,11 @@
         """cached group mapping"""
         return ss.group_mapping(self._cw)
 
+    @cached
+    def cstrtype_mapping(self):
+        """cached constraint types mapping"""
+        return ss.cstrtype_mapping(self._cw)
+
     def exec_event_script(self, event, cubepath=None, funcname=None,
                           *args, **kwargs):
         if cubepath:
@@ -305,7 +339,6 @@
                     self.cmd_reactivate_verification_hooks()
 
     def install_custom_sql_scripts(self, directory, driver):
-        self.session.set_pool() # ensure pool is set
         for fpath in glob(osp.join(directory, '*.sql.%s' % driver)):
             newname = osp.basename(fpath).replace('.sql.%s' % driver,
                                                   '.%s.sql' % driver)
@@ -399,14 +432,17 @@
             return
         self._synchronized.add(rtype)
         rschema = self.fs_schema.rschema(rtype)
+        reporschema = self.repo.schema.rschema(rtype)
         if syncprops:
-            self.rqlexecall(ss.updaterschema2rql(rschema),
+            assert reporschema.eid, reporschema
+            self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid),
                             ask_confirm=self.verbosity>=2)
         if syncrdefs:
-            reporschema = self.repo.schema.rschema(rtype)
             for subj, obj in rschema.rdefs:
                 if (subj, obj) not in reporschema.rdefs:
                     continue
+                if rschema in VIRTUAL_RTYPES:
+                    continue
                 self._synchronize_rdef_schema(subj, rschema, obj,
                                               syncprops=syncprops,
                                               syncperms=syncperms)
@@ -439,9 +475,11 @@
                          'Y is CWEType, Y name %(y)s',
                          {'x': str(repoeschema), 'y': str(espschema)},
                          ask_confirm=False)
-        self.rqlexecall(ss.updateeschema2rql(eschema),
+        self.rqlexecall(ss.updateeschema2rql(eschema, repoeschema.eid),
                         ask_confirm=self.verbosity >= 2)
         for rschema, targettypes, role in eschema.relation_definitions(True):
+            if rschema in VIRTUAL_RTYPES:
+                continue
             if role == 'subject':
                 if not rschema in repoeschema.subject_relations():
                     continue
@@ -483,7 +521,7 @@
         confirm = self.verbosity >= 2
         if syncprops:
             # properties
-            self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
+            self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid),
                             ask_confirm=confirm)
             # constraints
             newconstraints = list(rdef.constraints)
@@ -509,10 +547,10 @@
                                      {'x': cstr.eid, 'v': value}, 'x',
                                      ask_confirm=confirm)
             # 2. add new constraints
-            for newcstr in newconstraints:
-                self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
-                                                  newcstr),
-                                ask_confirm=confirm)
+            cstrtype_map = self.cstrtype_mapping()
+            self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints,
+                                               repordef.eid),
+                            ask_confirm=confirm)
         if syncperms and not rschema in VIRTUAL_RTYPES:
             self._synchronize_permissions(rdef, repordef.eid)
 
@@ -673,20 +711,29 @@
         targeted type is known
         """
         instschema = self.repo.schema
-        if etype in instschema:
-            # XXX (syt) plz explain: if we're adding an entity type, it should
-            # not be there...
-            eschema = instschema[etype]
-            if eschema.final:
-                instschema.del_entity_type(etype)
-        else:
-            eschema = self.fs_schema.eschema(etype)
+        assert not etype in instschema
+        #     # XXX (syt) plz explain: if we're adding an entity type, it should
+        #     # not be there...
+        #     eschema = instschema[etype]
+        #     if eschema.final:
+        #         instschema.del_entity_type(etype)
+        # else:
+        eschema = self.fs_schema.eschema(etype)
         confirm = self.verbosity >= 2
         groupmap = self.group_mapping()
+        cstrtypemap = self.cstrtype_mapping()
         # register the entity into CWEType
-        self.rqlexecall(ss.eschema2rql(eschema, groupmap), ask_confirm=confirm)
+        execute = self._cw.execute
+        ss.execschemarql(execute, eschema, ss.eschema2rql(eschema, groupmap))
         # add specializes relation if needed
-        self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
+        specialized = eschema.specializes()
+        if specialized:
+            try:
+                specialized.eid = instschema[specialized].eid
+            except KeyError:
+                raise Exception('trying to add entity type but parent type is '
+                                'not yet in the database schema')
+            self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
         # register entity's attributes
         for rschema, attrschema in eschema.attribute_definitions():
             # ignore those meta relations, they will be automatically added
@@ -697,9 +744,8 @@
                 # actually in the schema
                 self.cmd_add_relation_type(rschema.type, False, commit=True)
             # register relation definition
-            self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type,
-                                        groupmap=groupmap),
-                            ask_confirm=confirm)
+            rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema))
+            ss.execschemarql(execute, rdef, ss.rdef2rql(rdef, cstrtypemap, groupmap),)
         # take care to newly introduced base class
         # XXX some part of this should probably be under the "if auto" block
         for spschema in eschema.specialized_by(recursive=False):
@@ -759,10 +805,12 @@
                     # remember this two avoid adding twice non symmetric relation
                     # such as "Emailthread forked_from Emailthread"
                     added.append((etype, rschema.type, targettype))
-                    self.rqlexecall(ss.rdef2rql(rschema, etype, targettype,
-                                                groupmap=groupmap),
-                                    ask_confirm=confirm)
+                    rdef = self._get_rdef(rschema, eschema, targetschema)
+                    ss.execschemarql(execute, rdef,
+                                     ss.rdef2rql(rdef, cstrtypemap, groupmap))
             for rschema in eschema.object_relations():
+                if rschema.type in META_RTYPES:
+                    continue
                 rtypeadded = rschema.type in instschema or rschema.type in added
                 for targetschema in rschema.subjects(etype):
                     # ignore relations where the targeted type is not in the
@@ -780,9 +828,9 @@
                     elif (targettype, rschema.type, etype) in added:
                         continue
                     # register relation definition
-                    self.rqlexecall(ss.rdef2rql(rschema, targettype, etype,
-                                                groupmap=groupmap),
-                                    ask_confirm=confirm)
+                    rdef = self._get_rdef(rschema, targetschema, eschema)
+                    ss.execschemarql(execute, rdef,
+                                     ss.rdef2rql(rdef, cstrtypemap, groupmap))
         if commit:
             self.commit()
 
@@ -821,15 +869,23 @@
         committing depends on the `commit` argument value).
 
         """
+        reposchema = self.repo.schema
         rschema = self.fs_schema.rschema(rtype)
+        execute = self._cw.execute
         # register the relation into CWRType and insert necessary relation
         # definitions
-        self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
-                        ask_confirm=self.verbosity>=2)
+        ss.execschemarql(execute, rschema, ss.rschema2rql(rschema, addrdef=False))
         if addrdef:
             self.commit()
-            self.rqlexecall(ss.rdef2rql(rschema, groupmap=self.group_mapping()),
-                            ask_confirm=self.verbosity>=2)
+            gmap = self.group_mapping()
+            cmap = self.cstrtype_mapping()
+            for rdef in rschema.rdefs.itervalues():
+                if not (reposchema.has_entity(rdef.subject)
+                        and reposchema.has_entity(rdef.object)):
+                    continue
+                self._set_rdef_eid(rdef)
+                ss.execschemarql(execute, rdef,
+                                 ss.rdef2rql(rdef, cmap, gmap))
             if rtype in META_RTYPES:
                 # if the relation is in META_RTYPES, ensure we're adding it for
                 # all entity types *in the persistent schema*, not only those in
@@ -838,15 +894,14 @@
                     if not etype in self.fs_schema:
                         # get sample object type and rproperties
                         objtypes = rschema.objects()
-                        assert len(objtypes) == 1
+                        assert len(objtypes) == 1, objtypes
                         objtype = objtypes[0]
-                        props = rschema.rproperties(
-                            rschema.subjects(objtype)[0], objtype)
-                        assert props
-                        self.rqlexecall(ss.rdef2rql(rschema, etype, objtype, props,
-                                                    groupmap=self.group_mapping()),
-                                        ask_confirm=self.verbosity>=2)
-
+                        rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype))
+                        rdef.subject = etype
+                        rdef.rtype = self.repo.schema.rschema(rschema)
+                        rdef.object = self.repo.schema.rschema(objtype)
+                        ss.execschemarql(execute, rdef,
+                                         ss.rdef2rql(rdef, cmap, gmap))
         if commit:
             self.commit()
 
@@ -876,12 +931,25 @@
         rschema = self.fs_schema.rschema(rtype)
         if not rtype in self.repo.schema:
             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
-        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype,
-                                    groupmap=self.group_mapping()),
-                        ask_confirm=self.verbosity>=2)
+        execute = self._cw.execute
+        rdef = self._get_rdef(rschema, subjtype, objtype)
+        ss.execschemarql(execute, rdef,
+                         ss.rdef2rql(rdef, self.cstrtype_mapping(),
+                                     self.group_mapping()))
         if commit:
             self.commit()
 
+    def _get_rdef(self, rschema, subjtype, objtype):
+        return self._set_rdef_eid(rschema.rdefs[(subjtype, objtype)])
+
+    def _set_rdef_eid(self, rdef):
+        for attr in ('rtype', 'subject', 'object'):
+            schemaobj = getattr(rdef, attr)
+            if getattr(schemaobj, 'eid', None) is None:
+                schemaobj.eid =  self.repo.schema[schemaobj].eid
+                assert schemaobj.eid is not None
+        return rdef
+
     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
         """unregister an existing relation definition"""
         rschema = self.repo.schema.rschema(rtype)
@@ -928,7 +996,13 @@
                 if syncprops:
                     self._synchronize_eschema(etype, syncperms=syncperms)
                 else:
-                    self._synchronize_permissions(self.fs_schema[etype], etype.eid)
+                    try:
+                        fseschema = self.fs_schema[etype]
+                    except KeyError:
+                        # entity type in the repository schema but not anymore
+                        # on the fs schema
+                        continue
+                    self._synchronize_permissions(fseschema, etype.eid)
         if commit:
             self.commit()
 
@@ -1119,9 +1193,8 @@
             return session
         return self.cnx.request()
 
-    def cmd_create_entity(self, etype, **kwargs):
+    def cmd_create_entity(self, etype, commit=False, **kwargs):
         """add a new entity of the given type"""
-        commit = kwargs.pop('commit', False)
         entity = self._cw.create_entity(etype, **kwargs)
         if commit:
             self.commit()
@@ -1139,7 +1212,6 @@
         level actions
         """
         if not ask_confirm or self.confirm('Execute sql: %s ?' % sql):
-            self.session.set_pool() # ensure pool is set
             try:
                 cu = self.session.system_sql(sql, args)
             except:
@@ -1153,15 +1225,13 @@
                 # no result to fetch
                 return
 
-    def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True):
+    def rqlexec(self, rql, kwargs=None, cachekey=None, build_descr=True,
+                ask_confirm=True):
         """rql action"""
         if not isinstance(rql, (tuple, list)):
             rql = ( (rql, kwargs), )
         res = None
-        try:
-            execute = self._cw.unsafe_execute
-        except AttributeError:
-            execute = self._cw.execute
+        execute = self._cw.execute
         for rql, kwargs in rql:
             if kwargs:
                 msg = '%s (%s)' % (rql, kwargs)
@@ -1169,7 +1239,7 @@
                 msg = rql
             if not ask_confirm or self.confirm('Execute rql: %s ?' % msg):
                 try:
-                    res = execute(rql, kwargs, cachekey)
+                    res = execute(rql, kwargs, cachekey, build_descr=build_descr)
                 except Exception, ex:
                     if self.confirm('Error: %s\nabort?' % ex):
                         raise
@@ -1178,12 +1248,6 @@
     def rqliter(self, rql, kwargs=None, ask_confirm=True):
         return ForRqlIterator(self, rql, None, ask_confirm)
 
-    def cmd_deactivate_verification_hooks(self):
-        self.config.disabled_hooks_categories.add('integrity')
-
-    def cmd_reactivate_verification_hooks(self):
-        self.config.disabled_hooks_categories.remove('integrity')
-
     # broken db commands ######################################################
 
     def cmd_change_attribute_type(self, etype, attr, newtype, commit=True):
@@ -1234,6 +1298,14 @@
         if commit:
             self.commit()
 
+    @deprecated("[3.7] use session.disable_hook_categories('integrity')")
+    def cmd_deactivate_verification_hooks(self):
+        self.session.disable_hook_categories('integrity')
+
+    @deprecated("[3.7] use session.enable_hook_categories('integrity')")
+    def cmd_reactivate_verification_hooks(self):
+        self.session.enable_hook_categories('integrity')
+
 
 class ForRqlIterator:
     """specific rql iterator to make the loop skipable"""
--- a/server/msplanner.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/msplanner.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """plan execution of rql queries on multiple sources
 
 the best way to understand what are we trying to acheive here is to read the
@@ -69,10 +86,6 @@
      will directly get local eids)
 
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -313,8 +326,6 @@
             if varobj.stinfo['uidrels']:
                 vrels = varobj.stinfo['relations'] - varobj.stinfo['uidrels']
                 for rel in varobj.stinfo['uidrels']:
-                    if rel.neged(strict=True) or rel.operator() != '=':
-                        continue
                     for const in rel.children[1].get_nodes(Constant):
                         eid = const.eval(self.plan.args)
                         source = self._session.source_from_eid(eid)
@@ -352,8 +363,11 @@
                             source.support_relation, (r.r_type for r in rels))):
                             self.needsplit = True
         # add source for rewritten constants to sourcesterms
+        self._const_vars = {}
         for vconsts in self.rqlst.stinfo['rewritten'].itervalues():
-            const = vconsts[0]
+            # remember those consts come from the same variable
+            for const in vconsts:
+                self._const_vars[const] = vconsts
             source = self._session.source_from_eid(const.eval(self.plan.args))
             if source is self.system_source:
                 for const in vconsts:
@@ -539,6 +553,11 @@
                                                     rel in self._crossrelations[s]))
         if invalid_sources:
             self._remove_sources(term, invalid_sources)
+            # if term is a rewritten const, we can apply the same changes to
+            # all other consts inserted from the same original variable
+            for const in self._const_vars.get(term, ()):
+                if const is not term:
+                    self._remove_sources(const, invalid_sources)
             termsources -= invalid_sources
             self._remove_sources_until_stable(term, termssources)
             if isinstance(oterm, Constant):
@@ -1044,7 +1063,7 @@
                      for select in subquery.query.children]
             for sppi in sppis:
                 if sppi.needsplit or sppi.part_sources != ppi.part_sources:
-                    temptable = 'T%s' % make_uid(id(subquery))
+                    temptable = plan.make_temp_table_name('T%s' % make_uid(id(subquery)))
                     sstep = self._union_plan(plan, sppis, temptable)[0]
                     break
             else:
@@ -1077,7 +1096,7 @@
                 inputmap = self._ppi_subqueries(ppi)
                 aggrstep = need_aggr_step(select, sources)
                 if aggrstep:
-                    atemptable = 'T%s' % make_uid(id(select))
+                    atemptable = plan.make_temp_table_name('T%s' % make_uid(id(select)))
                     sunion = Union()
                     sunion.append(select)
                     selected = select.selection[:]
@@ -1121,7 +1140,7 @@
         subinputmap = self._ppi_subqueries(ppi)
         stepdefs = ppi.part_steps()
         if need_aggr_step(select, ppi.part_sources, stepdefs):
-            atemptable = 'T%s' % make_uid(id(select))
+            atemptable = plan.make_temp_table_name('T%s' % make_uid(id(select)))
             selection = select.selection[:]
             select_group_sort(select)
         else:
@@ -1171,6 +1190,7 @@
                 else:
                     table = '_T%s%s' % (''.join(sorted(v._ms_table_key() for v in terms)),
                                         ''.join(sorted(str(i) for i in solindices)))
+                    table = plan.make_temp_table_name(table)
                     ppi.build_non_final_part(minrqlst, solindices, sources,
                                              insertedvars, table)
         # finally: join parts, deal with aggregat/group/sorts if necessary
--- a/server/mssteps.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/mssteps.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Defines the diferent querier steps usable in plans.
 
 FIXME : this code needs refactoring. Some problems :
@@ -5,10 +22,6 @@
 * each step has is own members (this is not necessarily bad, but a bit messy
   for now)
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/server/pool.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/pool.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """CubicWeb server connections pool : the repository has a limited number of
 connections pools, each of them dealing with a set of connections on each source
 used by the repository. A connections pools (`ConnectionsPool`) is an
 abstraction for a group of connection to each source.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/server/querier.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/querier.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Helper classes to execute RQL queries on a set of sources, performing
 security checking and data aggregation.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 from itertools import repeat
@@ -22,9 +37,8 @@
 
 from cubicweb.server.utils import cleanup_solutions
 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
-from cubicweb.server.ssplanner import add_types_restriction
-
-READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
+from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
+from cubicweb.server.session import security_enabled
 
 def empty_rset(rql, args, rqlst=None):
     """build an empty result set object"""
@@ -41,24 +55,22 @@
 
 # permission utilities ########################################################
 
-def var_kwargs(restriction, args):
-    varkwargs = {}
-    for rel in restriction.iget_nodes(Relation):
-        cmp = rel.children[1]
-        if rel.r_type == 'eid' and cmp.operator == '=' and \
-               not rel.neged(strict=True) and \
-               isinstance(cmp.children[0], Constant) and \
-               cmp.children[0].type == 'Substitute':
-            varkwargs[rel.children[0].name] = typed_eid(cmp.children[0].eval(args))
-    return varkwargs
-
 def check_no_password_selected(rqlst):
     """check that Password entities are not selected"""
     for solution in rqlst.solutions:
         if 'Password' in solution.itervalues():
             raise Unauthorized('Password selection is not allowed')
 
-def check_read_access(schema, user, rqlst, solution):
+def term_etype(session, term, solution, args):
+    """return the entity type for the given term (a VariableRef or a Constant
+    node)
+    """
+    try:
+        return solution[term.name]
+    except AttributeError:
+        return session.describe(term.eval(args))[0]
+
+def check_read_access(session, rqlst, solution, args):
     """check that the given user has credentials to access data read the
     query
 
@@ -66,6 +78,10 @@
     in the schema), keys are variable names and values associated rql expression
     for the associated variable with the given solution
     """
+    # use `term_etype` since we've to deal with rewritten constants here,
+    # when used as an external source by another repository.
+    # XXX what about local read security w/ those rewritten constants...
+    schema = session.repo.schema
     if rqlst.where is not None:
         for rel in rqlst.where.iget_nodes(Relation):
             # XXX has_text may have specific perm ?
@@ -73,38 +89,40 @@
                 continue
             rschema = schema.rschema(rel.r_type)
             if rschema.final:
-                eschema = schema.eschema(solution[rel.children[0].name])
+                eschema = schema.eschema(term_etype(session, rel.children[0],
+                                                    solution, args))
                 rdef = eschema.rdef(rschema)
             else:
-                rdef = rschema.rdef(solution[rel.children[0].name],
-                                    solution[rel.children[1].children[0].name])
-            if not user.matching_groups(rdef.get_groups('read')):
+                rdef = rschema.rdef(term_etype(session, rel.children[0],
+                                               solution, args),
+                                    term_etype(session, rel.children[1].children[0],
+                                               solution, args))
+            if not session.user.matching_groups(rdef.get_groups('read')):
+                # XXX rqlexpr not allowed
                 raise Unauthorized('read', rel.r_type)
     localchecks = {}
     # iterate on defined_vars and not on solutions to ignore column aliases
     for varname in rqlst.defined_vars:
-        etype = solution[varname]
-        eschema = schema.eschema(etype)
+        eschema = schema.eschema(solution[varname])
         if eschema.final:
             continue
-        if not user.matching_groups(eschema.get_groups('read')):
+        if not session.user.matching_groups(eschema.get_groups('read')):
             erqlexprs = eschema.get_rqlexprs('read')
             if not erqlexprs:
-                ex = Unauthorized('read', etype)
+                ex = Unauthorized('read', solution[varname])
                 ex.var = varname
                 raise ex
-            #assert len(erqlexprs) == 1
-            localchecks[varname] = tuple(erqlexprs)
+            localchecks[varname] = erqlexprs
     return localchecks
 
-def noinvariant_vars(restricted, select, nbtrees):
+def add_noinvariant(noinvariant, restricted, select, nbtrees):
     # a variable can actually be invariant if it has not been restricted for
     # security reason or if security assertion hasn't modified the possible
     # solutions for the query
     if nbtrees != 1:
         for vname in restricted:
             try:
-                yield select.defined_vars[vname]
+                noinvariant.add(select.defined_vars[vname])
             except KeyError:
                 # this is an alias
                 continue
@@ -116,7 +134,7 @@
                 # this is an alias
                 continue
             if len(var.stinfo['possibletypes']) != 1:
-                yield var
+                noinvariant.add(var)
 
 def _expand_selection(terms, selected, aliases, select, newselect):
     for term in terms:
@@ -175,6 +193,13 @@
         finally:
             self.clean()
 
+    def make_temp_table_name(self, table):
+        """
+        return a temp table name according to db backend
+        """
+        return self.syssource.make_temp_table_name(table)
+
+
     def init_temp_table(self, table, selected, sol):
         """initialize sql schema and variable map for a temporary table which
         will be used to store result for the given rqlst
@@ -200,12 +225,35 @@
 
         return rqlst to actually execute
         """
-        noinvariant = set()
-        if security and not self.session.is_super_session:
-            self._insert_security(union, noinvariant)
-        self.rqlhelper.simplify(union)
-        self.sqlannotate(union)
-        set_qdata(self.schema.rschema, union, noinvariant)
+        cached = None
+        if security and self.session.read_security:
+            # ensure security is turned of when security is inserted,
+            # else we may loop for ever...
+            if self.session.transaction_data.get('security-rqlst-cache'):
+                key = self.cache_key
+            else:
+                key = None
+            if key is not None and key in self.session.transaction_data:
+                cachedunion, args = self.session.transaction_data[key]
+                union.children[:] = []
+                for select in cachedunion.children:
+                    union.append(select)
+                union.has_text_query = cachedunion.has_text_query
+                args.update(self.args)
+                self.args = args
+                cached = True
+            else:
+                noinvariant = set()
+                with security_enabled(self.session, read=False):
+                    self._insert_security(union, noinvariant)
+                if key is not None:
+                    self.session.transaction_data[key] = (union, self.args)
+        else:
+            noinvariant = ()
+        if cached is None:
+            self.rqlhelper.simplify(union)
+            self.sqlannotate(union)
+            set_qdata(self.schema.rschema, union, noinvariant)
         if union.has_text_query:
             self.cache_key = None
 
@@ -273,14 +321,13 @@
                     myrqlst = select.copy(solutions=lchecksolutions)
                     myunion.append(myrqlst)
                     # in-place rewrite + annotation / simplification
-                    lcheckdef = [((varmap, 'X'), rqlexprs)
-                                 for varmap, rqlexprs in lcheckdef]
+                    lcheckdef = [((var, 'X'), rqlexprs) for var, rqlexprs in lcheckdef]
                     rewrite(myrqlst, lcheckdef, lchecksolutions, self.args)
-                    noinvariant.update(noinvariant_vars(restricted, myrqlst, nbtrees))
+                    add_noinvariant(noinvariant, restricted, myrqlst, nbtrees)
                 if () in localchecks:
                     select.set_possible_types(localchecks[()])
                     add_types_restriction(self.schema, select)
-                    noinvariant.update(noinvariant_vars(restricted, select, nbtrees))
+                    add_noinvariant(noinvariant, restricted, select, nbtrees)
 
     def _check_permissions(self, rqlst):
         """return a dict defining "local checks", e.g. RQLExpression defined in
@@ -300,44 +347,50 @@
 
         note: rqlst should not have been simplified at this point
         """
-        assert not self.session.is_super_session
-        user = self.session.user
-        schema = self.schema
+        session = self.session
         msgs = []
+        neweids = session.transaction_data.get('neweids', ())
+        varkwargs = {}
+        if not session.transaction_data.get('security-rqlst-cache'):
+            for var in rqlst.defined_vars.itervalues():
+                for rel in var.stinfo['uidrels']:
+                    const = rel.children[1].children[0]
+                    try:
+                        varkwargs[var.name] = typed_eid(const.eval(self.args))
+                        break
+                    except AttributeError:
+                        #from rql.nodes import Function
+                        #assert isinstance(const, Function)
+                        # X eid IN(...)
+                        pass
         # dictionnary of variables restricted for security reason
         localchecks = {}
-        if rqlst.where is not None:
-            varkwargs = var_kwargs(rqlst.where, self.args)
-            neweids = self.session.transaction_data.get('neweids', ())
-        else:
-            varkwargs = None
         restricted_vars = set()
         newsolutions = []
         for solution in rqlst.solutions:
             try:
-                localcheck = check_read_access(schema, user, rqlst, solution)
+                localcheck = check_read_access(session, rqlst, solution, self.args)
             except Unauthorized, ex:
                 msg = 'remove %s from solutions since %s has no %s access to %s'
-                msg %= (solution, user.login, ex.args[0], ex.args[1])
+                msg %= (solution, session.user.login, ex.args[0], ex.args[1])
                 msgs.append(msg)
                 LOGGER.info(msg)
             else:
                 newsolutions.append(solution)
-                if varkwargs:
-                    # try to benefit of rqlexpr.check cache for entities which
-                    # are specified by eid in query'args
-                    for varname, eid in varkwargs.iteritems():
-                        try:
-                            rqlexprs = localcheck.pop(varname)
-                        except KeyError:
-                            continue
-                        if eid in neweids:
-                            continue
-                        for rqlexpr in rqlexprs:
-                            if rqlexpr.check(self.session, eid):
-                                break
-                        else:
-                            raise Unauthorized()
+                # try to benefit of rqlexpr.check cache for entities which
+                # are specified by eid in query'args
+                for varname, eid in varkwargs.iteritems():
+                    try:
+                        rqlexprs = localcheck.pop(varname)
+                    except KeyError:
+                        continue
+                    if eid in neweids:
+                        continue
+                    for rqlexpr in rqlexprs:
+                        if rqlexpr.check(session, eid):
+                            break
+                    else:
+                        raise Unauthorized()
                 restricted_vars.update(localcheck)
                 localchecks.setdefault(tuple(localcheck.iteritems()), []).append(solution)
         # raise Unautorized exception if the user can't access to any solution
@@ -377,39 +430,6 @@
         self._r_obj_index = {}
         self._expanded_r_defs = {}
 
-    def relation_definitions(self, rqlst, to_build):
-        """add constant values to entity def, mark variables to be selected
-        """
-        to_select = {}
-        for relation in rqlst.main_relations:
-            lhs, rhs = relation.get_variable_parts()
-            rtype = relation.r_type
-            if rtype in READ_ONLY_RTYPES:
-                raise QueryError("can't assign to %s" % rtype)
-            try:
-                edef = to_build[str(lhs)]
-            except KeyError:
-                # lhs var is not to build, should be selected and added as an
-                # object relation
-                edef = to_build[str(rhs)]
-                to_select.setdefault(edef, []).append((rtype, lhs, 1))
-            else:
-                if isinstance(rhs, Constant) and not rhs.uid:
-                    # add constant values to entity def
-                    value = rhs.eval(self.args)
-                    eschema = edef.e_schema
-                    attrtype = eschema.subjrels[rtype].objects(eschema)[0]
-                    if attrtype == 'Password' and isinstance(value, unicode):
-                        value = value.encode('UTF8')
-                    edef[rtype] = value
-                elif to_build.has_key(str(rhs)):
-                    # create a relation between two newly created variables
-                    self.add_relation_def((edef, rtype, to_build[rhs.name]))
-                else:
-                    to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
-        return to_select
-
-
     def add_entity_def(self, edef):
         """add an entity definition to build"""
         edef.querier_pending_relations = {}
@@ -629,20 +649,20 @@
             try:
                 self.solutions(session, rqlst, args)
             except UnknownEid:
-                # we want queries such as "Any X WHERE X eid 9999"
-                # return an empty result instead of raising UnknownEid
+                # we want queries such as "Any X WHERE X eid 9999" return an
+                # empty result instead of raising UnknownEid
                 return empty_rset(rql, args, rqlst)
             self._rql_cache[cachekey] = rqlst
         orig_rqlst = rqlst
-        if not rqlst.TYPE == 'select':
-            if not session.is_super_session:
+        if rqlst.TYPE != 'select':
+            if session.read_security:
                 check_no_password_selected(rqlst)
-            # write query, ensure session's mode is 'write' so connections
-            # won't be released until commit/rollback
+            # write query, ensure session's mode is 'write' so connections won't
+            # be released until commit/rollback
             session.mode = 'write'
             cachekey = None
         else:
-            if not session.is_super_session:
+            if session.read_security:
                 for select in rqlst.children:
                     check_no_password_selected(select)
             # on select query, always copy the cached rqlst so we don't have to
--- a/server/repository.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/repository.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Defines the central class for the CubicWeb RQL server: the repository.
 
 The repository is an abstraction allowing execution of rql queries against
@@ -10,11 +27,9 @@
 * provides method for pyro registration, to call if pyro is enabled
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
@@ -25,50 +40,21 @@
 
 from logilab.common.decorators import cached
 from logilab.common.compat import any
+from logilab.common import flatten
 
 from yams import BadSchemaDefinition
+from yams.schema import role_name
 from rql import RQLSyntaxError
 
 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
                       UnknownEid, AuthenticationError, ExecutionError,
                       ETypeNotSupportedBySources, MultiSourcesError,
                       BadConnectionId, Unauthorized, ValidationError,
-                      typed_eid)
+                      RepositoryError, typed_eid, onevent)
 from cubicweb import cwvreg, schema, server
 from cubicweb.server import utils, hook, pool, querier, sources
-from cubicweb.server.session import Session, InternalSession
-
-
-class CleanupEidTypeCacheOp(hook.SingleLastOperation):
-    """on rollback of a insert query or commit of delete query, we have to
-    clear repository's cache from no more valid entries
-
-    NOTE: querier's rqlst/solutions cache may have been polluted too with
-    queries such as Any X WHERE X eid 32 if 32 has been rollbacked however
-    generated queries are unpredictable and analysing all the cache probably
-    too expensive. Notice that there is no pb when using args to specify eids
-    instead of giving them into the rql string.
-    """
-
-    def commit_event(self):
-        """the observed connections pool has been rollbacked,
-        remove inserted eid from repository type/source cache
-        """
-        try:
-            self.session.repo.clear_caches(
-                self.session.transaction_data['pendingeids'])
-        except KeyError:
-            pass
-
-    def rollback_event(self):
-        """the observed connections pool has been rollbacked,
-        remove inserted eid from repository type/source cache
-        """
-        try:
-            self.session.repo.clear_caches(
-                self.session.transaction_data['neweids'])
-        except KeyError:
-            pass
+from cubicweb.server.session import Session, InternalSession, InternalManager, \
+     security_enabled
 
 
 def del_existing_rel_if_needed(session, eidfrom, rtype, eidto):
@@ -80,12 +66,12 @@
     this kind of behaviour has to be done in the repository so we don't have
     hooks order hazardness
     """
-    # XXX now that rql in migraction default to unsafe_execute we don't want to
-    #     skip that for super session (though we can still skip it for internal
-    #     sessions). Also we should imo rely on the orm to first fetch existing
-    #     entity if any then delete it.
+    # skip that for internal session or if integrity explicitly disabled
+    #
+    # XXX we should imo rely on the orm to first fetch existing entity if any
+    # then delete it.
     if session.is_internal_session \
-           or not session.vreg.config.is_hook_category_activated('integrity'):
+           or not session.is_hook_category_activated('activeintegrity'):
         return
     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
@@ -100,23 +86,15 @@
     rschema = session.repo.schema.rschema(rtype)
     if card[0] in '1?':
         if not rschema.inlined: # inlined relations will be implicitly deleted
-            rset = session.unsafe_execute('Any X,Y WHERE X %s Y, X eid %%(x)s, '
-                                          'NOT Y eid %%(y)s' % rtype,
-                                          {'x': eidfrom, 'y': eidto}, 'x')
-            if rset:
-                safe_delete_relation(session, rschema, *rset[0])
+            with security_enabled(session, read=False):
+                session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
+                                'NOT Y eid %%(y)s' % rtype,
+                                {'x': eidfrom, 'y': eidto}, 'x')
     if card[1] in '1?':
-        rset = session.unsafe_execute('Any X,Y WHERE X %s Y, Y eid %%(y)s, '
-                                      'NOT X eid %%(x)s' % rtype,
-                                      {'x': eidfrom, 'y': eidto}, 'y')
-        if rset:
-            safe_delete_relation(session, rschema, *rset[0])
-
-
-def safe_delete_relation(session, rschema, subject, object):
-    if not rschema.has_perm(session, 'delete', fromeid=subject, toeid=object):
-        raise Unauthorized()
-    session.repo.glob_delete_relation(session, subject, rschema.type, object)
+        with security_enabled(session, read=False):
+            session.execute('DELETE X %sY WHERE Y eid %%(y)s, '
+                            'NOT X eid %%(x)s' % rtype,
+                            {'x': eidfrom, 'y': eidto}, 'y')
 
 
 class Repository(object):
@@ -167,8 +145,14 @@
         # open some connections pools
         if config.open_connections_pools:
             self.open_connections_pools()
+        @onevent('after-registry-reload', self)
+        def fix_user_classes(self):
+            usercls = self.vreg['etypes'].etype_class('CWUser')
+            for session in self._sessions.values():
+                if not isinstance(session.user, InternalManager):
+                    session.user.__class__ = usercls
 
-    def _boostrap_hook_registry(self):
+    def _bootstrap_hook_registry(self):
         """called during bootstrap since we need the metadata hooks"""
         hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
         self.vreg.init_registration([hooksdirectory])
@@ -179,12 +163,19 @@
         config = self.config
         self._available_pools = Queue.Queue()
         self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
-        if config.read_instance_schema:
-            # normal start: load the instance schema from the database
-            self.fill_schema()
-        elif config.bootstrap_schema:
-            # usually during repository creation
-            self.warning("set fs instance'schema as bootstrap schema")
+        if config.quick_start:
+            # quick start, usually only to get a minimal repository to get cubes
+            # information (eg dump/restore/...)
+            config._cubes = ()
+            # only load hooks and entity classes in the registry
+            config.cube_appobject_path = set(('hooks', 'entities'))
+            config.cubicweb_appobject_path = set(('hooks', 'entities'))
+            self.set_schema(config.load_schema())
+            config['connections-pool-size'] = 1
+            # will be reinitialized later from cubes found in the database
+            config._cubes = None
+        elif config.creating:
+            # repository creation
             config.bootstrap_cubes()
             self.set_schema(config.load_schema(), resetvreg=False)
             # need to load the Any and CWUser entity types
@@ -192,8 +183,11 @@
             self.vreg.init_registration([etdirectory])
             for modname in ('__init__', 'authobjs', 'wfobjs'):
                 self.vreg.load_file(join(etdirectory, '%s.py' % modname),
-                                'cubicweb.entities.%s' % modname)
-            self._boostrap_hook_registry()
+                                    'cubicweb.entities.%s' % modname)
+            self._bootstrap_hook_registry()
+        elif config.read_instance_schema:
+            # normal start: load the instance schema from the database
+            self.fill_schema()
         else:
             # test start: use the file system schema (quicker)
             self.warning("set fs instance'schema")
@@ -222,12 +216,9 @@
             self.pools.append(pool.ConnectionsPool(self.sources))
             self._available_pools.put_nowait(self.pools[-1])
         self._shutting_down = False
-        self.hm = self.vreg['hooks']
-        if not (config.creating or config.repairing):
-            # call instance level initialisation hooks
-            self.hm.call_hooks('server_startup', repo=self)
-            # register a task to cleanup expired session
-            self.looping_task(config['session-time']/3., self.clean_sessions)
+        if config.quick_start:
+            config.init_cubes(self.get_cubes())
+        self.hm = hook.HooksManager(self.vreg)
 
     # internals ###############################################################
 
@@ -276,6 +267,12 @@
         self.set_schema(appschema)
 
     def start_looping_tasks(self):
+        if not (self.config.creating or self.config.repairing
+                or self.config.quick_start):
+            # call instance level initialisation hooks
+            self.hm.call_hooks('server_startup', repo=self)
+            # register a task to cleanup expired session
+            self.looping_task(self.config['session-time']/3., self.clean_sessions)
         assert isinstance(self._looping_tasks, list), 'already started'
         for i, (interval, func, args) in enumerate(self._looping_tasks):
             self._looping_tasks[i] = task = utils.LoopTask(interval, func, args)
@@ -327,6 +324,7 @@
         """called on server stop event to properly close opened sessions and
         connections
         """
+        assert not self._shutting_down, 'already shutting down'
         self._shutting_down = True
         if isinstance(self._looping_tasks, tuple): # if tasks have been started
             for looptask in self._looping_tasks:
@@ -335,10 +333,11 @@
                 looptask.join()
                 self.info('task %s finished', looptask.name)
         for thread in self._running_threads:
-            self.info('waiting thread %s...', thread.name)
+            self.info('waiting thread %s...', thread.getName())
             thread.join()
-            self.info('thread %s finished', thread.name)
-        if not (self.config.creating or self.config.repairing):
+            self.info('thread %s finished', thread.getName())
+        if not (self.config.creating or self.config.repairing
+                or self.config.quick_start):
             self.hm.call_hooks('server_shutdown', repo=self)
         self.close_sessions()
         while not self._available_pools.empty():
@@ -389,7 +388,8 @@
         session = self.internal_session()
         try:
             rset = session.execute('Any L WHERE U login L, U primary_email M, '
-                                   'M address %(login)s', {'login': login})
+                                   'M address %(login)s', {'login': login},
+                                   build_descr=False)
             if rset.rowcount == 1:
                 login = rset[0][0]
         finally:
@@ -450,6 +450,7 @@
         """
         versions = self.get_versions(not (self.config.creating
                                           or self.config.repairing
+                                          or self.config.quick_start
                                           or self.config.mode == 'test'))
         cubes = list(versions)
         cubes.remove('cubicweb')
@@ -509,22 +510,25 @@
         finally:
             session.close()
 
+    # XXX protect this method: anonymous should be allowed and registration
+    # plugged
     def register_user(self, login, password, email=None, **kwargs):
         """check a user with the given login exists, if not create it with the
         given password. This method is designed to be used for anonymous
         registration on public web site.
         """
-        # XXX should not be called from web interface
         session = self.internal_session()
         # for consistency, keep same error as unique check hook (although not required)
         errmsg = session._('the value "%s" is already used, use another one')
         try:
-            if (session.execute('CWUser X WHERE X login %(login)s', {'login': login})
+            if (session.execute('CWUser X WHERE X login %(login)s', {'login': login},
+                                build_descr=False)
                 or session.execute('CWUser X WHERE X use_email C, C address %(login)s',
-                                   {'login': login})):
-                raise ValidationError(None, {'login': errmsg % login})
+                                   {'login': login}, build_descr=False)):
+                qname = role_name('login', 'subject')
+                raise ValidationError(None, {qname: errmsg % login})
             # we have to create the user
-            user = self.vreg['etypes'].etype_class('CWUser')(session, None)
+            user = self.vreg['etypes'].etype_class('CWUser')(session)
             if isinstance(password, unicode):
                 # password should *always* be utf8 encoded
                 password = password.encode('UTF8')
@@ -536,10 +540,13 @@
                             {'x': user.eid})
             if email or '@' in login:
                 d = {'login': login, 'email': email or login}
-                if session.execute('EmailAddress X WHERE X address %(email)s', d):
-                    raise ValidationError(None, {'address': errmsg % d['email']})
+                if session.execute('EmailAddress X WHERE X address %(email)s', d,
+                                   build_descr=False):
+                    qname = role_name('address', 'subject')
+                    raise ValidationError(None, {qname: errmsg % d['email']})
                 session.execute('INSERT EmailAddress X: X address %(email)s, '
-                                'U primary_email X, U use_email X WHERE U login %(login)s', d)
+                                'U primary_email X, U use_email X '
+                                'WHERE U login %(login)s', d, build_descr=False)
             session.commit()
         finally:
             session.close()
@@ -599,7 +606,7 @@
                 raise
             except:
                 # FIXME: check error to catch internal errors
-                self.exception('unexpected error')
+                self.exception('unexpected error while executing %s with %s', rqlstring, args)
                 raise
         finally:
             session.reset_pool()
@@ -613,7 +620,7 @@
             session.reset_pool()
 
     def check_session(self, sessionid):
-        """raise `BadSessionId` if the connection is no more valid"""
+        """raise `BadConnectionId` if the connection is no more valid"""
         self._get_session(sessionid, setpool=False)
 
     def get_shared_data(self, sessionid, key, default=None, pop=False):
@@ -636,7 +643,7 @@
         """commit transaction for the session with the given id"""
         self.debug('begin commit for session %s', sessionid)
         try:
-            self._get_session(sessionid).commit()
+            return self._get_session(sessionid).commit()
         except (ValidationError, Unauthorized):
             raise
         except:
@@ -685,10 +692,42 @@
           custom properties)
         """
         session = self._get_session(sessionid, setpool=False)
-        # update session properties
         for prop, value in props.items():
             session.change_property(prop, value)
 
+    def undoable_transactions(self, sessionid, ueid=None, **actionfilters):
+        """See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
+        session = self._get_session(sessionid, setpool=True)
+        try:
+            return self.system_source.undoable_transactions(session, ueid,
+                                                            **actionfilters)
+        finally:
+            session.reset_pool()
+
+    def transaction_info(self, sessionid, txuuid):
+        """See :class:`cubicweb.dbapi.Connection.transaction_info`"""
+        session = self._get_session(sessionid, setpool=True)
+        try:
+            return self.system_source.tx_info(session, txuuid)
+        finally:
+            session.reset_pool()
+
+    def transaction_actions(self, sessionid, txuuid, public=True):
+        """See :class:`cubicweb.dbapi.Connection.transaction_actions`"""
+        session = self._get_session(sessionid, setpool=True)
+        try:
+            return self.system_source.tx_actions(session, txuuid, public)
+        finally:
+            session.reset_pool()
+
+    def undo_transaction(self, sessionid, txuuid):
+        """See :class:`cubicweb.dbapi.Connection.undo_transaction`"""
+        session = self._get_session(sessionid, setpool=True)
+        try:
+            return self.system_source.undo_transaction(session, txuuid)
+        finally:
+            session.reset_pool()
+
     # public (inter-repository) interface #####################################
 
     def entities_modified_since(self, etypes, mtime):
@@ -887,64 +926,51 @@
         and index the entity with the full text index
         """
         # begin by inserting eid/type/source/extid into the entities table
-        new = session.transaction_data.setdefault('neweids', set())
-        new.add(entity.eid)
+        hook.set_operation(session, 'neweids', entity.eid,
+                           hook.CleanupNewEidsCacheOp)
         self.system_source.add_info(session, entity, source, extid, complete)
-        CleanupEidTypeCacheOp(session)
-
-    def delete_info(self, session, eid):
-        self._prepare_delete_info(session, eid)
-        self._delete_info(session, eid)
 
-    def _prepare_delete_info(self, session, eid):
-        """prepare the repository for deletion of an entity:
-        * update the fti
-        * mark eid as being deleted in session info
-        * setup cache update operation
-        """
-        self.system_source.fti_unindex_entity(session, eid)
-        pending = session.transaction_data.setdefault('pendingeids', set())
-        pending.add(eid)
-        CleanupEidTypeCacheOp(session)
-
-    def _delete_info(self, session, eid):
-        """delete system information on deletion of an entity:
-        * delete all relations on this entity
-        * transfer record from the entities table to the deleted_entities table
+    def delete_info(self, session, entity, sourceuri, extid):
+        """called by external source when some entity known by the system source
+        has been deleted in the external source
         """
-        etype, uri, extid = self.type_and_source_from_eid(eid, session)
-        self._clear_eid_relations(session, etype, eid)
-        self.system_source.delete_info(session, eid, etype, uri, extid)
+        # mark eid as being deleted in session info and setup cache update
+        # operation
+        hook.set_operation(session, 'pendingeids', entity.eid,
+                           hook.CleanupDeletedEidsCacheOp)
+        self._delete_info(session, entity, sourceuri, extid)
 
-    def _clear_eid_relations(self, session, etype, eid):
-        """when a entity is deleted, build and execute rql query to delete all
-        its relations
+    def _delete_info(self, session, entity, sourceuri, extid):
+                     # attributes=None, relations=None):
+        """delete system information on deletion of an entity:
+        * delete all remaining relations from/to this entity
+        * call delete info on the system source which will transfer record from
+          the entities table to the deleted_entities table
         """
-        rql = []
-        eschema = self.schema.eschema(etype)
         pendingrtypes = session.transaction_data.get('pendingrtypes', ())
-        for rschema, targetschemas, x in eschema.relation_definitions():
-            rtype = rschema.type
-            if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
-                continue
-            var = '%s%s' % (rtype.upper(), x.upper())
-            if x == 'subject':
-                # don't skip inlined relation so they are regularly
-                # deleted and so hooks are correctly called
-                selection = 'X %s %s' % (rtype, var)
-            else:
-                selection = '%s %s X' % (var, rtype)
-            rql = 'DELETE %s WHERE X eid %%(x)s' % selection
-            # unsafe_execute since we suppose that if user can delete the entity,
-            # he can delete all its relations without security checking
-            session.unsafe_execute(rql, {'x': eid}, 'x', build_descr=False)
+        # delete remaining relations: if user can delete the entity, he can
+        # delete all its relations without security checking
+        with security_enabled(session, read=False, write=False):
+            eid = entity.eid
+            for rschema, _, role in entity.e_schema.relation_definitions():
+                rtype = rschema.type
+                if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
+                    continue
+                if role == 'subject':
+                    # don't skip inlined relation so they are regularly
+                    # deleted and so hooks are correctly called
+                    rql = 'DELETE X %s Y WHERE X eid %%(x)s' % rtype
+                else:
+                    rql = 'DELETE Y %s X WHERE X eid %%(x)s' % rtype
+                session.execute(rql, {'x': eid}, 'x', build_descr=False)
+        self.system_source.delete_info(session, entity, sourceuri, extid)
 
     def locate_relation_source(self, session, subject, rtype, object):
         subjsource = self.source_from_eid(subject, session)
         objsource = self.source_from_eid(object, session)
         if not subjsource is objsource:
             source = self.system_source
-            if not (subjsource.may_cross_relation(rtype) 
+            if not (subjsource.may_cross_relation(rtype)
                     and objsource.may_cross_relation(rtype)):
                 raise MultiSourcesError(
                     "relation %s can't be crossed among sources"
@@ -966,6 +992,20 @@
         else:
             raise ETypeNotSupportedBySources(etype)
 
+    def init_entity_caches(self, session, entity, source):
+        """add entity to session entities cache and repo's extid cache.
+        Return entity's ext id if the source isn't the system source.
+        """
+        session.set_entity_cache(entity)
+        suri = source.uri
+        if suri == 'system':
+            extid = None
+        else:
+            extid = source.get_extid(entity)
+            self._extid_cache[(str(extid), suri)] = entity.eid
+        self._type_source_cache[entity.eid] = (entity.__regid__, suri, extid)
+        return extid
+
     def glob_add_entity(self, session, entity):
         """add an entity to the repository
 
@@ -981,33 +1021,30 @@
             entity.__class__ = entity_.__class__
             entity.__dict__.update(entity_.__dict__)
         eschema = entity.e_schema
-        etype = str(eschema)
-        source = self.locate_etype_source(etype)
-        # attribute an eid to the entity before calling hooks
+        source = self.locate_etype_source(entity.__regid__)
+        # allocate an eid to the entity before calling hooks
         entity.set_eid(self.system_source.create_eid(session))
+        # set caches asap
+        extid = self.init_entity_caches(session, entity, source)
         if server.DEBUG & server.DBG_REPO:
-            print 'ADD entity', etype, entity.eid, dict(entity)
+            print 'ADD entity', entity.__regid__, entity.eid, dict(entity)
         relations = []
         if source.should_call_hooks:
             self.hm.call_hooks('before_add_entity', session, entity=entity)
         # XXX use entity.keys here since edited_attributes is not updated for
-        # inline relations
-        for attr in entity.keys():
+        # inline relations XXX not true, right? (see edited_attributes
+        # affectation above)
+        for attr in entity.iterkeys():
             rschema = eschema.subjrels[attr]
             if not rschema.final: # inlined relation
                 relations.append((attr, entity[attr]))
         entity.set_defaults()
-        entity.check(creation=True)
+        if session.is_hook_category_activated('integrity'):
+            entity.check(creation=True)
         source.add_entity(session, entity)
-        if source.uri != 'system':
-            extid = source.get_extid(entity)
-            self._extid_cache[(str(extid), source.uri)] = entity.eid
-        else:
-            extid = None
         self.add_info(session, entity, source, extid, complete=False)
         entity._is_saved = True # entity has an eid and is saved
         # prefill entity relation caches
-        session.set_entity_cache(entity)
         for rschema in eschema.subject_relations():
             rtype = str(rschema)
             if rtype in schema.VIRTUAL_RTYPES:
@@ -1039,81 +1076,83 @@
         """replace an entity in the repository
         the type and the eid of an entity must not be changed
         """
-        etype = str(entity.e_schema)
         if server.DEBUG & server.DBG_REPO:
-            print 'UPDATE entity', etype, entity.eid, \
+            print 'UPDATE entity', entity.__regid__, entity.eid, \
                   dict(entity), edited_attributes
-        entity.edited_attributes = edited_attributes
-        entity.check()
+        hm = self.hm
         eschema = entity.e_schema
         session.set_entity_cache(entity)
-        only_inline_rels, need_fti_update = True, False
-        relations = []
-        for attr in edited_attributes:
-            if attr == 'eid':
-                continue
-            rschema = eschema.subjrels[attr]
-            if rschema.final:
-                if getattr(eschema.rdef(attr), 'fulltextindexed', False):
-                    need_fti_update = True
-                only_inline_rels = False
-            else:
-                # inlined relation
-                previous_value = entity.related(attr) or None
-                if previous_value is not None:
-                    previous_value = previous_value[0][0] # got a result set
-                    if previous_value == entity[attr]:
-                        previous_value = None
+        orig_edited_attributes = getattr(entity, 'edited_attributes', None)
+        entity.edited_attributes = edited_attributes
+        try:
+            if session.is_hook_category_activated('integrity'):
+                entity.check()
+            only_inline_rels, need_fti_update = True, False
+            relations = []
+            source = self.source_from_eid(entity.eid, session)
+            for attr in list(edited_attributes):
+                if attr == 'eid':
+                    continue
+                rschema = eschema.subjrels[attr]
+                if rschema.final:
+                    if getattr(eschema.rdef(attr), 'fulltextindexed', False):
+                        need_fti_update = True
+                    only_inline_rels = False
+                else:
+                    # inlined relation
+                    previous_value = entity.related(attr) or None
+                    if previous_value is not None:
+                        previous_value = previous_value[0][0] # got a result set
+                        if previous_value == entity[attr]:
+                            previous_value = None
+                        elif source.should_call_hooks:
+                            hm.call_hooks('before_delete_relation', session,
+                                          eidfrom=entity.eid, rtype=attr,
+                                          eidto=previous_value)
+                    relations.append((attr, entity[attr], previous_value))
+            if source.should_call_hooks:
+                # call hooks for inlined relations
+                for attr, value, _ in relations:
+                    hm.call_hooks('before_add_relation', session,
+                                  eidfrom=entity.eid, rtype=attr, eidto=value)
+                if not only_inline_rels:
+                    hm.call_hooks('before_update_entity', session, entity=entity)
+            source.update_entity(session, entity)
+            self.system_source.update_info(session, entity, need_fti_update)
+            if source.should_call_hooks:
+                if not only_inline_rels:
+                    hm.call_hooks('after_update_entity', session, entity=entity)
+                for attr, value, prevvalue in relations:
+                    # if the relation is already cached, update existant cache
+                    relcache = entity.relation_cached(attr, 'subject')
+                    if prevvalue is not None:
+                        hm.call_hooks('after_delete_relation', session,
+                                      eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
+                        if relcache is not None:
+                            session.update_rel_cache_del(entity.eid, attr, prevvalue)
+                    del_existing_rel_if_needed(session, entity.eid, attr, value)
+                    if relcache is not None:
+                        session.update_rel_cache_add(entity.eid, attr, value)
                     else:
-                        self.hm.call_hooks('before_delete_relation', session,
-                                           eidfrom=entity.eid, rtype=attr,
-                                           eidto=previous_value)
-                relations.append((attr, entity[attr], previous_value))
-        source = self.source_from_eid(entity.eid, session)
-        if source.should_call_hooks:
-            # call hooks for inlined relations
-            for attr, value, _ in relations:
-                self.hm.call_hooks('before_add_relation', session,
-                                    eidfrom=entity.eid, rtype=attr, eidto=value)
-            if not only_inline_rels:
-                self.hm.call_hooks('before_update_entity', session, entity=entity)
-        source.update_entity(session, entity)
-        self.system_source.update_info(session, entity, need_fti_update)
-        if source.should_call_hooks:
-            if not only_inline_rels:
-                self.hm.call_hooks('after_update_entity', session, entity=entity)
-            for attr, value, prevvalue in relations:
-                # if the relation is already cached, update existant cache
-                relcache = entity.relation_cached(attr, 'subject')
-                if prevvalue is not None:
-                    self.hm.call_hooks('after_delete_relation', session,
-                                       eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
-                    if relcache is not None:
-                        session.update_rel_cache_del(entity.eid, attr, prevvalue)
-                del_existing_rel_if_needed(session, entity.eid, attr, value)
-                if relcache is not None:
-                    session.update_rel_cache_add(entity.eid, attr, value)
-                else:
-                    entity.set_related_cache(attr, 'subject',
-                                             session.eid_rset(value))
-                self.hm.call_hooks('after_add_relation', session,
-                                    eidfrom=entity.eid, rtype=attr, eidto=value)
+                        entity.set_related_cache(attr, 'subject',
+                                                 session.eid_rset(value))
+                    hm.call_hooks('after_add_relation', session,
+                                  eidfrom=entity.eid, rtype=attr, eidto=value)
+        finally:
+            if orig_edited_attributes is not None:
+                entity.edited_attributes = orig_edited_attributes
 
     def glob_delete_entity(self, session, eid):
         """delete an entity and all related entities from the repository"""
-        # call delete_info before hooks
-        self._prepare_delete_info(session, eid)
-        etype, uri, extid = self.type_and_source_from_eid(eid, session)
+        entity = session.entity_from_eid(eid)
+        etype, sourceuri, extid = self.type_and_source_from_eid(eid, session)
         if server.DEBUG & server.DBG_REPO:
             print 'DELETE entity', etype, eid
-            if eid == 937:
-                server.DEBUG |= (server.DBG_SQL | server.DBG_RQL | server.DBG_MORE)
-        source = self.sources_by_uri[uri]
+        source = self.sources_by_uri[sourceuri]
         if source.should_call_hooks:
-            entity = session.entity_from_eid(eid)
             self.hm.call_hooks('before_delete_entity', session, entity=entity)
-        self._delete_info(session, eid)
-        source.delete_entity(session, etype, eid)
+        self._delete_info(session, entity, sourceuri, extid)
+        source.delete_entity(session, entity)
         if source.should_call_hooks:
             self.hm.call_hooks('after_delete_entity', session, entity=entity)
         # don't clear cache here this is done in a hook on commit
--- a/server/rqlannotation.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/rqlannotation.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Functions to add additional annotations on a rql syntax tree to ease later
 code generation.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/server/schemaserial.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/schemaserial.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functions for schema / permissions (de)serialization using RQL
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -50,6 +63,10 @@
                     continue
     return res
 
+def cstrtype_mapping(cursor):
+    """cached constraint types mapping"""
+    return dict(cursor.execute('Any T, X WHERE X is CWConstraintType, X name T'))
+
 # schema / perms deserialization ##############################################
 
 def deserialize_schema(schema, session):
@@ -214,7 +231,7 @@
     if not quiet:
         _title = '-> storing the schema in the database '
         print _title,
-    execute = cursor.unsafe_execute
+    execute = cursor.execute
     eschemas = schema.entities()
     if not quiet:
         pb_size = (len(eschemas + schema.relations())
@@ -229,14 +246,15 @@
     eschemas.remove(schema.eschema('CWEType'))
     eschemas.insert(0, schema.eschema('CWEType'))
     for eschema in eschemas:
-        for rql, kwargs in eschema2rql(eschema, groupmap):
-            execute(rql, kwargs, build_descr=False)
+        execschemarql(execute, eschema, eschema2rql(eschema, groupmap))
         if pb is not None:
             pb.update()
     # serialize constraint types
+    cstrtypemap = {}
     rql = 'INSERT CWConstraintType X: X name %(ct)s'
     for cstrtype in CONSTRAINTS:
-        execute(rql, {'ct': unicode(cstrtype)}, build_descr=False)
+        cstrtypemap[cstrtype] = execute(rql, {'ct': unicode(cstrtype)},
+                                        build_descr=False)[0][0]
         if pb is not None:
             pb.update()
     # serialize relations
@@ -246,8 +264,15 @@
             if pb is not None:
                 pb.update()
             continue
-        for rql, kwargs in rschema2rql(rschema, groupmap=groupmap):
-            execute(rql, kwargs, build_descr=False)
+        execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
+        if rschema.symmetric:
+            rdefs = [rdef for k, rdef in rschema.rdefs.iteritems()
+                     if (rdef.subject, rdef.object) == k]
+        else:
+            rdefs = rschema.rdefs.itervalues()
+        for rdef in rdefs:
+            execschemarql(execute, rdef,
+                          rdef2rql(rdef, cstrtypemap, groupmap))
         if pb is not None:
             pb.update()
     for rql, kwargs in specialize2rql(schema):
@@ -258,6 +283,55 @@
         print
 
 
+# high level serialization functions
+
+def execschemarql(execute, schema, rqls):
+    for rql, kwargs in rqls:
+        kwargs['x'] = schema.eid
+        rset = execute(rql, kwargs, build_descr=False)
+        if schema.eid is None:
+            schema.eid = rset[0][0]
+        else:
+            assert rset
+
+def erschema2rql(erschema, groupmap):
+    if isinstance(erschema, schemamod.EntitySchema):
+        return eschema2rql(erschema, groupmap=groupmap)
+    return rschema2rql(erschema, groupmap=groupmap)
+
+def specialize2rql(schema):
+    for eschema in schema.entities():
+        if eschema.final:
+            continue
+        for rql, kwargs in eschemaspecialize2rql(eschema):
+            yield rql, kwargs
+
+# etype serialization
+
+def eschema2rql(eschema, groupmap=None):
+    """return a list of rql insert statements to enter an entity schema
+    in the database as an CWEType entity
+    """
+    relations, values = eschema_relations_values(eschema)
+    # NOTE: 'specializes' relation can't be inserted here since there's no
+    # way to make sure the parent type is inserted before the child type
+    yield 'INSERT CWEType X: %s' % ','.join(relations) , values
+    # entity permissions
+    if groupmap is not None:
+        for rql, args in _erperms2rql(eschema, groupmap):
+            yield rql, args
+
+def eschema_relations_values(eschema):
+    values = _ervalues(eschema)
+    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+    return relations, values
+
+def eschemaspecialize2rql(eschema):
+    specialized_type = eschema.specializes()
+    if specialized_type:
+        values = {'x': eschema.eid, 'et': specialized_type.eid}
+        yield 'SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s', values
+
 def _ervalues(erschema):
     try:
         type_ = unicode(erschema.type)
@@ -273,10 +347,23 @@
         'description': desc,
         }
 
-def eschema_relations_values(eschema):
-    values = _ervalues(eschema)
-    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
-    return relations, values
+# rtype serialization
+
+def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None):
+    """return a list of rql insert statements to enter a relation schema
+    in the database as an CWRType entity
+    """
+    if rschema.type == 'has_text':
+        return
+    relations, values = rschema_relations_values(rschema)
+    yield 'INSERT CWRType X: %s' % ','.join(relations), values
+    if addrdef:
+        assert cstrtypemap
+        # sort for testing purpose
+        for rdef in sorted(rschema.rdefs.itervalues(),
+                           key=lambda x: (x.subject, x.object)):
+            for rql, values in rdef2rql(rdef, cstrtypemap, groupmap):
+                yield rql, values
 
 def rschema_relations_values(rschema):
     values = _ervalues(rschema)
@@ -290,169 +377,58 @@
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-def _rdef_values(objtype, props):
-    amap = {'order': 'ordernum'}
+# rdef serialization
+
+def rdef2rql(rdef, cstrtypemap, groupmap=None):
+    # don't serialize infered relations
+    if rdef.infered:
+        return
+    relations, values = _rdef_values(rdef)
+    relations.append('X relation_type ER,X from_entity SE,X to_entity OE')
+    values.update({'se': rdef.subject.eid, 'rt': rdef.rtype.eid, 'oe': rdef.object.eid})
+    if rdef.final:
+        etype = 'CWAttribute'
+    else:
+        etype = 'CWRelation'
+    yield 'INSERT %s X: %s WHERE SE eid %%(se)s,ER eid %%(rt)s,OE eid %%(oe)s' % (
+        etype, ','.join(relations), ), values
+    for rql, values in constraints2rql(cstrtypemap, rdef.constraints):
+        yield rql, values
+    # no groupmap means "no security insertion"
+    if groupmap:
+        for rql, args in _erperms2rql(rdef, groupmap):
+            yield rql, args
+
+def _rdef_values(rdef):
+    amap = {'order': 'ordernum', 'default': 'defaultval'}
     values = {}
-    for prop, default in schemamod.RelationDefinitionSchema.rproperty_defs(objtype).iteritems():
+    for prop, default in rdef.rproperty_defs(rdef.object).iteritems():
         if prop in ('eid', 'constraints', 'uid', 'infered', 'permissions'):
             continue
-        value = props.get(prop, default)
+        value = getattr(rdef, prop)
+        # XXX type cast really necessary?
         if prop in ('indexed', 'fulltextindexed', 'internationalizable'):
             value = bool(value)
         elif prop == 'ordernum':
             value = int(value)
         elif isinstance(value, str):
             value = unicode(value)
+        if value is not None and prop == 'default':
+            if value is False:
+                value = u''
+            if not isinstance(value, unicode):
+                value = unicode(value)
         values[amap.get(prop, prop)] = value
-    return values
-
-def nfrdef_relations_values(objtype, props):
-    values = _rdef_values(objtype, props)
-    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
-    return relations, values
-
-def frdef_relations_values(objtype, props):
-    values = _rdef_values(objtype, props)
-    default = values['default']
-    del values['default']
-    if default is not None:
-        if default is False:
-            default = u''
-        elif not isinstance(default, unicode):
-            default = unicode(default)
-    values['defaultval'] = default
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-
-def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None,
-               groupmap=None):
-    if subjtype is None:
-        assert objtype is None
-        assert props is None
-        targets = sorted(rschema.rdefs)
-    else:
-        assert not objtype is None
-        targets = [(subjtype, objtype)]
-    # relation schema
-    if rschema.final:
-        etype = 'CWAttribute'
-    else:
-        etype = 'CWRelation'
-    for subjtype, objtype in targets:
-        if props is None:
-            _props = rschema.rdef(subjtype, objtype)
-        else:
-            _props = props
-        # don't serialize infered relations
-        if _props.get('infered'):
-            continue
-        gen = genmap[rschema.final]
-        for rql, values in gen(rschema, subjtype, objtype, _props):
-            yield rql, values
-        # no groupmap means "no security insertion"
-        if groupmap:
-            for rql, args in _erperms2rql(_props, groupmap):
-                args['st'] = str(subjtype)
-                args['rt'] = str(rschema)
-                args['ot'] = str(objtype)
-                yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
-                      'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
-                      'OT name %%(ot)s' % etype, args
-
-
-def schema2rql(schema, skip=None, allow=None):
-    """return a list of rql insert statements to enter the schema in the
-    database as CWRType and CWEType entities
-    """
-    assert not (skip is not None and allow is not None), \
-           'can\'t use both skip and allow'
-    all = schema.entities() + schema.relations()
-    if skip is not None:
-        return chain(*[erschema2rql(schema[t]) for t in all if not t in skip])
-    elif allow is not None:
-        return chain(*[erschema2rql(schema[t]) for t in all if t in allow])
-    return chain(*[erschema2rql(schema[t]) for t in all])
-
-def erschema2rql(erschema, groupmap):
-    if isinstance(erschema, schemamod.EntitySchema):
-        return eschema2rql(erschema, groupmap=groupmap)
-    return rschema2rql(erschema, groupmap=groupmap)
-
-def eschema2rql(eschema, groupmap=None):
-    """return a list of rql insert statements to enter an entity schema
-    in the database as an CWEType entity
-    """
-    relations, values = eschema_relations_values(eschema)
-    # NOTE: 'specializes' relation can't be inserted here since there's no
-    # way to make sure the parent type is inserted before the child type
-    yield 'INSERT CWEType X: %s' % ','.join(relations) , values
-    # entity permissions
-    if groupmap is not None:
-        for rql, args in _erperms2rql(eschema, groupmap):
-            args['name'] = str(eschema)
-            yield rql + 'X is CWEType, X name %(name)s', args
-
-def specialize2rql(schema):
-    for eschema in schema.entities():
-        for rql, kwargs in eschemaspecialize2rql(eschema):
-            yield rql, kwargs
-
-def eschemaspecialize2rql(eschema):
-    specialized_type = eschema.specializes()
-    if specialized_type:
-        values = {'x': eschema.type, 'et': specialized_type.type}
-        yield 'SET X specializes ET WHERE X name %(x)s, ET name %(et)s', values
-
-def rschema2rql(rschema, addrdef=True, groupmap=None):
-    """return a list of rql insert statements to enter a relation schema
-    in the database as an CWRType entity
-    """
-    if rschema.type == 'has_text':
-        return
-    relations, values = rschema_relations_values(rschema)
-    yield 'INSERT CWRType X: %s' % ','.join(relations), values
-    if addrdef:
-        for rql, values in rdef2rql(rschema, groupmap=groupmap):
-            yield rql, values
-
-def rdef2rql(rschema, subjtype=None, objtype=None, props=None, groupmap=None):
-    genmap = {True: frdef2rql, False: nfrdef2rql}
-    return __rdef2rql(genmap, rschema, subjtype, objtype, props, groupmap)
-
-
-_LOCATE_RDEF_RQL0 = 'X relation_type ER,X from_entity SE,X to_entity OE'
-_LOCATE_RDEF_RQL1 = 'SE name %(se)s,ER name %(rt)s,OE name %(oe)s'
-
-def frdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(objtype, props)
-    relations.append(_LOCATE_RDEF_RQL0)
-    values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
-    for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is CWAttribute', values
-
-def nfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(objtype, props)
-    relations.append(_LOCATE_RDEF_RQL0)
-    values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
-    for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is CWRelation', values
-
-def rdefrelations2rql(rschema, subjtype, objtype, props):
-    iterators = []
-    for constraint in props.constraints:
-        iterators.append(constraint2rql(rschema, subjtype, objtype, constraint))
-    return chain(*iterators)
-
-def constraint2rql(rschema, subjtype, objtype, constraint):
-    values = {'ctname': unicode(constraint.type()),
-              'value': unicode(constraint.serialize()),
-              'rt': str(rschema), 'se': str(subjtype), 'oe': str(objtype)}
-    yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
-CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, \
-ER name %(rt)s, SE name %(se)s, OE name %(oe)s', values
+def constraints2rql(cstrtypemap, constraints, rdefeid=None):
+    for constraint in constraints:
+        values = {'ct': cstrtypemap[constraint.type()],
+                  'value': unicode(constraint.serialize()),
+                  'x': rdefeid} # when not specified, will have to be set by the caller
+        yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
+CT eid %(ct)s, EDEF eid %(x)s', values
 
 
 def _erperms2rql(erschema, groupmap):
@@ -471,7 +447,7 @@
             if isinstance(group_or_rqlexpr, basestring):
                 # group
                 try:
-                    yield ('SET X %s_permission Y WHERE Y eid %%(g)s, ' % action,
+                    yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action,
                            {'g': groupmap[group_or_rqlexpr]})
                 except KeyError:
                     continue
@@ -479,36 +455,24 @@
                 # rqlexpr
                 rqlexpr = group_or_rqlexpr
                 yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
-                       'E mainvars %%(v)s, X %s_permission E WHERE ' % action,
+                       'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
                        {'e': unicode(rqlexpr.expression),
                         'v': unicode(rqlexpr.mainvars),
                         't': unicode(rqlexpr.__class__.__name__)})
 
+# update functions
 
-def updateeschema2rql(eschema):
+def updateeschema2rql(eschema, eid):
     relations, values = eschema_relations_values(eschema)
-    values['et'] = eschema.type
-    yield 'SET %s WHERE X is CWEType, X name %%(et)s' % ','.join(relations), values
-
-def updaterschema2rql(rschema):
-    relations, values = rschema_relations_values(rschema)
-    values['rt'] = rschema.type
-    yield 'SET %s WHERE X is CWRType, X name %%(rt)s' % ','.join(relations), values
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
 
-def updaterdef2rql(rschema, subjtype=None, objtype=None, props=None):
-    genmap = {True: updatefrdef2rql, False: updatenfrdef2rql}
-    return __rdef2rql(genmap, rschema, subjtype, objtype, props)
+def updaterschema2rql(rschema, eid):
+    relations, values = rschema_relations_values(rschema)
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
 
-def updatefrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(objtype, props)
-    values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations),
-                                                     _LOCATE_RDEF_RQL0,
-                                                     _LOCATE_RDEF_RQL1), values
-
-def updatenfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(objtype, props)
-    values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations),
-                                                    _LOCATE_RDEF_RQL0,
-                                                    _LOCATE_RDEF_RQL1), values
+def updaterdef2rql(rdef, eid):
+    relations, values = _rdef_values(rdef)
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
--- a/server/server.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/server.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Pyro RQL server
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -90,6 +103,7 @@
 
     def run(self, req_timeout=5.0):
         """enter the service loop"""
+        self.repo.start_looping_tasks()
         while self.quiting is None:
             try:
                 self.daemon.handleRequests(req_timeout)
--- a/server/serverconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/serverconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """server.serverconfig definition
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -109,8 +122,8 @@
           'group': 'main', 'inputlevel': (CubicWebConfiguration.mode == 'installed') and 0 or 1,
           }),
         ('session-time',
-         {'type' : 'int',
-          'default': 30*60,
+         {'type' : 'time',
+          'default': '30min',
           'help': 'session expiration time, default to 30 minutes',
           'group': 'main', 'inputlevel': 1,
           }),
@@ -127,6 +140,28 @@
           'help': 'size of the parsed rql cache size.',
           'group': 'main', 'inputlevel': 1,
           }),
+        ('undo-support',
+         {'type' : 'string', 'default': '',
+          'help': 'string defining actions that will have undo support: \
+[C]reate [U]pdate [D]elete entities / [A]dd [R]emove relation. Leave it empty \
+for no undo support, set it to CUDAR for full undo support, or to DR for \
+support undoing of deletion only.',
+          'group': 'main', 'inputlevel': 1,
+          }),
+        ('keep-transaction-lifetime',
+         {'type' : 'int', 'default': 7,
+          'help': 'number of days during which transaction records should be \
+kept (hence undoable).',
+          'group': 'main', 'inputlevel': 1,
+          }),
+        ('multi-sources-etypes',
+         {'type' : 'csv', 'default': (),
+          'help': 'defines which entity types from this repository are used \
+by some other instances. You should set this properly so those instances to \
+detect updates / deletions.',
+          'group': 'main', 'inputlevel': 1,
+          }),
+
         ('delay-full-text-indexation',
          {'type' : 'yn', 'default': False,
           'help': 'When full text indexation of entity has a too important cost'
@@ -180,68 +215,14 @@
 
     # read the schema from the database
     read_instance_schema = True
-    bootstrap_schema = True
-
+    # set to true while creating an instance
+    creating = False
+    # set this to true to get a minimal repository, for instance to get cubes
+    # information on commands such as i18ninstance, db-restore, etc...
+    quick_start = False
     # check user's state at login time
     consider_user_state = True
 
-    # XXX hooks control stuff should probably be on the session, not on the config
-
-    # hooks activation configuration
-    # all hooks should be activated during normal execution
-    disabled_hooks_categories = set()
-    enabled_hooks_categories = set()
-    ALLOW_ALL = object()
-    DENY_ALL = object()
-    hooks_mode = ALLOW_ALL
-
-    @classmethod
-    def set_hooks_mode(cls, mode):
-        assert mode is cls.ALLOW_ALL or mode is cls.DENY_ALL
-        oldmode = cls.hooks_mode
-        cls.hooks_mode = mode
-        return oldmode
-
-    @classmethod
-    def disable_hook_category(cls, *categories):
-        changes = set()
-        if cls.hooks_mode is cls.DENY_ALL:
-            for category in categories:
-                if category in cls.enabled_hooks_categories:
-                    cls.enabled_hooks_categories.remove(category)
-                    changes.add(category)
-        else:
-            for category in categories:
-                if category not in cls.disabled_hooks_categories:
-                    cls.disabled_hooks_categories.add(category)
-                    changes.add(category)
-        return changes
-
-    @classmethod
-    def enable_hook_category(cls, *categories):
-        changes = set()
-        if cls.hooks_mode is cls.DENY_ALL:
-            for category in categories:
-                if category not in cls.enabled_hooks_categories:
-                    cls.enabled_hooks_categories.add(category)
-                    changes.add(category)
-        else:
-            for category in categories:
-                if category in cls.disabled_hooks_categories:
-                    cls.disabled_hooks_categories.remove(category)
-                    changes.add(category)
-        return changes
-
-    @classmethod
-    def is_hook_activated(cls, hook):
-        return cls.is_hook_category_activated(hook.category)
-
-    @classmethod
-    def is_hook_category_activated(cls, category):
-        if cls.hooks_mode is cls.DENY_ALL:
-            return category in cls.enabled_hooks_categories
-        return category not in cls.disabled_hooks_categories
-
     # should some hooks be deactivated during [pre|post]create script execution
     free_wheel = False
 
--- a/server/serverctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/serverctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-ctl commands and command handlers specific to the server.serverconfig
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = 'restructuredtext en'
 
@@ -66,14 +79,13 @@
     cnx = get_connection(driver, dbhost, dbname, user, password=password,
                          port=source.get('db-port'),
                          **extra)
-    if not hasattr(cnx, 'logged_user'): # XXX logilab.db compat
-        try:
-            cnx.logged_user = user
-        except AttributeError:
-            # C object, __slots__
-            from logilab.database import _SimpleConnectionWrapper
-            cnx = _SimpleConnectionWrapper(cnx)
-            cnx.logged_user = user
+    try:
+        cnx.logged_user = user
+    except AttributeError:
+        # C object, __slots__
+        from logilab.database import _SimpleConnectionWrapper
+        cnx = _SimpleConnectionWrapper(cnx)
+        cnx.logged_user = user
     return cnx
 
 def system_source_cnx(source, dbms_system_base=False,
@@ -84,8 +96,8 @@
     create/drop the instance database)
     """
     if dbms_system_base:
-        from logilab.common.adbh import get_adv_func_helper
-        system_db = get_adv_func_helper(source['db-driver']).system_database()
+        from logilab.database import get_db_helper
+        system_db = get_db_helper(source['db-driver']).system_database()
         return source_cnx(source, system_db, special_privs=special_privs, verbose=verbose)
     return source_cnx(source, special_privs=special_privs, verbose=verbose)
 
@@ -94,11 +106,11 @@
     or a database
     """
     import logilab.common as lgp
-    from logilab.common.adbh import get_adv_func_helper
+    from logilab.database import get_db_helper
     lgp.USE_MX_DATETIME = False
     special_privs = ''
     driver = source['db-driver']
-    helper = get_adv_func_helper(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:
@@ -176,11 +188,11 @@
                     break
                 print '-> unknown source type, use one of the available types.'
             while True:
-                sourceuri = raw_input('source uri: ').strip()
+                sourceuri = raw_input('source identifier (a unique name used to tell sources apart): ').strip()
                 if sourceuri != 'admin' and sourceuri not in sourcescfg:
                     break
                 print '-> uri already used, choose another one.'
-            sourcescfg[sourceuri] = ask_source_config(sourcetype)
+            sourcescfg[sourceuri] = ask_source_config(sourcetype, inputlevel)
             sourcemodule = SOURCE_TYPES[sourcetype].module
             if not sourcemodule.startswith('cubicweb.'):
                 # module names look like cubes.mycube.themodule
@@ -211,10 +223,10 @@
 
     def cleanup(self):
         """remove instance's configuration and database"""
-        from logilab.common.adbh import get_adv_func_helper
+        from logilab.database import get_db_helper
         source = self.config.sources()['system']
         dbname = source['db-name']
-        helper = get_adv_func_helper(source['db-driver'])
+        helper = get_db_helper(source['db-driver'])
         if ASK.confirm('Delete database %s ?' % dbname):
             user = source['db-user'] or None
             cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
@@ -294,8 +306,7 @@
         )
     def run(self, args):
         """run the command with its specific arguments"""
-        from logilab.common.adbh import get_adv_func_helper
-        from indexer import get_indexer
+        from logilab.database import get_db_helper
         verbose = self.get('verbose')
         automatic = self.get('automatic')
         appid = pop_arg(args, msg='No instance specified !')
@@ -304,7 +315,7 @@
         dbname = source['db-name']
         driver = source['db-driver']
         create_db = self.config.create_db
-        helper = get_adv_func_helper(driver)
+        helper = get_db_helper(driver)
         if driver == 'sqlite':
             if os.path.exists(dbname) and automatic or \
                    ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
@@ -330,13 +341,8 @@
                     helper.create_database(cursor, dbname, source['db-user'],
                                            source['db-encoding'])
                 else:
-                    try:
-                        helper.create_database(cursor, dbname,
-                                               encoding=source['db-encoding'])
-                    except TypeError:
-                        # logilab.database
-                        helper.create_database(cursor, dbname,
-                                               dbencoding=source['db-encoding'])
+                    helper.create_database(cursor, dbname,
+                                           dbencoding=source['db-encoding'])
                 dbcnx.commit()
                 print '-> database %s created.' % dbname
             except:
@@ -344,8 +350,7 @@
                 raise
         cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=verbose)
         cursor = cnx.cursor()
-        indexer = get_indexer(driver)
-        indexer.init_extensions(cursor)
+        helper.init_fti_extensions(cursor)
         # postgres specific stuff
         if driver == 'postgres':
             # install plpythonu/plpgsql language if not installed by the cube
@@ -397,7 +402,7 @@
             get_connection(
                 system['db-driver'], database=system['db-name'],
                 host=system.get('db-host'), port=system.get('db-port'),
-                user=system.get('db-user'), password=system.get('db-password'), 
+                user=system.get('db-user'), password=system.get('db-password'),
                 **extra)
         except Exception, ex:
             raise ConfigurationError(
@@ -580,17 +585,16 @@
 
 def _local_dump(appid, output):
     config = ServerConfiguration.config_for(appid)
-    # schema=1 to avoid unnecessary schema loading
-    mih = config.migration_handler(connect=False, schema=1, verbosity=1)
+    config.quick_start = True
+    mih = config.migration_handler(connect=False, verbosity=1)
     mih.backup_database(output, askconfirm=False)
     mih.shutdown()
 
 def _local_restore(appid, backupfile, drop, systemonly=True):
     config = ServerConfiguration.config_for(appid)
     config.verbosity = 1 # else we won't be asked for confirmation on problems
-    config.repairing = 1 # don't check versions
-    # schema=1 to avoid unnecessary schema loading
-    mih = config.migration_handler(connect=False, schema=1, verbosity=1)
+    config.quick_start = True
+    mih = config.migration_handler(connect=False, verbosity=1)
     mih.restore_database(backupfile, drop, systemonly, askconfirm=False)
     repo = mih.repo_connect()
     # version of the database
--- a/server/session.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/session.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,28 +1,53 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Repository users' and internal' sessions.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 import sys
 import threading
 from time import time
+from uuid import uuid4
 
 from logilab.common.deprecation import deprecated
 from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
 from yams import BASE_TYPES
 
-from cubicweb import Binary, UnknownEid
+from cubicweb import Binary, UnknownEid, schema
 from cubicweb.req import RequestSessionBase
 from cubicweb.dbapi import ConnectionProperties
-from cubicweb.utils import make_uid
+from cubicweb.utils import make_uid, RepeatList
 from cubicweb.rqlrewrite import RQLRewriter
 
 ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
 
+NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
+NO_UNDO_TYPES.add('CWCache')
+# is / is_instance_of are usually added by sql hooks except when using
+# dataimport.NoHookRQLObjectStore, and we don't want to record them
+# anyway in the later case
+NO_UNDO_TYPES.add('is')
+NO_UNDO_TYPES.add('is_instance_of')
+# XXX rememberme,forgotpwd,apycot,vcsfile
+
 def is_final(rqlst, variable, args):
     # try to find if this is a final var or not
     for select in rqlst.children:
@@ -42,10 +67,73 @@
     return description
 
 
+class hooks_control(object):
+    """context manager to control activated hooks categories.
+
+    If mode is session.`HOOKS_DENY_ALL`, given hooks categories will
+    be enabled.
+
+    If mode is session.`HOOKS_ALLOW_ALL`, given hooks categories will
+    be disabled.
+    """
+    def __init__(self, session, mode, *categories):
+        self.session = session
+        self.mode = mode
+        self.categories = categories
+
+    def __enter__(self):
+        self.oldmode = self.session.set_hooks_mode(self.mode)
+        if self.mode is self.session.HOOKS_DENY_ALL:
+            self.changes = self.session.enable_hook_categories(*self.categories)
+        else:
+            self.changes = self.session.disable_hook_categories(*self.categories)
+
+    def __exit__(self, exctype, exc, traceback):
+        if self.changes:
+            if self.mode is self.session.HOOKS_DENY_ALL:
+                self.session.disable_hook_categories(*self.changes)
+            else:
+                self.session.enable_hook_categories(*self.changes)
+        self.session.set_hooks_mode(self.oldmode)
+
+INDENT = ''
+class security_enabled(object):
+    """context manager to control security w/ session.execute, since by
+    default security is disabled on queries executed on the repository
+    side.
+    """
+    def __init__(self, session, read=None, write=None):
+        self.session = session
+        self.read = read
+        self.write = write
+
+    def __enter__(self):
+#        global INDENT
+        if self.read is not None:
+            self.oldread = self.session.set_read_security(self.read)
+#            print INDENT + 'read', self.read, self.oldread
+        if self.write is not None:
+            self.oldwrite = self.session.set_write_security(self.write)
+#            print INDENT + 'write', self.write, self.oldwrite
+#        INDENT += '  '
+
+    def __exit__(self, exctype, exc, traceback):
+#        global INDENT
+#        INDENT = INDENT[:-2]
+        if self.read is not None:
+            self.session.set_read_security(self.oldread)
+#            print INDENT + 'reset read to', self.oldread
+        if self.write is not None:
+            self.session.set_write_security(self.oldwrite)
+#            print INDENT + 'reset write to', self.oldwrite
+
+
+
 class Session(RequestSessionBase):
     """tie session id, user, connections pool and other session data all
     together
     """
+    is_internal_session = False
 
     def __init__(self, user, repo, cnxprops=None, _id=None):
         super(Session, self).__init__(repo.vreg)
@@ -56,9 +144,14 @@
         self.cnxtype = cnxprops.cnxtype
         self.creation = time()
         self.timestamp = self.creation
-        self.is_internal_session = False
-        self.is_super_session = False
         self.default_mode = 'read'
+        # support undo for Create Update Delete entity / Add Remove relation
+        if repo.config.creating or repo.config.repairing or self.is_internal_session:
+            self.undo_actions = ()
+        else:
+            self.undo_actions = set(repo.config['undo-support'].upper())
+            if self.undo_actions - set('CUDAR'):
+                raise Exception('bad undo-support string in configuration')
         # short cut to querier .execute method
         self._execute = repo.querier.execute
         # shared data, used to communicate extra information between the client
@@ -78,19 +171,17 @@
     def hijack_user(self, user):
         """return a fake request/session using specified user"""
         session = Session(user, self.repo)
-        session._threaddata = self.actual_session()._threaddata
+        threaddata = session._threaddata
+        threaddata.pool = self.pool
+        # share pending_operations, else operation added in the hi-jacked
+        # session such as SendMailOp won't ever be processed
+        threaddata.pending_operations = self.pending_operations
+        # everything in transaction_data should be copied back but the entity
+        # type cache we don't want to avoid security pb
+        threaddata.transaction_data = self.transaction_data.copy()
+        threaddata.transaction_data.pop('ecache', None)
         return session
 
-    def _super_call(self, __cb, *args, **kwargs):
-        if self.is_super_session:
-            __cb(self, *args, **kwargs)
-            return
-        self.is_super_session = True
-        try:
-            __cb(self, *args, **kwargs)
-        finally:
-            self.is_super_session = False
-
     def add_relation(self, fromeid, rtype, toeid):
         """provide direct access to the repository method to add a relation.
 
@@ -102,14 +193,13 @@
         You may use this in hooks when you know both eids of the relation you
         want to add.
         """
-        if self.vreg.schema[rtype].inlined:
-            entity = self.entity_from_eid(fromeid)
-            entity[rtype] = toeid
-            self._super_call(self.repo.glob_update_entity,
-                             entity, set((rtype,)))
-        else:
-            self._super_call(self.repo.glob_add_relation,
-                             fromeid, rtype, toeid)
+        with security_enabled(self, False, False):
+            if self.vreg.schema[rtype].inlined:
+                entity = self.entity_from_eid(fromeid)
+                entity[rtype] = toeid
+                self.repo.glob_update_entity(self, entity, set((rtype,)))
+            else:
+                self.repo.glob_add_relation(self, fromeid, rtype, toeid)
 
     def delete_relation(self, fromeid, rtype, toeid):
         """provide direct access to the repository method to delete a relation.
@@ -122,14 +212,13 @@
         You may use this in hooks when you know both eids of the relation you
         want to delete.
         """
-        if self.vreg.schema[rtype].inlined:
-            entity = self.entity_from_eid(fromeid)
-            entity[rtype] = None
-            self._super_call(self.repo.glob_update_entity,
-                             entity, set((rtype,)))
-        else:
-            self._super_call(self.repo.glob_delete_relation,
-                             fromeid, rtype, toeid)
+        with security_enabled(self, False, False):
+            if self.vreg.schema[rtype].inlined:
+                entity = self.entity_from_eid(fromeid)
+                entity[rtype] = None
+                self.repo.glob_update_entity(self, entity, set((rtype,)))
+            else:
+                self.repo.glob_delete_relation(self, fromeid, rtype, toeid)
 
     # relations cache handling #################################################
 
@@ -198,16 +287,17 @@
 
     # resource accessors ######################################################
 
-    def actual_session(self):
-        """return the original parent session if any, else self"""
-        return self
-
     def system_sql(self, sql, args=None, rollback_on_failure=True):
         """return a sql cursor on the system database"""
-        if not sql.split(None, 1)[0].upper() == 'SELECT':
+        if sql.split(None, 1)[0].upper() != 'SELECT':
             self.mode = 'write'
-        return self.pool.source('system').doexec(self, sql, args,
-                                                 rollback=rollback_on_failure)
+        source = self.pool.source('system')
+        try:
+            return source.doexec(self, sql, args, rollback=rollback_on_failure)
+        except (source.OperationalError, source.InterfaceError):
+            source.warning("trying to reconnect")
+            self.pool.reconnect(self)
+            return source.doexec(self, sql, args, rollback=rollback_on_failure)
 
     def set_language(self, language):
         """i18n configuration for translation"""
@@ -251,6 +341,165 @@
         rdef = rschema.rdef(subjtype, objtype)
         return rdef.get(rprop)
 
+    # security control #########################################################
+
+    DEFAULT_SECURITY = object() # evaluated to true by design
+
+    @property
+    def read_security(self):
+        """return a boolean telling if read security is activated or not"""
+        try:
+            return self._threaddata.read_security
+        except AttributeError:
+            self._threaddata.read_security = self.DEFAULT_SECURITY
+            return self._threaddata.read_security
+
+    def set_read_security(self, activated):
+        """[de]activate read security, returning the previous value set for
+        later restoration.
+
+        you should usually use the `security_enabled` context manager instead
+        of this to change security settings.
+        """
+        oldmode = self.read_security
+        self._threaddata.read_security = activated
+        # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not
+        # issued on the session). This is tricky since we the execution model of
+        # a (write) user query is:
+        #
+        # repository.execute (security enabled)
+        #  \-> querier.execute
+        #       \-> repo.glob_xxx (add/update/delete entity/relation)
+        #            \-> deactivate security before calling hooks
+        #                 \-> WE WANT TO CHECK QUERY NATURE HERE
+        #                      \-> potentially, other calls to querier.execute
+        #
+        # so we can't rely on simply checking session.read_security, but
+        # recalling the first transition from DEFAULT_SECURITY to something
+        # else (False actually) is not perfect but should be enough
+        #
+        # also reset dbapi_query to true when we go back to DEFAULT_SECURITY
+        self._threaddata.dbapi_query = (oldmode is self.DEFAULT_SECURITY
+                                        or activated is self.DEFAULT_SECURITY)
+        return oldmode
+
+    @property
+    def write_security(self):
+        """return a boolean telling if write security is activated or not"""
+        try:
+            return self._threaddata.write_security
+        except:
+            self._threaddata.write_security = self.DEFAULT_SECURITY
+            return self._threaddata.write_security
+
+    def set_write_security(self, activated):
+        """[de]activate write security, returning the previous value set for
+        later restoration.
+
+        you should usually use the `security_enabled` context manager instead
+        of this to change security settings.
+        """
+        oldmode = self.write_security
+        self._threaddata.write_security = activated
+        return oldmode
+
+    @property
+    def running_dbapi_query(self):
+        """return a boolean telling if it's triggered by a db-api query or by
+        a session query.
+
+        To be used in hooks, else may have a wrong value.
+        """
+        return getattr(self._threaddata, 'dbapi_query', True)
+
+    # hooks activation control #################################################
+    # all hooks should be activated during normal execution
+
+    HOOKS_ALLOW_ALL = object()
+    HOOKS_DENY_ALL = object()
+
+    @property
+    def hooks_mode(self):
+        return getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL)
+
+    def set_hooks_mode(self, mode):
+        assert mode is self.HOOKS_ALLOW_ALL or mode is self.HOOKS_DENY_ALL
+        oldmode = getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL)
+        self._threaddata.hooks_mode = mode
+        return oldmode
+
+    @property
+    def disabled_hook_categories(self):
+        try:
+            return getattr(self._threaddata, 'disabled_hook_cats')
+        except AttributeError:
+            cats = self._threaddata.disabled_hook_cats = set()
+            return cats
+
+    @property
+    def enabled_hook_categories(self):
+        try:
+            return getattr(self._threaddata, 'enabled_hook_cats')
+        except AttributeError:
+            cats = self._threaddata.enabled_hook_cats = set()
+            return cats
+
+    def disable_hook_categories(self, *categories):
+        """disable the given hook categories:
+
+        - on HOOKS_DENY_ALL mode, ensure those categories are not enabled
+        - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled
+        """
+        changes = set()
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            enablecats = self.enabled_hook_categories
+            for category in categories:
+                if category in enablecats:
+                    enablecats.remove(category)
+                    changes.add(category)
+        else:
+            disablecats = self.disabled_hook_categories
+            for category in categories:
+                if category not in disablecats:
+                    disablecats.add(category)
+                    changes.add(category)
+        return tuple(changes)
+
+    def enable_hook_categories(self, *categories):
+        """enable the given hook categories:
+
+        - on HOOKS_DENY_ALL mode, ensure those categories are enabled
+        - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled
+        """
+        changes = set()
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            enablecats = self.enabled_hook_categories
+            for category in categories:
+                if category not in enablecats:
+                    enablecats.add(category)
+                    changes.add(category)
+        else:
+            disablecats = self.disabled_hook_categories
+            for category in categories:
+                if category in self.disabled_hook_categories:
+                    disablecats.remove(category)
+                    changes.add(category)
+        return tuple(changes)
+
+    def is_hook_category_activated(self, category):
+        """return a boolean telling if the given category is currently activated
+        or not
+        """
+        if self.hooks_mode is self.HOOKS_DENY_ALL:
+            return category in self.enabled_hook_categories
+        return category not in self.disabled_hook_categories
+
+    def is_hook_activated(self, hook):
+        """return a boolean telling if the given hook class is currently
+        activated or not
+        """
+        return self.is_hook_category_activated(hook.category)
+
     # connection management ###################################################
 
     def keep_pool_mode(self, mode):
@@ -327,7 +576,7 @@
     def _touch(self):
         """update latest session usage timestamp and reset mode to read"""
         self.timestamp = time()
-        self.local_perm_cache.clear()
+        self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
         self._threaddata.mode = self.default_mode
 
     # shared data handling ###################################################
@@ -365,10 +614,7 @@
             ecache[entity.eid] = entity
 
     def entity_cache(self, eid):
-        try:
-            return self.transaction_data['ecache'][eid]
-        except:
-            raise
+        return self.transaction_data['ecache'][eid]
 
     def cached_entities(self):
         return self.transaction_data.get('ecache', {}).values()
@@ -408,47 +654,12 @@
         """return the source where the entity with id <eid> is located"""
         return self.repo.source_from_eid(eid, self)
 
-    def decorate_rset(self, rset, propagate=False):
-        rset.vreg = self.vreg
-        rset.req = propagate and self or self.actual_session()
+    def execute(self, rql, kwargs=None, eid_key=None, build_descr=True):
+        """db-api like method directly linked to the querier execute method"""
+        rset = self._execute(self, rql, kwargs, eid_key, build_descr)
+        rset.req = self
         return rset
 
-    @property
-    def super_session(self):
-        try:
-            csession = self.childsession
-        except AttributeError:
-            if isinstance(self, (ChildSession, InternalSession)):
-                csession = self
-            else:
-                csession = ChildSession(self)
-            self.childsession = csession
-        # need shared pool set
-        self.set_pool(checkclosed=False)
-        return csession
-
-    def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
-                       propagate=False):
-        """like .execute but with security checking disabled (this method is
-        internal to the server, it's not part of the db-api)
-
-        if `propagate` is true, the super_session will be attached to the result
-        set instead of the parent session, hence further query done through
-        entities fetched from this result set will bypass security as well
-        """
-        return self.super_session.execute(rql, kwargs, eid_key, build_descr,
-                                          propagate)
-
-    def execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
-                propagate=False):
-        """db-api like method directly linked to the querier execute method
-
-        Becare that unlike actual cursor.execute, `build_descr` default to
-        false
-        """
-        rset = self._execute(self, rql, kwargs, eid_key, build_descr)
-        return self.decorate_rset(rset, propagate)
-
     def _clear_thread_data(self):
         """remove everything from the thread local storage, except pool
         which is explicitly removed by reset_pool, and mode which is set anyway
@@ -472,58 +683,61 @@
             return
         if self.commit_state:
             return
-        # on rollback, an operation should have the following state
-        # information:
-        # - processed by the precommit/commit event or not
-        # - if processed, is it the failed operation
-        try:
-            for trstate in ('precommit', 'commit'):
-                processed = []
-                self.commit_state = trstate
-                try:
-                    while self.pending_operations:
-                        operation = self.pending_operations.pop(0)
-                        operation.processed = trstate
-                        processed.append(operation)
+        # by default, operations are executed with security turned off
+        with security_enabled(self, False, False):
+            # on rollback, an operation should have the following state
+            # information:
+            # - processed by the precommit/commit event or not
+            # - if processed, is it the failed operation
+            try:
+                for trstate in ('precommit', 'commit'):
+                    processed = []
+                    self.commit_state = trstate
+                    try:
+                        while self.pending_operations:
+                            operation = self.pending_operations.pop(0)
+                            operation.processed = trstate
+                            processed.append(operation)
+                            operation.handle_event('%s_event' % trstate)
+                        self.pending_operations[:] = processed
+                        self.debug('%s session %s done', trstate, self.id)
+                    except:
+                        self.exception('error while %sing', trstate)
+                        # if error on [pre]commit:
+                        #
+                        # * set .failed = True on the operation causing the failure
+                        # * call revert<event>_event on processed operations
+                        # * call rollback_event on *all* operations
+                        #
+                        # that seems more natural than not calling rollback_event
+                        # for processed operations, and allow generic rollback
+                        # instead of having to implements rollback, revertprecommit
+                        # and revertcommit, that will be enough in mont case.
+                        operation.failed = True
+                        for operation in processed:
+                            operation.handle_event('revert%s_event' % trstate)
+                        # XXX use slice notation since self.pending_operations is a
+                        # read-only property.
+                        self.pending_operations[:] = processed + self.pending_operations
+                        self.rollback(reset_pool)
+                        raise
+                self.pool.commit()
+                self.commit_state = trstate = 'postcommit'
+                while self.pending_operations:
+                    operation = self.pending_operations.pop(0)
+                    operation.processed = trstate
+                    try:
                         operation.handle_event('%s_event' % trstate)
-                    self.pending_operations[:] = processed
-                    self.debug('%s session %s done', trstate, self.id)
-                except:
-                    self.exception('error while %sing', trstate)
-                    # if error on [pre]commit:
-                    #
-                    # * set .failed = True on the operation causing the failure
-                    # * call revert<event>_event on processed operations
-                    # * call rollback_event on *all* operations
-                    #
-                    # that seems more natural than not calling rollback_event
-                    # for processed operations, and allow generic rollback
-                    # instead of having to implements rollback, revertprecommit
-                    # and revertcommit, that will be enough in mont case.
-                    operation.failed = True
-                    for operation in processed:
-                        operation.handle_event('revert%s_event' % trstate)
-                    # XXX use slice notation since self.pending_operations is a
-                    # read-only property.
-                    self.pending_operations[:] = processed + self.pending_operations
-                    self.rollback(reset_pool)
-                    raise
-            self.pool.commit()
-            self.commit_state = trstate = 'postcommit'
-            while self.pending_operations:
-                operation = self.pending_operations.pop(0)
-                operation.processed = trstate
-                try:
-                    operation.handle_event('%s_event' % trstate)
-                except:
-                    self.critical('error while %sing', trstate,
-                                  exc_info=sys.exc_info())
-            self.info('%s session %s done', trstate, self.id)
-        finally:
-            self._clear_thread_data()
-            self._touch()
-            if reset_pool:
-                self.reset_pool(ignoremode=True)
+                    except:
+                        self.critical('error while %sing', trstate,
+                                      exc_info=sys.exc_info())
+                self.info('%s session %s done', trstate, self.id)
+                return self.transaction_uuid(set=False)
+            finally:
+                self._clear_thread_data()
+                self._touch()
+                if reset_pool:
+                    self.reset_pool(ignoremode=True)
 
     def rollback(self, reset_pool=True):
         """rollback the current session's transaction"""
@@ -533,21 +747,23 @@
             self._touch()
             self.debug('rollback session %s done (no db activity)', self.id)
             return
-        try:
-            while self.pending_operations:
-                try:
-                    operation = self.pending_operations.pop(0)
-                    operation.handle_event('rollback_event')
-                except:
-                    self.critical('rollback error', exc_info=sys.exc_info())
-                    continue
-            self.pool.rollback()
-            self.debug('rollback for session %s done', self.id)
-        finally:
-            self._clear_thread_data()
-            self._touch()
-            if reset_pool:
-                self.reset_pool(ignoremode=True)
+        # by default, operations are executed with security turned off
+        with security_enabled(self, False, False):
+            try:
+                while self.pending_operations:
+                    try:
+                        operation = self.pending_operations.pop(0)
+                        operation.handle_event('rollback_event')
+                    except:
+                        self.critical('rollback error', exc_info=sys.exc_info())
+                        continue
+                self.pool.rollback()
+                self.debug('rollback for session %s done', self.id)
+            finally:
+                self._clear_thread_data()
+                self._touch()
+                if reset_pool:
+                    self.reset_pool(ignoremode=True)
 
     def close(self):
         """do not close pool on session close, since they are shared now"""
@@ -592,10 +808,31 @@
     def add_operation(self, operation, index=None):
         """add an observer"""
         assert self.commit_state != 'commit'
-        if index is not None:
+        if index is None:
+            self.pending_operations.append(operation)
+        else:
             self.pending_operations.insert(index, operation)
-        else:
-            self.pending_operations.append(operation)
+
+    # undo support ############################################################
+
+    def undoable_action(self, action, ertype):
+        return action in self.undo_actions and not ertype in NO_UNDO_TYPES
+        # XXX elif transaction on mark it partial
+
+    def transaction_uuid(self, set=True):
+        try:
+            return self.transaction_data['tx_uuid']
+        except KeyError:
+            if not set:
+                return
+            self.transaction_data['tx_uuid'] = uuid = uuid4().hex
+            self.repo.system_source.start_undoable_transaction(self, uuid)
+            return uuid
+
+    def transaction_inc_action_counter(self):
+        num = self.transaction_data.setdefault('tx_action_count', 0) + 1
+        self.transaction_data['tx_action_count'] = num
+        return num
 
     # querier helpers #########################################################
 
@@ -615,7 +852,7 @@
             selected = rqlst.children[0].selection
             solution = rqlst.children[0].solutions[0]
             description = _make_description(selected, args, solution)
-            return [tuple(description)] * len(result)
+            return RepeatList(len(result), tuple(description))
         # hard, delegate the work :o)
         return self.manual_build_descr(rqlst, args, result)
 
@@ -644,7 +881,7 @@
                 etype = rqlst.children[0].solutions[0]
                 basedescription.append(term.get_type(etype, args))
         if not todetermine:
-            return [tuple(basedescription)] * len(result)
+            return RepeatList(len(result), tuple(basedescription))
         return self._build_descr(result, basedescription, todetermine)
 
     def _build_descr(self, result, basedescription, todetermine):
@@ -671,6 +908,28 @@
 
     # deprecated ###############################################################
 
+    @deprecated("[3.7] execute is now unsafe by default in hooks/operation. You"
+                " can also control security with the security_enabled context "
+                "manager")
+    def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
+                       propagate=False):
+        """like .execute but with security checking disabled (this method is
+        internal to the server, it's not part of the db-api)
+        """
+        with security_enabled(self, read=False, write=False):
+            return self.execute(rql, kwargs, eid_key, build_descr)
+
+    @property
+    @deprecated("[3.7] is_super_session is deprecated, test "
+                "session.read_security and or session.write_security")
+    def is_super_session(self):
+        return not self.read_security or not self.write_security
+
+    @deprecated("[3.7] session is actual session")
+    def actual_session(self):
+        """return the original parent session if any, else self"""
+        return self
+
     @property
     @deprecated("[3.6] use session.vreg.schema")
     def schema(self):
@@ -697,98 +956,17 @@
         return self.entity_from_eid(eid)
 
 
-class ChildSession(Session):
-    """child (or internal) session are used to hijack the security system
-    """
-    cnxtype = 'inmemory'
-
-    def __init__(self, parent_session):
-        self.id = None
-        self.is_internal_session = False
-        self.is_super_session = True
-        # session which has created this one
-        self.parent_session = parent_session
-        self.user = InternalManager()
-        self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
-        self.repo = parent_session.repo
-        self.vreg = parent_session.vreg
-        self.data = parent_session.data
-        self.encoding = parent_session.encoding
-        self.lang = parent_session.lang
-        self._ = self.__ = parent_session._
-        # short cut to querier .execute method
-        self._execute = self.repo.querier.execute
-
-    @property
-    def super_session(self):
-        return self
-
-    def get_mode(self):
-        return self.parent_session.mode
-    def set_mode(self, value):
-        self.parent_session.set_mode(value)
-    mode = property(get_mode, set_mode)
-
-    def get_commit_state(self):
-        return self.parent_session.commit_state
-    def set_commit_state(self, value):
-        self.parent_session.set_commit_state(value)
-    commit_state = property(get_commit_state, set_commit_state)
-
-    @property
-    def pool(self):
-        return self.parent_session.pool
-    @property
-    def pending_operations(self):
-        return self.parent_session.pending_operations
-    @property
-    def transaction_data(self):
-        return self.parent_session.transaction_data
-
-    def set_pool(self):
-        """the session need a pool to execute some queries"""
-        self.parent_session.set_pool()
-
-    def reset_pool(self):
-        """the session has no longer using its pool, at least for some time
-        """
-        self.parent_session.reset_pool()
-
-    def actual_session(self):
-        """return the original parent session if any, else self"""
-        return self.parent_session
-
-    def commit(self, reset_pool=True):
-        """commit the current session's transaction"""
-        self.parent_session.commit(reset_pool)
-
-    def rollback(self, reset_pool=True):
-        """rollback the current session's transaction"""
-        self.parent_session.rollback(reset_pool)
-
-    def close(self):
-        """do not close pool on session close, since they are shared now"""
-        self.parent_session.close()
-
-    def user_data(self):
-        """returns a dictionnary with this user's information"""
-        return self.parent_session.user_data()
-
-
 class InternalSession(Session):
     """special session created internaly by the repository"""
+    is_internal_session = True
+    running_dbapi_query = False
 
     def __init__(self, repo, cnxprops=None):
         super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
                                               _id='internal')
         self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
         self.cnxtype = 'inmemory'
-        self.is_internal_session = True
-        self.is_super_session = True
-
-    @property
-    def super_session(self):
-        return self
+        self.disable_hook_categories('integrity')
 
 
 class InternalManager(object):
--- a/server/sources/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb server sources support
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -100,7 +113,7 @@
         """method called by the repository once ready to handle request"""
         pass
 
-    def backup(self, backupfile):
+    def backup(self, backupfile, confirm):
         """method called to create a backup of source's data"""
         pass
 
@@ -351,7 +364,7 @@
         """update an entity in the source"""
         raise NotImplementedError()
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source"""
         raise NotImplementedError()
 
@@ -372,11 +385,15 @@
     def create_eid(self, session):
         raise NotImplementedError()
 
-    def add_info(self, session, entity, source, extid=None):
+    def add_info(self, session, entity, source, extid):
         """add type and source info for an eid into the system table"""
         raise NotImplementedError()
 
-    def delete_info(self, session, eid, etype, uri, extid):
+    def update_info(self, session, entity, need_fti_update):
+        """mark entity as being modified, fulltext reindex if needed"""
+        raise NotImplementedError()
+
+    def delete_info(self, session, entity, uri, extid, attributes, relations):
         """delete system information on deletion of an entity by transfering
         record from the entities table to the deleted_entities table
         """
--- a/server/sources/extlite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/extlite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provide an abstract class for external sources using a sqlite database helper
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -20,12 +33,6 @@
         self.source = source
         self._cnx = None
 
-    @property
-    def logged_user(self):
-        if self._cnx is None:
-            self._cnx = self.source._sqlcnx
-        return self._cnx.logged_user
-
     def cursor(self):
         if self._cnx is None:
             self._cnx = self.source._sqlcnx
@@ -193,9 +200,10 @@
         if self._need_sql_create:
             return []
         assert dbg_st_search(self.uri, union, varmap, args, cachekey)
-        sql, query_args = self.rqlsqlgen.generate(union, args)
-        args = self.sqladapter.merge_args(args, query_args)
-        results = self.sqladapter.process_result(self.doexec(session, sql, args))
+        sql, qargs, cbs = self.rqlsqlgen.generate(union, args)
+        args = self.sqladapter.merge_args(args, qargs)
+        cursor = self.doexec(session, sql, args)
+        results = self.sqladapter.process_result(cursor, cbs)
         assert dbg_results(results)
         return results
 
@@ -231,15 +239,15 @@
         """update an entity in the source"""
         raise NotImplementedError()
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source
 
         this is not deleting a file in the svn but deleting entities from the
         source. Main usage is to delete repository content when a Repository
         entity is deleted.
         """
-        attrs = {SQL_PREFIX + 'eid': eid}
-        sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + etype, attrs)
+        attrs = {'cw_eid': entity.eid}
+        sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
         self.doexec(session, sql, attrs)
 
     def local_add_relation(self, session, subject, rtype, object):
--- a/server/sources/ldapuser.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/ldapuser.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,24 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb ldap user source
 
 this source is for now limited to a read-only CWUser source
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
 
 Part of the code is coming form Zope's LDAPUserFolder
@@ -18,7 +32,6 @@
 WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 FOR A PARTICULAR PURPOSE.
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from base64 import b64decode
@@ -64,10 +77,9 @@
          {'type' : 'choice',
           'default': 'ldap',
           'choices': ('ldap', 'ldaps', 'ldapi'),
-          'help': 'ldap protocol',
+          'help': 'ldap protocol (allowed values: ldap, ldaps, ldapi)',
           'group': 'ldap-source', 'inputlevel': 1,
           }),
-
         ('auth-mode',
          {'type' : 'choice',
           'default': 'simple',
@@ -136,15 +148,15 @@
           }),
 
         ('synchronization-interval',
-         {'type' : 'int',
-          'default': 24*60*60,
+         {'type' : 'time',
+          'default': '1d',
           'help': 'interval between synchronization with the ldap \
 directory (default to once a day).',
           'group': 'ldap-source', 'inputlevel': 2,
           }),
         ('cache-life-time',
-         {'type' : 'int',
-          'default': 2*60,
+         {'type' : 'time',
+          'default': '2h',
           'help': 'life time of query cache in minutes (default to two hours).',
           'group': 'ldap-source', 'inputlevel': 2,
           }),
@@ -200,6 +212,7 @@
         except KeyError:
             return # no email in ldap, we're done
         session = self.repo.internal_session()
+        execute = session.execute
         try:
             cursor = session.system_sql("SELECT eid, extid FROM entities WHERE "
                                         "source='%s'" % self.uri)
@@ -210,20 +223,29 @@
                 if res:
                     ldapemailaddr = res[0].get(ldap_emailattr)
                     if ldapemailaddr:
-                        rset = session.execute('EmailAddress A WHERE '
-                                               'U use_email X, U eid %(u)s',
-                                               {'u': eid})
+                        rset = execute('Any X,A WHERE '
+                                       'X address A, U use_email X, U eid %(u)s',
+                                       {'u': eid})
                         ldapemailaddr = unicode(ldapemailaddr)
-                        for emailaddr, in rset:
+                        for emaileid, emailaddr, in rset:
                             if emailaddr == ldapemailaddr:
                                 break
                         else:
                             self.info('updating email address of user %s to %s',
                                       extid, ldapemailaddr)
-                            if rset:
-                                session.execute('SET X address %(addr)s WHERE '
-                                                'U primary_email X, U eid %(u)s',
-                                                {'addr': ldapemailaddr, 'u': eid})
+                            emailrset = execute('EmailAddress A WHERE A address %(addr)s',
+                                                {'addr': ldapemailaddr})
+                            if emailrset:
+                                execute('SET U use_email X WHERE '
+                                        'X eid %(x)s, U eid %(u)s',
+                                        {'x': emailrset[0][0], 'u': eid})
+                            elif rset:
+                                if not execute('SET X address %(addr)s WHERE '
+                                               'U primary_email X, U eid %(u)s',
+                                               {'addr': ldapemailaddr, 'u': eid}, 'u'):
+                                    execute('SET X address %(addr)s WHERE '
+                                            'X eid %(x)s',
+                                            {'addr': ldapemailaddr, 'x': rset[0][0]}, 'x')
                             else:
                                 # no email found, create it
                                 _insert_email(session, ldapemailaddr, eid)
@@ -412,6 +434,9 @@
             hostport = self.host
         self.info('connecting %s://%s as %s', self.protocol, hostport,
                   user and user['dn'] or 'anonymous')
+        # don't require server certificate when using ldaps (will
+        # enable self signed certs)
+        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
         url = LDAPUrl(urlscheme=self.protocol, hostport=hostport)
         conn = ReconnectLDAPObject(url.initializeUrl())
         # Set the protocol version - version 3 is preferred
@@ -476,7 +501,8 @@
             if eid:
                 self.warning('deleting ldap user with eid %s and dn %s',
                              eid, base)
-                self.repo.delete_info(session, eid)
+                entity = session.entity_from_eid(eid, 'CWUser')
+                self.repo.delete_info(session, entity, self.uri, base)
                 self._cache.pop(base, None)
             return []
 ##         except ldap.REFERRAL, e:
@@ -554,7 +580,7 @@
         """replace an entity in the source"""
         raise RepositoryError('this source is read only')
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source"""
         raise RepositoryError('this source is read only')
 
--- a/server/sources/native.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/native.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Adapters for native cubicweb sources.
 
 Notes:
@@ -6,32 +23,34 @@
   string. This is because it should actually be Bytes but we want an index on
   it for fast querying.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
+from pickle import loads, dumps
 from threading import Lock
 from datetime import datetime
 from base64 import b64decode, b64encode
+from contextlib import contextmanager
 
 from logilab.common.compat import any
 from logilab.common.cache import Cache
 from logilab.common.decorators import cached, clear_cache
 from logilab.common.configuration import Method
-from logilab.common.adbh import get_adv_func_helper
 from logilab.common.shellutils import getlogin
+from logilab.database import get_db_helper
 
-from indexer import get_indexer
-
-from cubicweb import UnknownEid, AuthenticationError, Binary, server
+from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary
+from cubicweb import transaction as tx, server, neg_role
+from cubicweb.schema import VIRTUAL_RTYPES
 from cubicweb.cwconfig import CubicWebNoAppConfiguration
 from cubicweb.server import hook
-from cubicweb.server.utils import crypt_password
+from cubicweb.server.utils import crypt_password, eschema_eid
 from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
 from cubicweb.server.rqlannotation import set_qdata
+from cubicweb.server.hook import CleanupDeletedEidsCacheOp
+from cubicweb.server.session import hooks_control, security_enabled
 from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
 from cubicweb.server.sources.rql2sql import SQLGenerator
 
@@ -95,6 +114,74 @@
         table, restr, attr)
 
 
+def sql_or_clauses(sql, clauses):
+    select, restr = sql.split(' WHERE ', 1)
+    restrclauses = restr.split(' AND ')
+    for clause in clauses:
+        restrclauses.remove(clause)
+    if restrclauses:
+        restr = '%s AND (%s)' % (' AND '.join(restrclauses),
+                                 ' OR '.join(clauses))
+    else:
+        restr = '(%s)' % ' OR '.join(clauses)
+    return '%s WHERE %s' % (select, restr)
+
+
+class UndoException(Exception):
+    """something went wrong during undoing"""
+
+
+def _undo_check_relation_target(tentity, rdef, role):
+    """check linked entity has not been redirected for this relation"""
+    card = rdef.role_cardinality(role)
+    if card in '?1' and tentity.related(rdef.rtype, role):
+        raise UndoException(tentity._cw._(
+            "Can't restore %(role)s relation %(rtype)s to entity %(eid)s which "
+            "is already linked using this relation.")
+                            % {'role': neg_role(role),
+                               'rtype': rdef.rtype,
+                               'eid': tentity.eid})
+
+def _undo_rel_info(session, subj, rtype, obj):
+    entities = []
+    for role, eid in (('subject', subj), ('object', obj)):
+        try:
+            entities.append(session.entity_from_eid(eid))
+        except UnknownEid:
+            raise UndoException(session._(
+                "Can't restore relation %(rtype)s, %(role)s entity %(eid)s"
+                " doesn't exist anymore.")
+                                % {'role': session._(role),
+                                   'rtype': session._(rtype),
+                                   'eid': eid})
+    sentity, oentity = entities
+    try:
+        rschema = session.vreg.schema.rschema(rtype)
+        rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)]
+    except KeyError:
+        raise UndoException(session._(
+            "Can't restore relation %(rtype)s between %(subj)s and "
+            "%(obj)s, that relation does not exists anymore in the "
+            "schema.")
+                            % {'rtype': session._(rtype),
+                               'subj': subj,
+                               'obj': obj})
+    return sentity, oentity, rdef
+
+def _undo_has_later_transaction(session, eid):
+    return session.system_sql('''\
+SELECT T.tx_uuid FROM transactions AS TREF, transactions AS T
+WHERE TREF.tx_uuid='%(txuuid)s' AND T.tx_uuid!='%(txuuid)s'
+AND T.tx_time>=TREF.tx_time
+AND (EXISTS(SELECT 1 FROM tx_entity_actions AS TEA
+            WHERE TEA.tx_uuid=T.tx_uuid AND TEA.eid=%(eid)s)
+     OR EXISTS(SELECT 1 FROM tx_relation_actions as TRA
+               WHERE TRA.tx_uuid=T.tx_uuid AND (
+                   TRA.eid_from=%(eid)s OR TRA.eid_to=%(eid)s))
+     )''' % {'txuuid': session.transaction_data['undoing_uuid'],
+             'eid': eid}).fetchone()
+
+
 class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
     """adapter for source using the native cubicweb schema (see below)
     """
@@ -103,7 +190,7 @@
         ('db-driver',
          {'type' : 'string',
           'default': 'postgres',
-          'help': 'database driver (postgres or sqlite)',
+          'help': 'database driver (postgres, sqlite, sqlserver2005)',
           'group': 'native-source', 'inputlevel': 1,
           }),
         ('db-host',
@@ -142,6 +229,13 @@
           'help': 'database encoding',
           'group': 'native-source', 'inputlevel': 1,
           }),
+        ('db-extra-arguments',
+         {'type' : 'string',
+          'default': '',
+          'help': 'set to "Trusted_Connection" if you are using SQLServer and '
+                  'want trusted authentication for the database connection',
+          'group': 'native-source', 'inputlevel': 2,
+          }),
     )
 
     def __init__(self, repo, appschema, source_config, *args, **kwargs):
@@ -149,24 +243,21 @@
         self.authentifiers = [LoginPasswordAuthentifier(self)]
         AbstractSource.__init__(self, repo, appschema, source_config,
                                 *args, **kwargs)
+        # sql generator
+        self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
+                                             ATTR_MAP.copy())
         # full text index helper
         self.do_fti = not repo.config['delay-full-text-indexation']
-        if self.do_fti:
-            self.indexer = get_indexer(self.dbdriver, self.encoding)
-            # XXX should go away with logilab.db
-            self.dbhelper.fti_uid_attr = self.indexer.uid_attr
-            self.dbhelper.fti_table = self.indexer.table
-            self.dbhelper.fti_restriction_sql = self.indexer.restriction_sql
-            self.dbhelper.fti_need_distinct_query = self.indexer.need_distinct
-        else:
-            self.dbhelper.fti_need_distinct_query = False
-        # sql generator
-        self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
-                                             self.encoding, ATTR_MAP.copy())
         # sql queries cache
         self._cache = Cache(repo.config['rql-cache-size'])
         self._temp_table_data = {}
+        # we need a lock to protect eid attribution function (XXX, really?
+        # explain)
         self._eid_creation_lock = Lock()
+        # (etype, attr) / storage mapping
+        self._storages = {}
+        # entity types that may be used by other multi-sources instances
+        self.multisources_etypes = set(repo.config['multi-sources-etypes'])
         # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
         # running unittest_multisources with the wrapping below
         if self.dbdriver == 'sqlite' and \
@@ -209,7 +300,7 @@
         pool.pool_set()
         # check full text index availibility
         if self.do_fti:
-            if not self.indexer.has_fti_table(pool['system']):
+            if not self.dbhelper.has_fti_table(pool['system']):
                 if not self.repo.config.creating:
                     self.critical('no text index table')
                 self.do_fti = False
@@ -237,12 +328,26 @@
     def init(self):
         self.init_creating()
 
-    def map_attribute(self, etype, attr, cb):
-        self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
+    # XXX deprecates [un]map_attribute ?
+    def map_attribute(self, etype, attr, cb, sourcedb=True):
+        self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = (cb, sourcedb)
 
     def unmap_attribute(self, etype, attr):
         self._rql_sqlgen.attr_map.pop('%s.%s' % (etype, attr), None)
 
+    def set_storage(self, etype, attr, storage):
+        storage_dict = self._storages.setdefault(etype, {})
+        storage_dict[attr] = storage
+        self.map_attribute(etype, attr,
+                           storage.callback, storage.is_source_callback)
+
+    def unset_storage(self, etype, attr):
+        self._storages[etype].pop(attr)
+        # if etype has no storage left, remove the entry
+        if not self._storages[etype]:
+            del self._storages[etype]
+        self.unmap_attribute(etype, attr)
+
     # ISource interface #######################################################
 
     def compile_rql(self, rql, sols):
@@ -309,27 +414,26 @@
         if cachekey is None:
             self.no_cache += 1
             # generate sql query if we are able to do so (not supported types...)
-            sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
+            sql, qargs, cbs = self._rql_sqlgen.generate(union, args, varmap)
         else:
             # sql may be cached
             try:
-                sql, query_args = self._cache[cachekey]
+                sql, qargs, cbs = self._cache[cachekey]
                 self.cache_hit += 1
             except KeyError:
                 self.cache_miss += 1
-                sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
-                self._cache[cachekey] = sql, query_args
-        args = self.merge_args(args, query_args)
+                sql, qargs, cbs = self._rql_sqlgen.generate(union, args, varmap)
+                self._cache[cachekey] = sql, qargs, cbs
+        args = self.merge_args(args, qargs)
         assert isinstance(sql, basestring), repr(sql)
         try:
             cursor = self.doexec(session, sql, args)
-        except (self.dbapi_module.OperationalError,
-                self.dbapi_module.InterfaceError):
+        except (self.OperationalError, self.InterfaceError):
             # FIXME: better detection of deconnection pb
-            self.info("request failed '%s' ... retry with a new cursor", sql)
+            self.warning("trying to reconnect")
             session.pool.reconnect(self)
             cursor = self.doexec(session, sql, args)
-        results = self.process_result(cursor)
+        results = self.process_result(cursor, cbs)
         assert dbg_results(results)
         return results
 
@@ -343,9 +447,9 @@
             self.uri, union, varmap, args,
             prefix='ON THE FLY temp data insertion into %s from' % table)
         # generate sql queries if we are able to do so
-        sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
-        query = 'INSERT INTO %s %s' % (table, sql.encode(self.encoding))
-        self.doexec(session, query, self.merge_args(args, query_args))
+        sql, qargs, cbs = self._rql_sqlgen.generate(union, args, varmap)
+        query = 'INSERT INTO %s %s' % (table, sql.encode(self._dbencoding))
+        self.doexec(session, query, self.merge_args(args, qargs))
 
     def manual_insert(self, results, table, session):
         """insert given result into a temporary table on the system source"""
@@ -361,7 +465,7 @@
             row = tuple(row)
             for index, cell in enumerate(row):
                 if isinstance(cell, Binary):
-                    cell = self.binary(cell.getvalue())
+                    cell = self._binary(cell.getvalue())
                 kwargs[str(index)] = cell
             kwargs_list.append(kwargs)
         self.doexecmany(session, query, kwargs_list)
@@ -379,41 +483,102 @@
                 except KeyError:
                     continue
 
+    @contextmanager
+    def _storage_handler(self, entity, event):
+        # 1/ memorize values as they are before the storage is called.
+        #    For instance, the BFSStorage will replace the `data`
+        #    binary value with a Binary containing the destination path
+        #    on the filesystem. To make the entity.data usage absolutely
+        #    transparent, we'll have to reset entity.data to its binary
+        #    value once the SQL query will be executed
+        restore_values = {}
+        etype = entity.__regid__
+        for attr, storage in self._storages.get(etype, {}).items():
+            try:
+                edited = entity.edited_attributes
+            except AttributeError:
+                assert event == 'deleted'
+                getattr(storage, 'entity_deleted')(entity, attr)
+            else:
+                if attr in edited:
+                    handler = getattr(storage, 'entity_%s' % event)
+                    real_value = handler(entity, attr)
+                    restore_values[attr] = real_value
+        try:
+            yield # 2/ execute the source's instructions
+        finally:
+            # 3/ restore original values
+            for attr, value in restore_values.items():
+                entity[attr] = value
+
     def add_entity(self, session, entity):
         """add a new entity to the source"""
-        attrs = self.preprocess_entity(entity)
-        sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
-        self.doexec(session, sql, attrs)
+        with self._storage_handler(entity, 'added'):
+            attrs = self.preprocess_entity(entity)
+            sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
+            self.doexec(session, sql, attrs)
+            if session.undoable_action('C', entity.__regid__):
+                self._record_tx_action(session, 'tx_entity_actions', 'C',
+                                       etype=entity.__regid__, eid=entity.eid)
 
     def update_entity(self, session, entity):
         """replace an entity in the source"""
-        attrs = self.preprocess_entity(entity)
-        sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs,
-                                 [SQL_PREFIX + 'eid'])
-        self.doexec(session, sql, attrs)
+        with self._storage_handler(entity, 'updated'):
+            attrs = self.preprocess_entity(entity)
+            if session.undoable_action('U', entity.__regid__):
+                changes = self._save_attrs(session, entity, attrs)
+                self._record_tx_action(session, 'tx_entity_actions', 'U',
+                                       etype=entity.__regid__, eid=entity.eid,
+                                       changes=self._binary(dumps(changes)))
+            sql = self.sqlgen.update(SQL_PREFIX + entity.__regid__, attrs,
+                                     ['cw_eid'])
+            self.doexec(session, sql, attrs)
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source"""
-        attrs = {SQL_PREFIX + 'eid': eid}
-        sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs)
-        self.doexec(session, sql, attrs)
+        with self._storage_handler(entity, 'deleted'):
+            if session.undoable_action('D', entity.__regid__):
+                attrs = [SQL_PREFIX + r.type
+                         for r in entity.e_schema.subject_relations()
+                         if (r.final or r.inlined) and not r in VIRTUAL_RTYPES]
+                changes = self._save_attrs(session, entity, attrs)
+                self._record_tx_action(session, 'tx_entity_actions', 'D',
+                                       etype=entity.__regid__, eid=entity.eid,
+                                       changes=self._binary(dumps(changes)))
+            attrs = {'cw_eid': entity.eid}
+            sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
+            self.doexec(session, sql, attrs)
 
     def add_relation(self, session, subject, rtype, object, inlined=False):
         """add a relation to the source"""
+        self._add_relation(session, subject, rtype, object, inlined)
+        if session.undoable_action('A', rtype):
+            self._record_tx_action(session, 'tx_relation_actions', 'A',
+                                   eid_from=subject, rtype=rtype, eid_to=object)
+
+    def _add_relation(self, session, subject, rtype, object, inlined=False):
+        """add a relation to the source"""
         if inlined is False:
             attrs = {'eid_from': subject, 'eid_to': object}
             sql = self.sqlgen.insert('%s_relation' % rtype, attrs)
         else: # used by data import
             etype = session.describe(subject)[0]
-            attrs = {SQL_PREFIX + 'eid': subject, SQL_PREFIX + rtype: object}
+            attrs = {'cw_eid': subject, SQL_PREFIX + rtype: object}
             sql = self.sqlgen.update(SQL_PREFIX + etype, attrs,
-                                     [SQL_PREFIX + 'eid'])
+                                     ['cw_eid'])
         self.doexec(session, sql, attrs)
 
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
         rschema = self.schema.rschema(rtype)
-        if rschema.inlined:
+        self._delete_relation(session, subject, rtype, object, rschema.inlined)
+        if session.undoable_action('R', rtype):
+            self._record_tx_action(session, 'tx_relation_actions', 'R',
+                                   eid_from=subject, rtype=rtype, eid_to=object)
+
+    def _delete_relation(self, session, subject, rtype, object, inlined=False):
+        """delete a relation from the source"""
+        if inlined:
             table = SQL_PREFIX + session.describe(subject)[0]
             column = SQL_PREFIX + rtype
             sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column,
@@ -479,6 +644,9 @@
 
     # short cut to method requiring advanced db helper usage ##################
 
+    def binary_to_str(self, value):
+        return self.dbhelper.dbapi_module.binary_to_str(value)
+
     def create_index(self, session, table, column, unique=False):
         cursor = LogCursor(session.pool[self.uri])
         self.dbhelper.create_index(cursor, table, column, unique)
@@ -493,7 +661,7 @@
         """return a tuple (type, source, extid) for the entity with id <eid>"""
         sql = 'SELECT type, source, extid FROM entities WHERE eid=%s' % eid
         try:
-            res = session.system_sql(sql).fetchone()
+            res = self.doexec(session, sql).fetchone()
         except:
             assert session.pool, 'session has no pool set'
             raise UnknownEid(eid)
@@ -508,9 +676,10 @@
     def extid2eid(self, session, source, extid):
         """get eid from an external id. Return None if no record found."""
         assert isinstance(extid, str)
-        cursor = session.system_sql('SELECT eid FROM entities WHERE '
-                                    'extid=%(x)s AND source=%(s)s',
-                                    {'x': b64encode(extid), 's': source.uri})
+        cursor = self.doexec(session,
+                             'SELECT eid FROM entities '
+                             'WHERE extid=%(x)s AND source=%(s)s',
+                             {'x': b64encode(extid), 's': source.uri})
         # XXX testing rowcount cause strange bug with sqlite, results are there
         #     but rowcount is 0
         #if cursor.rowcount > 0:
@@ -522,6 +691,15 @@
             pass
         return None
 
+    def make_temp_table_name(self, table):
+        try: # XXX remove this once 
+            return self.dbhelper.temporary_table_name(table)
+        except AttributeError:
+            import warnings
+            warnings.warn('Please hg up logilab.database')
+            return table
+
+
     def temp_table_def(self, selected, sol, table):
         return make_schema(selected, sol, table, self.dbhelper.TYPE_MAPPING)
 
@@ -541,7 +719,7 @@
         finally:
             self._eid_creation_lock.release()
 
-    def add_info(self, session, entity, source, extid=None, complete=True):
+    def add_info(self, session, entity, source, extid, complete):
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
         if extid is not None:
@@ -549,34 +727,42 @@
             extid = b64encode(extid)
         attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
                  'source': source.uri, 'mtime': datetime.now()}
-        session.system_sql(self.sqlgen.insert('entities', attrs), attrs)
+        self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
         # now we can update the full text index
         if self.do_fti and self.need_fti_indexation(entity.__regid__):
             if complete:
                 entity.complete(entity.e_schema.indexable_attributes())
-            FTIndexEntityOp(session, entity=entity)
+            self.index_entity(session, entity=entity)
 
     def update_info(self, session, entity, need_fti_update):
+        """mark entity as being modified, fulltext reindex if needed"""
         if self.do_fti and need_fti_update:
             # reindex the entity only if this query is updating at least
             # one indexable attribute
-            FTIndexEntityOp(session, entity=entity)
-        # update entities.mtime
+            self.index_entity(session, entity=entity)
+        # update entities.mtime.
+        # XXX Only if entity.__regid__ in self.multisources_etypes?
         attrs = {'eid': entity.eid, 'mtime': datetime.now()}
-        session.system_sql(self.sqlgen.update('entities', attrs, ['eid']), attrs)
+        self.doexec(session, self.sqlgen.update('entities', attrs, ['eid']), attrs)
 
-    def delete_info(self, session, eid, etype, uri, extid):
-        """delete system information on deletion of an entity by transfering
-        record from the entities table to the deleted_entities table
+    def delete_info(self, session, entity, uri, extid):
+        """delete system information on deletion of an entity:
+        * update the fti
+        * remove record from the entities table
+        * transfer it to the deleted_entities table if the entity's type is
+          multi-sources
         """
-        attrs = {'eid': eid}
-        session.system_sql(self.sqlgen.delete('entities', attrs), attrs)
+        self.fti_unindex_entity(session, entity.eid)
+        attrs = {'eid': entity.eid}
+        self.doexec(session, self.sqlgen.delete('entities', attrs), attrs)
+        if not entity.__regid__ in self.multisources_etypes:
+            return
         if extid is not None:
             assert isinstance(extid, str), type(extid)
             extid = b64encode(extid)
-        attrs = {'type': etype, 'eid': eid, 'extid': extid,
+        attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
                  'source': uri, 'dtime': datetime.now()}
-        session.system_sql(self.sqlgen.insert('deleted_entities', attrs), attrs)
+        self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs)
 
     def modified_entities(self, session, etypes, mtime):
         """return a 2-uple:
@@ -586,14 +772,355 @@
         * list of (etype, eid) of entities of the given types which have been
           deleted since the given timestamp
         """
+        for etype in etypes:
+            if not etype in self.multisources_etypes:
+                self.critical('%s not listed as a multi-sources entity types. '
+                              'Modify your configuration' % etype)
+                self.multisources_etypes.add(etype)
         modsql = _modified_sql('entities', etypes)
-        cursor = session.system_sql(modsql, {'time': mtime})
+        cursor = self.doexec(session, modsql, {'time': mtime})
         modentities = cursor.fetchall()
         delsql = _modified_sql('deleted_entities', etypes)
-        cursor = session.system_sql(delsql, {'time': mtime})
+        cursor = self.doexec(session, delsql, {'time': mtime})
         delentities = cursor.fetchall()
         return modentities, delentities
 
+    # undo support #############################################################
+
+    def undoable_transactions(self, session, ueid=None, **actionfilters):
+        """See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
+        # force filtering to session's user if not a manager
+        if not session.user.is_in_group('managers'):
+            ueid = session.user.eid
+        restr = {}
+        if ueid is not None:
+            restr['tx_user'] = ueid
+        sql = self.sqlgen.select('transactions', restr, ('tx_uuid', 'tx_time', 'tx_user'))
+        if actionfilters:
+            # we will need subqueries to filter transactions according to
+            # actions done
+            tearestr = {} # filters on the tx_entity_actions table
+            trarestr = {} # filters on the tx_relation_actions table
+            genrestr = {} # generic filters, appliyable to both table
+            # unless public explicitly set to false, we only consider public
+            # actions
+            if actionfilters.pop('public', True):
+                genrestr['txa_public'] = True
+            # put additional filters in trarestr and/or tearestr
+            for key, val in actionfilters.iteritems():
+                if key == 'etype':
+                    # filtering on etype implies filtering on entity actions
+                    # only, and with no eid specified
+                    assert actionfilters.get('action', 'C') in 'CUD'
+                    assert not 'eid' in actionfilters
+                    tearestr['etype'] = val
+                elif key == 'eid':
+                    # eid filter may apply to 'eid' of tx_entity_actions or to
+                    # 'eid_from' OR 'eid_to' of tx_relation_actions
+                    if actionfilters.get('action', 'C') in 'CUD':
+                        tearestr['eid'] = val
+                    if actionfilters.get('action', 'A') in 'AR':
+                        trarestr['eid_from'] = val
+                        trarestr['eid_to'] = val
+                elif key == 'action':
+                    if val in 'CUD':
+                        tearestr['txa_action'] = val
+                    else:
+                        assert val in 'AR'
+                        trarestr['txa_action'] = val
+                else:
+                    raise AssertionError('unknow filter %s' % key)
+            assert trarestr or tearestr, "can't only filter on 'public'"
+            subqsqls = []
+            # append subqueries to the original query, using EXISTS()
+            if trarestr or (genrestr and not tearestr):
+                trarestr.update(genrestr)
+                trasql = self.sqlgen.select('tx_relation_actions', trarestr, ('1',))
+                if 'eid_from' in trarestr:
+                    # replace AND by OR between eid_from/eid_to restriction
+                    trasql = sql_or_clauses(trasql, ['eid_from = %(eid_from)s',
+                                                     'eid_to = %(eid_to)s'])
+                trasql += ' AND transactions.tx_uuid=tx_relation_actions.tx_uuid'
+                subqsqls.append('EXISTS(%s)' % trasql)
+            if tearestr or (genrestr and not trarestr):
+                tearestr.update(genrestr)
+                teasql = self.sqlgen.select('tx_entity_actions', tearestr, ('1',))
+                teasql += ' AND transactions.tx_uuid=tx_entity_actions.tx_uuid'
+                subqsqls.append('EXISTS(%s)' % teasql)
+            if restr:
+                sql += ' AND %s' % ' OR '.join(subqsqls)
+            else:
+                sql += ' WHERE %s' % ' OR '.join(subqsqls)
+            restr.update(trarestr)
+            restr.update(tearestr)
+        # we want results ordered by transaction's time descendant
+        sql += ' ORDER BY tx_time DESC'
+        cu = self.doexec(session, sql, restr)
+        # turn results into transaction objects
+        return [tx.Transaction(*args) for args in cu.fetchall()]
+
+    def tx_info(self, session, txuuid):
+        """See :class:`cubicweb.dbapi.Connection.transaction_info`"""
+        return tx.Transaction(txuuid, *self._tx_info(session, txuuid))
+
+    def tx_actions(self, session, txuuid, public):
+        """See :class:`cubicweb.dbapi.Connection.transaction_actions`"""
+        self._tx_info(session, txuuid)
+        restr = {'tx_uuid': txuuid}
+        if public:
+            restr['txa_public'] = True
+        # XXX use generator to avoid loading everything in memory?
+        sql = self.sqlgen.select('tx_entity_actions', restr,
+                                 ('txa_action', 'txa_public', 'txa_order',
+                                  'etype', 'eid', 'changes'))
+        cu = self.doexec(session, sql, restr)
+        actions = [tx.EntityAction(a,p,o,et,e,c and loads(self.binary_to_str(c)))
+                   for a,p,o,et,e,c in cu.fetchall()]
+        sql = self.sqlgen.select('tx_relation_actions', restr,
+                                 ('txa_action', 'txa_public', 'txa_order',
+                                  'rtype', 'eid_from', 'eid_to'))
+        cu = self.doexec(session, sql, restr)
+        actions += [tx.RelationAction(*args) for args in cu.fetchall()]
+        return sorted(actions, key=lambda x: x.order)
+
+    def undo_transaction(self, session, txuuid):
+        """See :class:`cubicweb.dbapi.Connection.undo_transaction`
+
+        important note: while undoing of a transaction, only hooks in the
+        'integrity', 'activeintegrity' and 'undo' categories are called.
+        """
+        # set mode so pool isn't released subsquently until commit/rollback
+        session.mode = 'write'
+        errors = []
+        session.transaction_data['undoing_uuid'] = txuuid
+        with hooks_control(session, session.HOOKS_DENY_ALL,
+                           'integrity', 'activeintegrity', 'undo'):
+            with security_enabled(session, read=False):
+                for action in reversed(self.tx_actions(session, txuuid, False)):
+                    undomethod = getattr(self, '_undo_%s' % action.action.lower())
+                    errors += undomethod(session, action)
+        # remove the transactions record
+        self.doexec(session,
+                    "DELETE FROM transactions WHERE tx_uuid='%s'" % txuuid)
+        return errors
+
+    def start_undoable_transaction(self, session, uuid):
+        """session callback to insert a transaction record in the transactions
+        table when some undoable transaction is started
+        """
+        ueid = session.user.eid
+        attrs = {'tx_uuid': uuid, 'tx_user': ueid, 'tx_time': datetime.now()}
+        self.doexec(session, self.sqlgen.insert('transactions', attrs), attrs)
+
+    def _save_attrs(self, session, entity, attrs):
+        """return a pickleable dictionary containing current values for given
+        attributes of the entity
+        """
+        restr = {'cw_eid': entity.eid}
+        sql = self.sqlgen.select(SQL_PREFIX + entity.__regid__, restr, attrs)
+        cu = self.doexec(session, sql, restr)
+        values = dict(zip(attrs, cu.fetchone()))
+        # ensure backend specific binary are converted back to string
+        eschema = entity.e_schema
+        for column in attrs:
+            # [3:] remove 'cw_' prefix
+            attr = column[3:]
+            if not eschema.subjrels[attr].final:
+                continue
+            if eschema.destination(attr) in ('Password', 'Bytes'):
+                value = values[column]
+                if value is not None:
+                    values[column] = self.binary_to_str(value)
+        return values
+
+    def _record_tx_action(self, session, table, action, **kwargs):
+        """record a transaction action in the given table (either
+        'tx_entity_actions' or 'tx_relation_action')
+        """
+        kwargs['tx_uuid'] = session.transaction_uuid()
+        kwargs['txa_action'] = action
+        kwargs['txa_order'] = session.transaction_inc_action_counter()
+        kwargs['txa_public'] = session.running_dbapi_query
+        self.doexec(session, self.sqlgen.insert(table, kwargs), kwargs)
+
+    def _tx_info(self, session, txuuid):
+        """return transaction's time and user of the transaction with the given uuid.
+
+        raise `NoSuchTransaction` if there is no such transaction of if the
+        session's user isn't allowed to see it.
+        """
+        restr = {'tx_uuid': txuuid}
+        sql = self.sqlgen.select('transactions', restr, ('tx_time', 'tx_user'))
+        cu = self.doexec(session, sql, restr)
+        try:
+            time, ueid = cu.fetchone()
+        except TypeError:
+            raise tx.NoSuchTransaction()
+        if not (session.user.is_in_group('managers')
+                or session.user.eid == ueid):
+            raise tx.NoSuchTransaction()
+        return time, ueid
+
+    def _undo_d(self, session, action):
+        """undo an entity deletion"""
+        errors = []
+        err = errors.append
+        eid = action.eid
+        etype = action.etype
+        _ = session._
+        # get an entity instance
+        try:
+            entity = self.repo.vreg['etypes'].etype_class(etype)(session)
+        except Exception:
+            err("can't restore entity %s of type %s, type no more supported"
+                % (eid, etype))
+            return errors
+        # check for schema changes, entities linked through inlined relation
+        # still exists, rewrap binary values
+        eschema = entity.e_schema
+        getrschema = eschema.subjrels
+        for column, value in action.changes.items():
+            rtype = column[3:] # remove cw_ prefix
+            try:
+                rschema = getrschema[rtype]
+            except KeyError:
+                err(_("Can't restore relation %(rtype)s of entity %(eid)s, "
+                      "this relation does not exists anymore in the schema.")
+                    % {'rtype': rtype, 'eid': eid})
+            if not rschema.final:
+                assert value is None
+            elif eschema.destination(rtype) in ('Bytes', 'Password'):
+                action.changes[column] = self._binary(value)
+                entity[rtype] = Binary(value)
+            elif isinstance(value, str):
+                entity[rtype] = unicode(value, session.encoding, 'replace')
+            else:
+                entity[rtype] = value
+        entity.set_eid(eid)
+        session.repo.init_entity_caches(session, entity, self)
+        entity.edited_attributes = set(entity)
+        entity.check()
+        self.repo.hm.call_hooks('before_add_entity', session, entity=entity)
+        # restore the entity
+        action.changes['cw_eid'] = eid
+        sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
+        self.doexec(session, sql, action.changes)
+        # add explicitly is / is_instance_of whose deletion is not recorded for
+        # consistency with addition (done by sql in hooks)
+        self.doexec(session, 'INSERT INTO is_relation(eid_from, eid_to) '
+                    'VALUES(%s, %s)' % (eid, eschema_eid(session, eschema)))
+        for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+            self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,'
+                        'eid_to) VALUES(%s, %s)' % (eid, eschema_eid(session, eschema)))
+        # restore record in entities (will update fti if needed)
+        self.add_info(session, entity, self, None, True)
+        # remove record from deleted_entities if entity's type is multi-sources
+        if entity.__regid__ in self.multisources_etypes:
+            self.doexec(session,
+                        'DELETE FROM deleted_entities WHERE eid=%s' % eid)
+        self.repo.hm.call_hooks('after_add_entity', session, entity=entity)
+        return errors
+
+    def _undo_r(self, session, action):
+        """undo a relation removal"""
+        errors = []
+        subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
+        try:
+            sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
+        except UndoException, ex:
+            errors.append(unicode(ex))
+        else:
+            for role, entity in (('subject', sentity),
+                                 ('object', oentity)):
+                try:
+                    _undo_check_relation_target(entity, rdef, role)
+                except UndoException, ex:
+                    errors.append(unicode(ex))
+                    continue
+        if not errors:
+            self.repo.hm.call_hooks('before_add_relation', session,
+                                    eidfrom=subj, rtype=rtype, eidto=obj)
+            # add relation in the database
+            self._add_relation(session, subj, rtype, obj, rdef.rtype.inlined)
+            # set related cache
+            session.update_rel_cache_add(subj, rtype, obj, rdef.rtype.symmetric)
+            self.repo.hm.call_hooks('after_add_relation', session,
+                                    eidfrom=subj, rtype=rtype, eidto=obj)
+        return errors
+
+    def _undo_c(self, session, action):
+        """undo an entity creation"""
+        eid = action.eid
+        # XXX done to avoid fetching all remaining relation for the entity
+        # we should find an efficient way to do this (keeping current veolidf
+        # massive deletion performance)
+        if _undo_has_later_transaction(session, eid):
+            msg = session._('some later transaction(s) touch entity, undo them '
+                            'first')
+            raise ValidationError(eid, {None: msg})
+        etype = action.etype
+        # get an entity instance
+        try:
+            entity = self.repo.vreg['etypes'].etype_class(etype)(session)
+        except Exception:
+            return [session._(
+                "Can't undo creation of entity %(eid)s of type %(etype)s, type "
+                "no more supported" % {'eid': eid, 'etype': etype})]
+        entity.set_eid(eid)
+        # for proper eid/type cache update
+        hook.set_operation(session, 'pendingeids', eid,
+                           CleanupDeletedEidsCacheOp)
+        self.repo.hm.call_hooks('before_delete_entity', session, entity=entity)
+        # remove is / is_instance_of which are added using sql by hooks, hence
+        # unvisible as transaction action
+        self.doexec(session, 'DELETE FROM is_relation WHERE eid_from=%s' % eid)
+        self.doexec(session, 'DELETE FROM is_instance_of_relation WHERE eid_from=%s' % eid)
+        # XXX check removal of inlined relation?
+        # delete the entity
+        attrs = {'cw_eid': eid}
+        sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
+        self.doexec(session, sql, attrs)
+        # remove record from entities (will update fti if needed)
+        self.delete_info(session, entity, self.uri, None)
+        self.repo.hm.call_hooks('after_delete_entity', session, entity=entity)
+        return ()
+
+    def _undo_u(self, session, action):
+        """undo an entity update"""
+        return ['undoing of entity updating not yet supported.']
+
+    def _undo_a(self, session, action):
+        """undo a relation addition"""
+        errors = []
+        subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
+        try:
+            sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
+        except UndoException, ex:
+            errors.append(unicode(ex))
+        else:
+            rschema = rdef.rtype
+            if rschema.inlined:
+                sql = 'SELECT 1 FROM cw_%s WHERE cw_eid=%s and cw_%s=%s'\
+                      % (sentity.__regid__, subj, rtype, obj)
+            else:
+                sql = 'SELECT 1 FROM %s_relation WHERE eid_from=%s and eid_to=%s'\
+                      % (rtype, subj, obj)
+            cu = self.doexec(session, sql)
+            if cu.fetchone() is None:
+                errors.append(session._(
+                    "Can't undo addition of relation %(rtype)s from %(subj)s to"
+                    " %(obj)s, doesn't exist anymore" % locals()))
+        if not errors:
+            self.repo.hm.call_hooks('before_delete_relation', session,
+                                    eidfrom=subj, rtype=rtype, eidto=obj)
+            # delete relation from the database
+            self._delete_relation(session, subj, rtype, obj, rschema.inlined)
+            # set related cache
+            session.update_rel_cache_del(subj, rtype, obj, rschema.symmetric)
+            self.repo.hm.call_hooks('after_delete_relation', session,
+                                    eidfrom=subj, rtype=rtype, eidto=obj)
+        return errors
+
     # full text index handling #################################################
 
     @cached
@@ -609,14 +1136,14 @@
         """create an operation to [re]index textual content of the given entity
         on commit
         """
-        FTIndexEntityOp(session, entity=entity)
+        hook.set_operation(session, 'ftindex', entity.eid, FTIndexEntityOp)
 
     def fti_unindex_entity(self, session, eid):
         """remove text content for entity with the given eid from the full text
         index
         """
         try:
-            self.indexer.cursor_unindex_object(eid, session.pool['system'])
+            self.dbhelper.cursor_unindex_object(eid, session.pool['system'])
         except Exception: # let KeyboardInterrupt / SystemExit propagate
             self.exception('error while unindexing %s', eid)
 
@@ -627,8 +1154,8 @@
         try:
             # use cursor_index_object, not cursor_reindex_object since
             # unindexing done in the FTIndexEntityOp
-            self.indexer.cursor_index_object(entity.eid, entity,
-                                             session.pool['system'])
+            self.dbhelper.cursor_index_object(entity.eid, entity,
+                                              session.pool['system'])
         except Exception: # let KeyboardInterrupt / SystemExit propagate
             self.exception('error while reindexing %s', entity)
 
@@ -643,26 +1170,23 @@
 
     def precommit_event(self):
         session = self.session
-        entity = self.entity
-        if entity.eid in session.transaction_data.get('pendingeids', ()):
-            return # entity added and deleted in the same transaction
-        alreadydone = session.transaction_data.setdefault('indexedeids', set())
-        if entity.eid in alreadydone:
-            self.debug('skipping reindexation of %s, already done', entity.eid)
-            return
-        alreadydone.add(entity.eid)
         source = session.repo.system_source
-        for container in entity.fti_containers():
-            source.fti_unindex_entity(session, container.eid)
-            source.fti_index_entity(session, container)
-
-    def commit_event(self):
-        pass
+        pendingeids = session.transaction_data.get('pendingeids', ())
+        done = session.transaction_data.setdefault('indexedeids', set())
+        for eid in session.transaction_data.pop('ftindex', ()):
+            if eid in pendingeids or eid in done:
+                # entity added and deleted in the same transaction or already
+                # processed
+                return
+            done.add(eid)
+            for container in session.entity_from_eid(eid).fti_containers():
+                source.fti_unindex_entity(session, container.eid)
+                source.fti_index_entity(session, container)
 
 
 def sql_schema(driver):
-    helper = get_adv_func_helper(driver)
-    tstamp_col_type = helper.TYPE_MAPPING['Datetime']
+    helper = get_db_helper(driver)
+    typemap = helper.TYPE_MAPPING
     schema = """
 /* Create the repository's system database */
 
@@ -674,10 +1198,10 @@
   source VARCHAR(64) NOT NULL,
   mtime %s NOT NULL,
   extid VARCHAR(256)
-);
-CREATE INDEX entities_type_idx ON entities(type);
-CREATE INDEX entities_mtime_idx ON entities(mtime);
-CREATE INDEX entities_extid_idx ON entities(extid);
+);;
+CREATE INDEX entities_type_idx ON entities(type);;
+CREATE INDEX entities_mtime_idx ON entities(mtime);;
+CREATE INDEX entities_extid_idx ON entities(extid);;
 
 CREATE TABLE deleted_entities (
   eid INTEGER PRIMARY KEY NOT NULL,
@@ -685,32 +1209,80 @@
   source VARCHAR(64) NOT NULL,
   dtime %s NOT NULL,
   extid VARCHAR(256)
-);
-CREATE INDEX deleted_entities_type_idx ON deleted_entities(type);
-CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime);
-CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid);
-""" % (helper.sql_create_sequence('entities_id_seq'), tstamp_col_type, tstamp_col_type)
+);;
+CREATE INDEX deleted_entities_type_idx ON deleted_entities(type);;
+CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime);;
+CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid);;
+
+CREATE TABLE transactions (
+  tx_uuid CHAR(32) PRIMARY KEY NOT NULL,
+  tx_user INTEGER NOT NULL,
+  tx_time %s NOT NULL
+);;
+CREATE INDEX transactions_tx_user_idx ON transactions(tx_user);;
+
+CREATE TABLE tx_entity_actions (
+  tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
+  txa_action CHAR(1) NOT NULL,
+  txa_public %s NOT NULL,
+  txa_order INTEGER,
+  eid INTEGER NOT NULL,
+  etype VARCHAR(64) NOT NULL,
+  changes %s
+);;
+CREATE INDEX tx_entity_actions_txa_action_idx ON tx_entity_actions(txa_action);;
+CREATE INDEX tx_entity_actions_txa_public_idx ON tx_entity_actions(txa_public);;
+CREATE INDEX tx_entity_actions_eid_idx ON tx_entity_actions(eid);;
+CREATE INDEX tx_entity_actions_etype_idx ON tx_entity_actions(etype);;
+
+CREATE TABLE tx_relation_actions (
+  tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
+  txa_action CHAR(1) NOT NULL,
+  txa_public %s NOT NULL,
+  txa_order INTEGER,
+  eid_from INTEGER NOT NULL,
+  eid_to INTEGER NOT NULL,
+  rtype VARCHAR(256) NOT NULL
+);;
+CREATE INDEX tx_relation_actions_txa_action_idx ON tx_relation_actions(txa_action);;
+CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);;
+CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);;
+CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to);;
+""" % (helper.sql_create_sequence('entities_id_seq').replace(';', ';;'),
+       typemap['Datetime'], typemap['Datetime'], typemap['Datetime'],
+       typemap['Boolean'], typemap['Bytes'], typemap['Boolean'])
+    if helper.backend_name == 'sqlite':
+        # sqlite support the ON DELETE CASCADE syntax but do nothing
+        schema += '''
+CREATE TRIGGER fkd_transactions
+BEFORE DELETE ON transactions
+FOR EACH ROW BEGIN
+    DELETE FROM tx_entity_actions WHERE tx_uuid=OLD.tx_uuid;
+    DELETE FROM tx_relation_actions WHERE tx_uuid=OLD.tx_uuid;
+END;;
+'''
     return schema
 
 
 def sql_drop_schema(driver):
-    helper = get_adv_func_helper(driver)
+    helper = get_db_helper(driver)
     return """
 %s
 DROP TABLE entities;
 DROP TABLE deleted_entities;
+DROP TABLE tx_entity_actions;
+DROP TABLE tx_relation_actions;
+DROP TABLE transactions;
 """ % helper.sql_drop_sequence('entities_id_seq')
 
 
 def grant_schema(user, set_owner=True):
     result = ''
-    if set_owner:
-        result = 'ALTER TABLE entities OWNER TO %s;\n' % user
-        result += 'ALTER TABLE deleted_entities OWNER TO %s;\n' % user
-        result += 'ALTER TABLE entities_id_seq OWNER TO %s;\n' % user
-    result += 'GRANT ALL ON entities TO %s;\n' % user
-    result += 'GRANT ALL ON deleted_entities TO %s;\n' % user
-    result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user
+    for table in ('entities', 'deleted_entities', 'entities_id_seq',
+                  'transactions', 'tx_entity_actions', 'tx_relation_actions'):
+        if set_owner:
+            result = 'ALTER TABLE %s OWNER TO %s;\n' % (table, user)
+        result += 'GRANT ALL ON %s TO %s;\n' % (table, user)
     return result
 
 
--- a/server/sources/pyrorql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/pyrorql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Source to query another RQL repository using pyro
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -203,7 +216,8 @@
                                          insert=False)
                     # entity has been deleted from external repository but is not known here
                     if eid is not None:
-                        repo.delete_info(session, eid)
+                        entity = session.entity_from_eid(eid, etype)
+                        repo.delete_info(session, entity, self.uri, extid)
                 except:
                     self.exception('while updating %s with external id %s of source %s',
                                    etype, extid, self.uri)
@@ -350,11 +364,11 @@
         self._query_cache.clear()
         entity.clear_all_caches()
 
-    def delete_entity(self, session, etype, eid):
+    def delete_entity(self, session, entity):
         """delete an entity from the source"""
         cu = session.pool[self.uri]
-        cu.execute('DELETE %s X WHERE X eid %%(x)s' % etype,
-                   {'x': self.eid2extid(eid, session)}, 'x')
+        cu.execute('DELETE %s X WHERE X eid %%(x)s' % entity.__regid__,
+                   {'x': self.eid2extid(entity.eid, session)}, 'x')
         self._query_cache.clear()
 
     def add_relation(self, session, subject, rtype, object):
--- a/server/sources/rql2sql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/rql2sql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """RQL to SQL generator for native sources.
 
 
@@ -23,26 +40,42 @@
 Potential optimization information is collected by the querier, sql generation
 is done according to this information
 
+cross RDMS note : read `Comparison of different SQL implementations`_
+by Troels Arvin. Features SQL ISO Standard, PG, mysql, Oracle, MS SQL, DB2
+and Informix.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. _Comparison of different SQL implementations: http://www.troels.arvin.dk/db/rdbms 
+
+
 """
 __docformat__ = "restructuredtext en"
 
 import threading
 
+from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY
+
 from rql import BadRQLQuery, CoercionError
 from rql.stmts import Union, Select
 from rql.nodes import (SortTerm, VariableRef, Constant, Function, Not,
                        Variable, ColumnAlias, Relation, SubQuery, Exists)
 
+from cubicweb import QueryError
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import cleanup_solutions
 
 ColumnAlias._q_invariant = False # avoid to check for ColumnAlias / Variable
 
+FunctionDescr.source_execute = None
+
+def default_update_cb_stack(self, stack):
+    stack.append(self.source_execute)
+FunctionDescr.update_cb_stack = default_update_cb_stack
+
+LENGTH = SQL_FUNCTIONS_REGISTRY.get_function('LENGTH')
+def length_source_execute(source, value):
+    return len(value.getvalue())
+LENGTH.source_execute = length_source_execute
+
 def _new_var(select, varname):
     newvar = select.get_variable(varname)
     if not 'relations' in newvar.stinfo:
@@ -252,14 +285,44 @@
                         selectedidx.append(vref.name)
                         rqlst.selection.append(vref)
 
-# IGenerator implementation for RQL->SQL ######################################
+def iter_mapped_var_sels(stmt, variable):
+    # variable is a Variable or ColumnAlias node mapped to a source side
+    # callback
+    if not (len(variable.stinfo['rhsrelations']) <= 1 and # < 1 on column alias
+            variable.stinfo['selected']):
+        raise QueryError("can't use %s as a restriction variable"
+                         % variable.name)
+    for selectidx in variable.stinfo['selected']:
+        vrefs = stmt.selection[selectidx].get_nodes(VariableRef)
+        if len(vrefs) != 1:
+            raise QueryError()
+        yield selectidx, vrefs[0]
 
+def update_source_cb_stack(state, stmt, node, stack):
+    while True:
+        node = node.parent
+        if node is stmt:
+            break
+        if not isinstance(node, Function):
+            raise QueryError()
+        func = SQL_FUNCTIONS_REGISTRY.get_function(node.name)
+        if func.source_execute is None:
+            raise QueryError('%s can not be called on mapped attribute'
+                             % node.name)
+        state.source_cb_funcs.add(node)
+        func.update_cb_stack(stack)
+
+
+# IGenerator implementation for RQL->SQL #######################################
 
 class StateInfo(object):
     def __init__(self, existssols, unstablevars):
         self.existssols = existssols
         self.unstablevars = unstablevars
         self.subtables = {}
+        self.needs_source_cb = None
+        self.subquery_source_cb = None
+        self.source_cb_funcs = set()
 
     def reset(self, solution):
         """reset some visit variables"""
@@ -276,6 +339,17 @@
         self.restrictions = []
         self._restr_stack = []
         self.ignore_varmap = False
+        self._needs_source_cb = {}
+
+    def merge_source_cbs(self, needs_source_cb):
+        if self.needs_source_cb is None:
+            self.needs_source_cb = needs_source_cb
+        elif needs_source_cb != self.needs_source_cb:
+            raise QueryError('query fetch some source mapped attribute, some not')
+
+    def finalize_source_cbs(self):
+        if self.subquery_source_cb is not None:
+            self.needs_source_cb.update(self.subquery_source_cb)
 
     def add_restriction(self, restr):
         if restr:
@@ -332,16 +406,16 @@
     protected by a lock
     """
 
-    def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None):
+    def __init__(self, schema, dbhelper, attrmap=None):
         self.schema = schema
-        self.dbms_helper = dbms_helper
-        self.dbencoding = dbencoding
-        self.keyword_map = {'NOW' : self.dbms_helper.sql_current_timestamp,
-                            'TODAY': self.dbms_helper.sql_current_date,
+        self.dbhelper = dbhelper
+        self.dbencoding = dbhelper.dbencoding
+        self.keyword_map = {'NOW' : self.dbhelper.sql_current_timestamp,
+                            'TODAY': self.dbhelper.sql_current_date,
                             }
-        if not self.dbms_helper.union_parentheses_support:
+        if not self.dbhelper.union_parentheses_support:
             self.union_sql = self.noparen_union_sql
-        if self.dbms_helper.fti_need_distinct_query:
+        if self.dbhelper.fti_need_distinct:
             self.__union_sql = self.union_sql
             self.union_sql = self.has_text_need_distinct_union_sql
         self._lock = threading.Lock()
@@ -373,7 +447,7 @@
             # union query for each rqlst / solution
             sql = self.union_sql(union)
             # we are done
-            return sql, self._query_attrs
+            return sql, self._query_attrs, self._state.needs_source_cb
         finally:
             self._lock.release()
 
@@ -391,9 +465,10 @@
         return '\nUNION ALL\n'.join(sqls)
 
     def noparen_union_sql(self, union, needalias=False):
-        # needed for sqlite backend which doesn't like parentheses around
-        # union query. This may cause bug in some condition (sort in one of
-        # the subquery) but will work in most case
+        # needed for sqlite backend which doesn't like parentheses around union
+        # query. This may cause bug in some condition (sort in one of the
+        # subquery) but will work in most case
+        #
         # see http://www.sqlite.org/cvstrac/tktview?tn=3074
         sqls = (self.select_sql(select, needalias)
                 for i, select in enumerate(union.children))
@@ -435,6 +510,9 @@
         else:
             existssols, unstable = {}, ()
         state = StateInfo(existssols, unstable)
+        if self._state is not None:
+            # state from a previous unioned select
+            state.merge_source_cbs(self._state.needs_source_cb)
         # treat subqueries
         self._subqueries_sql(select, state)
         # generate sql for this select node
@@ -490,6 +568,7 @@
                 if fneedwrap:
                     selection = ['T1.C%s' % i for i in xrange(len(origselection))]
                     sql = 'SELECT %s FROM (%s) AS T1' % (','.join(selection), sql)
+            state.finalize_source_cbs()
         finally:
             select.selection = origselection
         # limit / offset
@@ -504,13 +583,24 @@
     def _subqueries_sql(self, select, state):
         for i, subquery in enumerate(select.with_):
             sql = self.union_sql(subquery.query, needalias=True)
-            tablealias = '_T%s' % i
+            tablealias = '_T%s' % i # XXX nested subqueries
             sql = '(%s) AS %s' % (sql, tablealias)
             state.subtables[tablealias] = (0, sql)
+            latest_state = self._state
             for vref in subquery.aliases:
                 alias = vref.variable
                 alias._q_sqltable = tablealias
                 alias._q_sql = '%s.C%s' % (tablealias, alias.colnum)
+                try:
+                    stack = latest_state.needs_source_cb[alias.colnum]
+                    if state.subquery_source_cb is None:
+                        state.subquery_source_cb = {}
+                    for selectidx, vref in iter_mapped_var_sels(select, alias):
+                        stack = stack[:]
+                        update_source_cb_stack(state, select, vref, stack)
+                        state.subquery_source_cb[selectidx] = stack
+                except KeyError:
+                    continue
 
     def _solutions_sql(self, select, solutions, distinct, needalias):
         sqls = []
@@ -522,17 +612,18 @@
             sql = [self._selection_sql(select.selection, distinct, needalias)]
             if self._state.restrictions:
                 sql.append('WHERE %s' % ' AND '.join(self._state.restrictions))
+            self._state.merge_source_cbs(self._state._needs_source_cb)
             # add required tables
             assert len(self._state.actual_tables) == 1, self._state.actual_tables
             tables = self._state.actual_tables[-1]
             if tables:
                 # sort for test predictability
                 sql.insert(1, 'FROM %s' % ', '.join(sorted(tables)))
-            elif self._state.restrictions and self.dbms_helper.needs_from_clause:
+            elif self._state.restrictions and self.dbhelper.needs_from_clause:
                 sql.insert(1, 'FROM (SELECT 1) AS _T')
             sqls.append('\n'.join(sql))
         if select.need_intersect:
-            #if distinct or not self.dbms_helper.intersect_all_support:
+            #if distinct or not self.dbhelper.intersect_all_support:
             return '\nINTERSECT\n'.join(sqls)
             #else:
             #    return '\nINTERSECT ALL\n'.join(sqls)
@@ -894,7 +985,13 @@
             except KeyError:
                 mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type)
                 if mapkey in self.attr_map:
-                    lhssql = self.attr_map[mapkey](self, lhs.variable, rel)
+                    cb, sourcecb = self.attr_map[mapkey]
+                    if sourcecb:
+                        # callback is a source callback, we can't use this
+                        # attribute in restriction
+                        raise QueryError("can't use %s (%s) in restriction"
+                                         % (mapkey, rel.as_string()))
+                    lhssql = cb(self, lhs.variable, rel)
                 elif rel.r_type == 'eid':
                     lhssql = lhs.variable._q_sql
                 else:
@@ -943,7 +1040,7 @@
             not_ = True
         else:
             not_ = False
-        return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args),
+        return self.dbhelper.fti_restriction_sql(alias, const.eval(self._args),
                                                     jointo, not_) + restriction
 
     def visit_comparison(self, cmp):
@@ -956,7 +1053,7 @@
             rhs = cmp.children[0]
         operator = cmp.operator
         if operator in ('IS', 'LIKE', 'ILIKE'):
-            if operator == 'ILIKE' and not self.dbms_helper.ilike_support:
+            if operator == 'ILIKE' and not self.dbhelper.ilike_support:
                 operator = ' LIKE '
             else:
                 operator = ' %s ' % operator
@@ -986,10 +1083,13 @@
 
     def visit_function(self, func):
         """generate SQL name for a function"""
-        # function_description will check function is supported by the backend
-        sqlname = self.dbms_helper.func_sqlname(func.name)
-        return '%s(%s)' % (sqlname, ', '.join(c.accept(self)
-                                              for c in func.children))
+        args = [c.accept(self) for c in func.children]
+        if func in self._state.source_cb_funcs:
+            # function executed as a callback on the source
+            assert len(args) == 1
+            return args[0]
+        # func_as_sql will check function is supported by the backend
+        return self.dbhelper.func_as_sql(func.name, args)
 
     def visit_constant(self, constant):
         """generate SQL name for a constant"""
@@ -1004,7 +1104,7 @@
                 rel._q_needcast = value
             return self.keyword_map[value]()
         if constant.type == 'Boolean':
-            value = self.dbms_helper.boolean_value(value)
+            value = self.dbhelper.boolean_value(value)
         if constant.type == 'Substitute':
             _id = constant.value
             if isinstance(_id, unicode):
@@ -1066,7 +1166,7 @@
                     self._state.add_restriction(restr)
             elif principal.r_type == 'has_text':
                 sql = '%s.%s' % (self._fti_table(principal),
-                                 self.dbms_helper.fti_uid_attr)
+                                 self.dbhelper.fti_uid_attr)
             elif principal in variable.stinfo['rhsrelations']:
                 if self.schema.rschema(principal.r_type).inlined:
                     sql = self._linked_var_sql(variable)
@@ -1156,12 +1256,20 @@
         if isinstance(linkedvar, ColumnAlias):
             raise BadRQLQuery('variable %s should be selected by the subquery'
                               % variable.name)
-        mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type)
-        if mapkey in self.attr_map:
-            return self.attr_map[mapkey](self, linkedvar, rel)
         try:
             sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)]
         except KeyError:
+            mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type)
+            if mapkey in self.attr_map:
+                cb, sourcecb = self.attr_map[mapkey]
+                if not sourcecb:
+                    return cb(self, linkedvar, rel)
+                # attribute mapped at the source level (bfss for instance)
+                stmt = rel.stmt
+                for selectidx, vref in iter_mapped_var_sels(stmt, variable):
+                    stack = [cb]
+                    update_source_cb_stack(self._state, stmt, vref, stack)
+                    self._state._needs_source_cb[selectidx] = stack
             linkedvar.accept(self)
             sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type)
         return sql
@@ -1268,7 +1376,7 @@
             except AttributeError:
                 pass
         self._state.done.add(relation)
-        alias = self.alias_and_add_table(self.dbms_helper.fti_table)
+        alias = self.alias_and_add_table(self.dbhelper.fti_table)
         relation._q_sqltable = alias
         return alias
 
--- a/server/sources/storages.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sources/storages.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,25 +1,60 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """custom storages for the system source"""
 from os import unlink, path as osp
 
+from yams.schema import role_name
+
 from cubicweb import Binary
 from cubicweb.server.hook import Operation
 
-
-ETYPE_ATTR_STORAGE = {}
 def set_attribute_storage(repo, etype, attr, storage):
-    ETYPE_ATTR_STORAGE.setdefault(etype, {})[attr] = storage
-    repo.system_source.map_attribute(etype, attr, storage.sqlgen_callback)
+    repo.system_source.set_storage(etype, attr, storage)
 
 def unset_attribute_storage(repo, etype, attr):
-    ETYPE_ATTR_STORAGE.setdefault(etype, {}).pop(attr, None)
-    repo.system_source.unmap_attribute(etype, attr)
-
+    repo.system_source.unset_storage(etype, attr)
 
 class Storage(object):
-    """abstract storage"""
-    def sqlgen_callback(self, generator, relation, linkedvar):
-        """sql generator callback when some attribute with a custom storage is
-        accessed
+    """abstract storage
+
+    * If `source_callback` is true (by default), the callback will be run during
+      query result process of fetched attribute's valu and should have the
+      following prototype::
+
+        callback(self, source, value)
+
+      where `value` is the value actually stored in the backend. None values
+      will be skipped (eg callback won't be called).
+
+    * if `source_callback` is false, the callback will be run during sql
+      generation when some attribute with a custom storage is accessed and
+      should have the following prototype::
+
+        callback(self, generator, relation, linkedvar)
+
+      where `generator` is the sql generator, `relation` the current rql syntax
+      tree relation and linkedvar the principal syntax tree variable holding the
+      attribute.
+    """
+    is_source_callback = True
+
+    def callback(self, *args):
+        """see docstring for prototype, which vary according to is_source_callback
         """
         raise NotImplementedError()
 
@@ -38,63 +73,97 @@
 # * better file path attribution
 # * handle backup/restore
 
+def uniquify_path(dirpath, basename):
+    """return a unique file name for `basename` in `dirpath`, or None
+    if all attemps failed.
+
+    XXX subject to race condition.
+    """
+    path = osp.join(dirpath, basename)
+    if not osp.isfile(path):
+        return path
+    base, ext = osp.splitext(path)
+    for i in xrange(1, 256):
+        path = '%s%s%s' % (base, i, ext)
+        if not osp.isfile(path):
+            return path
+    return None
+
 class BytesFileSystemStorage(Storage):
     """store Bytes attribute value on the file system"""
-    def __init__(self, defaultdir):
+    def __init__(self, defaultdir, fsencoding='utf-8'):
         self.default_directory = defaultdir
+        self.fsencoding = fsencoding
 
-    def sqlgen_callback(self, generator, linkedvar, relation):
+    def callback(self, source, value):
         """sql generator callback when some attribute with a custom storage is
         accessed
         """
-        linkedvar.accept(generator)
-        return '_fsopen(%s.cw_%s)' % (
-            linkedvar._q_sql.split('.', 1)[0], # table name
-            relation.r_type) # attribute name
+        fpath = source.binary_to_str(value)
+        try:
+            return Binary(file(fpath).read())
+        except OSError, ex:
+            source.critical("can't open %s: %s", value, ex)
+            return None
 
     def entity_added(self, entity, attr):
         """an entity using this storage for attr has been added"""
-        if not entity._cw.transaction_data.get('fs_importing'):
-            try:
-                value = entity.pop(attr)
-            except KeyError:
-                pass
-            else:
-                fpath = self.new_fs_path(entity, attr)
-                # bytes storage used to store file's path
-                entity[attr] = Binary(fpath)
-                file(fpath, 'w').write(value.getvalue())
-                AddFileOp(entity._cw, filepath=fpath)
-        # else entity[attr] is expected to be an already existant file path
+        if entity._cw.transaction_data.get('fs_importing'):
+            binary = Binary(file(entity[attr].getvalue()).read())
+        else:
+            binary = entity.pop(attr)
+            fpath = self.new_fs_path(entity, attr)
+            # bytes storage used to store file's path
+            entity[attr] = Binary(fpath)
+            file(fpath, 'w').write(binary.getvalue())
+            AddFileOp(entity._cw, filepath=fpath)
+        return binary
 
     def entity_updated(self, entity, attr):
         """an entity using this storage for attr has been updatded"""
-        try:
-            value = entity.pop(attr)
-        except KeyError:
-            pass
+        if entity._cw.transaction_data.get('fs_importing'):
+            oldpath = self.current_fs_path(entity, attr)
+            fpath = entity[attr].getvalue()
+            if oldpath != fpath:
+                DeleteFileOp(entity._cw, filepath=oldpath)
+            binary = Binary(file(fpath).read())
         else:
+            binary = entity.pop(attr)
             fpath = self.current_fs_path(entity, attr)
-            UpdateFileOp(entity._cw, filepath=fpath, filedata=value.getvalue())
+            UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue())
+        return binary
 
     def entity_deleted(self, entity, attr):
         """an entity using this storage for attr has been deleted"""
         DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
 
     def new_fs_path(self, entity, attr):
-        fspath = osp.join(self.default_directory, '%s_%s' % (entity.eid, attr))
-        while osp.exists(fspath):
-            fspath = '_' + fspath
+        # We try to get some hint about how to name the file using attribute's
+        # name metadata, so we use the real file name and extension when
+        # available. Keeping the extension is useful for example in the case of
+        # PIL processing that use filename extension to detect content-type, as
+        # well as providing more understandable file names on the fs.
+        basename = [str(entity.eid), attr]
+        name = entity.attr_metadata(attr, 'name')
+        if name is not None:
+            basename.append(name.encode(self.fsencoding))
+        fspath = uniquify_path(self.default_directory, '_'.join(basename))
+        if fspath is None:
+            msg = entity._cw._('failed to uniquify path (%s, %s)') % (
+                dirpath, '_'.join(basename))
+            raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
         return fspath
 
     def current_fs_path(self, entity, attr):
         sysource = entity._cw.pool.source('system')
         cu = sysource.doexec(entity._cw,
                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
-                                 attr, entity.__regid__, entity.eid))
-        dbmod = sysource.dbapi_module
-        return dbmod.process_value(cu.fetchone()[0], [None, dbmod.BINARY],
-                                   binarywrap=str)
+                             attr, entity.__regid__, entity.eid))
+        rawvalue = cu.fetchone()[0]
+        if rawvalue is None: # no previous value
+            return self.new_fs_path(entity, attr)
+        return sysource._process_value(rawvalue, cu.description[0],
+                                       binarywrap=str)
 
 
 class AddFileOp(Operation):
--- a/server/sqlutils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/sqlutils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """SQL utilities functions and classes.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -11,21 +24,17 @@
 import subprocess
 from datetime import datetime, date
 
-import logilab.common as lgc
-from logilab.common import db
+from logilab import database as db, common as lgc
 from logilab.common.shellutils import ProgressBar
-from logilab.common.adbh import get_adv_func_helper
-from logilab.common.sqlgen import SQLGenerator
 from logilab.common.date import todate, todatetime
-
-from indexer import get_indexer
+from logilab.database.sqlgen import SQLGenerator
 
 from cubicweb import Binary, ConfigurationError
 from cubicweb.uilib import remove_html_tags
 from cubicweb.schema import PURE_VIRTUAL_RTYPES
 from cubicweb.server import SQL_CONNECT_HOOKS
 from cubicweb.server.utils import crypt_password
-
+from rql.utils import RQL_FUNCTIONS_REGISTRY
 
 lgc.USE_MX_DATETIME = False
 SQL_PREFIX = 'cw_'
@@ -77,8 +86,8 @@
     w(native.grant_schema(user, set_owner))
     w('')
     if text_index:
-        indexer = get_indexer(driver)
-        w(indexer.sql_grant_user(user))
+        dbhelper = db.get_db_helper(driver)
+        w(dbhelper.sql_grant_user_on_fti(user))
         w('')
     w(grant_schema(schema, user, set_owner, skip_entities=skip_entities, prefix=SQL_PREFIX))
     return '\n'.join(output)
@@ -96,17 +105,17 @@
     w = output.append
     w(native.sql_schema(driver))
     w('')
+    dbhelper = db.get_db_helper(driver)
     if text_index:
-        indexer = get_indexer(driver)
-        w(indexer.sql_init_fti())
+        w(dbhelper.sql_init_fti().replace(';', ';;'))
         w('')
-    dbhelper = get_adv_func_helper(driver)
     w(schema2sql(dbhelper, schema, prefix=SQL_PREFIX,
-                 skip_entities=skip_entities, skip_relations=skip_relations))
+                 skip_entities=skip_entities,
+                 skip_relations=skip_relations).replace(';', ';;'))
     if dbhelper.users_support and user:
         w('')
         w(sqlgrants(schema, driver, user, text_index, set_owner,
-                    skip_relations, skip_entities))
+                    skip_relations, skip_entities).replace(';', ';;'))
     return '\n'.join(output)
 
 
@@ -117,15 +126,15 @@
     from cubicweb.server.sources import native
     output = []
     w = output.append
-    w(native.sql_drop_schema(driver))
-    w('')
     if text_index:
-        indexer = get_indexer(driver)
-        w(indexer.sql_drop_fti())
+        dbhelper = db.get_db_helper(driver)
+        w(dbhelper.sql_drop_fti())
         w('')
     w(dropschema2sql(schema, prefix=SQL_PREFIX,
                      skip_entities=skip_entities,
                      skip_relations=skip_relations))
+    w('')
+    w(native.sql_drop_schema(driver))
     return '\n'.join(output)
 
 
@@ -137,65 +146,44 @@
     def __init__(self, source_config):
         try:
             self.dbdriver = source_config['db-driver'].lower()
-            self.dbname = source_config['db-name']
+            dbname = source_config['db-name']
         except KeyError:
             raise ConfigurationError('missing some expected entries in sources file')
-        self.dbhost = source_config.get('db-host')
+        dbhost = source_config.get('db-host')
         port = source_config.get('db-port')
-        self.dbport = port and int(port) or None
-        self.dbuser = source_config.get('db-user')
-        self.dbpasswd = source_config.get('db-password')
-        self.encoding = source_config.get('db-encoding', 'UTF-8')
-        self.dbapi_module = db.get_dbapi_compliant_module(self.dbdriver)
-        self.dbdriver_extra_args = source_config.get('db-extra-arguments')
-        self.binary = self.dbapi_module.Binary
-        self.dbhelper = self.dbapi_module.adv_func_helper
+        dbport = port and int(port) or None
+        dbuser = source_config.get('db-user')
+        dbpassword = source_config.get('db-password')
+        dbencoding = source_config.get('db-encoding', 'UTF-8')
+        dbextraargs = source_config.get('db-extra-arguments')
+        self.dbhelper = db.get_db_helper(self.dbdriver)
+        self.dbhelper.record_connection_info(dbname, dbhost, dbport, dbuser,
+                                             dbpassword, dbextraargs,
+                                             dbencoding)
         self.sqlgen = SQLGenerator()
+        # copy back some commonly accessed attributes
+        dbapi_module = self.dbhelper.dbapi_module
+        self.OperationalError = dbapi_module.OperationalError
+        self.InterfaceError = dbapi_module.InterfaceError
+        self._binary = dbapi_module.Binary
+        self._process_value = dbapi_module.process_value
+        self._dbencoding = dbencoding
 
-    def get_connection(self, user=None, password=None):
+    def get_connection(self):
         """open and return a connection to the database"""
-        if user or self.dbuser:
-            self.info('connecting to %s@%s for user %s', self.dbname,
-                      self.dbhost or 'localhost', user or self.dbuser)
-        else:
-            self.info('connecting to %s@%s', self.dbname,
-                      self.dbhost or 'localhost')
-        extra = {}
-        if self.dbdriver_extra_args:
-            extra = {'extra_args': self.dbdriver_extra_args}
-        cnx = self.dbapi_module.connect(self.dbhost, self.dbname,
-                                        user or self.dbuser,
-                                        password or self.dbpasswd,
-                                        port=self.dbport,
-                                        **extra)
-        init_cnx(self.dbdriver, cnx)
-        #self.dbapi_module.type_code_test(cnx.cursor())
-        return cnx
+        return self.dbhelper.get_connection()
 
     def backup_to_file(self, backupfile, confirm):
-        for cmd in self.dbhelper.backup_commands(backupfile=backupfile,
-                                                 keepownership=False,
-                                                 dbname=self.dbname,
-                                                 dbhost=self.dbhost,
-                                                 dbuser=self.dbuser,
-                                                 dbport=self.dbport):
+        for cmd in self.dbhelper.backup_commands(backupfile,
+                                                 keepownership=False):
             if _run_command(cmd):
                 if not confirm('   [Failed] Continue anyway?', default='n'):
                     raise Exception('Failed command: %s' % cmd)
 
     def restore_from_file(self, backupfile, confirm, drop=True):
-        if 'dbencoding' in self.dbhelper.restore_commands.im_func.func_code.co_varnames:
-            kwargs = {'dbencoding': self.encoding}
-        else:
-            kwargs = {'encoding': self.encoding}
-        for cmd in self.dbhelper.restore_commands(backupfile=backupfile,
+        for cmd in self.dbhelper.restore_commands(backupfile,
                                                   keepownership=False,
-                                                  drop=drop,
-                                                  dbname=self.dbname,
-                                                  dbhost=self.dbhost,
-                                                  dbuser=self.dbuser,
-                                                  dbport=self.dbport,
-                                                  **kwargs):
+                                                  drop=drop):
             if _run_command(cmd):
                 if not confirm('   [Failed] Continue anyway?', default='n'):
                     raise Exception('Failed command: %s' % cmd)
@@ -206,20 +194,30 @@
             for key, val in args.iteritems():
                 # convert cubicweb binary into db binary
                 if isinstance(val, Binary):
-                    val = self.binary(val.getvalue())
+                    val = self._binary(val.getvalue())
                 newargs[key] = val
             # should not collide
             newargs.update(query_args)
             return newargs
         return query_args
 
-    def process_result(self, cursor):
+    def process_result(self, cursor, column_callbacks=None):
         """return a list of CubicWeb compliant values from data in the given cursor
         """
+        # use two different implementations to avoid paying the price of
+        # callback lookup for each *cell* in results when there is nothing to
+        # lookup
+        if not column_callbacks:
+            return self._process_result(cursor)
+        return self._cb_process_result(cursor, column_callbacks)
+
+    def _process_result(self, cursor, column_callbacks=None):
+        # begin bind to locals for optimization
         descr = cursor.description
-        encoding = self.encoding
-        process_value = self.dbapi_module.process_value
+        encoding = self._dbencoding
+        process_value = self._process_value
         binary = Binary
+        # /end
         results = cursor.fetchall()
         for i, line in enumerate(results):
             result = []
@@ -231,13 +229,38 @@
             results[i] = result
         return results
 
+    def _cb_process_result(self, cursor, column_callbacks):
+        # begin bind to locals for optimization
+        descr = cursor.description
+        encoding = self._dbencoding
+        process_value = self._process_value
+        binary = Binary
+        # /end
+        results = cursor.fetchall()
+        for i, line in enumerate(results):
+            result = []
+            for col, value in enumerate(line):
+                if value is None:
+                    result.append(value)
+                    continue
+                cbstack = column_callbacks.get(col, None)
+                if cbstack is None:
+                    value = process_value(value, descr[col], encoding, binary)
+                else:
+                    for cb in cbstack:
+                        value = cb(self, value)
+                result.append(value)
+            results[i] = result
+        return results
+
     def preprocess_entity(self, entity):
         """return a dictionary to use as extra argument to cursor.execute
         to insert/update an entity into a SQL database
         """
         attrs = {}
         eschema = entity.e_schema
-        for attr, value in entity.items():
+        for attr in entity.edited_attributes:
+            value = entity[attr]
             rschema = eschema.subjrels[attr]
             if rschema.final:
                 atype = str(entity.e_schema.destination(attr))
@@ -250,15 +273,16 @@
                         value = value.getvalue()
                     else:
                         value = crypt_password(value)
-                    value = self.binary(value)
+                    value = self._binary(value)
                 # XXX needed for sqlite but I don't think it is for other backends
                 elif atype == 'Datetime' and isinstance(value, date):
                     value = todatetime(value)
                 elif atype == 'Date' and isinstance(value, datetime):
                     value = todate(value)
                 elif isinstance(value, Binary):
-                    value = self.binary(value.getvalue())
+                    value = self._binary(value.getvalue())
             attrs[SQL_PREFIX+str(attr)] = value
+        attrs[SQL_PREFIX+'eid'] = entity.eid
         return attrs
 
 
@@ -267,12 +291,8 @@
 set_log_methods(SQLAdapterMixIn, getLogger('cubicweb.sqladapter'))
 
 def init_sqlite_connexion(cnx):
-    # XXX should not be publicly exposed
-    #def comma_join(strings):
-    #    return ', '.join(strings)
-    #cnx.create_function("COMMA_JOIN", 1, comma_join)
 
-    class concat_strings(object):
+    class group_concat(object):
         def __init__(self):
             self.values = []
         def step(self, value):
@@ -280,10 +300,7 @@
                 self.values.append(value)
         def finalize(self):
             return ', '.join(self.values)
-    # renamed to GROUP_CONCAT in cubicweb 2.45, keep old name for bw compat for
-    # some time
-    cnx.create_aggregate("CONCAT_STRINGS", 1, concat_strings)
-    cnx.create_aggregate("GROUP_CONCAT", 1, concat_strings)
+    cnx.create_aggregate("GROUP_CONCAT", 1, group_concat)
 
     def _limit_size(text, maxsize, format='text/plain'):
         if len(text) < maxsize:
@@ -301,37 +318,9 @@
     def limit_size2(text, maxsize):
         return _limit_size(text, maxsize)
     cnx.create_function("TEXT_LIMIT_SIZE", 2, limit_size2)
+
     import yams.constraints
-    if hasattr(yams.constraints, 'patch_sqlite_decimal'):
-        yams.constraints.patch_sqlite_decimal()
-
-    def fspath(eid, etype, attr):
-        try:
-            cu = cnx.cursor()
-            cu.execute('SELECT X.cw_%s FROM cw_%s as X '
-                       'WHERE X.cw_eid=%%(eid)s' % (attr, etype),
-                       {'eid': eid})
-            return cu.fetchone()[0]
-        except:
-            import traceback
-            traceback.print_exc()
-            raise
-    cnx.create_function('fspath', 3, fspath)
-
-    def _fsopen(fspath):
-        if fspath:
-            try:
-                return buffer(file(fspath).read())
-            except:
-                import traceback
-                traceback.print_exc()
-                raise
-    cnx.create_function('_fsopen', 1, _fsopen)
-
+    yams.constraints.patch_sqlite_decimal()
 
 sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
 sqlite_hooks.append(init_sqlite_connexion)
-
-def init_cnx(driver, cnx):
-    for hook in SQL_CONNECT_HOOKS.get(driver, ()):
-        hook(cnx)
--- a/server/ssplanner.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/ssplanner.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,20 +1,132 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """plan execution of rql queries on a single source
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
 __docformat__ = "restructuredtext en"
 
 from copy import copy
 
 from rql.stmts import Union, Select
-from rql.nodes import Constant
+from rql.nodes import Constant, Relation
 
 from cubicweb import QueryError, typed_eid
 from cubicweb.schema import VIRTUAL_RTYPES
 from cubicweb.rqlrewrite import add_types_restriction
+from cubicweb.server.session import security_enabled
+from cubicweb.server.hook import CleanupDeletedEidsCacheOp
+
+READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
+
+_CONSTANT = object()
+_FROM_SUBSTEP = object()
+
+def _extract_const_attributes(plan, rqlst, to_build):
+    """add constant values to entity def, mark variables to be selected
+    """
+    to_select = {}
+    for relation in rqlst.main_relations:
+        lhs, rhs = relation.get_variable_parts()
+        rtype = relation.r_type
+        if rtype in READ_ONLY_RTYPES:
+            raise QueryError("can't assign to %s" % rtype)
+        try:
+            edef = to_build[str(lhs)]
+        except KeyError:
+            # lhs var is not to build, should be selected and added as an
+            # object relation
+            edef = to_build[str(rhs)]
+            to_select.setdefault(edef, []).append((rtype, lhs, 1))
+        else:
+            if isinstance(rhs, Constant) and not rhs.uid:
+                # add constant values to entity def
+                value = rhs.eval(plan.args)
+                eschema = edef.e_schema
+                attrtype = eschema.subjrels[rtype].objects(eschema)[0]
+                if attrtype == 'Password' and isinstance(value, unicode):
+                    value = value.encode('UTF8')
+                edef[rtype] = value
+            elif to_build.has_key(str(rhs)):
+                # create a relation between two newly created variables
+                plan.add_relation_def((edef, rtype, to_build[rhs.name]))
+            else:
+                to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
+    return to_select
+
+def _extract_eid_consts(plan, rqlst):
+    """return a dict mapping rqlst variable object to their eid if specified in
+    the syntax tree
+    """
+    session = plan.session
+    if rqlst.where is None:
+        return {}
+    eidconsts = {}
+    neweids = session.transaction_data.get('neweids', ())
+    checkread = session.read_security
+    eschema = session.vreg.schema.eschema
+    for rel in rqlst.where.get_nodes(Relation):
+        if rel.r_type == 'eid' and not rel.neged(strict=True):
+            lhs, rhs = rel.get_variable_parts()
+            if isinstance(rhs, Constant):
+                eid = typed_eid(rhs.eval(plan.args))
+                # check read permission here since it may not be done by
+                # the generated select substep if not emited (eg nothing
+                # to be selected)
+                if checkread and eid not in neweids:
+                    with security_enabled(session, read=False):
+                        eschema(session.describe(eid)[0]).check_perm(
+                            session, 'read', eid=eid)
+                eidconsts[lhs.variable] = eid
+    return eidconsts
+
+def _build_substep_query(select, origrqlst):
+    """Finalize substep select query that should be executed to get proper
+    selection of stuff to insert/update.
+
+    Return None when no query actually needed, else the given select node that
+    will be used as substep query.
+
+    When select has nothing selected, search in origrqlst for restriction that
+    should be considered.
+    """
+    if select.selection:
+        if origrqlst.where is not None:
+            select.set_where(origrqlst.where.copy(select))
+        return select
+    if origrqlst.where is None:
+        return
+    for rel in origrqlst.where.iget_nodes(Relation):
+        # search for a relation which is neither a type restriction (is) nor an
+        # eid specification (not neged eid with constant node
+        if rel.neged(strict=True) or not (
+            rel.is_types_restriction() or
+            (rel.r_type == 'eid'
+             and isinstance(rel.get_variable_parts()[1], Constant))):
+            break
+    else:
+        return
+    select.set_where(origrqlst.where.copy(select))
+    if not select.selection:
+        # no selection, append one randomly
+        select.append_selected(rel.children[0].copy(select))
+    return select
 
 
 class SSPlanner(object):
@@ -56,34 +168,37 @@
             to_build[var.name] = etype_class(etype)(session)
             plan.add_entity_def(to_build[var.name])
         # add constant values to entity def, mark variables to be selected
-        to_select = plan.relation_definitions(rqlst, to_build)
+        to_select = _extract_const_attributes(plan, rqlst, to_build)
         # add necessary steps to add relations and update attributes
         step = InsertStep(plan) # insert each entity and its relations
-        step.children += self._compute_relation_steps(plan, rqlst.solutions,
-                                                      rqlst.where, to_select)
+        step.children += self._compute_relation_steps(plan, rqlst, to_select)
         return (step,)
 
-    def _compute_relation_steps(self, plan, solutions, restriction, to_select):
+    def _compute_relation_steps(self, plan, rqlst, to_select):
         """handle the selection of relations for an insert query"""
+        eidconsts = _extract_eid_consts(plan, rqlst)
         for edef, rdefs in to_select.items():
             # create a select rql st to fetch needed data
             select = Select()
             eschema = edef.e_schema
-            for i in range(len(rdefs)):
-                rtype, term, reverse = rdefs[i]
-                select.append_selected(term.copy(select))
+            for i, (rtype, term, reverse) in enumerate(rdefs):
+                if getattr(term, 'variable', None) in eidconsts:
+                    value = eidconsts[term.variable]
+                else:
+                    select.append_selected(term.copy(select))
+                    value = _FROM_SUBSTEP
                 if reverse:
-                    rdefs[i] = rtype, RelationsStep.REVERSE_RELATION
+                    rdefs[i] = (rtype, InsertRelationsStep.REVERSE_RELATION, value)
                 else:
                     rschema = eschema.subjrels[rtype]
                     if rschema.final or rschema.inlined:
-                        rdefs[i] = rtype, RelationsStep.FINAL
+                        rdefs[i] = (rtype, InsertRelationsStep.FINAL, value)
                     else:
-                        rdefs[i] = rtype, RelationsStep.RELATION
-            if restriction is not None:
-                select.set_where(restriction.copy(select))
-            step = RelationsStep(plan, edef, rdefs)
-            step.children += self._select_plan(plan, select, solutions)
+                        rdefs[i] = (rtype, InsertRelationsStep.RELATION, value)
+            step = InsertRelationsStep(plan, edef, rdefs)
+            select = _build_substep_query(select, rqlst)
+            if select is not None:
+                step.children += self._select_plan(plan, select, rqlst.solutions)
             yield step
 
     def build_delete_plan(self, plan, rqlst):
@@ -127,37 +242,61 @@
 
     def build_set_plan(self, plan, rqlst):
         """get an execution plan from an SET RQL query"""
-        select = Select()
-        # extract variables to add to the selection
-        selected_index = {}
-        index = 0
-        relations, attrrelations = [], []
         getrschema = self.schema.rschema
-        for relation in rqlst.main_relations:
+        select = Select()   # potential substep query
+        selectedidx = {}    # local state
+        attributes = set()  # edited attributes
+        updatedefs = []     # definition of update attributes/relations
+        selidx = residx = 0 # substep selection / resulting rset indexes
+        # search for eid const in the WHERE clause
+        eidconsts = _extract_eid_consts(plan, rqlst)
+        # build `updatedefs` describing things to update and add necessary
+        # variables to the substep selection
+        for i, relation in enumerate(rqlst.main_relations):
             if relation.r_type in VIRTUAL_RTYPES:
                 raise QueryError('can not assign to %r relation'
                                  % relation.r_type)
             lhs, rhs = relation.get_variable_parts()
-            if not lhs.as_string('utf-8') in selected_index:
-                select.append_selected(lhs.copy(select))
-                selected_index[lhs.as_string('utf-8')] = index
-                index += 1
-            if not rhs.as_string('utf-8') in selected_index:
-                select.append_selected(rhs.copy(select))
-                selected_index[rhs.as_string('utf-8')] = index
-                index += 1
+            lhskey = lhs.as_string('utf-8')
+            if not lhskey in selectedidx:
+                if lhs.variable in eidconsts:
+                    eid = eidconsts[lhs.variable]
+                    lhsinfo = (_CONSTANT, eid, residx)
+                else:
+                    select.append_selected(lhs.copy(select))
+                    lhsinfo = (_FROM_SUBSTEP, selidx, residx)
+                    selidx += 1
+                residx += 1
+                selectedidx[lhskey] = lhsinfo
+            else:
+                lhsinfo = selectedidx[lhskey][:-1] + (None,)
+            rhskey = rhs.as_string('utf-8')
+            if not rhskey in selectedidx:
+                if isinstance(rhs, Constant):
+                    rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx)
+                elif getattr(rhs, 'variable', None) in eidconsts:
+                    eid = eidconsts[rhs.variable]
+                    rhsinfo = (_CONSTANT, eid, residx)
+                else:
+                    select.append_selected(rhs.copy(select))
+                    rhsinfo = (_FROM_SUBSTEP, selidx, residx)
+                    selidx += 1
+                residx += 1
+                selectedidx[rhskey] = rhsinfo
+            else:
+                rhsinfo = selectedidx[rhskey][:-1] + (None,)
             rschema = getrschema(relation.r_type)
+            updatedefs.append( (lhsinfo, rhsinfo, rschema) )
             if rschema.final or rschema.inlined:
-                attrrelations.append(relation)
-            else:
-                relations.append(relation)
-        # add step necessary to fetch all selected variables values
-        if rqlst.where is not None:
-            select.set_where(rqlst.where.copy(select))
-        # set distinct to avoid potential duplicate key error
-        select.distinct = True
-        step = UpdateStep(plan, attrrelations, relations, selected_index)
-        step.children += self._select_plan(plan, select, rqlst.solutions)
+                attributes.add(relation.r_type)
+        # the update step
+        step = UpdateStep(plan, updatedefs, attributes)
+        # when necessary add substep to fetch yet unknown values
+        select = _build_substep_query(select, rqlst)
+        if select is not None:
+            # set distinct to avoid potential duplicate key error
+            select.distinct = True
+            step.children += self._select_plan(plan, select, rqlst.solutions)
         return (step,)
 
     # internal methods ########################################################
@@ -308,7 +447,7 @@
 
 # UPDATE/INSERT/DELETE steps ##################################################
 
-class RelationsStep(Step):
+class InsertRelationsStep(Step):
     """step consisting in adding attributes/relations to entity defs from a
     previous FetchStep
 
@@ -334,33 +473,38 @@
         """execute this step"""
         base_edef = self.edef
         edefs = []
-        result = self.execute_child()
+        if self.children:
+            result = self.execute_child()
+        else:
+            result = [[]]
         for row in result:
             # get a new entity definition for this row
             edef = copy(base_edef)
             # complete this entity def using row values
-            for i in range(len(self.rdefs)):
-                rtype, rorder = self.rdefs[i]
-                if rorder == RelationsStep.FINAL:
-                    edef[rtype] = row[i]
-                elif rorder == RelationsStep.RELATION:
-                    self.plan.add_relation_def( (edef, rtype, row[i]) )
-                    edef.querier_pending_relations[(rtype, 'subject')] = row[i]
+            index = 0
+            for rtype, rorder, value in self.rdefs:
+                if value is _FROM_SUBSTEP:
+                    value = row[index]
+                    index += 1
+                if rorder == InsertRelationsStep.FINAL:
+                    edef.rql_set_value(rtype, value)
+                elif rorder == InsertRelationsStep.RELATION:
+                    self.plan.add_relation_def( (edef, rtype, value) )
+                    edef.querier_pending_relations[(rtype, 'subject')] = value
                 else:
-                    self.plan.add_relation_def( (row[i], rtype, edef) )
-                    edef.querier_pending_relations[(rtype, 'object')] = row[i]
+                    self.plan.add_relation_def( (value, rtype, edef) )
+                    edef.querier_pending_relations[(rtype, 'object')] = value
             edefs.append(edef)
         self.plan.substitute_entity_def(base_edef, edefs)
         return result
 
-
 class InsertStep(Step):
     """step consisting in inserting new entities / relations"""
 
     def execute(self):
         """execute this step"""
         for step in self.children:
-            assert isinstance(step, RelationsStep)
+            assert isinstance(step, InsertRelationsStep)
             step.plan = self.plan
             step.execute()
         # insert entities first
@@ -377,11 +521,17 @@
     def execute(self):
         """execute this step"""
         results = self.execute_child()
-        todelete = frozenset(typed_eid(eid) for eid, in self.execute_child())
+        todelete = frozenset(typed_eid(eid) for eid, in results)
         session = self.plan.session
         delete = session.repo.glob_delete_entity
-        # register pending eids first to avoid multiple deletion
-        pending = session.transaction_data.setdefault('pendingeids', set())
+        # mark eids as being deleted in session info and setup cache update
+        # operation (register pending eids before actual deletion to avoid
+        # multiple call to glob_delete_entity)
+        try:
+            pending = session.transaction_data['pendingeids']
+        except KeyError:
+            pending = session.transaction_data['pendingeids'] = set()
+            CleanupDeletedEidsCacheOp(session)
         actual = todelete - pending
         pending |= actual
         for eid in actual:
@@ -408,40 +558,46 @@
     definitions and from results fetched in previous step
     """
 
-    def __init__(self, plan, attribute_relations, relations, selected_index):
+    def __init__(self, plan, updatedefs, attributes):
         Step.__init__(self, plan)
-        self.attribute_relations = attribute_relations
-        self.relations = relations
-        self.selected_index = selected_index
+        self.updatedefs = updatedefs
+        self.attributes = attributes
 
     def execute(self):
         """execute this step"""
-        plan = self.plan
         session = self.plan.session
         repo = session.repo
         edefs = {}
         # insert relations
-        attributes = set([relation.r_type for relation in self.attribute_relations])
-        result = self.execute_child()
-        for row in result:
-            for relation in self.attribute_relations:
-                lhs, rhs = relation.get_variable_parts()
-                eid = typed_eid(row[self.selected_index[str(lhs)]])
-                try:
-                    edef = edefs[eid]
-                except KeyError:
-                    edefs[eid] = edef = session.entity_from_eid(eid)
-                if isinstance(rhs, Constant):
-                    # add constant values to entity def
-                    value = rhs.eval(plan.args)
-                    edef[relation.r_type] = value
+        if self.children:
+            result = self.execute_child()
+        else:
+            result = [[]]
+        for i, row in enumerate(result):
+            newrow = []
+            for (lhsinfo, rhsinfo, rschema) in self.updatedefs:
+                lhsval = _handle_relterm(lhsinfo, row, newrow)
+                rhsval = _handle_relterm(rhsinfo, row, newrow)
+                if rschema.final or rschema.inlined:
+                    eid = typed_eid(lhsval)
+                    try:
+                        edef = edefs[eid]
+                    except KeyError:
+                        edefs[eid] = edef = session.entity_from_eid(eid)
+                    edef.rql_set_value(str(rschema), rhsval)
                 else:
-                    edef[relation.r_type] = row[self.selected_index[str(rhs)]]
-            for relation in self.relations:
-                subj = row[self.selected_index[str(relation.children[0])]]
-                obj = row[self.selected_index[str(relation.children[1])]]
-                repo.glob_add_relation(session, subj, relation.r_type, obj)
+                    repo.glob_add_relation(session, lhsval, str(rschema), rhsval)
+            result[i] = newrow
         # update entities
         for eid, edef in edefs.iteritems():
-            repo.glob_update_entity(session, edef, attributes)
+            repo.glob_update_entity(session, edef, set(self.attributes))
         return result
+
+def _handle_relterm(info, row, newrow):
+    if info[0] is _CONSTANT:
+        val = info[1]
+    else: # _FROM_SUBSTEP
+        val = row[info[1]]
+    if info[-1] is not None:
+        newrow.append(val)
+    return val
--- a/server/test/data/extern_mapping.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/extern_mapping.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 support_entities = {'Card': True, 'Affaire': True, 'State': True}
 support_relations = {'in_state': True, 'documented_by': True, 'multisource_inlined_rel': True}
--- a/server/test/data/hooks.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/hooks.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.server.hook import Hook
 
--- a/server/test/data/migratedapp/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/migratedapp/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- a/server/test/data/migratedapp/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/migratedapp/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, ObjectRelation,
--- a/server/test/data/migration/postcreate.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/migration/postcreate.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb post creation script, set note's workflow
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 wf = add_workflow(u'note workflow', 'Note')
--- a/server/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, ObjectRelation,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/site_cubicweb.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,36 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+"""
+
+from logilab.database import FunctionDescr
+from logilab.database.sqlite import register_sqlite_pyfunc
+from rql.utils import register_function
+
+try:
+    class DUMB_SORT(FunctionDescr):
+        supported_backends = ('sqlite',)
+
+    register_function(DUMB_SORT)
+    def dumb_sort(something):
+        return something
+    register_sqlite_pyfunc(dumb_sort)
+except:
+    # already registered
+    pass
--- a/server/test/data/site_erudi.py	Wed Mar 24 10:23:31 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-from logilab.common.adbh import FunctionDescr
-from rql.utils import register_function
-
-try:
-    class DUMB_SORT(FunctionDescr):
-        supported_backends = ('sqlite',)
-
-    register_function(DUMB_SORT)
-
-
-    def init_sqlite_connexion(cnx):
-        def dumb_sort(something):
-            return something
-        cnx.create_function("DUMB_SORT", 1, dumb_sort)
-
-    from cubicweb.server import sqlutils
-    sqlutils.SQL_CONNECT_HOOKS['sqlite'].append(init_sqlite_connexion)
-except:
-    # already registered
-    pass
--- a/server/test/unittest_checkintegrity.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_checkintegrity.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 from StringIO import StringIO
@@ -13,10 +26,9 @@
 
 from cubicweb.server.checkintegrity import check
 
-repo, cnx = init_test_database()
-
 class CheckIntegrityTC(TestCase):
     def test(self):
+        repo, cnx = init_test_database()
         sys.stderr = sys.stdout = StringIO()
         try:
             check(repo, cnx, ('entities', 'relations', 'text_index', 'metadata'),
@@ -24,6 +36,7 @@
         finally:
             sys.stderr = sys.__stderr__
             sys.stdout = sys.__stdout__
+        repo.shutdown()
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hook.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_hook.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit/functional tests for cubicweb.server.hook
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main, mock_object
@@ -69,6 +82,10 @@
 config.bootstrap_cubes()
 schema = config.load_schema()
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 class AddAnyHook(hook.Hook):
     __regid__ = 'addany'
     category = 'cat1'
@@ -77,7 +94,7 @@
         raise HookCalled()
 
 
-class HooksManagerTC(TestCase):
+class HooksRegistryTC(TestCase):
 
     def setUp(self):
         """ called before each test from this class """
@@ -88,29 +105,34 @@
         class _Hook(hook.Hook):
             events = ('before_add_entiti',)
         ex = self.assertRaises(Exception, self.o.register, _Hook)
-        self.assertEquals(str(ex), 'bad event before_add_entiti on unittest_hook._Hook')
+        self.assertEquals(str(ex), 'bad event before_add_entiti on %s._Hook' % __name__)
 
     def test_register_bad_hook2(self):
         class _Hook(hook.Hook):
             events = None
         ex = self.assertRaises(Exception, self.o.register, _Hook)
-        self.assertEquals(str(ex), 'bad .events attribute None on unittest_hook._Hook')
+        self.assertEquals(str(ex), 'bad .events attribute None on %s._Hook' % __name__)
 
     def test_register_bad_hook3(self):
         class _Hook(hook.Hook):
             events = 'before_add_entity'
         ex = self.assertRaises(Exception, self.o.register, _Hook)
-        self.assertEquals(str(ex), 'bad event b on unittest_hook._Hook')
+        self.assertEquals(str(ex), 'bad event b on %s._Hook' % __name__)
 
     def test_call_hook(self):
         self.o.register(AddAnyHook)
-        cw = mock_object(vreg=self.vreg)
-        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
-        self.o.call_hooks('before_delete_entity', cw) # nothing to call
-        config.disabled_hooks_categories.add('cat1')
+        dis = set()
+        cw = mock_object(vreg=self.vreg,
+                         set_read_security=lambda *a,**k: None,
+                         set_write_security=lambda *a,**k: None,
+                         is_hook_activated=lambda x, cls: cls.category not in dis)
+        self.assertRaises(HookCalled,
+                          self.o.call_hooks, 'before_add_entity', cw)
+        dis.add('cat1')
         self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
-        config.disabled_hooks_categories.remove('cat1')
-        self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+        dis.remove('cat1')
+        self.assertRaises(HookCalled,
+                          self.o.call_hooks, 'before_add_entity', cw)
         self.o.unregister(AddAnyHook)
         self.o.call_hooks('before_add_entity', cw) # nothing to call
 
--- a/server/test/unittest_ldapuser.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_ldapuser.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.server.sources.ldapusers unit and functional tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import socket
@@ -15,7 +28,7 @@
 
 from cubicweb.server.sources.ldapuser import *
 
-if socket.gethostbyname('ldap1').startswith('172'):
+if '17.1' in socket.gethostbyname('ldap1'):
     SYT = 'syt'
     ADIM = 'adim'
 else:
@@ -189,7 +202,7 @@
         self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
 
     def test_multiple_entities_from_different_sources(self):
-        self.create_user('cochon', req=self.session)
+        self.create_user('cochon')
         self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}))
 
     def test_exists1(self):
@@ -202,15 +215,15 @@
         self.assertEquals(rset.rows, [['admin', 'activated'], [SYT, 'activated']])
 
     def test_exists2(self):
-        self.create_user('comme', req=self.session)
-        self.create_user('cochon', req=self.session)
+        self.create_user('comme')
+        self.create_user('cochon')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
         rset = self.sexecute('Any GN ORDERBY GN WHERE X in_group G, G name GN, (G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))')
         self.assertEquals(rset.rows, [['managers'], ['users']])
 
     def test_exists3(self):
-        self.create_user('comme', req=self.session)
-        self.create_user('cochon', req=self.session)
+        self.create_user('comme')
+        self.create_user('cochon')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
         self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
         self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
@@ -219,9 +232,9 @@
         self.assertEquals(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', SYT]])
 
     def test_exists4(self):
-        self.create_user('comme', req=self.session)
-        self.create_user('cochon', groups=('users', 'guests'), req=self.session)
-        self.create_user('billy', req=self.session)
+        self.create_user('comme')
+        self.create_user('cochon', groups=('users', 'guests'))
+        self.create_user('billy')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
         self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
@@ -241,9 +254,9 @@
         self.assertEquals(sorted(rset.rows), sorted(all.rows))
 
     def test_exists5(self):
-        self.create_user('comme', req=self.session)
-        self.create_user('cochon', groups=('users', 'guests'), req=self.session)
-        self.create_user('billy', req=self.session)
+        self.create_user('comme')
+        self.create_user('cochon', groups=('users', 'guests'))
+        self.create_user('billy')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
         self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
         self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
@@ -273,7 +286,7 @@
                           sorted(r[0] for r in afeids + ueids))
 
     def _init_security_test(self):
-        self.create_user('iaminguestsgrouponly', groups=('guests',), req=self.session)
+        self.create_user('iaminguestsgrouponly', groups=('guests',))
         cnx = self.login('iaminguestsgrouponly')
         return cnx.cursor()
 
@@ -370,6 +383,11 @@
 LDAPUserSourceTC._init_repo()
 repo = LDAPUserSourceTC.repo
 
+def teardown_module(*args):
+    global repo
+    del repo
+    del RQL2LDAPFilterTC.schema
+
 class RQL2LDAPFilterTC(RQLGeneratorTC):
     schema = repo.schema
 
--- a/server/test/unittest_migractions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_migractions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,5 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.migractions
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from copy import deepcopy
@@ -14,6 +30,11 @@
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.migractions import *
 
+migrschema = None
+def teardown_module(*args):
+    global migrschema
+    del migrschema
+    del MigrationCommandsTC.origschema
 
 class MigrationCommandsTC(CubicWebTC):
 
@@ -35,6 +56,13 @@
     def _refresh_repo(cls):
         super(MigrationCommandsTC, cls)._refresh_repo()
         cls.repo.set_schema(deepcopy(cls.origschema), resetvreg=False)
+        # reset migration schema eids
+        for eschema in migrschema.entities():
+            eschema.eid = None
+        for rschema in migrschema.relations():
+            rschema.eid = None
+            for rdef in rschema.rdefs.values():
+                rdef.eid = None
 
     def setUp(self):
         CubicWebTC.setUp(self)
@@ -44,7 +72,6 @@
         assert self.cnx is self.mh._cnx
         assert self.session is self.mh.session, (self.session.id, self.mh.session.id)
 
-
     def test_add_attribute_int(self):
         self.failIf('whatever' in self.schema)
         self.request().create_entity('Note')
--- a/server/test/unittest_msplanner.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_msplanner.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools import init_test_database
 from cubicweb.devtools.repotest import BasePlannerTC, test_plan
@@ -60,6 +73,11 @@
 # keep cnx so it's not garbage collected and the associated session is closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class BaseMSPlannerTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
 
@@ -87,10 +105,10 @@
         self.add_source(FakeCardSource, 'cards')
 
     def tearDown(self):
-        super(BaseMSPlannerTC, self).tearDown()
         # restore hijacked security
         self.restore_orig_affaire_security()
         self.restore_orig_cwuser_security()
+        super(BaseMSPlannerTC, self).tearDown()
 
     def restore_orig_affaire_security(self):
         affreadperms = list(self.schema['Affaire'].permissions['read'])
@@ -620,7 +638,6 @@
         2. return the result of Any X,Y WHERE X login 'syt', Y login 'adim'
            on the system source
         """
-        ueid = self.session.user.eid
         self._test('Any X,Y LIMIT 10 OFFSET 10 WHERE X login "syt", Y login "adim"',
                    [('FetchStep',
                      [('Any X WHERE X login "syt", X is CWUser', [{'X': 'CWUser'}])],
@@ -632,7 +649,7 @@
                      [('Any X,Y LIMIT 10 OFFSET 10 WHERE X is CWUser, Y is CWUser', [{'X': 'CWUser', 'Y': 'CWUser'}])],
                      10, 10, [self.system],
                      {'X': 'table0.C0', 'Y': 'table1.C0'}, [])
-                    ], {'x': ueid})
+                    ])
 
     def test_complex_aggregat(self):
         self._test('Any MAX(X)',
@@ -1005,7 +1022,7 @@
         self.session = self.user_groups_session('guests')
         self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L',
                    [('FetchStep',
-                     [('Any U,L WHERE U identity 5, U login L, U is CWUser',
+                     [('Any U,L WHERE U login L, EXISTS(U identity 5), U is CWUser',
                        [{'L': 'String', u'U': 'CWUser'}])],
                      [self.system], {}, {'L': 'table0.C1', 'U': 'table0.C0', 'U.login': 'table0.C1'}, []),
                     ('FetchStep',
@@ -1412,7 +1429,7 @@
                      [])],
                    {'E': self.session.user.eid})
 
-    def test_eid_dont_cross_relation(self):
+    def test_eid_dont_cross_relation_1(self):
         repo._type_source_cache[999999] = ('Personne', 'system', 999999)
         self._test('Any Y,YT WHERE X eid %(x)s, X fiche Y, Y title YT',
                    [('OneFetchStep', [('Any Y,YT WHERE X eid 999999, X fiche Y, Y title YT',
@@ -1420,6 +1437,18 @@
                      None, None, [self.system], {}, [])],
                    {'x': 999999})
 
+    def test_eid_dont_cross_relation_2(self):
+        repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+        self.cards.dont_cross_relations.add('concerne')
+        try:
+            self._test('Any Y,S,YT,X WHERE Y concerne X, Y in_state S, X eid 999999, Y ref YT',
+                   [('OneFetchStep', [('Any Y,S,YT,999999 WHERE Y concerne 999999, Y in_state S, Y ref YT',
+                                       [{'Y': 'Affaire', 'YT': 'String', 'S': 'State'}])],
+                     None, None, [self.system], {}, [])],
+                   {'x': 999999})
+        finally:
+            self.cards.dont_cross_relations.remove('concerne')
+
 
     # external source w/ .cross_relations == ['multisource_crossed_rel'] ######
 
@@ -1517,15 +1546,11 @@
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type T WHERE S eid %(s)s, N eid %(n)s, N type T',
-                   [('FetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
-                                    [{'N': 'Note', 'T': 'String'}])],
-                     [self.cards], None, {'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
-                    ('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T WHERE N type T, N is Note',
+                   [('InsertStep',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
-                        None, None, [self.system],
-                        {'N.type': 'table0.C0', 'T': 'table0.C0'}, [])])
+                        None, None, [self.cards], {}, [])])
                       ])
                     ],
                    {'n': 999999, 's': 999998})
@@ -1534,15 +1559,11 @@
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type T, X migrated_from N WHERE S eid %(s)s, N eid %(n)s, N type T',
-                   [('FetchStep', [('Any T,N WHERE N eid 999999, N type T, N is Note',
-                                    [{'N': 'Note', 'T': 'String'}])],
-                     [self.cards], None, {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
-                    ('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T,N WHERE N type T, N is Note',
+                   [('InsertStep',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
-                         None, None, [self.system],
-                         {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, [])
+                         None, None, [self.cards], {}, [])
                         ])
                       ])
                     ],
@@ -1553,8 +1574,8 @@
         repo._type_source_cache[999998] = ('State', 'cards', 999998)
         self._test('INSERT Note X: X in_state S, X type T WHERE S eid %(s)s, N eid %(n)s, N type T',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T WHERE N eid 999999, N type T, N is Note',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
                          None, None, [self.cards], {}, [])]
                        )]
@@ -1566,10 +1587,7 @@
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type "bla", X migrated_from N WHERE S eid %(s)s, N eid %(n)s',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,999999', [{}])],
-                         None, None, [self.system], {}, [])]
-                       )]
+                      [('InsertRelationsStep', [])]
                      )],
                    {'n': 999999, 's': 999998})
 
@@ -1578,12 +1596,14 @@
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type "bla", X migrated_from N WHERE S eid %(s)s, N eid %(n)s, A concerne N',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,999999 WHERE A concerne 999999, A is Affaire',
-                                           [{'A': 'Affaire'}])],
-                         None, None, [self.system], {}, [])]
-                       )]
-                     )],
+                     [('InsertRelationsStep',
+                       [('OneFetchStep',
+                         [('Any A WHERE A concerne 999999, A is Affaire',
+                           [{'A': 'Affaire'}])],
+                         None, None, [self.system], {}, []),
+                        ]),
+                      ])
+                    ],
                    {'n': 999999, 's': 999998})
 
     def test_delete_relation1(self):
@@ -1664,7 +1684,7 @@
         # source, states should only be searched in the system source as well
         self._test('SET X in_state S WHERE X eid %(x)s, S name "deactivated"',
                    [('UpdateStep', [
-                       ('OneFetchStep', [('DISTINCT Any 5,S WHERE S name "deactivated", S is State',
+                       ('OneFetchStep', [('DISTINCT Any S WHERE S name "deactivated", S is State',
                                           [{'S': 'State'}])],
                         None, None, [self.system], {}, []),
                        ]),
@@ -1814,7 +1834,7 @@
                    [('FetchStep', [('Any Y WHERE Y multisource_rel 999998, Y is Note', [{'Y': 'Note'}])],
                      [self.cards], None, {'Y': u'table0.C0'}, []),
                     ('UpdateStep',
-                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y migrated_from 999998, Y is Note',
+                     [('OneFetchStep', [('DISTINCT Any Y WHERE Y migrated_from 999998, Y is Note',
                                          [{'Y': 'Note'}])],
                        None, None, [self.system],
                        {'Y': u'table0.C0'}, [])])],
@@ -1841,14 +1861,9 @@
     def test_nonregr11(self):
         repo._type_source_cache[999999] = ('Bookmark', 'system', 999999)
         self._test('SET X bookmarked_by Y WHERE X eid %(x)s, Y login "hop"',
-                   [('FetchStep',
-                     [('Any Y WHERE Y login "hop", Y is CWUser', [{'Y': 'CWUser'}])],
-                     [self.ldap, self.system],
-                     None, {'Y': 'table0.C0'}, []),
-                    ('UpdateStep',
-                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y is CWUser', [{'Y': 'CWUser'}])],
-                       None, None, [self.system], {'Y': 'table0.C0'},
-                       [])]
+                   [('UpdateStep',
+                     [('OneFetchStep', [('DISTINCT Any Y WHERE Y login "hop", Y is CWUser', [{'Y': 'CWUser'}])],
+                       None, None, [self.ldap, self.system], {}, [])]
                      )],
                    {'x': 999999})
 
--- a/server/test/unittest_multisources.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_multisources.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from os.path import dirname, join, abspath
 from datetime import datetime, timedelta
@@ -48,7 +61,12 @@
 def teardown_module(*args):
     PyroRQLSource.get_connection = PyroRQLSource_get_connection
     Connection.close = Connection_close
-
+    global repo2, cnx2, repo3, cnx3
+    repo2.shutdown()
+    repo3.shutdown()
+    del repo2, cnx2, repo3, cnx3
+    #del TwoSourcesTC.config.vreg
+    #del TwoSourcesTC.config
 
 class TwoSourcesTC(CubicWebTC):
     config = TwoSourcesConfiguration('data')
@@ -130,7 +148,7 @@
         cu = cnx.cursor()
         rset = cu.execute('Any X WHERE X has_text "card"')
         self.assertEquals(len(rset), 5, zip(rset.rows, rset.description))
-        cnx.close()
+        Connection_close(cnx)
 
     def test_synchronization(self):
         cu = cnx2.cursor()
--- a/server/test/unittest_querier.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_querier.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,6 +1,22 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.querier and cubicweb.server.querier_steps
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from datetime import date, datetime
 
@@ -35,7 +51,7 @@
 SQL_CONNECT_HOOKS['sqlite'].append(init_sqlite_connexion)
 
 
-from logilab.common.adbh import _GenericAdvFuncHelper
+from logilab.database import _GenericAdvFuncHelper
 TYPEMAP = _GenericAdvFuncHelper.TYPE_MAPPING
 
 class MakeSchemaTC(TestCase):
@@ -48,6 +64,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    cnx.close()
+    repo.shutdown()
+    del repo, cnx
 
 
 class UtilsTC(BaseQuerierTC):
@@ -392,6 +413,18 @@
         rset = self.execute('Note X WHERE NOT Y evaluee X')
         self.assertEquals(len(rset.rows), 1, rset.rows)
 
+    def test_select_date_extraction(self):
+        self.execute("INSERT Personne X: X nom 'foo', X datenaiss %(d)s",
+                     {'d': datetime(2001, 2,3, 12,13)})
+        test_data = [('YEAR', 2001), ('MONTH', 2), ('DAY', 3),
+                     ('HOUR', 12), ('MINUTE', 13)]
+        for funcname, result in test_data:
+            rset = self.execute('Any %s(D) WHERE X is Personne, X datenaiss D'
+                                % funcname)
+            self.assertEquals(len(rset.rows), 1)
+            self.assertEquals(rset.rows[0][0], result)
+            self.assertEquals(rset.description, [('Int',)])
+
     def test_select_aggregat_count(self):
         rset = self.execute('Any COUNT(X)')
         self.assertEquals(len(rset.rows), 1)
@@ -425,7 +458,7 @@
         self.assertEquals(rset.description, [('Int',)])
 
     def test_select_custom_aggregat_concat_string(self):
-        rset = self.execute('Any CONCAT_STRINGS(N) WHERE X is CWGroup, X name N')
+        rset = self.execute('Any GROUP_CONCAT(N) WHERE X is CWGroup, X name N')
         self.failUnless(rset)
         self.failUnlessEqual(sorted(rset[0][0].split(', ')), ['guests', 'managers',
                                                              'owners', 'users'])
@@ -1023,6 +1056,10 @@
                       {'x': str(eid1), 'y': str(eid2)})
         rset = self.execute('Any X, Y WHERE X travaille Y')
         self.assertEqual(len(rset.rows), 1)
+        # test add of an existant relation but with NOT X rel Y protection
+        self.failIf(self.execute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s,"
+                                 "NOT X travaille Y",
+                                 {'x': str(eid1), 'y': str(eid2)}))
 
     def test_update_2ter(self):
         rset = self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'")
--- a/server/test/unittest_repository.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_repository.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,27 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.repository
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+
+from __future__ import with_statement
 
 import os
 import sys
@@ -20,12 +36,14 @@
 
 from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
                       UnknownEid, AuthenticationError)
+from cubicweb.selectors import implements
 from cubicweb.schema import CubicWebSchema, RQLConstraint
-from cubicweb.dbapi import connect, repo_connect, multiple_connections_unfix
+from cubicweb.dbapi import connect, multiple_connections_unfix
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
 from cubicweb.server import repository, hook
 from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.hook import Hook
 from cubicweb.server.sources import native
 
 # start name server anyway, process will fail if already running
@@ -38,25 +56,29 @@
     """
 
     def test_fill_schema(self):
-        self.repo.schema = CubicWebSchema(self.repo.config.appid)
-        self.repo.config._cubes = None # avoid assertion error
-        self.repo.config.repairing = True # avoid versions checking
-        self.repo.fill_schema()
-        table = SQL_PREFIX + 'CWEType'
-        namecol = SQL_PREFIX + 'name'
-        finalcol = SQL_PREFIX + 'final'
-        self.session.set_pool()
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
-            namecol, table, finalcol))
-        self.assertEquals(cu.fetchall(), [])
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
-                          % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
-        self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
-                                          (u'Date',), (u'Datetime',),
-                                          (u'Decimal',),(u'Float',),
-                                          (u'Int',),
-                                          (u'Interval',), (u'Password',),
-                                          (u'String',), (u'Time',)])
+        origshema = self.repo.schema
+        try:
+            self.repo.schema = CubicWebSchema(self.repo.config.appid)
+            self.repo.config._cubes = None # avoid assertion error
+            self.repo.config.repairing = True # avoid versions checking
+            self.repo.fill_schema()
+            table = SQL_PREFIX + 'CWEType'
+            namecol = SQL_PREFIX + 'name'
+            finalcol = SQL_PREFIX + 'final'
+            self.session.set_pool()
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
+                namecol, table, finalcol))
+            self.assertEquals(cu.fetchall(), [])
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+                                         % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
+            self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
+                                              (u'Date',), (u'Datetime',),
+                                              (u'Decimal',),(u'Float',),
+                                              (u'Int',),
+                                              (u'Interval',), (u'Password',),
+                                              (u'String',), (u'Time',)])
+        finally:
+            self.repo.set_schema(origshema)
 
     def test_schema_has_owner(self):
         repo = self.repo
@@ -180,7 +202,9 @@
         repo = self.repo
         cnxid = repo.connect(self.admlogin, password=self.admpassword)
         # rollback state change which trigger TrInfo insertion
-        user = repo._get_session(cnxid).user
+        session = repo._get_session(cnxid)
+        session.set_pool()
+        user = session.user
         user.fire_transition('deactivate')
         rset = repo.execute(cnxid, 'TrInfo T WHERE T wf_info_for X, X eid %(x)s', {'x': user.eid})
         self.assertEquals(len(rset), 1)
@@ -263,9 +287,13 @@
                 self.fail('something went wrong, thread still alive')
         finally:
             repository.pyro_unregister(self.repo.config)
+            from logilab.common import pyro_ext
+            pyro_ext._DAEMONS.clear()
+
 
     def _pyro_client(self, done):
-        cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
+        cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
+                      initlog=False) # don't reset logging configuration
         try:
             # check we can get the schema
             schema = cnx.get_schema()
@@ -275,7 +303,7 @@
             cnx.close()
             done.append(True)
         finally:
-            # connect monkey path some method by default, remove them
+            # connect monkey patch some method by default, remove them
             multiple_connections_unfix()
 
     def test_internal_api(self):
@@ -349,6 +377,64 @@
         self.assertEquals(rset.rows[0][0], p2.eid)
 
 
+    def test_set_attributes_in_before_update(self):
+        # local hook
+        class DummyBeforeHook(Hook):
+            __regid__ = 'dummy-before-hook'
+            __select__ = Hook.__select__ & implements('EmailAddress')
+            events = ('before_update_entity',)
+            def __call__(self):
+                # safety belt: avoid potential infinite recursion if the test
+                #              fails (i.e. RuntimeError not raised)
+                pendings = self._cw.transaction_data.setdefault('pending', set())
+                if self.entity.eid not in pendings:
+                    pendings.add(self.entity.eid)
+                    self.entity.set_attributes(alias=u'foo')
+        with self.temporary_appobjects(DummyBeforeHook):
+            req = self.request()
+            addr = req.create_entity('EmailAddress', address=u'a@b.fr')
+            addr.set_attributes(address=u'a@b.com')
+            rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
+                                {'x': addr.eid})
+            self.assertEquals(rset.rows, [[u'a@b.com', u'foo']])
+
+    def test_set_attributes_in_before_add(self):
+        # local hook
+        class DummyBeforeHook(Hook):
+            __regid__ = 'dummy-before-hook'
+            __select__ = Hook.__select__ & implements('EmailAddress')
+            events = ('before_add_entity',)
+            def __call__(self):
+                # set_attributes is forbidden within before_add_entity()
+                self.entity.set_attributes(alias=u'foo')
+        with self.temporary_appobjects(DummyBeforeHook):
+            req = self.request()
+            # XXX will fail with python -O
+            self.assertRaises(AssertionError, req.create_entity,
+                              'EmailAddress', address=u'a@b.fr')
+
+    def test_multiple_edit_set_attributes(self):
+        """make sure edited_attributes doesn't get cluttered
+        by previous entities on multiple set
+        """
+        # local hook
+        class DummyBeforeHook(Hook):
+            _test = self # keep reference to test instance
+            __regid__ = 'dummy-before-hook'
+            __select__ = Hook.__select__ & implements('Affaire')
+            events = ('before_update_entity',)
+            def __call__(self):
+                # invoiced attribute shouldn't be considered "edited" before the hook
+                self._test.failIf('invoiced' in self.entity.edited_attributes,
+                                  'edited_attributes cluttered by previous update')
+                self.entity['invoiced'] = 10
+        with self.temporary_appobjects(DummyBeforeHook):
+            req = self.request()
+            req.create_entity('Affaire', ref=u'AFF01')
+            req.create_entity('Affaire', ref=u'AFF02')
+            req.execute('SET A duration 10 WHERE A is Affaire')
+
+
 class DataHelpersTC(CubicWebTC):
 
     def test_create_eid(self):
@@ -377,14 +463,14 @@
         entity.eid = -1
         entity.complete = lambda x: None
         self.session.set_pool()
-        self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system'])
+        self.repo.add_info(self.session, entity, self.repo.system_source)
         cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
         data = cu.fetchall()
         self.assertIsInstance(data[0][3], datetime)
         data[0] = list(data[0])
         data[0][3] = None
         self.assertEquals(tuplify(data), [(-1, 'Personne', 'system', None, None)])
-        self.repo.delete_info(self.session, -1)
+        self.repo.delete_info(self.session, entity, 'system', None)
         #self.repo.commit()
         cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
         data = cu.fetchall()
@@ -394,6 +480,7 @@
 class FTITC(CubicWebTC):
 
     def test_reindex_and_modified_since(self):
+        self.repo.system_source.multisources_etypes.add('Personne')
         eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
         self.commit()
         ts = datetime.now()
@@ -470,20 +557,12 @@
                            u'system.version.tag'])
 
 CALLED = []
-class EcritParHook(hook.Hook):
-    __regid__ = 'inlinedrelhook'
-    __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
-    events = ('before_add_relation', 'after_add_relation',
-              'before_delete_relation', 'after_delete_relation')
-    def __call__(self):
-        CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
 
 class InlineRelHooksTC(CubicWebTC):
     """test relation hooks are called for inlined relations
     """
     def setUp(self):
         CubicWebTC.setUp(self)
-        self.hm = self.repo.hm
         CALLED[:] = ()
 
     def _after_relation_hook(self, pool, fromeid, rtype, toeid):
@@ -491,20 +570,28 @@
 
     def test_inline_relation(self):
         """make sure <event>_relation hooks are called for inlined relation"""
-        self.hm.register(EcritParHook)
-        eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
-        eidn = self.execute('INSERT Note X: X type "T"')[0][0]
-        self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
-        CALLED[:] = ()
-        self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
-        self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_delete_relation', eidn, 'ecrit_par', eidp)])
-        CALLED[:] = ()
-        eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
-        self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
-                                   ('after_add_relation', eidn, 'ecrit_par', eidp)])
+        class EcritParHook(hook.Hook):
+            __regid__ = 'inlinedrelhook'
+            __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
+            events = ('before_add_relation', 'after_add_relation',
+                      'before_delete_relation', 'after_delete_relation')
+            def __call__(self):
+                CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
+
+        with self.temporary_appobjects(EcritParHook):
+            eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
+            eidn = self.execute('INSERT Note X: X type "T"')[0][0]
+            self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
+            self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_add_relation', eidn, 'ecrit_par', eidp)])
+            CALLED[:] = ()
+            self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
+            self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_delete_relation', eidn, 'ecrit_par', eidp)])
+            CALLED[:] = ()
+            eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
+            self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+                                       ('after_add_relation', eidn, 'ecrit_par', eidp)])
 
 
 if __name__ == '__main__':
--- a/server/test/unittest_rql2sql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_rql2sql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 """unit tests for module cubicweb.server.sources.rql2sql"""
@@ -13,7 +26,6 @@
 from logilab.common.testlib import TestCase, unittest_main, mock_object
 
 from rql import BadRQLQuery
-from indexer import get_indexer
 
 #from cubicweb.server.sources.native import remove_unused_solutions
 from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions
@@ -37,6 +49,10 @@
 schema['state_of'].inlined = False
 schema['comments'].inlined = False
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 PARSER = [
     (r"Personne P WHERE P nom 'Zig\'oto';",
      '''SELECT _P.cw_eid
@@ -540,13 +556,10 @@
 ORDER BY 4 DESC'''),
 
 
-    ("Any X WHERE X eid 0, X eid 0",
-     '''SELECT 0'''),
-
-    ("Any X WHERE X eid 0, X eid 0, X test TRUE",
+    ("Any X WHERE X eid 0, X test TRUE",
      '''SELECT _X.cw_eid
 FROM cw_Personne AS _X
-WHERE _X.cw_eid=0 AND _X.cw_eid=0 AND _X.cw_test=TRUE'''),
+WHERE _X.cw_eid=0 AND _X.cw_test=TRUE'''),
 
     ("Any X,GROUP_CONCAT(TN) GROUPBY X ORDERBY XN WHERE T tags X, X name XN, T name TN, X is CWGroup",
      '''SELECT _X.cw_eid, GROUP_CONCAT(_T.cw_name)
@@ -1068,7 +1081,7 @@
 WHERE rel_is0.eid_to=2'''),
 
     ]
-from logilab.common.adbh import get_adv_func_helper
+from logilab.database import get_db_helper
 
 class CWRQLTC(RQLGeneratorTC):
     schema = schema
@@ -1102,13 +1115,8 @@
     #capture = True
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('postgres', 'utf8')
-        dbms_helper = get_adv_func_helper('postgres')
-        dbms_helper.fti_uid_attr = indexer.uid_attr
-        dbms_helper.fti_table = indexer.table
-        dbms_helper.fti_restriction_sql = indexer.restriction_sql
-        dbms_helper.fti_need_distinct_query = indexer.need_distinct
-        self.o = SQLGenerator(schema, dbms_helper)
+        dbhelper = get_db_helper('postgres')
+        self.o = SQLGenerator(schema, dbhelper)
 
     def _norm_sql(self, sql):
         return sql.strip()
@@ -1118,8 +1126,8 @@
             args = {'text': 'hip hop momo'}
         try:
             union = self._prepare(rql)
-            r, nargs = self.o.generate(union, args,
-                                      varmap=varmap)
+            r, nargs, cbs = self.o.generate(union, args,
+                                            varmap=varmap)
             args.update(nargs)
             self.assertLinesEquals((r % args).strip(), self._norm_sql(sql), striplines=True)
         except Exception, ex:
@@ -1140,7 +1148,7 @@
     def _checkall(self, rql, sql):
         try:
             rqlst = self._prepare(rql)
-            r, args = self.o.generate(rqlst)
+            r, args, cbs = self.o.generate(rqlst)
             self.assertEqual((r.strip(), args), sql)
         except Exception, ex:
             print rql
@@ -1202,12 +1210,19 @@
 
     def test_is_null_transform(self):
         union = self._prepare('Any X WHERE X login %(login)s')
-        r, args = self.o.generate(union, {'login': None})
+        r, args, cbs = self.o.generate(union, {'login': None})
         self.assertLinesEquals((r % args).strip(),
                                '''SELECT _X.cw_eid
 FROM cw_CWUser AS _X
 WHERE _X.cw_login IS NULL''')
 
+
+    def test_date_extraction(self):
+        self._check("Any MONTH(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT CAST(EXTRACT(MONTH from _P.cw_creation_date) AS INTEGER)
+FROM cw_Personne AS _P''')
+
+
     def test_parser_parse(self):
         for t in self._parse(PARSER):
             yield t
@@ -1384,11 +1399,11 @@
                     '''SELECT COUNT(1)
 WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS _P WHERE rel_owned_by0.eid_from=_P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS _P WHERE rel_owned_by1.eid_from=_P.cw_eid AND rel_owned_by1.eid_to=1)''')
 
-    def test_attr_map(self):
+    def test_attr_map_sqlcb(self):
         def generate_ref(gen, linkedvar, rel):
             linkedvar.accept(gen)
             return 'VERSION_DATA(%s)' % linkedvar._q_sql
-        self.o.attr_map['Affaire.ref'] = generate_ref
+        self.o.attr_map['Affaire.ref'] = (generate_ref, False)
         try:
             self._check('Any R WHERE X ref R',
                         '''SELECT VERSION_DATA(_X.cw_eid)
@@ -1400,22 +1415,33 @@
         finally:
             self.o.attr_map.clear()
 
+    def test_attr_map_sourcecb(self):
+        cb = lambda x,y: None
+        self.o.attr_map['Affaire.ref'] = (cb, True)
+        try:
+            union = self._prepare('Any R WHERE X ref R')
+            r, nargs, cbs = self.o.generate(union, args={})
+            self.assertLinesEquals(r.strip(), 'SELECT _X.cw_ref\nFROM cw_Affaire AS _X')
+            self.assertEquals(cbs, {0: [cb]})
+        finally:
+            self.o.attr_map.clear()
+
 
 class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):
 
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('sqlite', 'utf8')
-        dbms_helper = get_adv_func_helper('sqlite')
-        dbms_helper.fti_uid_attr = indexer.uid_attr
-        dbms_helper.fti_table = indexer.table
-        dbms_helper.fti_restriction_sql = indexer.restriction_sql
-        dbms_helper.fti_need_distinct_query = indexer.need_distinct
-        self.o = SQLGenerator(schema, dbms_helper)
+        dbhelper = get_db_helper('sqlite')
+        self.o = SQLGenerator(schema, dbhelper)
 
     def _norm_sql(self, sql):
         return sql.strip().replace(' ILIKE ', ' LIKE ').replace('\nINTERSECT ALL\n', '\nINTERSECT\n')
 
+    def test_date_extraction(self):
+        self._check("Any MONTH(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT MONTH(_P.cw_creation_date)
+FROM cw_Personne AS _P''')
+
     def test_union(self):
         for t in self._parse((
             ('(Any N ORDERBY 1 WHERE X name N, X is State)'
@@ -1513,13 +1539,8 @@
 
     def setUp(self):
         RQLGeneratorTC.setUp(self)
-        indexer = get_indexer('mysql', 'utf8')
-        dbms_helper = get_adv_func_helper('mysql')
-        dbms_helper.fti_uid_attr = indexer.uid_attr
-        dbms_helper.fti_table = indexer.table
-        dbms_helper.fti_restriction_sql = indexer.restriction_sql
-        dbms_helper.fti_need_distinct_query = indexer.need_distinct
-        self.o = SQLGenerator(schema, dbms_helper)
+        dbhelper = get_db_helper('mysql')
+        self.o = SQLGenerator(schema, dbhelper)
 
     def _norm_sql(self, sql):
         sql = sql.strip().replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0')
@@ -1533,6 +1554,11 @@
             latest = firstword
         return '\n'.join(newsql)
 
+    def test_date_extraction(self):
+        self._check("Any MONTH(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT EXTRACT(MONTH from _P.cw_creation_date)
+FROM cw_Personne AS _P''')
+
     def test_from_clause_needed(self):
         queries = [("Any 1 WHERE EXISTS(T is CWGroup, T name 'managers')",
                     '''SELECT 1
--- a/server/test/unittest_rqlannotation.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_rqlannotation.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,6 +1,22 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for modules cubicweb.server.rqlannotation
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cubicweb.devtools import init_test_database
@@ -8,6 +24,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class SQLGenAnnotatorTC(BaseQuerierTC):
     repo = repo
 
--- a/server/test/unittest_schemaserial.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_schemaserial.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,5 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for schema rql (de)serialization
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import sys
@@ -15,9 +31,19 @@
 config.bootstrap_cubes()
 schema = loader.load(config)
 
+def teardown_module(*args):
+    global schema, config, loader
+    del schema, config, loader
+
 from cubicweb.server.schemaserial import *
 from cubicweb.server.schemaserial import _erperms2rql as erperms2rql
 
+cstrtypemap = {'RQLConstraint': 'RQLConstraint_eid',
+               'SizeConstraint': 'SizeConstraint_eid',
+               'StaticVocabularyConstraint': 'StaticVocabularyConstraint_eid',
+               'FormatConstraint': 'FormatConstraint_eid',
+               }
+
 class Schema2RQLTC(TestCase):
 
     def test_eschema2rql1(self):
@@ -34,104 +60,124 @@
                  {'description': u'', 'final': True, 'name': u'String'})])
 
     def test_eschema2rql_specialization(self):
+        # x: None since eschema.eid are None
         self.assertListEquals(sorted(specialize2rql(schema)),
-                              [('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'BaseTransition', 'x': 'Transition'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'BaseTransition', 'x': 'WorkflowTransition'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'Division', 'x': 'SubDivision'}),
-                               # ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
+                              [('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               # ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
                                #  {'et': 'File', 'x': 'Image'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'Societe', 'x': 'Division'})])
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None})])
 
     def test_rschema2rql1(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'), cstrtypemap)),
                              [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
              {'description': u'link a relation definition to its relation type', 'symmetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWAttribute'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u';O;O final TRUE\n'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'composite': u'object', 'cardinality': u'1*',
+              'ordernum': 1}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u';O;O final TRUE\n'}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u';O;O final FALSE\n'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'composite': u'object', 
+              'ordernum': 1, 'cardinality': u'1*'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u';O;O final FALSE\n'}),
             ])
 
     def test_rschema2rql2(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'), cstrtypemap)),
                               [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWEType'}),
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWEType'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'ordernum': 9999, 'cardinality': u'**'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'ordernum': 9999, 'cardinality': u'*?'}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWRelation'}),
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWRelation'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'ordernum': 9999, 'cardinality': u'**'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'ordernum': 9999, 'cardinality': u'*?'}),
             ])
 
     def test_rschema2rql3(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'), cstrtypemap)),
                              [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
              {'description': u'', 'symmetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
 
-            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWAttribute'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'SizeConstraint_eid', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'StaticVocabularyConstraint_eid', 'value': u"u'?1', u'11'"}),
 
-            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWRelation'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWRelation', 'value': u'max=2'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWRelation', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'SizeConstraint_eid', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'StaticVocabularyConstraint_eid', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
             ])
 
+    def test_rdef2rql(self):
+        self.assertListEquals(list(rdef2rql(schema['description_format'].rdefs[('CWRType', 'String')], cstrtypemap)),
+                              [
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 7, 'defaultval': u'text/plain', 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'value': u'None', 'ct': 'FormatConstraint_eid'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'value': u'max=50', 'ct': 'SizeConstraint_eid'})])
+
 
     def test_updateeschema2rql1(self):
-        self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'))),
-                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
-                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema', 'et': 'CWAttribute', 'final': False, 'name': u'CWAttribute'}),
+        self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'), 1)),
+                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X eid %(x)s',
+                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema', 'x': 1, 'final': False, 'name': u'CWAttribute'}),
                                ])
 
     def test_updateeschema2rql2(self):
-        self.assertListEquals(list(updateeschema2rql(schema.eschema('String'))),
-                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
-                                {'description': u'', 'et': 'String', 'final': True, 'name': u'String'})
+        self.assertListEquals(list(updateeschema2rql(schema.eschema('String'), 1)),
+                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X eid %(x)s',
+                                {'description': u'', 'x': 1, 'final': True, 'name': u'String'})
                                ])
 
     def test_updaterschema2rql1(self):
-        self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'))),
+        self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'), 1)),
                              [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
-             {'rt': 'relation_type', 'symmetric': False,
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X eid %(x)s',
+             {'x': 1, 'symmetric': False,
               'description': u'link a relation definition to its relation type',
               'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
             ])
 
     def test_updaterschema2rql2(self):
         expected = [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
-             {'rt': 'add_permission', 'symmetric': False,
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X eid %(x)s',
+             {'x': 1, 'symmetric': False,
               'description': u'', 'final': False, 'fulltext_container': None,
               'inlined': False, 'name': u'add_permission'})
             ]
-        for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'))):
+        for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'), 1)):
             yield self.assertEquals, (rql, args), expected[i]
 
 class Perms2RQLTC(TestCase):
@@ -144,29 +190,29 @@
 
     def test_eperms2rql1(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X add_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X update_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X delete_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     def test_rperms2rql2(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('read_permission').rdef('CWEType', 'CWGroup'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X add_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X delete_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     def test_rperms2rql3(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('name').rdef('CWEType', 'String'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X update_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     #def test_perms2rql(self):
--- a/server/test/unittest_security.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_security.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,5 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functional tests for server'security
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 
@@ -32,12 +48,12 @@
         self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
         self.repo.vreg.solutions(self.session, rqlst, None)
         solution = rqlst.solutions[0]
-        check_read_access(self.schema, self.session.user, rqlst, solution)
+        check_read_access(self.session, rqlst, solution, {})
         cnx = self.login('anon')
         cu = cnx.cursor()
         self.assertRaises(Unauthorized,
                           check_read_access,
-                          self.schema, cnx.user(self.session), rqlst, solution)
+                          self.session, rqlst, solution, {})
         self.assertRaises(Unauthorized, cu.execute, rql)
 
     def test_upassword_not_selectable(self):
--- a/server/test/unittest_session.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_session.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main, mock_object
 
+from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server.session import _make_description
 
 class Variable:
@@ -32,5 +46,11 @@
         self.assertEquals(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
                           ['Int','CWUser'])
 
+class InternalSessionTC(CubicWebTC):
+    def test_dbapi_query(self):
+        session = self.repo.internal_session()
+        self.assertFalse(session.running_dbapi_query)
+        session.close()
+
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_sqlutils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_sqlutils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,5 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.sqlutils
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import sys
@@ -20,13 +36,13 @@
 
     def test_init(self):
         o = SQLAdapterMixIn(BASE_CONFIG)
-        self.assertEquals(o.encoding, 'UTF-8')
+        self.assertEquals(o.dbhelper.dbencoding, 'UTF-8')
 
     def test_init_encoding(self):
         config = BASE_CONFIG.copy()
         config['db-encoding'] = 'ISO-8859-1'
         o = SQLAdapterMixIn(config)
-        self.assertEquals(o.encoding, 'ISO-8859-1')
+        self.assertEquals(o.dbhelper.dbencoding, 'ISO-8859-1')
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_ssplanner.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_ssplanner.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools import init_test_database
 from cubicweb.devtools.repotest import BasePlannerTC, test_plan
@@ -12,6 +25,10 @@
 # keep cnx so it's not garbage collected and the associated session closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
 class SSPlannerTC(BasePlannerTC):
     repo = repo
     _test = test_plan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_storage.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,215 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""unit tests for module cubicweb.server.sources.storages
+
+"""
+
+from __future__ import with_statement
+
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.testlib import CubicWebTC
+
+import os.path as osp
+import shutil
+import tempfile
+
+from cubicweb import Binary, QueryError
+from cubicweb.selectors import implements
+from cubicweb.server.sources import storages
+from cubicweb.server.hook import Hook, Operation
+
+class DummyBeforeHook(Hook):
+    __regid__ = 'dummy-before-hook'
+    __select__ = Hook.__select__ & implements('File')
+    events = ('before_add_entity',)
+
+    def __call__(self):
+        self._cw.transaction_data['orig_file_value'] = self.entity.data.getvalue()
+
+
+class DummyAfterHook(Hook):
+    __regid__ = 'dummy-after-hook'
+    __select__ = Hook.__select__ & implements('File')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        # new value of entity.data should be the same as before
+        oldvalue = self._cw.transaction_data['orig_file_value']
+        assert oldvalue == self.entity.data.getvalue()
+
+class StorageTC(CubicWebTC):
+
+    def setup_database(self):
+        self.tempdir = tempfile.mkdtemp()
+        bfs_storage = storages.BytesFileSystemStorage(self.tempdir)
+        storages.set_attribute_storage(self.repo, 'File', 'data', bfs_storage)
+
+    def tearDown(self):
+        super(CubicWebTC, self).tearDown()
+        storages.unset_attribute_storage(self.repo, 'File', 'data')
+        shutil.rmtree(self.tempdir)
+
+
+    def create_file(self, content='the-data'):
+        req = self.request()
+        return req.create_entity('File', data=Binary(content),
+                                 data_format=u'text/plain', data_name=u'foo.pdf')
+
+    def fspath(self, entity):
+        fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D',
+                              {'f': entity.eid})[0][0]
+        return fspath.getvalue()
+
+    def test_bfss_storage(self):
+        f1 = self.create_file()
+        expected_filepath = osp.join(self.tempdir, '%s_data_%s' %
+                                     (f1.eid, f1.data_name))
+        self.failUnless(osp.isfile(expected_filepath))
+        self.assertEquals(file(expected_filepath).read(), 'the-data')
+        self.rollback()
+        self.failIf(osp.isfile(expected_filepath))
+        f1 = self.create_file()
+        self.commit()
+        self.assertEquals(file(expected_filepath).read(), 'the-data')
+        f1.set_attributes(data=Binary('the new data'))
+        self.rollback()
+        self.assertEquals(file(expected_filepath).read(), 'the-data')
+        f1.delete()
+        self.failUnless(osp.isfile(expected_filepath))
+        self.rollback()
+        self.failUnless(osp.isfile(expected_filepath))
+        f1.delete()
+        self.commit()
+        self.failIf(osp.isfile(expected_filepath))
+
+    def test_bfss_sqlite_fspath(self):
+        f1 = self.create_file()
+        expected_filepath = osp.join(self.tempdir, '%s_data_%s' % (f1.eid, f1.data_name))
+        self.assertEquals(self.fspath(f1), expected_filepath)
+
+    def test_bfss_fs_importing_doesnt_touch_path(self):
+        self.session.transaction_data['fs_importing'] = True
+        filepath = osp.abspath(__file__)
+        f1 = self.session.create_entity('File', data=Binary(filepath),
+                                        data_format=u'text/plain', data_name=u'foo')
+        self.assertEquals(self.fspath(f1), filepath)
+
+    def test_source_storage_transparency(self):
+        with self.temporary_appobjects(DummyBeforeHook, DummyAfterHook):
+            self.create_file()
+
+    def test_source_mapped_attribute_error_cases(self):
+        ex = self.assertRaises(QueryError, self.execute,
+                               'Any X WHERE X data ~= "hop", X is File')
+        self.assertEquals(str(ex), 'can\'t use File.data (X data ILIKE "hop") in restriction')
+        ex = self.assertRaises(QueryError, self.execute,
+                               'Any X, Y WHERE X data D, Y data D, '
+                               'NOT X identity Y, X is File, Y is File')
+        self.assertEquals(str(ex), "can't use D as a restriction variable")
+        # query returning mix of mapped / regular attributes (only file.data
+        # mapped, not image.data for instance)
+        ex = self.assertRaises(QueryError, self.execute,
+                               'Any X WITH X BEING ('
+                               ' (Any NULL)'
+                               '  UNION '
+                               ' (Any D WHERE X data D, X is File)'
+                               ')')
+        self.assertEquals(str(ex), 'query fetch some source mapped attribute, some not')
+        ex = self.assertRaises(QueryError, self.execute,
+                               '(Any D WHERE X data D, X is File)'
+                               ' UNION '
+                               '(Any D WHERE X data D, X is Image)')
+        self.assertEquals(str(ex), 'query fetch some source mapped attribute, some not')
+        ex = self.assertRaises(QueryError,
+                               self.execute, 'Any D WHERE X data D')
+        self.assertEquals(str(ex), 'query fetch some source mapped attribute, some not')
+
+    def test_source_mapped_attribute_advanced(self):
+        f1 = self.create_file()
+        rset = self.execute('Any X,D WITH D,X BEING ('
+                            ' (Any D, X WHERE X eid %(x)s, X data D)'
+                            '  UNION '
+                            ' (Any D, X WHERE X eid %(x)s, X data D)'
+                            ')', {'x': f1.eid}, 'x')
+        self.assertEquals(len(rset), 2)
+        self.assertEquals(rset[0][0], f1.eid)
+        self.assertEquals(rset[1][0], f1.eid)
+        self.assertEquals(rset[0][1].getvalue(), 'the-data')
+        self.assertEquals(rset[1][1].getvalue(), 'the-data')
+        rset = self.execute('Any X,LENGTH(D) WHERE X eid %(x)s, X data D',
+                            {'x': f1.eid}, 'x')
+        self.assertEquals(len(rset), 1)
+        self.assertEquals(rset[0][0], f1.eid)
+        self.assertEquals(rset[0][1], len('the-data'))
+        rset = self.execute('Any X,LENGTH(D) WITH D,X BEING ('
+                            ' (Any D, X WHERE X eid %(x)s, X data D)'
+                            '  UNION '
+                            ' (Any D, X WHERE X eid %(x)s, X data D)'
+                            ')', {'x': f1.eid}, 'x')
+        self.assertEquals(len(rset), 2)
+        self.assertEquals(rset[0][0], f1.eid)
+        self.assertEquals(rset[1][0], f1.eid)
+        self.assertEquals(rset[0][1], len('the-data'))
+        self.assertEquals(rset[1][1], len('the-data'))
+        ex = self.assertRaises(QueryError, self.execute,
+                               'Any X,UPPER(D) WHERE X eid %(x)s, X data D',
+                               {'x': f1.eid}, 'x')
+        self.assertEquals(str(ex), 'UPPER can not be called on mapped attribute')
+
+
+    def test_bfss_fs_importing_transparency(self):
+        self.session.transaction_data['fs_importing'] = True
+        filepath = osp.abspath(__file__)
+        f1 = self.session.create_entity('File', data=Binary(filepath),
+                                        data_format=u'text/plain', data_name=u'foo')
+        self.assertEquals(f1.data.getvalue(), file(filepath).read(),
+                          'files content differ')
+
+
+    def test_bfss_update_with_existing_data(self):
+        # use self.session to use server-side cache
+        f1 = self.session.create_entity('File', data=Binary('some data'),
+                                        data_format=u'text/plain', data_name=u'foo')
+        # NOTE: do not use set_attributes() which would automatically
+        #       update f1's local dict. We want the pure rql version to work
+        self.execute('SET F data %(d)s WHERE F eid %(f)s',
+                     {'d': Binary('some other data'), 'f': f1.eid})
+        self.assertEquals(f1.data.getvalue(), 'some other data')
+        self.commit()
+        f2 = self.entity('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid})
+        self.assertEquals(f2.data.getvalue(), 'some other data')
+
+
+    def test_bfss_update_with_fs_importing(self):
+        # use self.session to use server-side cache
+        f1 = self.session.create_entity('File', data=Binary('some data'),
+                                        data_format=u'text/plain', data_name=u'foo')
+        old_fspath = self.fspath(f1)
+        self.session.transaction_data['fs_importing'] = True
+        new_fspath = osp.join(self.tempdir, 'newfile.txt')
+        file(new_fspath, 'w').write('the new data')
+        self.execute('SET F data %(d)s WHERE F eid %(f)s',
+                     {'d': Binary(new_fspath), 'f': f1.eid})
+        self.commit()
+        self.assertEquals(f1.data.getvalue(), 'the new data')
+        self.assertEquals(self.fspath(f1), new_fspath)
+        self.failIf(osp.isfile(old_fspath))
+
+
+if __name__ == '__main__':
+    unittest_main()
--- a/server/test/unittest_tools.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/test/unittest_tools.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_undo.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,285 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+"""
+from __future__ import with_statement
+
+from cubicweb import ValidationError
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.transaction import *
+
+class UndoableTransactionTC(CubicWebTC):
+
+    def setup_database(self):
+        self.session.undo_actions = set('CUDAR')
+        self.toto = self.create_user('toto', password='toto', groups=('users',),
+                                     commit=False)
+        self.txuuid = self.commit()
+
+    def tearDown(self):
+        self.restore_connection()
+        self.session.undo_support = set()
+        super(UndoableTransactionTC, self).tearDown()
+
+    def check_transaction_deleted(self, txuuid):
+        # also check transaction actions have been properly deleted
+        cu = self.session.system_sql(
+            "SELECT * from tx_entity_actions WHERE tx_uuid='%s'" % txuuid)
+        self.failIf(cu.fetchall())
+        cu = self.session.system_sql(
+            "SELECT * from tx_relation_actions WHERE tx_uuid='%s'" % txuuid)
+        self.failIf(cu.fetchall())
+
+    def test_undo_api(self):
+        self.failUnless(self.txuuid)
+        # test transaction api
+        self.assertRaises(NoSuchTransaction,
+                          self.cnx.transaction_info, 'hop')
+        self.assertRaises(NoSuchTransaction,
+                          self.cnx.transaction_actions, 'hop')
+        self.assertRaises(NoSuchTransaction,
+                          self.cnx.undo_transaction, 'hop')
+        txinfo = self.cnx.transaction_info(self.txuuid)
+        self.failUnless(txinfo.datetime)
+        self.assertEquals(txinfo.user_eid, self.session.user.eid)
+        self.assertEquals(txinfo.user().login, 'admin')
+        actions = txinfo.actions_list()
+        self.assertEquals(len(actions), 2)
+        actions = txinfo.actions_list(public=False)
+        self.assertEquals(len(actions), 6)
+        a1 = actions[0]
+        self.assertEquals(a1.action, 'C')
+        self.assertEquals(a1.eid, self.toto.eid)
+        self.assertEquals(a1.etype,'CWUser')
+        self.assertEquals(a1.changes, None)
+        self.assertEquals(a1.public, True)
+        self.assertEquals(a1.order, 1)
+        a4 = actions[3]
+        self.assertEquals(a4.action, 'A')
+        self.assertEquals(a4.rtype, 'in_group')
+        self.assertEquals(a4.eid_from, self.toto.eid)
+        self.assertEquals(a4.eid_to, self.toto.in_group[0].eid)
+        self.assertEquals(a4.order, 4)
+        for i, rtype in ((1, 'owned_by'), (2, 'owned_by'),
+                         (4, 'in_state'), (5, 'created_by')):
+            a = actions[i]
+            self.assertEquals(a.action, 'A')
+            self.assertEquals(a.eid_from, self.toto.eid)
+            self.assertEquals(a.rtype, rtype)
+            self.assertEquals(a.order, i+1)
+        # test undoable_transactions
+        txs = self.cnx.undoable_transactions()
+        self.assertEquals(len(txs), 1)
+        self.assertEquals(txs[0].uuid, self.txuuid)
+        # test transaction_info / undoable_transactions security
+        cnx = self.login('anon')
+        self.assertRaises(NoSuchTransaction,
+                          cnx.transaction_info, self.txuuid)
+        self.assertRaises(NoSuchTransaction,
+                          cnx.transaction_actions, self.txuuid)
+        self.assertRaises(NoSuchTransaction,
+                          cnx.undo_transaction, self.txuuid)
+        txs = cnx.undoable_transactions()
+        self.assertEquals(len(txs), 0)
+
+    def test_undoable_transactions(self):
+        toto = self.toto
+        e = self.session.create_entity('EmailAddress',
+                                       address=u'toto@logilab.org',
+                                       reverse_use_email=toto)
+        txuuid1 = self.commit()
+        toto.delete()
+        txuuid2 = self.commit()
+        undoable_transactions = self.cnx.undoable_transactions
+        txs = undoable_transactions(action='D')
+        self.assertEquals(len(txs), 1, txs)
+        self.assertEquals(txs[0].uuid, txuuid2)
+        txs = undoable_transactions(action='C')
+        self.assertEquals(len(txs), 2, txs)
+        self.assertEquals(txs[0].uuid, txuuid1)
+        self.assertEquals(txs[1].uuid, self.txuuid)
+        txs = undoable_transactions(eid=toto.eid)
+        self.assertEquals(len(txs), 3)
+        self.assertEquals(txs[0].uuid, txuuid2)
+        self.assertEquals(txs[1].uuid, txuuid1)
+        self.assertEquals(txs[2].uuid, self.txuuid)
+        txs = undoable_transactions(etype='CWUser')
+        self.assertEquals(len(txs), 2)
+        txs = undoable_transactions(etype='CWUser', action='C')
+        self.assertEquals(len(txs), 1)
+        self.assertEquals(txs[0].uuid, self.txuuid)
+        txs = undoable_transactions(etype='EmailAddress', action='D')
+        self.assertEquals(len(txs), 0)
+        txs = undoable_transactions(etype='EmailAddress', action='D',
+                                    public=False)
+        self.assertEquals(len(txs), 1)
+        self.assertEquals(txs[0].uuid, txuuid2)
+        txs = undoable_transactions(eid=toto.eid, action='R', public=False)
+        self.assertEquals(len(txs), 1)
+        self.assertEquals(txs[0].uuid, txuuid2)
+
+    def test_undo_deletion_base(self):
+        toto = self.toto
+        e = self.session.create_entity('EmailAddress',
+                                       address=u'toto@logilab.org',
+                                       reverse_use_email=toto)
+        # entity with inlined relation
+        p = self.session.create_entity('CWProperty',
+                                       pkey=u'ui.default-text-format',
+                                       value=u'text/rest',
+                                       for_user=toto)
+        self.commit()
+        txs = self.cnx.undoable_transactions()
+        self.assertEquals(len(txs), 2)
+        toto.delete()
+        txuuid = self.commit()
+        actions = self.cnx.transaction_info(txuuid).actions_list()
+        self.assertEquals(len(actions), 1)
+        toto.clear_all_caches()
+        e.clear_all_caches()
+        errors = self.cnx.undo_transaction(txuuid)
+        undotxuuid = self.commit()
+        self.assertEquals(undotxuuid, None) # undo not undoable
+        self.assertEquals(errors, [])
+        self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': toto.eid}, 'x'))
+        self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': e.eid}, 'x'))
+        self.failUnless(self.execute('Any X WHERE X has_text "toto@logilab"'))
+        self.assertEquals(toto.state, 'activated')
+        self.assertEquals(toto.get_email(), 'toto@logilab.org')
+        self.assertEquals([(p.pkey, p.value) for p in toto.reverse_for_user],
+                          [('ui.default-text-format', 'text/rest')])
+        self.assertEquals([g.name for g in toto.in_group],
+                          ['users'])
+        self.assertEquals([et.name for et in toto.related('is', entities=True)],
+                          ['CWUser'])
+        self.assertEquals([et.name for et in toto.is_instance_of],
+                          ['CWUser'])
+        # undoing shouldn't be visble in undoable transaction, and the undoed
+        # transaction should be removed
+        txs = self.cnx.undoable_transactions()
+        self.assertEquals(len(txs), 2)
+        self.assertRaises(NoSuchTransaction,
+                          self.cnx.transaction_info, txuuid)
+        self.check_transaction_deleted(txuuid)
+        # the final test: check we can login with the previously deleted user
+        self.login('toto')
+
+    def test_undo_deletion_integrity_1(self):
+        session = self.session
+        # 'Personne fiche Card with' '??' cardinality
+        c = session.create_entity('Card', title=u'hop', content=u'hop')
+        p = session.create_entity('Personne', nom=u'louis', fiche=c)
+        self.commit()
+        c.delete()
+        txuuid = self.commit()
+        c2 = session.create_entity('Card', title=u'hip', content=u'hip')
+        p.set_relations(fiche=c2)
+        self.commit()
+        errors = self.cnx.undo_transaction(txuuid)
+        self.commit()
+        p.clear_all_caches()
+        self.assertEquals(p.fiche[0].eid, c2.eid)
+        self.assertEquals(len(errors), 1)
+        self.assertEquals(errors[0],
+                          "Can't restore object relation fiche to entity "
+                          "%s which is already linked using this relation." % p.eid)
+
+    def test_undo_deletion_integrity_2(self):
+        # test validation error raised if we can't restore a required relation
+        session = self.session
+        g = session.create_entity('CWGroup', name=u'staff')
+        session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
+        self.toto.set_relations(in_group=g)
+        self.commit()
+        self.toto.delete()
+        txuuid = self.commit()
+        g.delete()
+        self.commit()
+        errors = self.cnx.undo_transaction(txuuid)
+        self.assertEquals(errors,
+                          [u"Can't restore relation in_group, object entity "
+                          "%s doesn't exist anymore." % g.eid])
+        ex = self.assertRaises(ValidationError, self.commit)
+        self.assertEquals(ex.entity, self.toto.eid)
+        self.assertEquals(ex.errors,
+                          {'in_group-subject': u'at least one relation in_group is '
+                           'required on CWUser (%s)' % self.toto.eid})
+
+    def test_undo_creation_1(self):
+        session = self.session
+        c = session.create_entity('Card', title=u'hop', content=u'hop')
+        p = session.create_entity('Personne', nom=u'louis', fiche=c)
+        txuuid = self.commit()
+        errors = self.cnx.undo_transaction(txuuid)
+        self.commit()
+        self.failIf(errors)
+        self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': c.eid}, 'x'))
+        self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': p.eid}, 'x'))
+        self.failIf(self.execute('Any X,Y WHERE X fiche Y'))
+        self.session.set_pool()
+        for eid in (p.eid, c.eid):
+            self.failIf(session.system_sql(
+                'SELECT * FROM entities WHERE eid=%s' % eid).fetchall())
+            self.failIf(session.system_sql(
+                'SELECT 1 FROM owned_by_relation WHERE eid_from=%s' % eid).fetchall())
+            # added by sql in hooks (except when using dataimport)
+            self.failIf(session.system_sql(
+                'SELECT 1 FROM is_relation WHERE eid_from=%s' % eid).fetchall())
+            self.failIf(session.system_sql(
+                'SELECT 1 FROM is_instance_of_relation WHERE eid_from=%s' % eid).fetchall())
+        self.check_transaction_deleted(txuuid)
+
+
+    def test_undo_creation_integrity_1(self):
+        session = self.session
+        tutu = self.create_user('tutu', commit=False)
+        txuuid = self.commit()
+        email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
+        prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
+                                            value=u'text/html')
+        tutu.set_relations(use_email=email, reverse_for_user=prop)
+        self.commit()
+        ex = self.assertRaises(ValidationError,
+                               self.cnx.undo_transaction, txuuid)
+        self.assertEquals(ex.entity, tutu.eid)
+        self.assertEquals(ex.errors,
+                          {None: 'some later transaction(s) touch entity, undo them first'})
+
+    def test_undo_creation_integrity_2(self):
+        session = self.session
+        g = session.create_entity('CWGroup', name=u'staff')
+        txuuid = self.commit()
+        session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
+        self.toto.set_relations(in_group=g)
+        self.commit()
+        ex = self.assertRaises(ValidationError,
+                               self.cnx.undo_transaction, txuuid)
+        self.assertEquals(ex.entity, g.eid)
+        self.assertEquals(ex.errors,
+                          {None: 'some later transaction(s) touch entity, undo them first'})
+        # self.assertEquals(errors,
+        #                   [u"Can't restore relation in_group, object entity "
+        #                   "%s doesn't exist anymore." % g.eid])
+        # ex = self.assertRaises(ValidationError, self.commit)
+        # self.assertEquals(ex.entity, self.toto.eid)
+        # self.assertEquals(ex.errors,
+        #                   {'in_group-subject': u'at least one relation in_group is '
+        #                    'required on CWUser (%s)' % self.toto.eid})
+
+    # test implicit 'replacement' of an inlined relation
--- a/server/utils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/server/utils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Some utilities for the CubicWeb server.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -65,6 +78,18 @@
                 del sol[vname]
 
 
+def eschema_eid(session, eschema):
+    """get eid of the CWEType entity for the given yams type. You should use
+    this because when schema has been loaded from the file-system, not from the
+    database, (e.g. during tests), eschema.eid is not set.
+    """
+    if eschema.eid is None:
+        eschema.eid = session.execute(
+            'Any X WHERE X is CWEType, X name %(name)s',
+            {'name': str(eschema)})[0][0]
+    return eschema.eid
+
+
 DEFAULT_MSG = 'we need a manager connection on the repository \
 (the server doesn\'t have to run, even should better not)'
 
@@ -140,6 +165,5 @@
         self.daemon = True
         Thread.start(self)
 
-    @property
-    def name(self):
+    def getName(self):
         return '%s(%s)' % (self._name, Thread.getName(self))
--- a/setup.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/setup.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,25 +1,25 @@
 #!/usr/bin/env python
-"""Generic Setup script, takes package info from __pkginfo__.py file
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
 # pylint: disable-msg=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
 #
-# This program is distributed in the hope that it will be useful, but WITHOUT
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
 #
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Generic Setup script, takes package info from __pkginfo__.py file
+"""
 
 import os
 import sys
--- a/skeleton/__pkginfo__.py.tmpl	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/__pkginfo__.py.tmpl	Wed Apr 28 11:54:13 2010 +0200
@@ -43,8 +43,12 @@
 # Note: here, you'll need to add subdirectories if you want
 # them to be included in the debian package
 
-__depends_cubes__ = {}
-__depends__ = {'cubicweb': '>= 3.6.0'}
-__use__ = (%(dependancies)s)
-__recommend__ = ()
+# a dict; you might want to provide a version specification
+# of the form '>= x.y.z'
+__depends__ = {'cubicweb': '>= 3.7.0'}
+__depends_cubes__ = %(dependencies)s
+__recommends_cubes__ = {}
+# obsolete (will be gone in cw 3.8.0)
+__use__ = tuple(__depends_cubes__)
+__recommend__ = tuple(__recommends_cubes__)
 
--- a/skeleton/entities.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/entities.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """this contains the cube-specific entities' classes
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/skeleton/migration/postcreate.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/migration/postcreate.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # postcreate script. You could setup site properties or a workflow here for example
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 # Example of site property change
--- a/skeleton/migration/precreate.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/migration/precreate.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # Instructions here will be read before reading the schema
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 # You could create your own groups here, like in :
 #   create_entity('CWGroup', name=u'mygroup')
--- a/skeleton/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
 # cube's specific schema
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/skeleton/setup.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/setup.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 #!/usr/bin/env python
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 # pylint: disable-msg=W0404,W0622,W0704,W0613,W0152
 # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
--- a/skeleton/test/pytestconf.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/test/pytestconf.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import os
 import pwd
@@ -26,11 +39,11 @@
     parser.add_option('-u', '--dbuser', dest='dbuser', action='store',
                       default=login, help="database user")
     parser.add_option('-w', '--dbpassword', dest='dbpassword', action='store',
-                      default=login, help="database name")
+                      default=login, help="database user's password")
     parser.add_option('-n', '--dbname', dest='dbname', action='store',
                       default=None, help="database name")
     parser.add_option('--euser', dest='euser', action='store',
-                      default=login, help="esuer name")
+                      default=login, help="euser name")
     parser.add_option('--epassword', dest='epassword', action='store',
                       default=login, help="euser's password' name")
     return parser
--- a/skeleton/test/realdb_test_CUBENAME.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/test/realdb_test_CUBENAME.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools import buildconfig, loadconfig
 from cubicweb.devtools.testlib import RealDBTest
--- a/skeleton/test/test_CUBENAME.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/test/test_CUBENAME.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """template automatic tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main
--- a/skeleton/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/skeleton/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cube-specific forms/views/actions/components
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/sobjects/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """server side objects
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/sobjects/notification.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/notification.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some views to handle notification on data changes
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -33,11 +46,9 @@
     def recipients(self):
         mode = self._cw.vreg.config['default-recipients-mode']
         if mode == 'users':
-            # use unsafe execute else we may don't have the right to see users
-            # to notify...
-            execute = self._cw.unsafe_execute
+            execute = self._cw.execute
             dests = [(u.get_email(), u.property_value('ui.language'))
-                     for u in execute(self.user_rql, build_descr=True, propagate=True).entities()]
+                     for u in execute(self.user_rql, build_descr=True).entities()]
         elif mode == 'default-dest-addrs':
             lang = self._cw.vreg.property_value('ui.language')
             dests = zip(self._cw.vreg.config['default-dest-addrs'], repeat(lang))
@@ -158,7 +169,8 @@
                 if not rdef.has_perm(self._cw, 'read', eid=self.cw_rset[0][0]):
                     continue
             # XXX suppose it's a subject relation...
-            elif not rschema.has_perm(self._cw, 'read', fromeid=self.cw_rset[0][0]): # XXX toeid
+            elif not rschema.has_perm(self._cw, 'read',
+                                      fromeid=self.cw_rset[0][0]):
                 continue
             if attr in self.no_detailed_change_attrs:
                 msg = _('%s updated') % _(attr)
--- a/sobjects/supervising.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/supervising.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some hooks and views to handle supervising of any data changes
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -92,7 +105,7 @@
         return self._cw._('[%s supervision] changes summary') % self._cw.vreg.config.appid
 
     def call(self, changes):
-        user = self._cw.actual_session().user
+        user = self._cw.user
         self.w(self._cw._('user %s has made the following change(s):\n\n')
                % user.login)
         for event, changedescr in filter_changes(changes):
@@ -129,17 +142,16 @@
         self.w(u'  %s' % entity.absolute_url())
 
     def _relation_context(self, changedescr):
-        _ = self._cw._
-        session = self._cw.actual_session()
+        session = self._cw
         def describe(eid):
             try:
-                return _(session.describe(eid)[0]).lower()
+                return session._(session.describe(eid)[0]).lower()
             except UnknownEid:
                 # may occurs when an entity has been deleted from an external
                 # source and we're cleaning its relation
-                return _('unknown external entity')
+                return session._('unknown external entity')
         eidfrom, rtype, eidto = changedescr.eidfrom, changedescr.rtype, changedescr.eidto
-        return {'rtype': _(rtype),
+        return {'rtype': session._(rtype),
                 'eidfrom': eidfrom,
                 'frometype': describe(eidfrom),
                 'eidto': eidto,
--- a/sobjects/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import RelationDefinition
 
--- a/sobjects/test/data/sobjects/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/test/data/sobjects/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.selectors import implements
 from cubicweb.sobjects.notification import StatusChangeMixIn, NotificationView
--- a/sobjects/test/unittest_email.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/test/unittest_email.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cubicweb import Unauthorized
--- a/sobjects/test/unittest_notification.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/test/unittest_notification.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from socket import gethostname
 
--- a/sobjects/test/unittest_supervising.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/test/unittest_supervising.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import re
 
--- a/sobjects/textparsers.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/sobjects/textparsers.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """hooks triggered on email entities creation:
 
 * look for state change instruction (XXX security)
 * set email content as a comment on an entity when comments are supported and
   linking information are found
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
--- a/spa2rql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/spa2rql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """SPARQL -> RQL translator
 
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common import make_domains
 from rql import TypeResolverException
--- a/tags.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/tags.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """helper classes to generate simple (X)HTML tags
 
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/test/data/cubes/file/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/cubes/file/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- a/test/data/cubes/file/__pkginfo__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/cubes/file/__pkginfo__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # pylint: disable-msg=W0622
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-file packaging information
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 distname = "cubicweb-file"
--- a/test/data/cubes/mycube/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/cubes/mycube/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """mycube's __init__
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/test/data/cubes/mycube/__pkginfo__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/cubes/mycube/__pkginfo__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 distname = 'cubicweb-mycube'
--- a/test/data/entities.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/entities.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.entities import AnyEntity, fetch_config
 
--- a/test/data/erqlexpr_on_ertype.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/erqlexpr_on_ertype.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import EntityType, RelationType, SubjectRelation
 from cubicweb.schema import ERQLExpression
--- a/test/data/migration/0.0.3_Any.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.0.3_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 coucou
--- a/test/data/migration/0.0.4_Any.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.0.4_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 coucou
--- a/test/data/migration/0.1.0_Any.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.1.0_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 coucou
--- a/test/data/migration/0.1.0_common.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.1.0_common.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """common to all configuration
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/test/data/migration/0.1.0_repository.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.1.0_repository.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """repository specific
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/test/data/migration/0.1.0_web.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.1.0_web.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """web only
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/test/data/migration/0.1.2_Any.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/migration/0.1.2_Any.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,8 +1,21 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 coucou
--- a/test/data/rewrite/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/rewrite/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,17 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
--- a/test/data/rewrite/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/rewrite/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from yams.buildobjs import EntityType, RelationDefinition, String, SubjectRelation
 from cubicweb.schema import ERQLExpression
 
@@ -39,3 +56,10 @@
 class require_state(RelationDefinition):
     subject = 'CWPermission'
     object = 'State'
+
+
+class inlined_card(RelationDefinition):
+    subject = 'Affaire'
+    object = 'Card'
+    inlined = True
+    cardinality = '?*'
--- a/test/data/rqlexpr_on_ertype_read.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/rqlexpr_on_ertype_read.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import EntityType, RelationType, SubjectRelation
 from cubicweb.schema import RRQLExpression
--- a/test/data/rrqlexpr_on_attr.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/rrqlexpr_on_attr.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import EntityType, RelationType, String
 from cubicweb.schema import RRQLExpression
--- a/test/data/rrqlexpr_on_eetype.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/rrqlexpr_on_eetype.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from yams.buildobjs import EntityType, String
 from cubicweb.schema import RRQLExpression
--- a/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from yams.buildobjs import EntityType, String, SubjectRelation, RelationDefinition
--- a/test/data/server_migration/bootstrapmigration_repository.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/server_migration/bootstrapmigration_repository.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,7 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """allways executed before all others in server migration
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
--- a/test/data/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/data/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,2 +1,19 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from cubicweb.web.views import xmlrss
 xmlrss.RSSIconBox.visible = True
--- a/test/unittest_cwconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_cwconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 import os
--- a/test/unittest_cwctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_cwctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import sys
 import os
--- a/test/unittest_dbapi.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_dbapi.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,15 +1,30 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
+from __future__ import with_statement
+from copy import copy
+
 from cubicweb import ConnectionError
 from cubicweb.dbapi import ProgrammingError
 from cubicweb.devtools.testlib import CubicWebTC
 
-
 class DBAPITC(CubicWebTC):
 
     def test_public_repo_api(self):
@@ -35,8 +50,8 @@
         self.assertEquals(cnx.user(None).login, 'anon')
         self.assertEquals(cnx.describe(1), (u'CWGroup', u'system', None))
         self.restore_connection() # proper way to close cnx
-        self.assertRaises(ConnectionError, cnx.user, None)
-        self.assertRaises(ConnectionError, cnx.describe, 1)
+        self.assertRaises(ProgrammingError, cnx.user, None)
+        self.assertRaises(ProgrammingError, cnx.describe, 1)
 
     def test_session_data_api(self):
         cnx = self.login('anon')
@@ -64,9 +79,10 @@
         cnx.set_shared_data('data', 4)
         self.assertEquals(cnx.get_shared_data('data'), 4)
         self.restore_connection() # proper way to close cnx
-        self.assertRaises(ConnectionError, cnx.check)
-        self.assertRaises(ConnectionError, cnx.set_shared_data, 'data', 0)
-        self.assertRaises(ConnectionError, cnx.get_shared_data, 'data')
+        self.assertRaises(ProgrammingError, cnx.check)
+        self.assertRaises(ProgrammingError, cnx.set_shared_data, 'data', 0)
+        self.assertRaises(ProgrammingError, cnx.get_shared_data, 'data')
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/test/unittest_entity.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_entity.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.web.views.entities module
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from datetime import datetime
@@ -436,7 +449,7 @@
 
     def test_complete_relation(self):
         session = self.session
-        eid = session.unsafe_execute(
+        eid = session.execute(
             'INSERT TrInfo X: X comment "zou", X wf_info_for U, X from_state S1, X to_state S2 '
             'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
         trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
--- a/test/unittest_mail.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_mail.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.mail
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import os
--- a/test/unittest_migration.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_migration.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.migration unit tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from os.path import abspath
--- a/test/unittest_req.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_req.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.req import RequestSessionBase
 
--- a/test/unittest_rqlrewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_rqlrewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main, TestCase
 from logilab.common.testlib import mock_object
-
+from yams import BadSchemaDefinition
 from rql import parse, nodes, RQLHelper
 
 from cubicweb import Unauthorized
@@ -123,7 +136,7 @@
                              "EXISTS(2 in_state A, B in_group D, E require_state A, "
                              "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)")
 
-    def test_optional_var(self):
+    def test_optional_var_base(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
         rqlst = parse('Any A,C WHERE A documented_by C?')
@@ -131,15 +144,51 @@
         self.failUnlessEqual(rqlst.as_string(),
                              "Any A,C WHERE A documented_by C?, A is Affaire "
                              "WITH C BEING "
-                             "(Any C WHERE C in_state B, D in_group F, G require_state B, G name 'read', "
-                             "G require_group F, D eid %(A)s, C is Card)")
+                             "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', "
+                             "G require_group F), D eid %(A)s, C is Card)")
         rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T')
         rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {})
         self.failUnlessEqual(rqlst.as_string(),
                              "Any A,C,T WHERE A documented_by C?, A is Affaire "
                              "WITH C,T BEING "
-                             "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', "
-                             "G require_group F, C title T, D eid %(A)s, C is Card)")
+                             "(Any C,T WHERE C title T, EXISTS(C in_state B, D in_group F, "
+                             "G require_state B, G name 'read', G require_group F), "
+                             "D eid %(A)s, C is Card)")
+
+    def test_optional_var_inlined(self):
+        c1 = ('X require_permission P')
+        c2 = ('X inlined_card O, O require_permission P')
+        rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
+        rewrite(rqlst, {('C', 'X'): (c1,),
+                        ('A', 'X'): (c2,),
+                        }, {})
+        # XXX suboptimal
+        self.failUnlessEqual(rqlst.as_string(),
+                             "Any C,A,R WITH A,R,C BEING "
+                             "(Any A,R,C WHERE A ref R, A? inlined_card C, "
+                             "(A is NULL) OR (EXISTS(A inlined_card B, B require_permission D, "
+                             "B is Card, D is CWPermission)), "
+                             "A is Affaire, C is Card, EXISTS(C require_permission E, E is CWPermission))")
+
+    # def test_optional_var_inlined_has_perm(self):
+    #     c1 = ('X require_permission P')
+    #     c2 = ('X inlined_card O, U has_read_permission O')
+    #     rqlst = parse('Any C,A,R WHERE A? inlined_card C, A ref R')
+    #     rewrite(rqlst, {('C', 'X'): (c1,),
+    #                     ('A', 'X'): (c2,),
+    #                     }, {})
+    #     self.failUnlessEqual(rqlst.as_string(),
+    #                          "")
+
+    def test_optional_var_inlined_imbricated_error(self):
+        c1 = ('X require_permission P')
+        c2 = ('X inlined_card O, O require_permission P')
+        rqlst = parse('Any C,A,R,A2,R2 WHERE A? inlined_card C, A ref R,A2? inlined_card C, A2 ref R2')
+        self.assertRaises(BadSchemaDefinition,
+                          rewrite, rqlst, {('C', 'X'): (c1,),
+                                           ('A', 'X'): (c2,),
+                                           ('A2', 'X'): (c2,),
+                                           }, {})
 
     def test_relation_optimization_1_lhs(self):
         # since Card in_state State as monovalued cardinality, the in_state
@@ -243,7 +292,7 @@
         rewrite(rqlst, {('X', 'X'): (constraint,)}, {})
         # ambiguity are kept in the sub-query, no need to be resolved using OR
         self.failUnlessEqual(rqlst.as_string(),
-                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)")
+                             u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE EXISTS(X concerne A), X is Affaire)")
 
 
     def test_rrqlexpr_nonexistant_subject_1(self):
--- a/test/unittest_rset.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_rset.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,17 +1,30 @@
 # coding: utf-8
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.utils
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from urlparse import urlsplit
 
 from rql import parse
 
-from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.testlib import TestCase, unittest_main, mock_object
 
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.rset import NotAnEntity, ResultSet, attr_desc_iterator
@@ -60,7 +73,7 @@
         self.rset = ResultSet([[12, 'adim'], [13, 'syt']],
                               'Any U,L where U is CWUser, U login L',
                               description=[['CWUser', 'String'], ['Bar', 'String']])
-        self.rset.vreg = self.vreg
+        self.rset.req = mock_object(vreg=self.vreg)
 
     def compare_urls(self, url1, url2):
         info1 = urlsplit(url1)
--- a/test/unittest_rtags.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_rtags.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict
--- a/test/unittest_schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.schema
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import sys
@@ -241,6 +254,17 @@
         self.failUnless('has_text' in schema['CWUser'].subject_relations())
         self.failIf('has_text' in schema['EmailAddress'].subject_relations())
 
+    def test_permission_settings(self):
+        schema = loader.load(config)
+        aschema = schema['TrInfo'].rdef('comment')
+        self.assertEquals(aschema.get_groups('read'),
+                          set(('managers', 'users', 'guests')))
+        self.assertEquals(aschema.get_rqlexprs('read'),
+                          ())
+        self.assertEquals(aschema.get_groups('update'),
+                          set(('managers',)))
+        self.assertEquals([x.expression for x in aschema.get_rqlexprs('update')],
+                          ['U has_update_permission X'])
 
 class BadSchemaRQLExprTC(TestCase):
     def setUp(self):
@@ -257,16 +281,20 @@
         self.assertEquals(str(ex), msg)
 
     def test_rrqlexpr_on_etype(self):
-        self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on ToTo, use an ERQLExpression")
+        self._test('rrqlexpr_on_eetype.py',
+                   "can't use RRQLExpression on ToTo, use an ERQLExpression")
 
     def test_erqlexpr_on_rtype(self):
-        self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
+        self._test('erqlexpr_on_ertype.py',
+                   "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
 
     def test_rqlexpr_on_rtype_read(self):
-        self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu")
+        self._test('rqlexpr_on_ertype_read.py',
+                   "can't use rql expression for read permission of relation ToTo toto TuTu")
 
     def test_rrqlexpr_on_attr(self):
-        self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
+        self._test('rrqlexpr_on_attr.py',
+                   "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
 
 
 class NormalizeExpressionTC(TestCase):
@@ -277,8 +305,10 @@
 
 class RQLExpressionTC(TestCase):
     def test_comparison(self):
-        self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
-        self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
+        self.assertEquals(ERQLExpression('X is CWUser', 'X', 0),
+                          ERQLExpression('X is CWUser', 'X', 0))
+        self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0),
+                             ERQLExpression('X is CWGroup', 'X', 0))
 
 class GuessRrqlExprMainVarsTC(TestCase):
     def test_exists(self):
--- a/test/unittest_selectors.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_selectors.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for selectors mechanism
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main
--- a/test/unittest_spa2rql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_spa2rql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.devtools import TestServerConfiguration
 from cubicweb.xy import xy
--- a/test/unittest_uilib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_uilib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unittests for cubicweb.uilib
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/test/unittest_utils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_utils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.utils
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import re
@@ -11,13 +24,12 @@
 import datetime
 
 from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList
+from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList, RepeatList
 
 try:
-    import simplejson
-    from cubicweb.utils import CubicWebJsonEncoder
+    from cubicweb.utils import CubicWebJsonEncoder, json
 except ImportError:
-    simplejson = None
+    json = None
 
 class MakeUidTC(TestCase):
     def test_1(self):
@@ -41,6 +53,52 @@
         self.assert_(UStringIO())
 
 
+class RepeatListTC(TestCase):
+
+    def test_base(self):
+        l = RepeatList(3, (1, 3))
+        self.assertEquals(l[0], (1, 3))
+        self.assertEquals(l[2], (1, 3))
+        self.assertEquals(l[-1], (1, 3))
+        self.assertEquals(len(l), 3)
+        # XXX
+        self.assertEquals(l[4], (1, 3))
+
+        self.failIf(RepeatList(0, None))
+
+    def test_slice(self):
+        l = RepeatList(3, (1, 3))
+        self.assertEquals(l[0:1], [(1, 3)])
+        self.assertEquals(l[0:4], [(1, 3)]*3)
+        self.assertEquals(l[:], [(1, 3)]*3)
+
+    def test_iter(self):
+        self.assertEquals(list(RepeatList(3, (1, 3))),
+                          [(1, 3)]*3)
+
+    def test_add(self):
+        l = RepeatList(3, (1, 3))
+        self.assertEquals(l + [(1, 4)], [(1, 3)]*3  + [(1, 4)])
+        self.assertEquals([(1, 4)] + l, [(1, 4)] + [(1, 3)]*3)
+        self.assertEquals(l + RepeatList(2, (2, 3)), [(1, 3)]*3 + [(2, 3)]*2)
+
+        x = l + RepeatList(2, (1, 3))
+        self.assertIsInstance(x, RepeatList)
+        self.assertEquals(len(x), 5)
+        self.assertEquals(x[0], (1, 3))
+
+        x = l + [(1, 3)] * 2
+        self.assertEquals(x, [(1, 3)] * 5)
+
+    def test_eq(self):
+        self.assertEquals(RepeatList(3, (1, 3)),
+                          [(1, 3)]*3)
+
+    def test_pop(self):
+        l = RepeatList(3, (1, 3))
+        l.pop(2)
+        self.assertEquals(l, [(1, 3)]*2)
+
 class SizeConstrainedListTC(TestCase):
 
     def test_append(self):
@@ -61,11 +119,11 @@
 
 class JSONEncoderTC(TestCase):
     def setUp(self):
-        if simplejson is None:
-            self.skip('simplejson not available')
+        if json is None:
+            self.skip('json not available')
 
     def encode(self, value):
-        return simplejson.dumps(value, cls=CubicWebJsonEncoder)
+        return json.dumps(value, cls=CubicWebJsonEncoder)
 
     def test_encoding_dates(self):
         self.assertEquals(self.encode(datetime.datetime(2009, 9, 9, 20, 30)),
--- a/test/unittest_vregistry.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/test/unittest_vregistry.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main, TestCase
 
--- a/toolsutils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/toolsutils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """some utilities for cubicweb tools
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -82,7 +95,7 @@
         if askconfirm:
             print
             print diffs
-            action = ASK.ask('Replace ?', ('N', 'y', 'q'), 'N')
+            action = ASK.ask('Replace ?', ('Y', 'n', 'q'), 'Y').lower()
         else:
             action = 'y'
         if action == 'y':
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/transaction.py	Wed Apr 28 11:54:13 2010 +0200
@@ -0,0 +1,109 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""undoable transaction objects.
+
+
+This module is in the cubicweb package and not in cubicweb.server because those
+objects should be accessible to client through pyro, where the cubicweb.server
+package may not be installed.
+
+"""
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from cubicweb import RepositoryError
+
+
+ACTION_LABELS = {
+    'C': _('entity creation'),
+    'U': _('entity update'),
+    'D': _('entity deletion'),
+    'A': _('relation add'),
+    'R': _('relation removal'),
+    }
+
+
+class NoSuchTransaction(RepositoryError):
+    pass
+
+
+class Transaction(object):
+    """an undoable transaction"""
+
+    def __init__(self, uuid, time, ueid):
+        self.uuid = uuid
+        self.datetime = time
+        self.user_eid = ueid
+        # should be set by the dbapi connection
+        self.req = None
+
+    def __repr__(self):
+        return '<Transaction %s by %s on %s>' % (
+            self.uuid, self.user_eid, self.datetime)
+
+    def user(self):
+        """return the user entity which has done the transaction,
+        none if not found.
+        """
+        return self.req.execute('Any X WHERE X eid %(x)s',
+                                {'x': self.user_eid}, 'x').get_entity(0, 0)
+
+    def actions_list(self, public=True):
+        """return an ordered list of action effectued during that transaction
+
+        if public is true, return only 'public' action, eg not ones triggered
+        under the cover by hooks.
+        """
+        return self.req.cnx.transaction_actions(self.uuid, public)
+
+
+class AbstractAction(object):
+    def __init__(self, action, public, order):
+        self.action = action
+        self.public = public
+        self.order = order
+
+    @property
+    def label(self):
+        return ACTION_LABELS[self.action]
+
+
+class EntityAction(AbstractAction):
+    def __init__(self, action, public, order, etype, eid, changes):
+        AbstractAction.__init__(self, action, public, order)
+        self.etype = etype
+        self.eid = eid
+        self.changes = changes
+
+    def __repr__(self):
+        return '<%s: %s %s (%s)>' % (
+            self.label, self.eid, self.changes,
+            self.public and 'dbapi' or 'hook')
+
+
+class RelationAction(AbstractAction):
+    def __init__(self, action, public, order, rtype, eidfrom, eidto):
+        AbstractAction.__init__(self, action, public, order)
+        self.rtype = rtype
+        self.eid_from = eidfrom
+        self.eid_to = eidto
+
+    def __repr__(self):
+        return '<%s: %s %s %s (%s)>' % (
+            self.label, self.eid_from, self.rtype, self.eid_to,
+            self.public and 'dbapi' or 'hook')
--- a/uilib.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/uilib.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,25 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """user interface libraries
 
 contains some functions designed to help implementation of cubicweb user interface
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/utils.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/utils.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Some utilities for CubicWeb server/clients.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -12,35 +25,30 @@
 import decimal
 import datetime
 import random
+from itertools import repeat
+from uuid import uuid4
+from warnings import warn
 
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import deprecated
 
+_MARKER = object()
+
 # initialize random seed from current time
 random.seed()
 
-if sys.version_info[:2] < (2, 5):
+def make_uid(key=None):
+    """Return a unique identifier string.
 
-    from time import time
-    from md5 import md5
-    from random import randint
+    if specified, `key` is used to prefix the generated uid so it can be used
+    for instance as a DOM id or as sql table names.
 
-    def make_uid(key):
-        """forge a unique identifier
-        XXX not that unique on win32
-        """
-        key = str(key)
-        msg = key + "%.10f" % time() + str(randint(0, 1000000))
-        return key + md5(msg).hexdigest()
-
-else:
-
-    from uuid import uuid4
-
-    def make_uid(key):
-        # remove dash, generated uid are used as identifier sometimes (sql table
-        # names at least)
-        return str(key) + str(uuid4()).replace('-', '')
+    See uuid.uuid4 documentation for the shape of the generated identifier, but
+    this is basicallly a 32 bits hexadecimal string.
+    """
+    if key is None:
+        return uuid4().hex
+    return str(key) + uuid4().hex
 
 
 def dump_class(cls, clsname):
@@ -53,14 +61,9 @@
     # type doesn't accept unicode name
     # return type.__new__(type, str(clsname), (cls,), {})
     # __autogenerated__ attribute is just a marker
-    return type(str(clsname), (cls,), {'__autogenerated__': True})
-
-
-def merge_dicts(dict1, dict2):
-    """update a copy of `dict1` with `dict2`"""
-    dict1 = dict(dict1)
-    dict1.update(dict2)
-    return dict1
+    return type(str(clsname), (cls,), {'__autogenerated__': True,
+                                       '__doc__': cls.__doc__,
+                                       '__module__': cls.__module__})
 
 
 # use networkX instead ?
@@ -112,6 +115,42 @@
     __iadd__ = extend
 
 
+class RepeatList(object):
+    """fake a list with the same element in each row"""
+    __slots__ = ('_size', '_item')
+    def __init__(self, size, item):
+        self._size = size
+        self._item = item
+    def __len__(self):
+        return self._size
+    def __nonzero__(self):
+        return self._size
+    def __iter__(self):
+        return repeat(self._item, self._size)
+    def __getitem__(self, index):
+        return self._item
+    def __getslice__(self, i, j):
+        # XXX could be more efficient, but do we bother?
+        return ([self._item] * self._size)[i:j]
+    def __add__(self, other):
+        if isinstance(other, RepeatList):
+            if other._item == self._item:
+                return RepeatList(self._size + other._size, self._item)
+            return ([self._item] * self._size) + other[:]
+        return ([self._item] * self._size) + other
+    def __radd__(self, other):
+        if isinstance(other, RepeatList):
+            if other._item == self._item:
+                return RepeatList(self._size + other._size, self._item)
+            return other[:] + ([self._item] * self._size)
+        return other[:] + ([self._item] * self._size)
+    def __eq__(self, other):
+        if isinstance(other, RepeatList):
+            return other._size == self.size and other._item == self.item
+        return self[:] == other
+    def pop(self, i):
+        self._size -= 1
+
 class UStringIO(list):
     """a file wrapper which automatically encode unicode string to an encoding
     specifed in the constructor
@@ -167,15 +206,12 @@
     def add_post_inline_script(self, content):
         self.post_inlined_scripts.append(content)
 
-    def add_onload(self, jscode, jsoncall=False):
-        if jsoncall:
-            self.add_post_inline_script(u"""jQuery(CubicWeb).one('ajax-loaded', function(event) {
-%s
-});""" % jscode)
-        else:
-            self.add_post_inline_script(u"""jQuery(document).ready(function () {
- %s
- });""" % jscode)
+    def add_onload(self, jscode, jsoncall=_MARKER):
+        if jsoncall is not _MARKER:
+            warn('[3.7] specifying jsoncall is not needed anymore',
+                 DeprecationWarning, stacklevel=2)
+        self.add_post_inline_script(u"""jQuery(CubicWeb).one('server-response', function(event) {
+%s});""" % jscode)
 
 
     def add_js(self, jsfile):
@@ -217,7 +253,7 @@
         if self.jsvars:
             w(u'<script type="text/javascript"><!--//--><![CDATA[//><!--\n')
             for var, value, override in self.jsvars:
-                vardecl = u'%s = %s;' % (var, dumps(value))
+                vardecl = u'%s = %s;' % (var, json.dumps(value))
                 if not override:
                     vardecl = (u'if (typeof %s == "undefined") {%s}' %
                                (var, vardecl))
@@ -317,13 +353,31 @@
 
 try:
     # may not be there if cubicweb-web not installed
-    from simplejson import dumps, JSONEncoder
+    if sys.version_info < (2,6):
+        import simplejson as json
+    else:
+        import json
 except ImportError:
     pass
 else:
 
-    class CubicWebJsonEncoder(JSONEncoder):
-        """define a simplejson encoder to be able to encode yams std types"""
+    class CubicWebJsonEncoder(json.JSONEncoder):
+        """define a json encoder to be able to encode yams std types"""
+
+        # _iterencode is the only entry point I've found to use a custom encode
+        # hook early enough: .default() is called if nothing else matched before,
+        # .iterencode() is called once on the main structure to encode and then
+        # never gets called again.
+        # For the record, our main use case is in FormValidateController with:
+        #   json.dumps((status, args, entity), cls=CubicWebJsonEncoder)
+        # where we want all the entity attributes, including eid, to be part
+        # of the json object dumped.
+        # This would have once more been easier if Entity didn't extend dict.
+        def _iterencode(self, obj, markers=None):
+            if hasattr(obj, '__json_encode__'):
+                obj = obj.__json_encode__()
+            return json.JSONEncoder._iterencode(self, obj, markers)
+
         def default(self, obj):
             if isinstance(obj, datetime.datetime):
                 return obj.strftime('%Y/%m/%d %H:%M:%S')
@@ -336,12 +390,20 @@
             elif isinstance(obj, decimal.Decimal):
                 return float(obj)
             try:
-                return JSONEncoder.default(self, obj)
+                return json.JSONEncoder.default(self, obj)
             except TypeError:
                 # we never ever want to fail because of an unknown type,
                 # just return None in those cases.
                 return None
 
+
+@deprecated('[3.7] merge_dicts is deprecated')
+def merge_dicts(dict1, dict2):
+    """update a copy of `dict1` with `dict2`"""
+    dict1 = dict(dict1)
+    dict1.update(dict2)
+    return dict1
+
 from logilab.common import date
 _THIS_MOD_NS = globals()
 for funcname in ('date_range', 'todate', 'todatetime', 'datetime2ticks',
--- a/view.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/view.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract views and templates classes for CubicWeb web client
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -12,8 +25,7 @@
 from cStringIO import StringIO
 from warnings import warn
 
-from simplejson import dumps
-
+from cubicweb.utils import json
 from logilab.common.deprecation import deprecated
 from logilab.mtconverter import xml_escape
 
@@ -484,11 +496,11 @@
     def build_update_js_call(self, cbname, msg):
         rql = self.cw_rset.printable_rql()
         return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % (
-            cbname, self.id, dumps(rql), dumps(msg),
+            cbname, self.id, json.dumps(rql), json.dumps(msg),
             self.__registry__, self.div_id())
 
     def build_reload_js_call(self, cbname, msg):
-        return "javascript:userCallbackThenReloadPage('%s', %s)" % (cbname, dumps(msg))
+        return "javascript:userCallbackThenReloadPage('%s', %s)" % (cbname, json.dumps(msg))
 
     build_js = build_update_js_call # expect updatable component by default
 
--- a/vregistry.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/vregistry.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 * the vregistry handles various types of objects interacting
   together. The vregistry handles registration of dynamically loaded
@@ -14,10 +31,6 @@
   returned. The selector is instantiated at appobject registration
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -31,8 +44,7 @@
 from logilab.common.logging_ext import set_log_methods
 
 from cubicweb import CW_SOFTWARE_ROOT
-from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
-                      RegistryOutOfDate)
+from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
 from cubicweb.appobject import AppObject
 
 def _toload_info(path, extrapath, _toload=None):
@@ -48,7 +60,21 @@
             subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
             _toload_info(subfiles, extrapath, _toload)
         elif fileordir[-3:] == '.py':
-            modname = '.'.join(modpath_from_file(fileordir, extrapath))
+            modpath = modpath_from_file(fileordir, extrapath)
+            # omit '__init__' from package's name to avoid loading that module
+            # once for each name when it is imported by some other appobject
+            # module. This supposes import in modules are done as::
+            #
+            #   from package import something
+            #
+            # not::
+            #
+            #  from package.__init__ import something
+            #
+            # which seems quite correct.
+            if modpath[-1] == '__init__':
+                modpath.pop()
+            modname = '.'.join(modpath)
             _toload[0][modname] = fileordir
             _toload[1].append((fileordir, modname))
     return _toload
@@ -68,6 +94,11 @@
         return cls.id
     return cls.__regid__
 
+def class_registries(cls, registryname):
+    if registryname:
+        return (registryname,)
+    return cls.__registries__
+
 
 class Registry(dict):
 
@@ -142,11 +173,12 @@
     # dynamic selection methods ################################################
 
     def object_by_id(self, oid, *args, **kwargs):
-        """return object with the given oid. Only one object is expected to be
-        found.
+        """return object with the `oid` identifier. Only one object is expected
+        to be found.
 
-        raise `ObjectNotFound` if not object with id <oid> in <registry>
-        raise `AssertionError` if there is more than one object there
+        raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
+
+        raise :exc:`AssertionError` if there is more than one object there
         """
         objects = self[oid]
         assert len(objects) == 1, objects
@@ -156,8 +188,9 @@
         """return the most specific object among those with the given oid
         according to the given context.
 
-        raise `ObjectNotFound` if not object with id <oid> in <registry>
-        raise `NoSelectableObject` if not object apply
+        raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
+
+        raise :exc:`NoSelectableObject` if not object apply
         """
         return self._select_best(self[oid], *args, **kwargs)
 
@@ -191,25 +224,25 @@
         if len(args) > 1:
             warn('[3.5] only the request param can not be named when calling select*',
                  DeprecationWarning, stacklevel=3)
-        score, winners = 0, []
+        score, winners = 0, None
         for appobject in appobjects:
             appobjectscore = appobject.__select__(appobject, *args, **kwargs)
             if appobjectscore > score:
                 score, winners = appobjectscore, [appobject]
             elif appobjectscore > 0 and appobjectscore == score:
                 winners.append(appobject)
-        if not winners:
+        if winners is None:
             raise NoSelectableObject('args: %s\nkwargs: %s %s'
                                      % (args, kwargs.keys(),
                                         [repr(v) for v in appobjects]))
         if len(winners) > 1:
+            # log in production environement, error while debugging
             if self.config.debugmode:
-                self.error('select ambiguity, args: %s\nkwargs: %s %s',
-                           args, kwargs.keys(), [repr(v) for v in winners])
-            else:
                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
                                 % (args, kwargs.keys(),
                                    [repr(v) for v in winners]))
+            self.error('select ambiguity, args: %s\nkwargs: %s %s',
+                       args, kwargs.keys(), [repr(v) for v in winners])
         # return the result of calling the appobject
         return winners[0](*args, **kwargs)
 
@@ -225,8 +258,18 @@
     def __init__(self, config):
         super(VRegistry, self).__init__()
         self.config = config
+        # need to clean sys.path this to avoid import confusion pb (i.e.  having
+        # the same module loaded as 'cubicweb.web.views' subpackage and as
+        # views' or 'web.views' subpackage. This is mainly for testing purpose,
+        # we should'nt need this in production environment
+        for webdir in (join(dirname(realpath(__file__)), 'web'),
+                       join(dirname(__file__), 'web')):
+            if webdir in sys.path:
+                sys.path.remove(webdir)
+        if CW_SOFTWARE_ROOT in sys.path:
+            sys.path.remove(CW_SOFTWARE_ROOT)
 
-    def reset(self, path=None, force_reload=None):
+    def reset(self):
         # don't use self.clear, we want to keep existing subdictionaries
         for subdict in self.itervalues():
             subdict.clear()
@@ -299,36 +342,66 @@
 #         self[regname].pop(oid, None)
 
     def register_all(self, objects, modname, butclasses=()):
+        """register all `objects` given. Objects which are not from the module
+        `modname` or which are in `butclasses` won't be registered.
+
+        Typical usage is:
+
+        .. sourcecode:: python
+
+            vreg.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
+
+        So you get partially automatic registration, keeping manual registration
+        for some object (to use
+        :meth:`~cubicweb.cwvreg.CubicWebRegistry.register_and_replace` for
+        instance)
+        """
         for obj in objects:
             try:
                 if obj.__module__ != modname or obj in butclasses:
                     continue
                 oid = class_regid(obj)
-                registryname = obj.__registry__
             except AttributeError:
                 continue
             if oid and not '__abstract__' in obj.__dict__:
-                self.register(obj, registryname)
+                self.register(obj, oid=oid)
 
     def register(self, obj, registryname=None, oid=None, clear=False):
-        """base method to add an object in the registry"""
+        """register `obj` application object into `registryname` or
+        `obj.__registry__` if not specified, with identifier `oid` or
+        `obj.__regid__` if not specified.
+
+        If `clear` is true, all objects with the same identifier will be
+        previously unregistered.
+        """
         assert not '__abstract__' in obj.__dict__
-        registryname = registryname or obj.__registry__
-        registry = self.setdefault(registryname)
-        registry.register(obj, oid=oid, clear=clear)
         try:
             vname = obj.__name__
         except AttributeError:
+            # XXX may occurs?
             vname = obj.__class__.__name__
-        self.debug('registered appobject %s in registry %s with id %s',
-                   vname, registryname, oid or class_regid(obj))
+        for registryname in class_registries(obj, registryname):
+            registry = self.setdefault(registryname)
+            registry.register(obj, oid=oid, clear=clear)
+            self.debug('registered appobject %s in registry %s with id %s',
+                       vname, registryname, oid or class_regid(obj))
         self._loadedmods[obj.__module__][classid(obj)] = obj
 
     def unregister(self, obj, registryname=None):
-        self[registryname or obj.__registry__].unregister(obj)
+        """unregister `obj` application object from the registry `registryname` or
+        `obj.__registry__` if not specified.
+        """
+        for registryname in class_registries(obj, registryname):
+            self[registryname].unregister(obj)
 
     def register_and_replace(self, obj, replaced, registryname=None):
-        self[registryname or obj.__registry__].register_and_replace(obj, replaced)
+        """register `obj` application object into `registryname` or
+        `obj.__registry__` if not specified. If found, the `replaced` object
+        will be unregistered first (else a warning will be issued as it's
+        generally unexpected).
+        """
+        for registryname in class_registries(obj, registryname):
+            self[registryname].register_and_replace(obj, replaced)
 
     # initialization methods ###################################################
 
@@ -341,60 +414,61 @@
         self._loadedmods = {}
         return filemods
 
-    def register_objects(self, path, force_reload, extrapath=None):
-        # need to clean sys.path this to avoid import confusion pb (i.e.
-        # having the same module loaded as 'cubicweb.web.views' subpackage and
-        # as views'  or 'web.views' subpackage
-        # this is mainly for testing purpose, we should'nt need this in
-        # production environment
-        for webdir in (join(dirname(realpath(__file__)), 'web'),
-                       join(dirname(__file__), 'web')):
-            if webdir in sys.path:
-                sys.path.remove(webdir)
-        if CW_SOFTWARE_ROOT in sys.path:
-            sys.path.remove(CW_SOFTWARE_ROOT)
+    def register_objects(self, path, force_reload=False, extrapath=None):
         # load views from each directory in the instance's path
         filemods = self.init_registration(path, extrapath)
-        change = False
         for filepath, modname in filemods:
-            if self.load_file(filepath, modname, force_reload):
-                change = True
-        if change:
-            self.initialization_completed()
-        return change
+            self.load_file(filepath, modname, force_reload)
+        self.initialization_completed()
 
     def initialization_completed(self):
         for regname, reg in self.iteritems():
             reg.initialization_completed()
 
+    def _mdate(self, filepath):
+        try:
+            return stat(filepath)[-2]
+        except OSError:
+            # this typically happens on emacs backup files (.#foo.py)
+            self.warning('Unable to load %s. It is likely to be a backup file',
+                         filepath)
+            return None
+
+    def is_reload_needed(self, path):
+        """return True if something module changed and the registry should be
+        reloaded
+        """
+        lastmodifs = self._lastmodifs
+        for fileordir in path:
+            if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
+                if self.is_reload_needed([join(fileordir, fname)
+                                          for fname in listdir(fileordir)]):
+                    return True
+            elif fileordir[-3:] == '.py':
+                mdate = self._mdate(fileordir)
+                if mdate is None:
+                    continue # backup file, see _mdate implementation
+                if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
+                    self.info('File %s changed since last visit', fileordir)
+                    return True
+        return False
+
     def load_file(self, filepath, modname, force_reload=False):
         """load app objects from a python file"""
         from logilab.common.modutils import load_module_from_name
         if modname in self._loadedmods:
             return
         self._loadedmods[modname] = {}
-        try:
-            modified_on = stat(filepath)[-2]
-        except OSError:
-            # this typically happens on emacs backup files (.#foo.py)
-            self.warning('Unable to load %s. It is likely to be a backup file',
-                         filepath)
-            return False
-        if filepath in self._lastmodifs:
-            # only load file if it was modified
-            if modified_on <= self._lastmodifs[filepath]:
-                return
-            # if it was modified, raise RegistryOutOfDate to reload everything
-            self.info('File %s changed since last visit', filepath)
-            raise RegistryOutOfDate()
+        mdate = self._mdate(filepath)
+        if mdate is None:
+            return # backup file, see _mdate implementation
         # set update time before module loading, else we get some reloading
         # weirdness in case of syntax error or other error while importing the
         # module
-        self._lastmodifs[filepath] = modified_on
+        self._lastmodifs[filepath] = mdate
         # load the module
         module = load_module_from_name(modname, use_sys=not force_reload)
         self.load_module(module)
-        return True
 
     def load_module(self, module):
         self.info('loading %s', module)
@@ -437,7 +511,7 @@
             self._load_ancestors_then_object(modname, parent)
         if (appobjectcls.__dict__.get('__abstract__')
             or appobjectcls.__name__[0] == '_'
-            or not appobjectcls.__registry__
+            or not appobjectcls.__registries__
             or not class_regid(appobjectcls)):
             return
         try:
--- a/web/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,16 +1,36 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """CubicWeb web client core. You'll need a apache-modpython or twisted
 publisher to get a full CubicWeb web application
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from simplejson import dumps
+import sys
+if sys.version_info < (2,6):
+    import simplejson as json
+else:
+    import json
+
+dumps = json.dumps
+
 from urllib import quote as urlquote
 
 from logilab.common.deprecation import deprecated
--- a/web/_exceptions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/_exceptions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # pylint: disable-msg=W0401,W0614
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """exceptions used in the core of the CubicWeb web application
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -53,10 +66,9 @@
     """raised when a json remote call fails
     """
     def __init__(self, reason=''):
-        #super(RequestError, self).__init__() # XXX require py >= 2.5
-        RequestError.__init__(self)
+        super(RequestError, self).__init__()
         self.reason = reason
 
     def dumps(self):
-        import simplejson
-        return simplejson.dumps({'reason': self.reason})
+        from cubicweb.web import json
+        return json.dumps({'reason': self.reason})
--- a/web/action.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/action.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract action classes for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/application.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/application.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """CubicWeb web client application object
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -33,15 +46,24 @@
 
     def __init__(self, vreg):
         self.session_time = vreg.config['http-session-time'] or None
-        assert self.session_time is None or self.session_time > 0
-        self.cleanup_session_time = vreg.config['cleanup-session-time'] or 43200
-        assert self.cleanup_session_time > 0
-        self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 120
+        if self.session_time is not None:
+            assert self.session_time > 0
+            self.cleanup_session_time = self.session_time
+        else:
+            self.cleanup_session_time = vreg.config['cleanup-session-time'] or 1440 * 60
+            assert self.cleanup_session_time > 0
+        self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60
         assert self.cleanup_anon_session_time > 0
-        if self.session_time:
-            assert self.cleanup_session_time < self.session_time
-            assert self.cleanup_anon_session_time < self.session_time
         self.authmanager = vreg['components'].select('authmanager', vreg=vreg)
+        if vreg.config.anonymous_user() is not None:
+            self.clean_sessions_interval = min(
+                5 * 60,
+                self.cleanup_session_time / 2.,
+                self.cleanup_anon_session_time / 2.)
+        else:
+            self.clean_sessions_interval = min(
+                5 * 60,
+                self.cleanup_session_time / 2.)
 
     def clean_sessions(self):
         """cleanup sessions which has not been unused since a given amount of
@@ -123,7 +145,11 @@
         SESSION_MANAGER = self.session_manager
         if not 'last_login_time' in self.vreg.schema:
             self._update_last_login_time = lambda x: None
-        CW_EVENT_MANAGER.bind('after-registry-reload', self.reset_session_manager)
+        if self.vreg.config.mode != 'test':
+            # don't try to reset session manager during test, this leads to
+            # weird failures when running multiple tests
+            CW_EVENT_MANAGER.bind('after-registry-reload',
+                                  self.reset_session_manager)
 
     def reset_session_manager(self):
         data = self.session_manager.dump_data()
@@ -133,6 +159,10 @@
         global SESSION_MANAGER
         SESSION_MANAGER = self.session_manager
 
+    @property
+    def clean_sessions_interval(self):
+        return self.session_manager.clean_sessions_interval
+
     def clean_sessions(self):
         """cleanup sessions which has not been unused since a given amount of
         time
@@ -342,7 +372,11 @@
                 # redirect is raised by edit controller when everything went fine,
                 # so try to commit
                 try:
-                    req.cnx.commit()
+                    txuuid = req.cnx.commit()
+                    if txuuid is not None:
+                        msg = u'<span class="undo">[<a href="%s">%s</a>]</span>' %(
+                            req.build_url('undo', txuuid=txuuid), req._('undo'))
+                        req.append_to_redirect_message(msg)
                 except ValidationError, ex:
                     self.validation_error_handler(req, ex)
                 except Unauthorized, ex:
@@ -364,6 +398,9 @@
                 self.error_handler(req, ex, tb=False)
             except Exception, ex:
                 self.error_handler(req, ex, tb=True)
+            except:
+                self.critical('Catch all triggered!!!')
+                self.exception('this is what happened')
         finally:
             if req.cnx is not None:
                 try:
@@ -393,7 +430,7 @@
         self.exception(repr(ex))
         req.set_header('Cache-Control', 'no-cache')
         req.remove_header('Etag')
-        req.message = None
+        req.reset_message()
         req.reset_headers()
         if req.json_request:
             raise RemoteCallFailed(unicode(ex))
--- a/web/box.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/box.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract box classes for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/captcha.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/captcha.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Simple captcha library, based on PIL. Monkey patch functions in this module
 if you want something better...
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -17,7 +30,7 @@
 from time import time
 
 from cubicweb import tags
-from cubicweb.web import formwidgets as fw
+from cubicweb.web import ProcessFormError, formwidgets as fw
 
 
 def pil_captcha(text, fontfile, fontsize):
@@ -63,7 +76,22 @@
 class CaptchaWidget(fw.TextInput):
     def render(self, form, field, renderer=None):
         # t=int(time()*100) to make sure img is not cached
-        src = form._cw.build_url('view', vid='captcha', t=int(time()*100))
+        src = form._cw.build_url('view', vid='captcha', t=int(time()*100),
+                                 captchakey=field.input_name(form))
         img = tags.img(src=src, alt=u'captcha')
         img = u'<div class="captcha">%s</div>' % img
         return img + super(CaptchaWidget, self).render(form, field, renderer)
+
+    def process_field_data(self, form, field):
+        captcha = form._cw.get_session_data(field.input_name(form), None,
+                                            pop=True)
+        val = super(CaptchaWidget, self).process_field_data(form, field)
+        if val is None:
+            return val # required will be checked by field
+        if captcha is None:
+            msg = form._cw._('unable to check captcha, please try again')
+            raise ProcessFormError(msg)
+        elif val.lower() != captcha.lower():
+            msg = form._cw._('incorrect captcha value')
+            raise ProcessFormError(msg)
+        return val
--- a/web/component.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/component.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,20 +1,31 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract component class and base components definition for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from simplejson import dumps
-
 from logilab.common.deprecation import class_renamed
 from logilab.mtconverter import xml_escape
 
 from cubicweb import role
-from cubicweb.utils import merge_dicts
+from cubicweb.web import json
 from cubicweb.view import Component
 from cubicweb.selectors import (
     paginated_rset, one_line_rset, primary_view, match_context_prop,
@@ -116,14 +127,15 @@
             del params[self.stop_param]
 
     def page_url(self, path, params, start, stop):
-        params = merge_dicts(params, {self.start_param : start,
-                                      self.stop_param : stop,})
+        params = dict(params)
+        params.update({self.start_param : start,
+                       self.stop_param : stop,})
         if path == 'json':
             rql = params.pop('rql', self.cw_rset.printable_rql())
             # latest 'true' used for 'swap' mode
             url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
-                dumps(params.get('divid', 'paginated-content')),
-                dumps(rql), dumps(params.pop('vid', None)), dumps(params))
+                json.dumps(params.get('divid', 'paginated-content')),
+                json.dumps(rql), json.dumps(params.pop('vid', None)), json.dumps(params))
         else:
             url = self._cw.build_url(path, **params)
         return url
--- a/web/controller.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/controller.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,28 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract controller classe for CubicWeb web client
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
+from logilab.mtconverter import xml_escape
+
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
 from cubicweb.web import LOGGER, Redirect, RequestError
@@ -79,19 +94,6 @@
                 self.cw_rset = pp.process_query(rql)
         return self.cw_rset
 
-    def check_expected_params(self, params):
-        """check that the given list of parameters are specified in the form
-        dictionary
-        """
-        missing = []
-        for param in params:
-            if not self._cw.form.get(param):
-                missing.append(param)
-        if missing:
-            raise RequestError('missing required parameter(s): %s'
-                               % ','.join(missing))
-
-
     def notify_edited(self, entity):
         """called by edit_entity() to notify which entity is edited"""
         # NOTE: we can't use entity.rest_path() at this point because
@@ -100,31 +102,10 @@
         if not self._edited_entity:
             self._edited_entity = entity
 
-    # XXX move to EditController (only customer)
-    def delete_entities(self, eidtypes):
-        """delete entities from the repository"""
-        redirect_info = set()
-        eidtypes = tuple(eidtypes)
-        for eid, etype in eidtypes:
-            entity = self._cw.entity_from_eid(eid, etype)
-            path, params = entity.after_deletion_path()
-            redirect_info.add( (path, tuple(params.iteritems())) )
-            entity.delete()
-        if len(redirect_info) > 1:
-            # In the face of ambiguity, refuse the temptation to guess.
-            self._after_deletion_path = 'view', ()
-        else:
-            self._after_deletion_path = iter(redirect_info).next()
-        if len(eidtypes) > 1:
-            self._cw.set_message(self._cw._('entities deleted'))
-        else:
-            self._cw.set_message(self._cw._('entity deleted'))
-
     def validate_cache(self, view):
         view.set_http_cache_headers()
         self._cw.validate_cache()
 
-    # XXX is that used AT ALL ?
     def reset(self):
         """reset form parameters and redirect to a view determinated by given
         parameters
@@ -132,7 +113,7 @@
         newparams = {}
         # sets message if needed
         if self._cw.message:
-            newparams['__message'] = self._cw.message
+            newparams['_cwmsgid'] = self._cw.set_redirect_message(self._cw.message)
         if self._cw.form.has_key('__action_apply'):
             self._return_to_edition_view(newparams)
         if self._cw.form.has_key('__action_cancel'):
@@ -140,8 +121,6 @@
         else:
             self._return_to_original_view(newparams)
 
-
-    # XXX is that used AT ALL ?
     def _return_to_original_view(self, newparams):
         """validate-button case"""
         # transforms __redirect[*] parameters into regular form parameters
@@ -156,10 +135,13 @@
         elif '__redirectpath' in self._cw.form:
             # if redirect path was explicitly specified in the form, use it
             path = self._cw.form['__redirectpath']
-            if self._edited_entity and path != self._edited_entity.rest_path():
-                # XXX may be here on modification? if yes the message should be
-                # modified where __createdpath is detected (cw.web.request)
-                newparams['__createdpath'] = self._edited_entity.rest_path()
+            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'))
+                self._cw.append_to_redirect_message(msg)
         elif self._after_deletion_path:
             # else it should have been set during form processing
             path, params = self._after_deletion_path
@@ -167,6 +149,9 @@
             params.update(newparams)
             newparams = params
         elif self._edited_entity:
+            # clear caches in case some attribute participating to the rest path
+            # has been modified
+            self._edited_entity.clear_all_caches()
             path = self._edited_entity.rest_path()
         else:
             path = 'view'
@@ -174,7 +159,6 @@
         url = append_url_params(url, self._cw.form.get('__redirectparams'))
         raise Redirect(url)
 
-    # XXX is that used AT ALL ?
     def _return_to_edition_view(self, newparams):
         """apply-button case"""
         form = self._cw.form
@@ -186,7 +170,7 @@
             path = 'view'
             newparams['rql'] = form['rql']
         else:
-            self.warning("the edited data seems inconsistent")
+            self.warning('the edited data seems inconsistent')
             path = 'view'
         # pick up the correction edition view
         if form.get('__form_id'):
@@ -198,7 +182,6 @@
         raise Redirect(self._cw.build_url(path, **newparams))
 
 
-    # XXX is that used AT ALL ?
     def _return_to_lastpage(self, newparams):
         """cancel-button case: in this case we are always expecting to go back
         where we came from, and this is not easy. Currently we suppose that
--- a/web/data/cubicweb.ajax.js	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.ajax.js	Wed Apr 28 11:54:13 2010 +0200
@@ -92,12 +92,9 @@
        setFormsTarget(node);
     }
     loadDynamicFragments(node);
-    // XXX simulates document.ready, but the former
-    // only runs once, this one potentially many times
-    // we probably need to unbind the fired events
-    // When this is done, jquery.treeview.js (for instance)
-    // can be unpatched.
-    jQuery(CubicWeb).trigger('ajax-loaded');
+    // XXX [3.7] jQuery.one is now used instead jQuery.bind,
+    // jquery.treeview.js can be unpatched accordingly.
+    jQuery(CubicWeb).trigger('server-response', [true, node]);
 }
 
 /* cubicweb loadxhtml plugin to make jquery handle xhtml response
@@ -242,7 +239,8 @@
 function asyncRemoteExec(fname /* ... */) {
     setProgressCursor();
     var props = {'fname' : fname, 'pageid' : pageid,
-                      'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+                 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+    // XXX we should inline the content of loadRemote here
     var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
     deferred = deferred.addErrback(remoteCallFailed);
     deferred = deferred.addErrback(resetCursor);
--- a/web/data/cubicweb.compat.js	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.compat.js	Wed Apr 28 11:54:13 2010 +0200
@@ -6,22 +6,26 @@
     }
 }
 
+// XXX looks completely unused (candidate for removal)
 function getElementsByTagAndClassName(tag, klass, root) {
     root = root || document;
     // FIXME root is not used in this compat implementation
     return jQuery(tag + '.' + klass);
 }
 
+/* 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 ?
+*/
 function map(func, array) {
-    // XXX jQUery tends to simplify lists with only one element :
-    // >>> y = ['a:b:c']
-    // >>> jQuery.map(y, function(y) { return y.split(':');})
-    // ["a", "b", "c"]
-    // where I would expect :
-    // [ ["a", "b", "c"] ]
-    // return jQuery.map(array, func);
     var result = [];
-    for (var i=0,length=array.length;i<length;i++) {
+    for (var i=0, length=array.length;
+         i<length;
+         i++) {
 	result.push(func(array[i]));
     }
     return result;
@@ -41,6 +45,7 @@
     jQuery(node).addClass(klass);
 }
 
+// XXX looks completely unused (candidate for removal)
 function toggleElementClass(node, klass) {
     jQuery(node).toggleClass(klass);
 }
@@ -281,26 +286,35 @@
 
 jQuery.extend(Deferred.prototype, {
     __init__: function() {
-	this.onSuccess = [];
-	this.onFailure = [];
-	this.req = null;
+	this._onSuccess = [];
+	this._onFailure = [];
+	this._req = null;
+        this._result = null;
+        this._error = null;
     },
 
     addCallback: function(callback) {
-	this.onSuccess.push([callback, sliceList(arguments, 1)]);
+        if (this._req.readyState == 4) {
+            if (this._result) { callback.apply(null, this._result, this._req); }
+        }
+        else { this._onSuccess.push([callback, sliceList(arguments, 1)]); }
 	return this;
     },
 
     addErrback: function(callback) {
-	this.onFailure.push([callback, sliceList(arguments, 1)]);
+        if (this._req.readyState == 4) {
+            if (this._error) { callback.apply(null, this._error, this._req); }
+        }
+        else { this._onFailure.push([callback, sliceList(arguments, 1)]); }
 	return this;
     },
 
     success: function(result) {
+        this._result = result;
 	try {
-	    for (var i=0; i<this.onSuccess.length; i++) {
-		var callback = this.onSuccess[i][0];
-		var args = merge([result, this.req], this.onSuccess[i][1]);
+	    for (var i=0; i<this._onSuccess.length; i++) {
+		var callback = this._onSuccess[i][0];
+		var args = merge([result, this._req], this._onSuccess[i][1]);
 		callback.apply(null, args);
 	    }
 	} catch (error) {
@@ -309,9 +323,10 @@
     },
 
     error: function(xhr, status, error) {
-	for (var i=0; i<this.onFailure.length; i++) {
-	    var callback = this.onFailure[i][0];
-	    var args = merge([error, this.req], this.onFailure[i][1]);
+        this._error = error;
+	for (var i=0; i<this._onFailure.length; i++) {
+	    var callback = this._onFailure[i][0];
+	    var args = merge([error, this._req], this._onFailure[i][1]);
 	    callback.apply(null, args);
 	}
     }
@@ -319,6 +334,46 @@
 });
 
 
+/*
+ * Asynchronously load an url and return a deferred
+ * whose callbacks args are decoded according to
+ * the Content-Type response header
+ */
+function loadRemote(url, data, reqtype) {
+    var d = new Deferred();
+    jQuery.ajax({
+	url: url,
+	type: reqtype,
+	data: data,
+
+	beforeSend: function(xhr) {
+	    d._req = xhr;
+	},
+
+	success: function(data, status) {
+            if (d._req.getResponseHeader("content-type") == 'application/json') {
+              data = evalJSON(data);
+            }
+	    d.success(data);
+	},
+
+	error: function(xhr, status, error) {
+          try {
+            if (xhr.status == 500) {
+                var reason_dict = evalJSON(xhr.responseText);
+                d.error(xhr, status, reason_dict['reason']);
+                return;
+            }
+          } catch(exc) {
+            log('error with server side error report:' + exc);
+          }
+          d.error(xhr, status, null);
+	}
+    });
+    return d;
+}
+
+
 /** @id MochiKit.DateTime.toISOTime */
 toISOTime = function (date, realISO/* = false */) {
     if (typeof(date) == "undefined" || date === null) {
@@ -366,36 +421,6 @@
 };
 
 
-/*
- * Asynchronously load an url and return a deferred
- * whose callbacks args are decoded according to
- * the Content-Type response header
- */
-function loadRemote(url, data, reqtype) {
-    var d = new Deferred();
-    jQuery.ajax({
-	url: url,
-	type: reqtype,
-	data: data,
-
-	beforeSend: function(xhr) {
-	    d.req = xhr;
-	},
-
-	success: function(data, status) {
-            if (d.req.getResponseHeader("content-type") == 'application/json') {
-              data = evalJSON(data);
-            }
-	    d.success(data);
-	},
-
-	error: function(xhr, status, error) {
-	    error = evalJSON(xhr.responseText);
-	    d.error(xhr, status, error['reason']);
-	}
-    });
-    return d;
-}
 
 /* depth-first implementation of the nodeWalk function found
  * in MochiKit.Base
--- a/web/data/cubicweb.edition.js	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.edition.js	Wed Apr 28 11:54:13 2010 +0200
@@ -322,7 +322,7 @@
 
 function _clearPreviousErrors(formid) {
     jQuery('#' + formid + 'ErrorMessage').remove();
-    jQuery('#' + formid + ' span.error').remove();
+    jQuery('#' + formid + ' span.errorMsg').remove();
     jQuery('#' + formid + ' .error').removeClass('error');
 }
 
@@ -331,25 +331,30 @@
     var firsterrfield = null;
     for (fieldname in errors) {
 	var errmsg = errors[fieldname];
-	var fieldid = fieldname + ':' + eid;
-	var suffixes = ['', '-subject', '-object'];
-	var found = false;
-	for (var i=0, length=suffixes.length; i<length;i++) {
-	    var field = jqNode(fieldname + suffixes[i] + ':' + eid);
-	    if (field && getNodeAttribute(field, 'type') != 'hidden') {
-		if ( !firsterrfield ) {
-		    firsterrfield = 'err-' + fieldid;
+	if (!fieldname) {
+	    globalerrors.push(errmsg);
+	} else {
+	    var fieldid = fieldname + ':' + eid;
+	    var suffixes = ['', '-subject', '-object'];
+	    var found = false;
+	    // XXX remove suffixes at some point
+	    for (var i=0, length=suffixes.length; i<length;i++) {
+		var field = jqNode(fieldname + suffixes[i] + ':' + eid);
+		if (field && getNodeAttribute(field, 'type') != 'hidden') {
+		    if ( !firsterrfield ) {
+			firsterrfield = 'err-' + fieldid;
+		    }
+		    addElementClass(field, 'error');
+		    var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
+		    field.before(span);
+		    found = true;
+		    break;
 		}
-		addElementClass(field, 'error');
-		var span = SPAN({'id': 'err-' + fieldid, 'class': "error"}, errmsg);
-		field.before(span);
-		found = true;
-		break;
 	    }
-	}
-	if (!found) {
-	    firsterrfield = formid;
-	    globalerrors.push(_(fieldname) + ' : ' + errmsg);
+	    if (!found) {
+		firsterrfield = formid;
+		globalerrors.push(_(fieldname) + ' : ' + errmsg);
+	    }
 	}
     }
     if (globalerrors.length) {
--- a/web/data/cubicweb.facets.js	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.facets.js	Wed Apr 28 11:54:13 2010 +0200
@@ -47,10 +47,11 @@
 	var rql = result[0];
 	var $bkLink = jQuery('#facetBkLink');
 	if ($bkLink.length) {
-	    var bkUrl = $bkLink.attr('cubicweb:target') + '&path=view?rql=' + rql;
+	    var bkPath = 'view?rql=' + escape(rql);
 	    if (vid) {
-		bkUrl += '&vid=' + vid;
+		bkPath += '&vid=' + escape(vid);
 	    }
+	    var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + escape(bkPath);
 	    $bkLink.attr('href', bkUrl);
 	}
 	var toupdate = result[1];
--- a/web/data/cubicweb.form.css	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.form.css	Wed Apr 28 11:54:13 2010 +0200
@@ -192,11 +192,14 @@
   background-color: #eeedd9;
 }
 
-input.error {
+.error input { /* error added by the form renderer */
+  background: transparent url("error.png") 100% 50% no-repeat;
+}
+input.error { /* error added by javascript */
   background: transparent url("error.png") 100% 50% no-repeat;
 }
 
-span.error {
+span.errorMsg {
   display: block;
   font-weight: bold;
   color: #ed0d0d;
--- a/web/data/cubicweb.python.js	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/data/cubicweb.python.js	Wed Apr 28 11:54:13 2010 +0200
@@ -237,9 +237,9 @@
  * ['d', 'e', 'f']
  */
 function sliceList(lst, start, stop, step) {
-    var start = start || 0;
-    var stop = stop || lst.length;
-    var step = step || 1;
+    start = start || 0;
+    stop = stop || lst.length;
+    step = step || 1;
     if (stop < 0) {
 	stop = max(lst.length+stop, 0);
     }
@@ -256,6 +256,7 @@
 /* returns a partial func that calls a mehod on its argument
  * py-equiv: return lambda obj: getattr(obj, methname)(*args)
  */
+// XXX looks completely unused (candidate for removal)
 function methodcaller(methname) {
     var args = sliceList(arguments, 1);
     return function(obj) {
@@ -394,4 +395,14 @@
     }
 };
 
+jQuery(document).ready(function() {
+    jQuery(CubicWeb).trigger('server-response', [false, document]);
+});
+
+// XXX as of 2010-04-07, no known cube uses this
+jQuery(CubicWeb).bind('ajax-loaded', function() {
+    log('[3.7] "ajax-loaded" event is deprecated, use "server-response" instead');
+    jQuery(CubicWeb).trigger('server-response', [false, document]);
+});
+
 CubicWeb.provide('python.js');
--- a/web/facet.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/facet.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """contains utility functions and some visual component to restrict results of
 a search
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -338,10 +351,11 @@
 
 class RelationFacet(VocabularyFacet):
     __select__ = partial_relation_possible() & match_context_prop()
-    # class attributes to configure the rel ation facet
+    # class attributes to configure the relation facet
     rtype = None
     role = 'subject'
     target_attr = 'eid'
+    target_type = None
     # set this to a stored procedure name if you want to sort on the result of
     # this function's result instead of direct value
     sortfunc = None
@@ -365,8 +379,11 @@
             sort = self.sortasc
         try:
             mainvar = self.filtered_variable
-            insert_attr_select_relation(rqlst, mainvar, self.rtype, self.role,
-                                        self.target_attr, self.sortfunc, sort)
+            var = insert_attr_select_relation(
+                rqlst, mainvar, self.rtype, self.role, self.target_attr,
+                self.sortfunc, sort)
+            if self.target_type is not None:
+                rqlst.add_type_restriction(var, self.target_type)
             try:
                 rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args, self.cw_rset.cachekey)
             except:
@@ -375,7 +392,9 @@
                 return ()
         finally:
             rqlst.recover()
-        return self.rset_vocabulary(rset)
+        # don't call rset_vocabulary on empty result set, it may be an empty
+        # *list* (see rqlexec implementation)
+        return rset and self.rset_vocabulary(rset)
 
     def possible_values(self):
         """return a list of possible values (as string since it's used to
@@ -464,7 +483,9 @@
                 return ()
         finally:
             rqlst.recover()
-        return self.rset_vocabulary(rset)
+        # don't call rset_vocabulary on empty result set, it may be an empty
+        # *list* (see rqlexec implementation)
+        return rset and self.rset_vocabulary(rset)
 
     def rset_vocabulary(self, rset):
         _ = self._cw._
--- a/web/form.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/form.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract form classes for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -14,8 +27,7 @@
 
 from cubicweb.appobject import AppObject
 from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.web import httpcache, formfields, controller
-
+from cubicweb.web import httpcache, formfields, controller, formwidgets as fwdgs
 
 class FormViewMixIn(object):
     """abstract form view mix-in"""
@@ -137,8 +149,9 @@
 
     @iclassmethod
     def field_by_name(cls_or_self, name, role=None):
-        """return field with the given name and role.
-        Raise FieldNotFound if the field can't be found.
+        """Return field with the given name and role.
+
+        Raise :exc:`FieldNotFound` if the field can't be found.
         """
         for field in cls_or_self._fieldsattr():
             if field.name == name and field.role == role:
@@ -147,31 +160,49 @@
 
     @iclassmethod
     def fields_by_name(cls_or_self, name, role=None):
-        """return a list of fields with the given name and role"""
+        """Return a list of fields with the given name and role."""
         return [field for field in cls_or_self._fieldsattr()
                 if field.name == name and field.role == role]
 
     @iclassmethod
     def remove_field(cls_or_self, field):
-        """remove a field from form class or instance"""
+        """Remove the given field."""
         cls_or_self._fieldsattr().remove(field)
 
     @iclassmethod
     def append_field(cls_or_self, field):
-        """append a field to form class or instance"""
+        """Append the given field."""
         cls_or_self._fieldsattr().append(field)
 
     @iclassmethod
-    def insert_field_before(cls_or_self, new_field, name, role='subject'):
-        field = cls_or_self.field_by_name(name, role)
+    def insert_field_before(cls_or_self, field, name, role=None):
+        """Insert the given field before the field of given name and role."""
+        bfield = cls_or_self.field_by_name(name, role)
         fields = cls_or_self._fieldsattr()
-        fields.insert(fields.index(field), new_field)
+        fields.insert(fields.index(bfield), field)
+
+    @iclassmethod
+    def insert_field_after(cls_or_self, field, name, role=None):
+        """Insert the given field after the field of given name and role."""
+        afield = cls_or_self.field_by_name(name, role)
+        fields = cls_or_self._fieldsattr()
+        fields.insert(fields.index(afield)+1, field)
 
     @iclassmethod
-    def insert_field_after(cls_or_self, new_field, name, role='subject'):
-        field = cls_or_self.field_by_name(name, role)
-        fields = cls_or_self._fieldsattr()
-        fields.insert(fields.index(field)+1, new_field)
+    def add_hidden(cls_or_self, name, value=None, **kwargs):
+        """Append an hidden field to the form. `name`, `value` and extra keyword
+        arguments will be given to the field constructor. The inserted field is
+        returned.
+        """
+        kwargs.setdefault('ignore_req_params', True)
+        kwargs.setdefault('widget', fwdgs.HiddenInput)
+        field = formfields.StringField(name=name, value=value, **kwargs)
+        if 'id' in kwargs:
+            # by default, hidden input don't set id attribute. If one is
+            # explicitly specified, ensure it will be set
+            field.widget.setdomid = True
+        cls_or_self.append_field(field)
+        return field
 
     def session_key(self):
         """return the key that may be used to store / retreive data about a
--- a/web/formfields.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/formfields.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,63 @@
-"""Fields are used to control what's displayed in forms. It makes the link
-between something to edit and its display in the form. Actual display is handled
-by a widget associated to the field.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+The Field class and basic fields
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. Note::
+  Fields are used to control what's edited in forms. They makes the link between
+  something to edit and its display in the form. Actual display is handled by a
+  widget associated to the field.
+
+Let first see the base class for fields:
+
+.. autoclass:: cubicweb.web.formfields.Field
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+Now, you usually don't use that class but one of the concret field classes
+described below, according to what you want to edit.
+
+Basic fields
+''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.StringField()
+.. autoclass:: cubicweb.web.formfields.PasswordField()
+.. autoclass:: cubicweb.web.formfields.IntField()
+.. autoclass:: cubicweb.web.formfields.FloatField()
+.. autoclass:: cubicweb.web.formfields.BooleanField()
+.. autoclass:: cubicweb.web.formfields.DateField()
+.. autoclass:: cubicweb.web.formfields.DateTimeField()
+.. autoclass:: cubicweb.web.formfields.TimeField()
+
+Compound fields
+''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RichTextField()
+.. autoclass:: cubicweb.web.formfields.FileField()
+.. autoclass:: cubicweb.web.formfields.CompoundField()
+
+.. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget
+
+Entity specific fields and function
+'''''''''''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formfields.RelationField()
+.. autofunction:: cubicweb.web.formfields.guess_field
+
 """
 __docformat__ = "restructuredtext en"
 
@@ -13,15 +65,16 @@
 from datetime import datetime
 
 from logilab.mtconverter import xml_escape
+from logilab.common import nullobject
 from logilab.common.date import ustrftime
 
-from yams.schema import KNOWN_METAATTRIBUTES
+from yams.schema import KNOWN_METAATTRIBUTES, role_name
 from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
                               FormatConstraint)
 
 from cubicweb import Binary, tags, uilib
 from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
-     formwidgets as fw
+     formwidgets as fw, uicfg
 
 
 class UnmodifiedField(Exception):
@@ -45,55 +98,80 @@
     result += sorted(partresult)
     return result
 
-_MARKER = object()
+_MARKER = nullobject()
 
 class Field(object):
     """This class is the abstract base class for all fields. It hold a bunch
     of attributes which may be used for fine control of the behaviour of a
     concret field.
 
+    **Attributes**
+
     All the attributes described below have sensible default value which may be
-    overriden by value given to field's constructor.
+    overriden by named arguments given to field's constructor.
 
-    :name:
-       name of the field (basestring), should be unique in a form.
-    :id:
-       dom identifier (default to the same value as `name`), should be unique in
+    :attr:`name`
+       base name of the field (basestring). The actual input name is returned by
+       the :meth:`input_name` method and may differ from that name (for instance
+       if `eidparam` is true).
+    :attr:`id`
+       DOM identifier (default to the same value as `name`), should be unique in
        a form.
-    :label:
+    :attr:`label`
        label of the field (default to the same value as `name`).
-    :help:
+    :attr:`help`
        help message about this field.
-    :widget:
+    :attr:`widget`
        widget associated to the field. Each field class has a default widget
        class which may be overriden per instance.
-    :required:
+    :attr:`value`
+       field value. May be an actual value or a callable which should take the
+       form as argument and return a value.
+    :attr:`choices`
+       static vocabulary for this field. May be a list of values, a list of
+       (label, value) tuples or a callable which should take the form and field
+       as arguments and return a list of values or a list of (label, value).
+    :attr:`required`
        bool flag telling if the field is required or not.
-    :value:
-       field's value, used when no value specified by other means. XXX explain
-    :choices:
-       static vocabulary for this field. May be a list of values or a list of
-       (label, value) tuples if specified.
-    :sort:
+    :attr:`sort`
        bool flag telling if the vocabulary (either static vocabulary specified
        in `choices` or dynamic vocabulary fetched from the form) should be
        sorted on label.
-    :internationalizable:
+    :attr:`internationalizable`
        bool flag telling if the vocabulary labels should be translated using the
        current request language.
-    :eidparam:
+    :attr:`eidparam`
        bool flag telling if this field is linked to a specific entity
-    :role:
+    :attr:`role`
        when the field is linked to an entity attribute or relation, tells the
        role of the entity in the relation (eg 'subject' or 'object')
-    :fieldset:
+    :attr:`fieldset`
        optional fieldset to which this field belongs to
-    :order:
+    :attr:`order`
        key used by automatic forms to sort fields
-    :ignore_req_params:
+    :attr:`ignore_req_params`
        when true, this field won't consider value potentialy specified using
        request's form parameters (eg you won't be able to specify a value using for
        instance url like http://mywebsite.com/form?field=value)
+
+    .. currentmodule:: cubicweb.web.formfields
+
+    **Generic methods**
+
+    .. automethod:: Field.input_name
+    .. automethod:: Field.dom_id
+    .. automethod:: Field.actual_fields
+
+    **Form generation methods**
+
+    .. automethod:: form_init
+    .. automethod:: typed_value
+
+    **Post handling methods**
+
+    .. automethod:: process_posted
+    .. automethod:: process_form_value
+
     """
     # default widget associated to this class of fields. May be overriden per
     # instance
@@ -163,8 +241,11 @@
         return not isinstance(self.widget, fw.HiddenInput)
 
     def actual_fields(self, form):
-        """return actual fields composing this field in case of a compound
-        field, usually simply return self
+        """Fields may be composed of other fields. For instance the
+        :class:`~cubicweb.web.formfields.RichTextField` is containing a format
+        field to define the text format. This method returns actual fields that
+        should be considered for display / edition. It usually simply return
+        self.
         """
         yield self
 
@@ -189,7 +270,10 @@
         return self.widget
 
     def input_name(self, form, suffix=None):
-        """return 'qualified name' for this field"""
+        """Return the 'qualified name' for this field, e.g. something suitable
+        to use as HTML input name. You can specify a suffix that will be
+        included in the name when widget needs several inputs.
+        """
         # caching is necessary else we get some pb on entity creation :
         # entity.eid is modified from creation mark (eg 'X') to its actual eid
         # (eg 123), and then `field.input_name()` won't return the right key
@@ -214,11 +298,14 @@
     def role_name(self):
         """return <field.name>-<field.role> if role is specified, else field.name"""
         if self.role is not None:
-            return '%s-%s' % (self.name, self.role)
+            return role_name(self.name, self.role)
         return self.name
 
     def dom_id(self, form, suffix=None):
-        """return an html dom identifier for this field"""
+        """Return the HTML DOM identifier for this field, e.g. something
+        suitable to use as HTML input id. You can specify a suffix that will be
+        included in the name when widget needs several inputs.
+        """
         id = self.id or self.role_name()
         if suffix is not None:
             id += suffix
@@ -227,6 +314,8 @@
         return id
 
     def typed_value(self, form, load_bytes=False):
+        """Return the correctly typed value for this field in the form context.
+        """
         if self.eidparam and self.role is not None:
             entity = form.edited_entity
             if form._cw.vreg.schema.rschema(self.name).final:
@@ -323,8 +412,8 @@
         return form._cw.encoding
 
     def form_init(self, form):
-        """method called before by build_context to trigger potential field
-        initialization requiring the form instance
+        """Method called at form initialization to trigger potential field
+        initialization requiring the form instance. Do nothing by default.
         """
         pass
 
@@ -358,7 +447,7 @@
         return True
 
     def process_form_value(self, form):
-        """process posted form and return correctly typed value"""
+        """Return the correctly typed value posted for this field."""
         try:
             return form.formvalues[(self, form)]
         except KeyError:
@@ -378,10 +467,16 @@
         return value or None
 
     def process_posted(self, form):
+        """Return an iterator on (field, value) that has been posted for
+        field returned by :meth:`~cubicweb.web.formfields.Field.actual_fields`.
+        """
         for field in self.actual_fields(form):
             if field is self:
                 try:
-                    yield field, field.process_form_value(form)
+                    value = field.process_form_value(form)
+                    if value is None and field.required:
+                        raise ProcessFormError(form._cw._("required field"))
+                    yield field, value
                 except UnmodifiedField:
                     continue
             else:
@@ -392,6 +487,20 @@
 
 
 class StringField(Field):
+    """Use this field to edit unicode string (`String` yams type). This field
+    additionaly support a `max_length` attribute that specify a maximum size for
+    the string (`None` meaning no limit).
+
+    Unless explicitly specified, the widget for this field will be:
+
+    * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified
+      using `choices` attribute
+
+    * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified
+      using `max_length` attribute and this length is inferior to 257.
+
+    * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases
+    """
     widget = fw.TextArea
     size = 45
 
@@ -424,6 +533,12 @@
 
 
 class PasswordField(StringField):
+    """Use this field to edit password (`Password` yams type, encoded python
+    string).
+
+    Unless explicitly specified, the widget for this field will be
+    a :class:`~cubicweb.web.formwidgets.PasswordInput`.
+    """
     widget = fw.PasswordInput
     def form_init(self, form):
         if self.eidparam and form.edited_entity.has_eid():
@@ -441,6 +556,17 @@
 
 
 class RichTextField(StringField):
+    """This compound field allow edition of text (unicode string) in
+    a particular format. It has an inner field holding the text format,
+    that can be specified using `format_field` argument. If not specified
+    one will be automaticall generated.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.FCKEditor` or a
+    :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's
+    format and to user's preferences.
+    """
+
     widget = None
     def __init__(self, format_field=None, **kwargs):
         super(RichTextField, self).__init__(**kwargs)
@@ -512,6 +638,17 @@
 
 
 class FileField(StringField):
+    """This compound field allow edition of binary stream (`Bytes` yams
+    type). Three inner fields may be specified:
+
+    * `format_field`, holding the file's format.
+    * `encoding_field`, holding the file's content encoding.
+    * `name_field`, holding the file's name.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any,
+    will be added to a drop down menu at the right of the file input.
+    """
     widget = fw.FileInput
     needs_multipart = True
 
@@ -598,7 +735,17 @@
         return value
 
 
+# XXX turn into a widget
 class EditableFileField(FileField):
+    """This compound field allow edition of binary stream as
+    :class:`~cubicweb.web.formfields.FileField` but expect that stream to
+    actually contains some text.
+
+    If the stream format is one of text/plain, text/html, text/rest,
+    then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly
+    displayed, allowing to directly the file's content when desired, instead
+    of choosing a file from user's file system.
+    """
     editable_formats = ('text/plain', 'text/html', 'text/rest')
 
     def render(self, form, renderer):
@@ -636,6 +783,13 @@
 
 
 class IntField(Field):
+    """Use this field to edit integers (`Int` yams type). This field additionaly
+    support `min` and `max` attributes that specify a minimum and/or maximum
+    value for the integer (`None` meaning no boundary).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.TextInput`.
+    """
     def __init__(self, min=None, max=None, **kwargs):
         super(IntField, self).__init__(**kwargs)
         self.min = min
@@ -657,6 +811,12 @@
 
 
 class BooleanField(Field):
+    """Use this field to edit booleans (`Boolean` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.Radio` with yes/no values. You
+    can change that values by specifing `choices`.
+    """
     widget = fw.Radio
 
     def vocabulary(self, form):
@@ -669,6 +829,13 @@
 
 
 class FloatField(IntField):
+    """Use this field to edit floats (`Float` yams type). This field additionaly
+    support `min` and `max` attributes as the
+    :class:`~cubicweb.web.formfields.IntField`.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.TextInput`.
+    """
     def format_single_value(self, req, value):
         formatstr = req.property_value('ui.float-format')
         if value is None:
@@ -691,6 +858,11 @@
 
 
 class DateField(StringField):
+    """Use this field to edit date (`Date` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryDatePicker`.
+    """
     widget = fw.JQueryDatePicker
     format_prop = 'ui.date-format'
     etype = 'Date'
@@ -716,17 +888,47 @@
 
 
 class DateTimeField(DateField):
+    """Use this field to edit datetime (`Datetime` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`.
+    """
     widget = fw.JQueryDateTimePicker
     format_prop = 'ui.datetime-format'
     etype = 'Datetime'
 
 
 class TimeField(DateField):
+    """Use this field to edit time (`Time` yams type).
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.JQueryTimePicker`.
+    """
     widget = fw.JQueryTimePicker
     format_prop = 'ui.time-format'
     etype = 'Time'
 
 
+# XXX use cases where we don't actually want a better widget?
+class CompoundField(Field):
+    """This field shouldn't be used directly, it's designed to hold inner
+    fields that should be conceptually groupped together.
+    """
+    def __init__(self, fields, *args, **kwargs):
+        super(CompoundField, self).__init__(*args, **kwargs)
+        self.fields = fields
+
+    def subfields(self, form):
+        return self.fields
+
+    def actual_fields(self, form):
+        # don't add [self] to actual fields, compound field is usually kinda
+        # virtual, all interesting values are in subfield. Skipping it may avoid
+        # error when processed by the editcontroller : it may be marked as required
+        # while it has no value, hence generating a false error.
+        return list(self.fields)
+
+
 # relation vocabulary helper functions #########################################
 
 def relvoc_linkedto(entity, rtype, role):
@@ -781,7 +983,11 @@
 
 
 class RelationField(Field):
-    """the relation field to edit non final relations of an entity"""
+    """Use this field to edit a relation of an entity.
+
+    Unless explicitly specified, the widget for this field will be a
+    :class:`~cubicweb.web.formwidgets.Select`.
+    """
 
     @staticmethod
     def fromcardinality(card, **kwargs):
@@ -865,21 +1071,20 @@
         return eids
 
 
-class CompoundField(Field):
-    def __init__(self, fields, *args, **kwargs):
-        super(CompoundField, self).__init__(*args, **kwargs)
-        self.fields = fields
-
-    def subfields(self, form):
-        return self.fields
-
-    def actual_fields(self, form):
-        return [self] + list(self.fields)
-
+_AFF_KWARGS = uicfg.autoform_field_kwargs
 
 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
-    """return the most adapated widget to edit the relation
-    'subjschema rschema objschema' according to information found in the schema
+    """This function return the most adapted field to edit the given relation
+    (`rschema`) where the given entity type (`eschema`) is the subject or object
+    (`role`).
+
+    The field is initialized according to information found in the schema,
+    though any value can be explicitly specified using `kwargs`.
+
+    The `skip_meta_attr` flag is used to specify wether this function should
+    return a field for attributes considered as a meta-attributes
+    (e.g. describing an other attribute, such as the format or file name of a
+    file (`Bytes`) attribute).
     """
     fieldclass = None
     rdef = eschema.rdef(rschema, role)
@@ -891,14 +1096,14 @@
     else:
         targetschema = rdef.subject
     card = rdef.role_cardinality(role)
-    kwargs['required'] = card in '1+'
     kwargs['name'] = rschema.type
     kwargs['role'] = role
+    kwargs['eidparam'] = True
+    kwargs.setdefault('required', card in '1+')
     if role == 'object':
         kwargs.setdefault('label', (eschema.type, rschema.type + '_object'))
     else:
         kwargs.setdefault('label', (eschema.type, rschema.type))
-    kwargs['eidparam'] = True
     kwargs.setdefault('help', rdef.description)
     if rschema.final:
         if skip_meta_attr and rschema in eschema.meta_attributes():
@@ -926,8 +1131,10 @@
             for metadata in KNOWN_METAATTRIBUTES:
                 metaschema = eschema.has_metadata(rschema, metadata)
                 if metaschema is not None:
+                    metakwargs = _AFF_KWARGS.etype_get(eschema, metaschema, 'subject')
                     kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
-                                                                skip_meta_attr=False)
+                                                                skip_meta_attr=False,
+                                                                **metakwargs)
         return fieldclass(**kwargs)
     return RelationField.fromcardinality(card, **kwargs)
 
--- a/web/formwidgets.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/formwidgets.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,88 @@
-"""widget classes for form construction
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Widgets
+~~~~~~~
+
+.. Note::
+   A widget is responsible for the display of a field. It may use more than one
+   HTML input tags. When the form is posted, a widget is also reponsible to give
+   back to the field something it can understand.
+
+   Of course you can not use any widget with any field...
+
+.. autoclass:: cubicweb.web.formwidgets.FieldWidget
+
+HTML <input> based widgets
+''''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.HiddenInput
+.. autoclass:: cubicweb.web.formwidgets.TextInput
+.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
+.. autoclass:: cubicweb.web.formwidgets.FileInput
+.. autoclass:: cubicweb.web.formwidgets.ButtonInput
+
+Other standard HTML widgets
+'''''''''''''''''''''''''''
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. autoclass:: cubicweb.web.formwidgets.TextArea
+.. autoclass:: cubicweb.web.formwidgets.Select
+.. autoclass:: cubicweb.web.formwidgets.CheckBox
+.. autoclass:: cubicweb.web.formwidgets.Radio
+
+Date and time widgets
+'''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
+
+Ajax / javascript widgets
+'''''''''''''''''''''''''
+
+.. autoclass:: cubicweb.web.formwidgets.FCKEditor
+.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
+.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
+
+.. kill or document AddComboBoxWidget
+.. kill or document StaticFileAutoCompletionWidget
+.. 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`.
+
+.. autoclass:: cubicweb.web.formwidgets.Button
+.. autoclass:: cubicweb.web.formwidgets.SubmitButton
+.. autoclass:: cubicweb.web.formwidgets.ResetButton
+.. autoclass:: cubicweb.web.formwidgets.ImgButton
 """
 __docformat__ = "restructuredtext en"
 
@@ -19,14 +98,50 @@
 
 
 class FieldWidget(object):
-    """abstract widget class"""
-    # javascript / css files required by the widget
+    """The abstract base class for widgets.
+
+    **Attributes**
+
+    Here are standard attributes of a widget, that may be set on concret
+    class to override default behaviours:
+
+    :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
+
+    Also, widget instances takes as first argument a `attrs` dictionary which
+    will be stored in the attribute of the same name. It contains HTML
+    attributes that should be set in the widget's input tag (though concret
+    classes may ignore it).
+
+    .. currentmodule:: cubicweb.web.formwidgets
+
+    **Form generation methods**
+
+    .. automethod:: render
+    .. automethod:: _render
+    .. automethod:: values
+    .. automethod:: attributes
+
+    **Post handling methods**
+
+    .. automethod:: process_field_data
+
+    """
     needs_js = ()
     needs_css = ()
-    # automatically set id and tabindex attributes ?
     setdomid = True
     settabindex = True
-    # to ease usage as a sub-widgets (eg widget used by another widget)
     suffix = None
     # does this widget expect a vocabulary
     vocabulary_widget = False
@@ -51,37 +166,61 @@
         if self.needs_css:
             form._cw.add_css(self.needs_css)
 
+    def render(self, form, field, renderer=None):
+        """Called to render the widget for the given `field` in the given
+        `form`.  Return a unicode string containing the HTML snippet.
 
-    def render(self, form, field, renderer=None):
+        You will usually prefer to override the :meth:`_render` method so you
+        don't have to handle addition of needed javascript / css files.
+        """
         self.add_media(form)
         return self._render(form, field, renderer)
 
     def _render(self, form, field, renderer):
+        """This is the method you have to implement in concret widget classes.
+        """
         raise NotImplementedError()
 
     def format_value(self, form, field, value):
         return field.format_value(form._cw, value)
 
-    def values_and_attributes(self, form, field):
-        """found field's *string* value in:
-        1. previously submitted form values if any (eg on validation error)
-        2. req.form
-        3. extra form values given to render()
-        4. field's typed value
-
-        values found in 1. and 2. are expected te be already some 'display'
-        value while those found in 3. and 4. are expected to be correctly typed.
-
-        3 and 4 are handle by the .typed_value(form, field) method
+    def attributes(self, form, field):
+        """Return HTML attributes for the widget, automatically setting DOM
+        identifier and tabindex when desired (see :attr:`setdomid` and
+        :attr:`settabindex` attributes)
         """
         attrs = dict(self.attrs)
         if self.setdomid:
             attrs['id'] = field.dom_id(form, self.suffix)
         if self.settabindex and not 'tabindex' in attrs:
             attrs['tabindex'] = form._cw.next_tabindex()
-        return self.values(form, field), attrs
+        return attrs
 
     def values(self, form, field):
+        """Return the current *string* values (i.e. for display in an HTML
+        string) for the given field. This method returns a list of values since
+        it's suitable for all kind of widgets, some of them taking multiple
+        values, but you'll get a single value in the list in most cases.
+
+        Those values are searched in:
+
+        1. previously submitted form values if any (on validation error)
+
+        2. req.form (specified using request parameters)
+
+        3. extra form values given to form.render call (specified the code
+           generating the form)
+
+        4. field's typed value (returned by its
+          :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
+        correctly typed value.
+
+        3 and 4 are handle by the :meth:`typed_value` method to ease reuse in
+        concret classes.
+        """
         values = None
         if not field.ignore_req_params:
             qname = field.input_name(form, self.suffix)
@@ -119,12 +258,20 @@
         return field.typed_value(form)
 
     def process_field_data(self, form, field):
+        """Return process posted value(s) for widget and return something
+        understandable by the associated `field`. That value may be correctly
+        typed or a string that the field may parse.
+        """
         posted = form._cw.form
         val = posted.get(field.input_name(form, self.suffix))
         if isinstance(val, basestring):
             val = val.strip()
         return val
 
+    # XXX deprecates
+    def values_and_attributes(self, form, field):
+        return self.values(form, field), self.attributes(form, field)
+
     @deprecated('[3.6] use values_and_attributes')
     def _render_attrs(self, form, field):
         """return html tag name, attributes and a list of values for the field
@@ -155,13 +302,29 @@
 # basic html widgets ###########################################################
 
 class TextInput(Input):
-    """<input type='text'>"""
+    """Simple <input type='text'>, will return an unicode string."""
     type = 'text'
 
 
+class PasswordSingleInput(Input):
+    """Simple <input type='password'>, will return an utf-8 encoded string.
+
+    You may prefer using the :class:`~cubicweb.web.formwidgets.PasswordInput`
+    widget which handles password confirmation.
+    """
+    type = 'password'
+
+    def process_field_data(self, form, field):
+        value = super(PasswordSingleInput, self).process_field_data(form, field)
+        if value is not None:
+            return value.encode('utf-8')
+        return value
+
+
 class PasswordInput(Input):
-    """<input type='password'> and its confirmation field (using
-    <field's name>-confirm as name)
+    """<input type='password'> and a confirmation input. Form processing will
+    fail if password and confirmation differs, else it will return the password
+    as an utf-8 encoded string.
     """
     type = 'password'
 
@@ -189,45 +352,38 @@
         raise ProcessFormError(form._cw._("password and confirmation don't match"))
 
 
-class PasswordSingleInput(Input):
-    """<input type='password'> without a confirmation field"""
-    type = 'password'
-
-    def process_field_data(self, form, field):
-        value = super(PasswordSingleInput, self).process_field_data(form, field)
-        if value is not None:
-            return value.encode('utf-8')
-        return value
-
-
 class FileInput(Input):
-    """<input type='file'>"""
+    """Simple <input type='file'>, will return a tuple (name, stream) where
+    name is the posted file name and stream a file like object containing the
+    posted file data.
+    """
     type = 'file'
 
-    def values_and_attributes(self, form, field):
+    def values(self, form, field):
         # ignore value which makes no sense here (XXX even on form validation error?)
-        values, attrs = super(FileInput, self).values_and_attributes(form, field)
-        return ('',), attrs
+        return ('',)
 
 
 class HiddenInput(Input):
-    """<input type='hidden'>"""
+    """Simple <input type='hidden'> for hidden value, will return an unicode
+    string.
+    """
     type = 'hidden'
     setdomid = False # by default, don't set id attribute on hidden input
     settabindex = False
 
 
 class ButtonInput(Input):
-    """<input type='button'>
+    """Simple <input type='button'>, will return an unicode string.
 
-    if you want a global form button, look at the Button, SubmitButton,
-    ResetButton and ImgButton classes below.
+    If you want a global form button, look at the :class:`Button`,
+    :class:`SubmitButton`, :class:`ResetButton` and :class:`ImgButton` below.
     """
     type = 'button'
 
 
 class TextArea(FieldWidget):
-    """<textarea>"""
+    """Simple <textarea>, will return an unicode string."""
 
     def _render(self, form, field, renderer):
         values, attrs = self.values_and_attributes(form, field)
@@ -249,7 +405,9 @@
 
 
 class FCKEditor(TextArea):
-    """FCKEditor enabled <textarea>"""
+    """FCKEditor enabled <textarea>, will return an unicode string containing
+    HTML formated text.
+    """
     def __init__(self, *args, **kwargs):
         super(FCKEditor, self).__init__(*args, **kwargs)
         self.attrs['cubicweb:type'] = 'wysiwyg'
@@ -260,14 +418,16 @@
 
 
 class Select(FieldWidget):
-    """<select>, for field having a specific vocabulary"""
+    """Simple <select>, for field having a specific vocabulary. Will return
+    an unicode string, or a list of unicode strings.
+    """
     vocabulary_widget = True
 
     def __init__(self, attrs=None, multiple=False, **kwargs):
         super(Select, self).__init__(attrs, **kwargs)
         self._multiple = multiple
 
-    def render(self, form, field, renderer):
+    def _render(self, form, field, renderer):
         curvalues, attrs = self.values_and_attributes(form, field)
         if not 'size' in attrs:
             attrs['size'] = self._multiple and '5' or '1'
@@ -298,16 +458,29 @@
 
 
 class CheckBox(Input):
-    """<input type='checkbox'>, for field having a specific vocabulary. One
-    input will be generated for each possible value.
+    """Simple <input type='checkbox'>, for field having a specific
+    vocabulary. One input will be generated for each possible value.
+
+    You can specify separator using the `separator` constructor argument, by
+    default <br/> is used.
     """
     type = 'checkbox'
     vocabulary_widget = True
 
-    def render(self, form, field, renderer):
+    def __init__(self, attrs=None, separator=u'<br/>\n', **kwargs):
+        super(CheckBox, self).__init__(attrs, **kwargs)
+        self.separator = separator
+
+    def _render(self, form, field, renderer):
         curvalues, attrs = self.values_and_attributes(form, field)
         domid = attrs.pop('id', None)
-        sep = attrs.pop('separator', u'<br/>\n')
+        # XXX turn this as initializer argument
+        try:
+            sep = attrs.pop('separator')
+            warn('[3.8] separator should be specified using initializer argument',
+                 DeprecationWarning)
+        except KeyError:
+            sep = self.separator
         options = []
         for i, option in enumerate(field.vocabulary(form)):
             try:
@@ -328,62 +501,20 @@
 
 
 class Radio(CheckBox):
-    """<input type='radio'>, for field having a specific vocabulary. One
+    """Simle <input type='radio'>, for field having a specific vocabulary. One
     input will be generated for each possible value.
+
+    You can specify separator using the `separator` constructor argument, by
+    default <br/> is used.
     """
     type = 'radio'
 
 
-# compound widgets #############################################################
-
-class IntervalWidget(FieldWidget):
-    """custom widget to display an interval composed by 2 fields. This widget
-    is expected to be used with a CompoundField containing the two actual
-    fields.
-
-    Exemple usage::
-
-from uicfg import autoform_field, autoform_section
-autoform_field.tag_attribute(('Concert', 'minprice'),
-                              CompoundField(fields=(IntField(name='minprice'),
-                                                    IntField(name='maxprice')),
-                                            label=_('price'),
-                                            widget=IntervalWidget()
-                                            ))
-# we've to hide the other field manually for now
-autoform_section.tag_attribute(('Concert', 'maxprice'), 'generated')
-    """
-    def render(self, form, field, renderer):
-        actual_fields = field.fields
-        assert len(actual_fields) == 2
-        return u'<div>%s %s %s %s</div>' % (
-            form._cw._('from_interval_start'),
-            actual_fields[0].render(form, renderer),
-            form._cw._('to_interval_end'),
-            actual_fields[1].render(form, renderer),
-            )
-
-
-class HorizontalLayoutWidget(FieldWidget):
-    """custom widget to display a set of fields grouped together horizontally
-    in a form. See `IntervalWidget` for example usage.
-    """
-    def render(self, form, field, renderer):
-        if self.attrs.get('display_label', True):
-            subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
-            fields = [subst % {'label': renderer.render_label(form, f),
-                              'input': f.render(form, renderer)}
-                      for f in field.subfields(form)]
-        else:
-            fields = [f.render(form, renderer) for f in field.subfields(form)]
-        return u'<div>%s</div>' % ' '.join(fields)
-
-
 # javascript widgets ###########################################################
 
 class DateTimePicker(TextInput):
-    """<input type='text' + javascript date/time picker for date or datetime
-    fields
+    """<input type='text'> + javascript date/time picker for date or datetime
+    fields. Will return the date or datetime as an unicode string.
     """
     monthnames = ('january', 'february', 'march', 'april',
                   'may', 'june', 'july', 'august',
@@ -397,15 +528,14 @@
     @classmethod
     def add_localized_infos(cls, req):
         """inserts JS variables defining localized months and days"""
-        # import here to avoid dependancy from cubicweb to simplejson
         _ = req._
         monthnames = [_(mname) for mname in cls.monthnames]
         daynames = [_(dname) for dname in cls.daynames]
         req.html_headers.define_var('MONTHNAMES', monthnames)
         req.html_headers.define_var('DAYNAMES', daynames)
 
-    def render(self, form, field, renderer):
-        txtwidget = super(DateTimePicker, self).render(form, field, renderer)
+    def _render(self, form, field, renderer):
+        txtwidget = super(DateTimePicker, self)._render(form, field, renderer)
         self.add_localized_infos(form._cw)
         cal_button = self._render_calendar_popup(form, field)
         return txtwidget + cal_button
@@ -425,7 +555,9 @@
 
 
 class JQueryDatePicker(FieldWidget):
-    """use jquery.ui.datepicker to define a date time picker"""
+    """Use jquery.ui.datepicker to define a date picker. Will return the date as
+    an unicode string.
+    """
     needs_js = ('jquery.ui.js', )
     needs_css = ('jquery.ui.css',)
 
@@ -452,7 +584,9 @@
 
 
 class JQueryTimePicker(FieldWidget):
-    """use jquery.timePicker.js to define a js time picker"""
+    """Use jquery.timePicker to define a time picker. Will return the time as an
+    unicode string.
+    """
     needs_js = ('jquery.timePicker.js',)
     needs_css = ('jquery.timepicker.css',)
 
@@ -476,6 +610,10 @@
 
 
 class JQueryDateTimePicker(FieldWidget):
+    """Compound widget using :class:`JQueryDatePicker` and
+    :class:`JQueryTimePicker` widgets to define a date and time picker. Will
+    return the date and time as python datetime instance.
+    """
     def __init__(self, initialtime=None, timesteps=15, **kwargs):
         super(JQueryDateTimePicker, self).__init__(**kwargs)
         self.initialtime = initialtime
@@ -507,8 +645,8 @@
         timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps,
                                       suffix='time')
         return u'<div id="%s">%s%s</div>' % (field.dom_id(form),
-                                            datepicker.render(form, field),
-                                            timepicker.render(form, field))
+                                            datepicker.render(form, field, renderer),
+                                            timepicker.render(form, field, renderer))
 
     def process_field_data(self, form, field):
         req = form._cw
@@ -535,7 +673,9 @@
 
 
 class AjaxWidget(FieldWidget):
-    """simple <div> based ajax widget"""
+    """Simple <div> based ajax widget, requiring a `wdgtype` argument telling
+    which javascript widget should be used.
+    """
     def __init__(self, wdgtype, inputid=None, **kwargs):
         super(AjaxWidget, self).__init__(**kwargs)
         init_ajax_attributes(self.attrs, wdgtype)
@@ -548,8 +688,10 @@
 
 
 class AutoCompletionWidget(TextInput):
-    """ajax widget for StringField, proposing matching existing values as you
-    type.
+    """<input type='text'> based ajax widget, taking a `autocomplete_initfunc`
+    argument which should specify the name of a method of the json
+    controller. This method is expected to return allowed values for the input,
+    that the widget will use to propose matching values as you type.
     """
     needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
     needs_css = ('jquery.autocomplete.css',)
@@ -560,21 +702,25 @@
         try:
             self.autocomplete_initfunc = kwargs.pop('autocomplete_initfunc')
         except KeyError:
-            warn('use autocomplete_initfunc argument of %s constructor '
+            warn('[3.6] use autocomplete_initfunc argument of %s constructor '
                  'instead of relying on autocomplete_initfuncs dictionary on '
                  'the entity class' % self.__class__.__name__,
                  DeprecationWarning)
             self.autocomplete_initfunc = None
         super(AutoCompletionWidget, self).__init__(*args, **kwargs)
 
-    def values_and_attributes(self, form, field):
-        values, attrs = super(AutoCompletionWidget, self).values_and_attributes(form, field)
+    def values(self, form, field):
+        values = super(AutoCompletionWidget, self).values(form, field)
+        if not values:
+            values = ('',)
+        return values
+
+    def attributes(self, form, field):
+        attrs = super(AutoCompletionWidget, self).attributes(form, field)
         init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
         # XXX entity form specific
         attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
-        if not values:
-            values = ('',)
-        return values, attrs
+        return attrs
 
     def _get_url(self, entity, field):
         if self.autocomplete_initfunc is None:
@@ -609,8 +755,6 @@
     wdgtype = 'LazySuggestField'
 
     def values_and_attributes(self, form, field):
-        self.add_media(form)
-
         """override values_and_attributes to handle initial displayed values"""
         values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes(form, field)
         assert len(values) == 1, "multiple selection is not supported yet by LazyWidget"
@@ -628,35 +772,154 @@
         return values, attrs
 
     def display_value_for(self, form, value):
-        entity =form._cw.entity_from_eid(value)
+        entity = form._cw.entity_from_eid(value)
         return entity.view('combobox')
 
 
 class AddComboBoxWidget(Select):
-    def values_and_attributes(self, form, field):
-        values, attrs = super(AddComboBoxWidget, self).values_and_attributes(form, field)
-        init_ajax_attributes(self.attrs, 'AddComboBox')
+    def attributes(self, form, field):
+        attrs = super(AddComboBoxWidget, self).attributes(form, field)
+        init_ajax_attributes(attrs, 'AddComboBox')
         # XXX entity form specific
         entity = form.edited_entity
         attrs['cubicweb:etype_to'] = entity.e_schema
         etype_from = entity.e_schema.subjrels[field.name].objects(entity.e_schema)[0]
         attrs['cubicweb:etype_from'] = etype_from
-        return values, attrs
+        return attrs
 
-    def render(self, form, field, renderer):
-        return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
+    def _render(self, form, field, renderer):
+        return super(AddComboBoxWidget, self)._render(form, field, renderer) + u'''
 <div id="newvalue">
   <input type="text" id="newopt" />
   <a href="javascript:noop()" id="add_newopt">&#160;</a></div>
 '''
 
-# buttons ######################################################################
+# more widgets #################################################################
+
+class IntervalWidget(FieldWidget):
+    """Custom widget to display an interval composed by 2 fields. This widget is
+    expected to be used with a :class:`CompoundField` containing the two actual
+    fields.
+
+    Exemple usage::
+
+      class MyForm(FieldsForm):
+         price = CompoundField(fields=(IntField(name='minprice'),
+                                       IntField(name='maxprice')),
+                               label=_('price'),
+                               widget=IntervalWidget())
+    """
+    def _render(self, form, field, renderer):
+        actual_fields = field.fields
+        assert len(actual_fields) == 2
+        return u'<div>%s %s %s %s</div>' % (
+            form._cw._('from_interval_start'),
+            actual_fields[0].render(form, renderer),
+            form._cw._('to_interval_end'),
+            actual_fields[1].render(form, renderer),
+            )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+    """Custom widget to display a set of fields grouped together horizontally in
+    a form. See `IntervalWidget` for example usage.
+    """
+    def _render(self, form, field, renderer):
+        if self.attrs.get('display_label', True):
+            subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+            fields = [subst % {'label': renderer.render_label(form, f),
+                              'input': f.render(form, renderer)}
+                      for f in field.subfields(form)]
+        else:
+            fields = [f.render(form, renderer) for f in field.subfields(form)]
+        return u'<div>%s</div>' % ' '.join(fields)
+
+
+class EditableURLWidget(FieldWidget):
+    """Custom widget to edit separatly an url path / query string (used by
+    default for the `path` attribute of `Bookmark` entities).
+
+    It deals with url quoting nicely so that the user edit the unquoted value.
+    """
+
+    def _render(self, form, field, renderer):
+        assert self.suffix is None, 'not supported'
+        req = form._cw
+        pathqname = field.input_name(form, 'path')
+        fqsqname = field.input_name(form, 'fqs') # formatted query string
+        if pathqname in form.form_previous_values:
+            path = form.form_previous_values[pathqname]
+            fqs = form.form_previous_values[fqsqname]
+        else:
+            if field.name in req.form:
+                value = req.form[field.name]
+            else:
+                value = self.typed_value(form, field)
+            if value:
+                try:
+                    path, qs = value.split('?', 1)
+                except ValueError:
+                    path = value
+                    qs = ''
+            else:
+                path = qs = ''
+            fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs))
+        attrs = dict(self.attrs)
+        if self.setdomid:
+            attrs['id'] = field.dom_id(form)
+        if self.settabindex and not 'tabindex' in attrs:
+            attrs['tabindex'] = req.next_tabindex()
+        # ensure something is rendered
+        inputs = [u'<table><tr><th>',
+                  req._('i18n_bookmark_url_path'),
+                  u'</th><td>',
+                  tags.input(name=pathqname, type='string', value=path, **attrs),
+                  u'</td></tr><tr><th>',
+                  req._('i18n_bookmark_url_fqs'),
+                  u'</th><td>']
+        if self.setdomid:
+            attrs['id'] = field.dom_id(form, 'fqs')
+        if self.settabindex:
+            attrs['tabindex'] = req.next_tabindex()
+        attrs.setdefault('cols', 60)
+        attrs.setdefault('onkeyup', 'autogrow(this)')
+        inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
+                   u'</td></tr></table>']
+        # surrounding div necessary for proper error localization
+        return u'<div id="%s">%s</div>' % (
+            field.dom_id(form), u'\n'.join(inputs))
+
+    def process_field_data(self, form, field):
+        req = form._cw
+        values = {}
+        path = req.form.get(field.input_name(form, 'path'))
+        if isinstance(path, basestring):
+            path = path.strip() or None
+        fqs = req.form.get(field.input_name(form, 'fqs'))
+        if isinstance(fqs, basestring):
+            fqs = fqs.strip() or None
+            if fqs:
+                for i, line in enumerate(fqs.split('\n')):
+                    line = line.strip()
+                    if line:
+                        try:
+                            key, val = line.split('=', 1)
+                        except ValueError:
+                            raise ProcessFormError(req._("wrong query parameter line %s") % (i+1))
+                        # value will be url quoted by build_url_params
+                        values.setdefault(key.encode(req.encoding), []).append(val)
+        if not values:
+            return path
+        return u'%s?%s' % (path, req.build_url_params(**values))
+
+
+# form controls ######################################################################
 
 class Button(Input):
-    """<input type='button'>, base class for global form buttons
+    """Simple <input type='button'>, base class for global form buttons.
 
-    note label is a msgid which will be translated at form generation time, you
-    should not give an already translated string.
+    Note that `label` is a msgid which will be translated at form generation
+    time, you should not give an already translated string.
     """
     type = 'button'
     def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
@@ -699,23 +962,20 @@
 
 
 class SubmitButton(Button):
-    """<input type='submit'>, main button to submit a form"""
+    """Simple <input type='submit'>, main button to submit a form"""
     type = 'submit'
 
 
 class ResetButton(Button):
-    """<input type='reset'>, main button to reset a form.
-    You usually don't want this.
+    """Simple <input type='reset'>, main button to reset a form. You usually
+    don't want to use this.
     """
     type = 'reset'
 
 
 class ImgButton(object):
-    """<img> wrapped into a <a> tag with href triggering something (usually a
-    javascript call)
-
-    note label is a msgid which will be translated at form generation time, you
-    should not give an already translated string.
+    """Simple <img> wrapped into a <a> tag with href triggering something (usually a
+    javascript call).
     """
     def __init__(self, domid, href, label, imgressource):
         self.domid = domid
@@ -731,84 +991,3 @@
             'label': label, 'imgsrc': imgsrc,
             'domid': self.domid, 'href': self.href}
 
-
-# more widgets #################################################################
-
-class EditableURLWidget(FieldWidget):
-    """custom widget to edit separatly an url path / query string (used by
-    default for Bookmark.path for instance), dealing with url quoting nicely
-    (eg user edit the unquoted value).
-    """
-
-    def _render(self, form, field, renderer):
-        """render the widget for the given `field` of `form`.
-
-        Generate one <input> tag for each field's value
-        """
-        assert self.suffix is None, 'not supported'
-        req = form._cw
-        pathqname = field.input_name(form, 'path')
-        fqsqname = field.input_name(form, 'fqs') # formatted query string
-        if pathqname in form.form_previous_values:
-            path = form.form_previous_values[pathqname]
-            fqs = form.form_previous_values[fqsqname]
-        else:
-            if field.name in req.form:
-                value = req.form[field.name]
-            else:
-                value = self.typed_value(form, field)
-            if value:
-                try:
-                    path, qs = value.split('?', 1)
-                except ValueError:
-                    path = value
-                    qs = ''
-            else:
-                path = qs = ''
-            fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs))
-        attrs = dict(self.attrs)
-        if self.setdomid:
-            attrs['id'] = field.dom_id(form)
-        if self.settabindex and not 'tabindex' in attrs:
-            attrs['tabindex'] = req.next_tabindex()
-        # ensure something is rendered
-        inputs = [u'<table><tr><th>',
-                  req._('i18n_bookmark_url_path'),
-                  u'</th><td>',
-                  tags.input(name=pathqname, type='string', value=path, **attrs),
-                  u'</td></tr><tr><th>',
-                  req._('i18n_bookmark_url_fqs'),
-                  u'</th><td>']
-        if self.setdomid:
-            attrs['id'] = field.dom_id(form, 'fqs')
-        if self.settabindex:
-            attrs['tabindex'] = req.next_tabindex()
-        attrs.setdefault('onkeyup', 'autogrow(this)')
-        inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
-                   u'</td></tr></table>']
-        # surrounding div necessary for proper error localization
-        return u'<div id="%s">%s</div>' % (
-            field.dom_id(form), u'\n'.join(inputs))
-
-    def process_field_data(self, form, field):
-        req = form._cw
-        values = {}
-        path = req.form.get(field.input_name(form, 'path'))
-        if isinstance(path, basestring):
-            path = path.strip() or None
-        fqs = req.form.get(field.input_name(form, 'fqs'))
-        if isinstance(fqs, basestring):
-            fqs = fqs.strip() or None
-            if fqs:
-                for i, line in enumerate(fqs.split('\n')):
-                    line = line.strip()
-                    if line:
-                        try:
-                            key, val = line.split('=', 1)
-                        except ValueError:
-                            raise ProcessFormError(req._("wrong query parameter line %s") % (i+1))
-                        # value will be url quoted by build_url_params
-                        values.setdefault(key.encode(req.encoding), []).append(val)
-        if not values:
-            return path
-        return u'%s?%s' % (path, req.build_url_params(**values))
--- a/web/htmlwidgets.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/htmlwidgets.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,25 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """html widgets
 
 those are in cubicweb since we need to know available widgets at schema
 serialization time
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from math import floor
--- a/web/httpcache.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/httpcache.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """HTTP cache managers
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/request.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/request.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract class for http request
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -16,8 +29,6 @@
 from urlparse import urlsplit
 from itertools import count
 
-from simplejson import dumps
-
 from rql.utils import rqlvar_maker
 
 from logilab.common.decorators import cached
@@ -30,7 +41,8 @@
 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
-                          RequestError, StatusResponse)
+                          RequestError, StatusResponse, json)
+dumps = json.dumps
 
 _MARKER = object()
 
@@ -68,7 +80,6 @@
 
     def __init__(self, vreg, https, form=None):
         super(CubicWebRequestBase, self).__init__(vreg)
-        self.message = None
         self.authmode = vreg.config['auth-mode']
         self.https = https
         # raw html headers that can be added from any view
@@ -126,35 +137,24 @@
         """
         super(CubicWebRequestBase, self).set_connection(cnx, user)
         # set request language
-        try:
-            vreg = self.vreg
-            if self.user:
-                try:
-                    # 1. user specified language
-                    lang = vreg.typed_value('ui.language',
-                                            self.user.properties['ui.language'])
+        vreg = self.vreg
+        if self.user:
+            try:
+                # 1. user specified language
+                lang = vreg.typed_value('ui.language',
+                                        self.user.properties['ui.language'])
+                self.set_language(lang)
+                return
+            except KeyError:
+                pass
+        if vreg.config['language-negociation']:
+            # 2. http negociated language
+            for lang in self.header_accept_language():
+                if lang in self.translations:
                     self.set_language(lang)
                     return
-                except KeyError:
-                    pass
-            if vreg.config['language-negociation']:
-                # 2. http negociated language
-                for lang in self.header_accept_language():
-                    if lang in self.translations:
-                        self.set_language(lang)
-                        return
-            # 3. default language
-            self.set_default_language(vreg)
-        finally:
-            # XXX code smell
-            # have to be done here because language is not yet set in setup_params
-            #
-            # special key for created entity, added in controller's reset method
-            # if no message set, we don't want this neither
-            if '__createdpath' in self.form and self.message:
-                self.message += ' (<a href="%s">%s</a>)' % (
-                    self.build_url(self.form.pop('__createdpath')),
-                    self._('click here to see created entity'))
+        # 3. default language
+        self.set_default_language(vreg)
 
     def set_language(self, lang):
         gettext, self.pgettext = self.translations[lang]
@@ -179,26 +179,27 @@
 
         subclasses should overrides to
         """
+        self.form = {}
         if params is None:
-            params = {}
-        self.form = params
+            return
         encoding = self.encoding
-        for k, v in params.items():
-            if isinstance(v, (tuple, list)):
-                v = [unicode(x, encoding) for x in v]
-                if len(v) == 1:
-                    v = v[0]
-            if k in self.no_script_form_params:
-                v = self.no_script_form_param(k, value=v)
-            if isinstance(v, str):
-                v = unicode(v, encoding)
-            if k == '__message':
-                self.set_message(v)
-                del self.form[k]
+        for param, val in params.iteritems():
+            if isinstance(val, (tuple, list)):
+                val = [unicode(x, encoding) for x in val]
+                if len(val) == 1:
+                    val = val[0]
+            elif isinstance(val, str):
+                val = unicode(val, encoding)
+            if param in self.no_script_form_params and val:
+                val = self.no_script_form_param(param, val)
+            if param == '_cwmsgid':
+                self.set_message_id(val)
+            elif param == '__message':
+                self.set_message(val)
             else:
-                self.form[k] = v
+                self.form[param] = val
 
-    def no_script_form_param(self, param, default=None, value=None):
+    def no_script_form_param(self, param, value):
         """ensure there is no script in a user form param
 
         by default return a cleaned string instead of raising a security
@@ -208,16 +209,12 @@
         that are at some point inserted in a generated html page to protect
         against script kiddies
         """
-        if value is None:
-            value = self.form.get(param, default)
-        if not value is default and value:
-            # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
-            if isinstance(value, (list, tuple)):
-                self.error('no_script_form_param got a list (%s). Who generated the URL ?',
-                           repr(value))
-                value = value[0]
-            return remove_html_tags(value)
-        return value
+        # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
+        if isinstance(value, (list, tuple)):
+            self.error('no_script_form_param got a list (%s). Who generated the URL ?',
+                       repr(value))
+            value = value[0]
+        return remove_html_tags(value)
 
     def list_form_param(self, param, form=None, pop=False):
         """get param from form parameters and return its value as a list,
@@ -245,9 +242,48 @@
 
     # web state helpers #######################################################
 
+    @property
+    def message(self):
+        try:
+            return self.get_session_data(self._msgid, default=u'', pop=True)
+        except AttributeError:
+            try:
+                return self._msg
+            except AttributeError:
+                return None
+
     def set_message(self, msg):
         assert isinstance(msg, unicode)
-        self.message = msg
+        self._msg = msg
+
+    def set_message_id(self, msgid):
+        self._msgid = msgid
+
+    @cached
+    def redirect_message_id(self):
+        return make_uid()
+
+    def set_redirect_message(self, msg):
+        assert isinstance(msg, unicode)
+        msgid = self.redirect_message_id()
+        self.set_session_data(msgid, msg)
+        return msgid
+
+    def append_to_redirect_message(self, msg):
+        msgid = self.redirect_message_id()
+        currentmsg = self.get_session_data(msgid)
+        if currentmsg is not None:
+            currentmsg = '%s %s' % (currentmsg, msg)
+        else:
+            currentmsg = msg
+        self.set_session_data(msgid, currentmsg)
+        return msgid
+
+    def reset_message(self):
+        if hasattr(self, '_msg'):
+            del self._msg
+        if hasattr(self, '_msgid'):
+            del self._msgid
 
     def update_search_state(self):
         """update the current search state"""
@@ -481,7 +517,7 @@
     # high level methods for HTML headers management ##########################
 
     def add_onload(self, jscode):
-        self.html_headers.add_onload(jscode, self.json_request)
+        self.html_headers.add_onload(jscode)
 
     def add_js(self, jsfiles, localfile=True):
         """specify a list of JS files to include in the HTML headers
@@ -499,6 +535,7 @@
     def add_css(self, cssfiles, media=u'all', localfile=True, ieonly=False,
                 iespec=u'[if lt IE 8]'):
         """specify a CSS file to include in the HTML headers
+
         :param cssfiles: a CSS filename or a list of CSS filenames
         :param media: the CSS's media if necessary
         :param localfile: if True, the default data dir prefix is added to the
@@ -526,15 +563,17 @@
 
     def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace',
                                **extraparams):
-        """builds an ajax url that will replace `nodeid`s content
+        """builds an ajax url that will replace nodeid's content
+
         :param nodeid: the dom id of the node to replace
         :param rql: rql to execute
         :param vid: the view to apply on the resultset
         :param replacemode: defines how the replacement should be done.
+
         Possible values are :
-         - 'replace' to replace the node's content with the generated HTML
-         - 'swap' to replace the node itself with the generated HTML
-         - 'append' to append the generated HTML to the node's content
+        - 'replace' to replace the node's content with the generated HTML
+        - 'swap' to replace the node itself with the generated HTML
+        - 'append' to append the generated HTML to the node's content
         """
         url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
                              **extraparams)
--- a/web/test/data/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/data/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
--- a/web/test/data/views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/data/views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.web import Redirect
 from cubicweb.web.application import CubicWebPublisher
--- a/web/test/test_views.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/test_views.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """automatic tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from cubicweb.devtools.testlib import CubicWebTC, AutoPopulateTest, AutomaticWebTest
--- a/web/test/unittest_application.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_application.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: iso-8859-1 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for cubicweb.web.application
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import base64, Cookie
@@ -186,36 +199,66 @@
         self.assertEquals(values['eid'], eid)
         error = forminfo['error']
         self.assertEquals(error.entity, user.eid)
-        self.assertEquals(error.errors['login'], 'required attribute')
+        self.assertEquals(error.errors['login-subject'], 'required field')
 
 
-    def test_validation_error_dont_loose_subentity_data(self):
+    def test_validation_error_dont_loose_subentity_data_ctrl(self):
         """test creation of two linked entities
+
+        error occurs on the web controller
         """
         req = self.request()
-        form = {'eid': ['X', 'Y'], '__maineid': 'X',
-                '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,surname-subject',
-                # missing required field
-                'login-subject:X': u'',
-                'surname-subject:X': u'Mr Ouaoua',
-                # but email address is set
-                '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject,alias-subject,use_email-object',
-                'address-subject:Y': u'bougloup@logilab.fr',
-                'alias-subject:Y': u'',
-                'use_email-object:Y': 'X',
-                # necessary to get validation error handling
-                '__errorurl': 'view?vid=edition...',
-                }
-        req.form = form
-        # monkey patch edited_eid to ensure both entities are edited, not only X
-        req.edited_eids = lambda : ('Y', 'X')
+        # set Y before X to ensure both entities are edited, not only X
+        req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
+                    '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject',
+                    # missing required field
+                    'login-subject:X': u'',
+                    # but email address is set
+                    '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+                    'address-subject:Y': u'bougloup@logilab.fr',
+                    'use_email-object:Y': 'X',
+                    # necessary to get validation error handling
+                    '__errorurl': 'view?vid=edition...',
+                    }
         path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
         forminfo = req.get_session_data('view?vid=edition...')
         self.assertEquals(set(forminfo['eidmap']), set('XY'))
+        self.assertEquals(forminfo['eidmap']['X'], None)
+        self.assertIsInstance(forminfo['eidmap']['Y'], int)
+        self.assertEquals(forminfo['error'].entity, 'X')
+        self.assertEquals(forminfo['error'].errors,
+                          {'login-subject': 'required field'})
+        self.assertEquals(forminfo['values'], req.form)
+
+
+    def test_validation_error_dont_loose_subentity_data_repo(self):
+        """test creation of two linked entities
+
+        error occurs on the repository
+        """
+        req = self.request()
+        # set Y before X to ensure both entities are edited, not only X
+        req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
+                    '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,upassword-subject',
+                    # already existent user
+                    'login-subject:X': u'admin',
+                    'upassword-subject:X': u'admin', 'upassword-subject-confirm:X': u'admin',
+                    '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+                    'address-subject:Y': u'bougloup@logilab.fr',
+                    'use_email-object:Y': 'X',
+                    # necessary to get validation error handling
+                    '__errorurl': 'view?vid=edition...',
+                    }
+        path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
+        forminfo = req.get_session_data('view?vid=edition...')
+        self.assertEquals(set(forminfo['eidmap']), set('XY'))
+        self.assertIsInstance(forminfo['eidmap']['X'], int)
+        self.assertIsInstance(forminfo['eidmap']['Y'], int)
         self.assertEquals(forminfo['error'].entity, forminfo['eidmap']['X'])
-        self.assertEquals(forminfo['error'].errors, {'login': 'required attribute',
-                                                     'upassword': 'required attribute'})
-        self.assertEquals(forminfo['values'], form)
+        self.assertEquals(forminfo['error'].errors,
+                          {'login-subject': u'the value "admin" is already used, use another one'})
+        self.assertEquals(forminfo['values'], req.form)
+
 
     def _test_cleaned(self, kwargs, injected, cleaned):
         req = self.request(**kwargs)
--- a/web/test/unittest_breadcrumbs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_breadcrumbs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from cubicweb.devtools.testlib import CubicWebTC
 
 class BreadCrumbsTC(CubicWebTC):
@@ -15,7 +32,7 @@
         ibc = self.vreg['components'].select('breadcrumbs', self.request(), rset=childrset)
         self.assertEquals(ibc.render(),
                           """<span id="breadcrumbs" class="pathbar">&#160;&gt;&#160;<a href="http://testing.fr/cubicweb/Folder">folder_plural</a>&#160;&gt;&#160;<a href="http://testing.fr/cubicweb/folder/%s" title="">par&amp;ent</a>&#160;&gt;&#160;
-chi&amp;ld</span>""" % f1.eid)
+<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&amp;ld</a></span>""" % (f1.eid, f2.eid))
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/web/test/unittest_controller.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_controller.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,6 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.controller unit tests
 
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from datetime import datetime, date, time
--- a/web/test/unittest_form.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_form.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from xml.etree.ElementTree import fromstring
--- a/web/test/unittest_formfields.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_formfields.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unittests for cw.web.formfields
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
--- a/web/test/unittest_magicsearch.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_magicsearch.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Unit tests for magic_search service
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import sys
--- a/web/test/unittest_pdf.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_pdf.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,28 @@
-from unittest import TestCase
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 import os.path as osp
+from tempfile import NamedTemporaryFile
+from subprocess import Popen as sub
 from xml.etree.cElementTree import ElementTree, fromstring, tostring, dump
 
-from tempfile import NamedTemporaryFile
-from subprocess import Popen as sub
+from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb.utils import can_do_pdf_conversion
-
 from cubicweb.ext.xhtml2fo import ReportTransformer
 
 DATADIR = osp.join(osp.dirname(__file__), 'data')
@@ -36,3 +52,7 @@
         self.assertEquals( len(output), len(reference) )
         # cut begin & end 'cause they contain variyng data
         self.assertTextEquals(output[150:1500], reference[150:1500])
+
+if __name__ == '__main__':
+    unittest_main()
+
--- a/web/test/unittest_uicfg.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_uicfg.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,29 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web import uicfg
 
+abaa = uicfg.actionbox_appearsin_addmenu
+
 class UICFGTC(CubicWebTC):
 
-    def test(self):
-        self.skip('write some tests')
+    def test_default_actionbox_appearsin_addmenu_config(self):
+        self.failIf(abaa.etype_get('TrInfo', 'wf_info_for', 'object', 'CWUser'))
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
--- a/web/test/unittest_urlpublisher.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_urlpublisher.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Unit tests for url publishing service
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 import re
--- a/web/test/unittest_urlrewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_urlrewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/web/test/unittest_views_actions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_actions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
 
--- a/web/test/unittest_views_apacherewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_apacherewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 
--- a/web/test/unittest_views_basecontrollers.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_basecontrollers.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.views.basecontrollers unit tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-import simplejson
 
 from logilab.common.testlib import unittest_main, mock_object
 
@@ -13,7 +25,7 @@
 from cubicweb.view import STRICT_DOCTYPE
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.uilib import rql_for_eid
-from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
+from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError, json
 from cubicweb.entities.authobjs import CWUser
 from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
 u = unicode
@@ -51,7 +63,7 @@
                     'upassword-subject-confirm:X': u'toto',
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+        self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
     def test_user_editing_itself(self):
         """checking that a manager user can edit itself
@@ -219,7 +231,7 @@
                     'described_by_test-subject:X': u(feid),
                 }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value -10'})
+        self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value -10'})
         req = self.request()
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -228,7 +240,7 @@
                     'described_by_test-subject:X': u(feid),
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value 110'})
+        self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value 110'})
         req = self.request()
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -343,7 +355,7 @@
                     '__action_delete': ''}
         path, params = self.expect_redirect_publish(req, 'edit')
         self.assertEquals(path, 'blogentry')
-        self.assertEquals(params, {u'__message': u'entity deleted'})
+        self.assertIn('_cwmsgid', params)
         eid = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
         self.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
                      {'x': self.session.user.eid, 'e': eid}, 'x')
@@ -353,7 +365,7 @@
                     '__action_delete': ''}
         path, params = self.expect_redirect_publish(req, 'edit')
         self.assertEquals(path, 'cwuser/admin')
-        self.assertEquals(params, {u'__message': u'entity deleted'})
+        self.assertIn('_cwmsgid', params)
         eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
         eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
         req = self.request()
@@ -363,7 +375,7 @@
                     '__action_delete': ''}
         path, params = self.expect_redirect_publish(req, 'edit')
         self.assertEquals(path, 'view')
-        self.assertEquals(params, {u'__message': u'entities deleted'})
+        self.assertIn('_cwmsgid', params)
 
     def test_nonregr_eetype_etype_editing(self):
         """non-regression test checking that a manager user can edit a CWEType entity
@@ -430,7 +442,7 @@
                     'use_email-object:Y': 'X',
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'address': u'required attribute'})
+        self.assertEquals(ex.errors, {'address-subject': u'required field'})
 
     def test_nonregr_copy(self):
         user = self.user()
@@ -498,12 +510,20 @@
 
     def test_usable_by_guets(self):
         self.login('anon')
-        self.vreg['controllers'].select('reportbug', self.request())
+        self.assertRaises(NoSelectableObject,
+                          self.vreg['controllers'].select, 'reportbug', self.request())
+        self.vreg['controllers'].select('reportbug', self.request(description='hop'))
 
 
 class SendMailControllerTC(CubicWebTC):
 
     def test_not_usable_by_guets(self):
+        self.assertRaises(NoSelectableObject,
+                          self.vreg['controllers'].select, 'sendmail', self.request())
+        self.vreg['controllers'].select('sendmail',
+                                        self.request(subject='toto',
+                                                     recipient='toto@logilab.fr',
+                                                     mailbody='hop'))
         self.login('anon')
         self.assertRaises(NoSelectableObject,
                           self.vreg['controllers'].select, 'sendmail', self.request())
@@ -542,7 +562,7 @@
 #         rql = 'Any T,N WHERE T is Tag, T name N'
 #         ctrl = self.ctrl(self.request(mode='json', rql=rql, pageid='123'))
 #         self.assertEquals(ctrl.publish(),
-#                           simplejson.dumps(self.execute(rql).rows))
+#                           json.dumps(self.execute(rql).rows))
 
     def test_remote_add_existing_tag(self):
         self.remote_call('tag_entity', self.john.eid, ['python'])
@@ -619,14 +639,14 @@
     # silly tests
     def test_external_resource(self):
         self.assertEquals(self.remote_call('external_resource', 'RSS_LOGO')[0],
-                          simplejson.dumps(self.request().external_resource('RSS_LOGO')))
+                          json.dumps(self.request().external_resource('RSS_LOGO')))
     def test_i18n(self):
         self.assertEquals(self.remote_call('i18n', ['bimboom'])[0],
-                          simplejson.dumps(['bimboom']))
+                          json.dumps(['bimboom']))
 
     def test_format_date(self):
         self.assertEquals(self.remote_call('format_date', '2007-01-01 12:00:00')[0],
-                          simplejson.dumps('2007/01/01'))
+                          json.dumps('2007/01/01'))
 
 
 
--- a/web/test/unittest_views_basetemplates.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_basetemplates.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.htmlparser import DTDValidator
--- a/web/test/unittest_views_baseviews.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_baseviews.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
-from simplejson import loads
-
 from logilab.common.testlib import unittest_main
 from logilab.mtconverter import html_unescape
 
@@ -14,6 +25,8 @@
 
 from cubicweb.web.htmlwidgets import TableWidget
 from cubicweb.web.views import vid_from_rset
+from cubicweb.web import json
+loads = json.loads
 
 def loadjson(value):
     return loads(html_unescape(value))
--- a/web/test/unittest_views_editforms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_editforms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main, mock_object
 from logilab.common.compat import any
--- a/web/test/unittest_views_embeding.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_embeding.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import TestCase, unittest_main
--- a/web/test/unittest_views_navigation.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_navigation.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.views.navigation unit tests
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.common.testlib import unittest_main, mock_object
--- a/web/test/unittest_views_pyviews.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_pyviews.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.testlib import unittest_main
 from cubicweb.devtools.testlib import CubicWebTC
 
--- a/web/test/unittest_views_searchrestriction.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_views_searchrestriction.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.facet import insert_attr_select_relation, prepare_facets_rqlst
--- a/web/test/unittest_viewselector.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_viewselector.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,6 +1,22 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """XXX rename, split, reorganize this
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import unittest_main
 
--- a/web/test/unittest_web.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_web.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb.devtools.fake import FakeRequest
--- a/web/test/unittest_webconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/test/unittest_webconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import os
 
--- a/web/uicfg.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/uicfg.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,108 +1,30 @@
-#:organization: Logilab
-#:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-#:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-
-"""This module (``cubicweb.web.uicfg``) regroups a set of structures that may be used to configure
-various options of the generated web interface.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""This module (``cubicweb.web.uicfg``) regroups a set of structures that may be
+used to configure various options of the generated web interface.
 
 To configure the interface generation, we use ``RelationTag`` objects.
 
-Primary view configuration
-``````````````````````````
-
-If you want to customize the primary view of an entity, overriding the
-primary view class may not be necessary. For simple adjustments
-(attributes or relations display locations and styles), a much simpler
-way is to use uicfg.
-
-Attributes/relations display location
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In the primary view, there are 3 sections where attributes and
-relations can be displayed (represented in pink in the image below):
-   * attributes
-   * relations
-   * sideboxes
-
-.. image:: ../../images/primaryview_template.png
-
-
-**Attributes** can only be displayed in the attributes section (default behavior). They can also be hidden.
-
-For instance, to hide the ``title`` attribute of the ``Blog`` entity:
-
-.. sourcecode:: python
-
-   from cubicweb.web import uicfg
-   uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden')
-
-
-**Relations** can be either displayed in one of the three sections or hidden.
-
-For relations, there are two methods:
-   * ``tag_object_of`` for modifying the primary view of the object
-   * ``tag_subject_of`` for modifying the primary view of the subject
-
-These two methods take two arguments:
-   * a triplet ``(subject, relation_name, object)``, where subject or object can be replaced with ``'*'``
-   * the section name or ``hidden``
-
-.. sourcecode:: python
-
-   # hide every relation ``entry_of`` in the ``Blog`` primary view
-   uicfg.primaryview_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden')
-
-   # display ``entry_of`` relations in the ``relations`` section in the ``BlogEntry`` primary view
-   uicfg.primaryview_section.tag_subject_of(('BlogEntry', 'entry_of', '*'),
-                                             'relations')
-
-
-Display content
-^^^^^^^^^^^^^^^
-
-You can use ``primaryview_display_ctrl`` to customize the display of attributes or relations. Values of ``primaryview_display_ctrl`` are dictionaries.
-
-
-Common keys for attributes and relations are:
-   * ``vid``: specifies the regid of the view for displaying the attribute or the relation.
-
-     If ``vid`` is not specified, the default value depends on the section:
-       * ``attributes`` section: 'reledit' view
-       * ``relations`` section: 'autolimited' view
-       * ``sideboxes`` section: 'sidebox' view
-
-   * ``order``: int used to control order within a section. When not specified, automatically set according to order in which tags are added.
-
-
-Keys for relations only:
-
-   * ``label``: label for the relations section or side box
-
-   * ``showlabel``: boolean telling whether the label is displayed
-
-   * ``limit``: boolean telling if the results should be limited. If so, a link to all results is displayed
-
-   * ``filter``: callback taking the related result set as argument and returning it filtered
-
-.. sourcecode:: python
-
-   # in ``CWUser`` primary view, display ``created_by`` relations in relations section
-   uicfg.primaryview_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations')
-
-   # displays this relation as a list, sets the label, limits the number of results and filters on comments
-   uicfg.primaryview_display_ctrl.tag_object_of(
-       ('*', 'created_by', 'CWUser'),
-       {'vid': 'list', 'label': _('latest comment(s):'), 'limit': True,
-        'filter': lambda rset: rset.filtered_rset(lambda x: x.e_schema == 'Comment')})
-
-.. warning:: with the ``primaryview_display_ctrl`` rtag, the subject or the object of the relation is ignored for respectively ``tag_object_of`` or  ``tag_subject_of``. To avoid warnings during execution, they should be set to ``'*'``.
-
-
 Index view configuration
 ````````````````````````
 :indexview_etype_section:
    entity type category in the index/manage page. May be one of:
+
       * ``application``
       * ``system``
       * ``schema``
@@ -117,68 +39,11 @@
 
 .. sourcecode:: python
 
-   # Adds all subjects of the entry_of relation in the add menu of the ``Blog`` primary view
+   # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
+   # primary view
    uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
 
 
-
-Automatic form configuration
-````````````````````````````
-
-Attributes/relations display location
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-``uicfg.autoform_section`` specifies where to display a relation in creation/edition entity form for a given form type.
-``tag_attribute``, ``tag_subject_of`` and ``tag_object_of`` methods for this
-relation tag expect two arguments additionally to the relation key: a
-``formtype`` and a ``section``.
-
-formtype may be one of:
-   * ``main``, the main entity form (via the modify action)
-   * ``inlined``, the form for an entity inlined into another form
-   * ``muledit``, the table form to edit multiple entities
-
-section may be one of:
-   * ``hidden``, don't display
-   * ``attributes``, display in the attributes section
-   * ``relations``, display in the relations section, using the generic relation
-     selector combobox (available in main form only, and not for attribute
-     relation)
-   * ``inlined``, display target entity of the relation in an inlined form
-     (available in main form only, and not for attribute relation)
-   * ``metadata``, display in a special metadata form (NOT YET IMPLEMENTED,
-     subject to changes)
-
-By default, mandatory relations are displayed in the ``attributes`` section, others in ``relations`` section.
-
-Change default fields
-^^^^^^^^^^^^^^^^^^^^^
-
-Use ``autoform_field`` to replace the default field type of an attribute.
-
-.. warning: ``autoform_field_kwargs`` should usually be used instead of ``autoform_field``. Do not use both methods for the same relation!
-
-
-Customize field options
-^^^^^^^^^^^^^^^^^^^^^^^
-
-In order to customize field options (see :class:`cubicweb.web.formfields.Field` for a detailed list of options), use ``autoform_field_kwargs``. This rtag takes a relation triplet and a dictionary as arguments.
-
-.. sourcecode:: python
-
-   # Change the content of the combobox
-   # here ``ticket_done_in_choices`` is a function which returns a list of elements to populate the combobox
-   uicfg.autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'), {'sort': False,
-                                                  'choices': ticket_done_in_choices})
-
-
-
-Overriding permissions
-^^^^^^^^^^^^^^^^^^^^^^
-
-``autoform_permissions_overrides`` provides a way to by-pass security checking for dark-corner case where it can't
-be verified properly. XXX documents.
-
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Views, forms, actions... for the CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/actions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/actions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of HTML base actions
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -404,16 +417,8 @@
 
 addmenu = uicfg.actionbox_appearsin_addmenu
 addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
-addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
-addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
-addmenu.tag_object_of(('*', 'state_of', 'CWEType'), True)
-addmenu.tag_object_of(('*', 'transition_of', 'CWEType'), True)
 addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
 addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
 addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
 addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
 addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
-addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
-addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
-addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
-addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
--- a/web/views/ajaxedit.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/ajaxedit.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of views allowing edition of entities/relations using ajax
 
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/apacherewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/apacherewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """provide class to do Apache rewrite rules'job inside cubicweb (though functionnalities
 are much more limited for the moment)
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/web/views/authentication.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/authentication.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """user authentication component
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -58,7 +71,6 @@
         self.log_queries = vreg.config['query-log-file']
         self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg),
                                     key=lambda x: x.order)
-        assert self.authinforetreivers
         self.anoninfo = vreg.config.anonymous_user()
 
     def validate_session(self, req, session):
--- a/web/views/autoform.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/autoform.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,120 @@
-"""The automatic entity form.
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+The automatic entity form
+-------------------------
+
+.. autodocstring:: cubicweb.web.views.autoform::AutomaticEntityForm
+
+Configuration through uicfg
+```````````````````````````
+
+It is possible to manage which and how an entity's attributes and relations
+will be edited in the various context where the automatic entity form is used
+by using proper uicfg tags.
+
+The details of the uicfg syntax can be found in the :ref:`uicfg` chapter.
+
+Possible relation tags that apply to entity forms are detailled below.
+They are all in the :mod:`cubicweb.web.uicfg` module.
+
+Attributes/relations display location
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``autoform_section`` specifies where to display a relation in form for a given
+form type.  :meth:`tag_attribute`, :meth:`tag_subject_of` and
+:meth:`tag_object_of` methods for this relation tag expect two arguments
+additionally to the relation key: a `formtype` and a `section`.
+
+`formtype` may be one of:
+
+* 'main', the main entity form (e.g. the one you get when creating or editing an
+  entity)
+
+* 'inlined', the form for an entity inlined into another form
+
+* 'muledit', the table form when editing multiple entities of the same type
+
+
+section may be one of:
+
+* 'hidden', don't display (not even in an hidden input, right?)
+
+* 'attributes', display in the attributes section
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+* 'relations', display in the relations section, using the generic relation
+  selector combobox (available in main form only, and not usable for attributes)
+
+* 'inlined', display target entity of the relation into an inlined form
+  (available in main form only, and not for attributes)
+
+By default, mandatory relations are displayed in the 'attributes' section,
+others in 'relations' section.
+
+
+Change default fields
+^^^^^^^^^^^^^^^^^^^^^
+
+Use ``autoform_field`` to replace the default field class to use for a relation
+or attribute. You can put either a field class or instance as value (put a class
+whenether it's possible).
+
+.. Warning::
+
+   `autoform_field_kwargs` should usually be used instead of
+   `autoform_field`. If you put a field instance into `autoform_field`,
+   `autoform_field_kwargs` values for this relation will be ignored.
+
+
+Customize field options
+^^^^^^^^^^^^^^^^^^^^^^^
+
+In order to customize field options (see :class:`~cubicweb.web.formfields.Field`
+for a detailed list of options), use `autoform_field_kwargs`. This rtag takes
+a dictionary as arguments, that will be given to the field's contructor.
+
+You can then put in that dictionary any arguments supported by the field
+class. For instance:
+
+.. sourcecode:: python
+
+   # Change the content of the combobox. Here `ticket_done_in_choices` is a
+   # function which returns a list of elements to populate the combobox
+   autoform_field_kwargs.tag_subject_of(('Ticket', 'done_in', '*'),
+                                        {'sort': False,
+                                         'choices': ticket_done_in_choices})
+
+   # Force usage of a TextInput widget for the expression attribute of
+   # RQLExpression entities
+   autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
+                                       {'widget': fw.TextInput})
+
+
+
+Overriding permissions
+^^^^^^^^^^^^^^^^^^^^^^
+
+The `autoform_permissions_overrides` rtag provides a way to by-pass security
+checking for dark-corner case where it can't be verified properly.
+
+
+.. More about inlined forms
+.. Controlling the generic relation fields
 """
 
 __docformat__ = "restructuredtext en"
@@ -11,8 +122,6 @@
 
 from warnings import warn
 
-from simplejson import dumps
-
 from logilab.mtconverter import xml_escape
 from logilab.common.decorators import iclassmethod, cached
 
@@ -22,7 +131,7 @@
 from cubicweb.selectors import (
     match_kwargs, match_form_params, non_final_entity,
     specified_etype_implements)
-from cubicweb.web import stdmsgs, uicfg, eid_param, \
+from cubicweb.web import stdmsgs, uicfg, eid_param, dumps, \
      form as f, formwidgets as fw, formfields as ff
 from cubicweb.web.views import forms
 
@@ -512,16 +621,13 @@
 # The automatic entity form ####################################################
 
 class AutomaticEntityForm(forms.EntityFieldsForm):
-    """base automatic form to edit any entity.
-
-    Designed to be fully generated from schema but highly configurable through:
+    """AutomaticEntityForm is an automagic form to edit any entity. It is
+    designed to be fully generated from schema but highly configurable through
+    :ref:`uicfg`.
 
-    * uicfg (autoform_* relation tags)
-    * various standard form parameters
-    * overriding
-
-    You can also easily customise it by adding/removing fields in
-    AutomaticEntityForm instances or by inheriting from it.
+    Of course, as for other forms, you can also customise it by specifying
+    various standard form parameters on selection, overriding, or
+    adding/removing fields in a selected instances.
     """
     __regid__ = 'edition'
 
--- a/web/views/basecomponents.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/basecomponents.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Bases HTML components:
 
 * the rql input form
 * the logged user link
 * pdf view link
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -15,7 +28,8 @@
 from logilab.mtconverter import xml_escape
 from rql import parse
 
-from cubicweb.selectors import yes, multi_etypes_rset, match_form_params
+from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params,
+                                anonymous_user, authenticated_user)
 from cubicweb.schema import display_name
 from cubicweb.uilib import toggle_action
 from cubicweb.web import component
@@ -79,35 +93,19 @@
                   self._cw._(u'help'),))
 
 
-class UserLink(component.Component):
-    """if the user is the anonymous user, build a link to login
-    else a link to the connected user object with a loggout link
+class _UserLink(component.Component):
+    """if the user is the anonymous user, build a link to login else display a menu
+    with user'action (preference, logout, etc...)
     """
     cw_property_defs = VISIBLE_PROP_DEF
     # don't want user to hide this component using an cwproperty
     site_wide = True
     __regid__ = 'loggeduserlink'
 
+
+class AnonUserLink(_UserLink):
+    __select__ = _UserLink.__select__ & anonymous_user()
     def call(self):
-        if not self._cw.cnx.anonymous_connection:
-            # display useractions and siteactions
-            actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
-            box = MenuWidget('', 'userActionsBox', _class='', islist=False)
-            menu = PopupBoxMenu(self._cw.user.login, isitem=False)
-            box.append(menu)
-            for action in actions.get('useractions', ()):
-                menu.append(BoxLink(action.url(), self._cw._(action.title),
-                                    action.html_class()))
-            if actions.get('useractions') and actions.get('siteactions'):
-                menu.append(BoxSeparator())
-            for action in actions.get('siteactions', ()):
-                menu.append(BoxLink(action.url(), self._cw._(action.title),
-                                    action.html_class()))
-            box.render(w=self.w)
-        else:
-            self.anon_user_link()
-
-    def anon_user_link(self):
         if self._cw.vreg.config['auth-mode'] == 'cookie':
             self.w(self._cw._('anonymous'))
             self.w(u'''&#160;[<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
@@ -118,6 +116,26 @@
                    % (self._cw.build_url('login'), self._cw._('login')))
 
 
+class UserLink(_UserLink):
+    __select__ = _UserLink.__select__ & authenticated_user()
+
+    def call(self):
+        # display useractions and siteactions
+        actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
+        box = MenuWidget('', 'userActionsBox', _class='', islist=False)
+        menu = PopupBoxMenu(self._cw.user.login, isitem=False)
+        box.append(menu)
+        for action in actions.get('useractions', ()):
+            menu.append(BoxLink(action.url(), self._cw._(action.title),
+                                action.html_class()))
+        if actions.get('useractions') and actions.get('siteactions'):
+            menu.append(BoxSeparator())
+        for action in actions.get('siteactions', ()):
+            menu.append(BoxLink(action.url(), self._cw._(action.title),
+                                action.html_class()))
+        box.render(w=self.w)
+
+
 class ApplicationMessage(component.Component):
     """display messages given using the __message parameter into a special div
     section
--- a/web/views/basecontrollers.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/basecontrollers.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,28 +1,39 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of base controllers, which are directly plugged into the application
 object to handle publication.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
 from smtplib import SMTP
 
-import simplejson
-
 from logilab.common.decorators import cached
 from logilab.common.date import strptime
 
 from cubicweb import (NoSelectableObject, ValidationError, ObjectNotFound,
                       typed_eid)
 from cubicweb.utils import CubicWebJsonEncoder
-from cubicweb.selectors import yes, match_user_groups
+from cubicweb.selectors import authenticated_user, match_form_params
 from cubicweb.mail import format_mail
-from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
+from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps, json
 from cubicweb.web.controller import Controller
 from cubicweb.web.views import vid_from_rset
 from cubicweb.web.views.formrenderers import FormRenderer
@@ -34,7 +45,7 @@
     HAS_SEARCH_RESTRICTION = False
 
 def jsonize(func):
-    """decorator to sets correct content_type and calls `simplejson.dumps` on
+    """decorator to sets correct content_type and calls `json.dumps` on
     results
     """
     def wrapper(self, *args, **kwargs):
@@ -91,11 +102,11 @@
         #   anonymous connection is allowed and the page will be displayed or
         #   we'll be redirected to the login form
         msg = self._cw._('you have been logged out')
-        if self._cw.https:
-            # XXX hack to generate an url on the http version of the site
-            self._cw._base_url =  self._cw.vreg.config['base-url']
-            self._cw.https = False
-        return self._cw.build_url('view', vid='index', __message=msg)
+        # force base_url so on dual http/https configuration, we generate an url
+        # on the http version of the site
+        return self._cw.build_url('view', vid='index', __message=msg,
+                                  base_url=self._cw.vreg.config['base-url'])
+
 
 class ViewController(Controller):
     """standard entry point :
@@ -236,7 +247,7 @@
         errback = str(self._cw.form.get('__onfailure', 'null'))
         cbargs = str(self._cw.form.get('__cbargs', 'null'))
         self._cw.set_content_type('text/html')
-        jsargs = simplejson.dumps((status, args, entity), cls=CubicWebJsonEncoder)
+        jsargs = json.dumps((status, args, entity), cls=CubicWebJsonEncoder)
         return """<script type="text/javascript">
  wp = window.parent;
  window.parent.handleFormValidationResponse('%s', %s, %s, %s, %s);
@@ -276,14 +287,16 @@
         args = self._cw.form.get('arg', ())
         if not isinstance(args, (list, tuple)):
             args = (args,)
-        args = [simplejson.loads(arg) for arg in args]
+        try:
+            args = [json.loads(arg) for arg in args]
+        except ValueError, exc:
+            self.exception('error while decoding json arguments for js_%s: %s', fname, args, exc)
+            raise RemoteCallFailed(repr(exc))
         try:
             result = func(*args)
         except RemoteCallFailed:
             raise
         except Exception, ex:
-            import traceback
-            traceback.print_exc()
             self.exception('an exception occured while calling js_%s(%s): %s',
                            fname, args, ex)
             raise RemoteCallFailed(repr(ex))
@@ -392,10 +405,22 @@
         else: # we receive unicode keys which is not supported by the **syntax
             extraargs = dict((str(key), value)
                              for key, value in extraargs.items())
-        comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset, **extraargs)
+        # XXX while it sounds good, addition of the try/except below cause pb:
+        # when filtering using facets return an empty rset, the edition box
+        # isn't anymore selectable, as expected. The pb is that with the
+        # try/except below, we see a "an error occured" message in the ui, while
+        # we don't see it without it. Proper fix would probably be to deal with
+        # this by allowing facet handling code to tell to js_component that such
+        # error is expected and should'nt be reported.
+        #try:
+        comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset,
+                                              **extraargs)
+        #except NoSelectableObject:
+        #    raise RemoteCallFailed('unselectable')
         extraargs = extraargs or {}
         stream = comp.set_stream()
         comp.render(**extraargs)
+        # XXX why not _call_view ?
         extresources = self._cw.html_headers.getvalue(skiphead=True)
         if extresources:
             stream.write(u'<div class="ajaxHtmlHead">\n')
@@ -427,11 +452,12 @@
         entity = self._cw.entity_from_eid(int(self._cw.form['eid']))
         # note: default is reserved in js land
         args['default'] = self._cw.form['default_value']
-        args['reload'] = simplejson.loads(args['reload'])
+        args['reload'] = json.loads(args['reload'])
         rset = req.eid_rset(int(self._cw.form['eid']))
         view = req.vreg['views'].select('doreledit', req, rset=rset, rtype=args['rtype'])
         stream = view.set_stream()
         view.render(**args)
+        # XXX why not _call_view ?
         extresources = req.html_headers.getvalue(skiphead=True)
         if extresources:
             stream.write(u'<div class="ajaxHtmlHead">\n')
@@ -564,41 +590,29 @@
         rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
         return eid_from
 
-
+# XXX move to massmailing
 class SendMailController(Controller):
     __regid__ = 'sendmail'
-    __select__ = match_user_groups('managers', 'users')
+    __select__ = authenticated_user() & match_form_params('recipient', 'mailbody', 'subject')
 
     def recipients(self):
         """returns an iterator on email's recipients as entities"""
         eids = self._cw.form['recipient']
-        # make sure we have a list even though only one recipient was specified
+        # eids may be a string if only one recipient was specified
         if isinstance(eids, basestring):
-            eids = (eids,)
-        rql = 'Any X WHERE X eid in (%s)' % (','.join(eids))
-        rset = self._cw.execute(rql)
-        for entity in rset.entities():
-            yield entity
-
-    @property
-    @cached
-    def smtp(self):
-        mailhost, port = self._cw.config['smtp-host'], self._cw.config['smtp-port']
-        try:
-            return SMTP(mailhost, port)
-        except Exception, ex:
-            self.exception("can't connect to smtp server %s:%s (%s)",
-                             mailhost, port, ex)
-            url = self._cw.build_url(__message=self._cw._('could not connect to the SMTP server'))
-            raise Redirect(url)
+            rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids})
+        else:
+            rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids)))
+        return rset.entities()
 
     def sendmail(self, recipient, subject, body):
-        helo_addr = '%s <%s>' % (self._cw.config['sender-name'],
-                                 self._cw.config['sender-addr'])
         msg = format_mail({'email' : self._cw.user.get_email(),
                            'name' : self._cw.user.dc_title(),},
                           [recipient], body, subject)
-        self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
+        if not self._cw.vreg.config.sendmails([(msg, [recipient])]):
+            msg = self._cw._('could not connect to the SMTP server')
+            url = self._cw.build_url(__message=msg)
+            raise Redirect(url)
 
     def publish(self, rset=None):
         # XXX this allows users with access to an cubicweb instance to use it as
@@ -608,14 +622,13 @@
         for recipient in self.recipients():
             text = body % recipient.as_email_context()
             self.sendmail(recipient.get_email(), subject, text)
-        # breadcrumbs = self._cw.get_session_data('breadcrumbs', None)
         url = self._cw.build_url(__message=self._cw._('emails successfully sent'))
         raise Redirect(url)
 
 
 class MailBugReportController(SendMailController):
     __regid__ = 'reportbug'
-    __select__ = yes()
+    __select__ = match_form_params('description')
 
     def publish(self, rset=None):
         body = self._cw.form['description']
@@ -623,3 +636,27 @@
         url = self._cw.build_url(__message=self._cw._('bug report sent'))
         raise Redirect(url)
 
+
+class UndoController(SendMailController):
+    __regid__ = 'undo'
+    __select__ = authenticated_user() & match_form_params('txuuid')
+
+    def publish(self, rset=None):
+        txuuid = self._cw.form['txuuid']
+        errors = self._cw.cnx.undo_transaction(txuuid)
+        if errors:
+            self.w(self._cw._('some errors occured:'))
+            self.wview('pyvalist', pyvalue=errors)
+        else:
+            self.redirect()
+
+    def redirect(self):
+        req = self._cw
+        breadcrumbs = req.get_session_data('breadcrumbs', None)
+        if breadcrumbs is not None and len(breadcrumbs) > 1:
+            url = req.rebuild_url(breadcrumbs[-2],
+                                  __message=req._('transaction undoed'))
+        else:
+            url = req.build_url(__message=req._('transaction undoed'))
+        raise Redirect(url)
+
--- a/web/views/basetemplates.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/basetemplates.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
 # -*- coding: utf-8 -*-
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """default templates for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/baseviews.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/baseviews.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of HTML generic base views:
 
 * noresult, final
@@ -6,10 +23,6 @@
 * list
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -297,7 +310,7 @@
 
 
 class SameETypeListView(EntityView):
-    """list of entities of the same type, when asked explicitly for adapted list
+    """list of entities of the same type, when asked explicitly for same etype list
     view (for instance, display gallery if only images)
     """
     __regid__ = 'sameetypelist'
@@ -311,7 +324,8 @@
 
     def call(self, **kwargs):
         """display a list of entities by calling their <item_vid> view"""
-        if not 'vtitle' in self._cw.form:
+        showtitle = kwargs.pop('showtitle', not 'vtitle' in self._cw.form)
+        if showtitle:
             self.w(u'<h1>%s</h1>' % self.title)
         super(SameETypeListView, self).call(**kwargs)
 
--- a/web/views/bookmark.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/bookmark.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Primary view for bookmarks + user's bookmarks box
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -113,7 +126,7 @@
                     # we can't edit shared bookmarks we don't own
                     bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, B owned_by U, U eid %(x)s'
                     erset = req.execute(bookmarksrql, {'x': ueid}, 'x',
-                                                build_descr=False)
+                                        build_descr=False)
                     bookmarksrql %= {'x': ueid}
                 if erset:
                     url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
--- a/web/views/boxes.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/boxes.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """
 generic boxes for CubicWeb web client:
 
@@ -8,10 +25,6 @@
 * schema box
 * startup views box
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/calendar.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/calendar.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """html calendar views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/csvexport.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/csvexport.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """csv export views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/cwproperties.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/cwproperties.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for CWProperty
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/cwuser.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/cwuser.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for users
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/debug.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/debug.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """management and error screens
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -165,5 +178,5 @@
         self.wview('pyvaltable', pyvalue=values[:self._cw.form.get('nb', 20)])
         if garbage:
             self.w(u'<h3>%s</h3>' % _('Unreachable objects'))
-            values = sorted(xml_escape(repr(o) for o in garbage))
+            values = sorted(xml_escape(repr(o)) for o in garbage)
             self.wview('pyvallist', pyvalue=values)
--- a/web/views/editcontroller.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/editcontroller.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """The edit controller, handling form submitting.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -17,6 +30,11 @@
 from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
 from cubicweb.web.views import basecontrollers, autoform
 
+def valerror_eid(eid):
+    try:
+        return typed_eid(eid)
+    except (ValueError, TypeError):
+        return eid
 
 class RqlQuery(object):
     def __init__(self):
@@ -110,7 +128,7 @@
         self._cw.remove_pending_operations()
         if self.errors:
             errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
-            raise ValidationError(form.get('__maineid'), errors)
+            raise ValidationError(valerror_eid(form.get('__maineid')), errors)
 
     def _insert_entity(self, etype, eid, rqlquery):
         rql = rqlquery.insert_query(etype)
@@ -166,7 +184,7 @@
                     self.handle_formfield(form, field, rqlquery)
         if self.errors:
             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
-            raise ValidationError(entity.eid, errors)
+            raise ValidationError(valerror_eid(entity.eid), errors)
         if eid is None: # creation or copy
             entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
         elif rqlquery.edited: # edition of an existant entity
@@ -252,6 +270,25 @@
             for reid in seteids:
                 self.relations_rql.append((rql, {'x': eid, 'y': reid}, ('x', 'y')))
 
+    def delete_entities(self, eidtypes):
+        """delete entities from the repository"""
+        redirect_info = set()
+        eidtypes = tuple(eidtypes)
+        for eid, etype in eidtypes:
+            entity = self._cw.entity_from_eid(eid, etype)
+            path, params = entity.after_deletion_path()
+            redirect_info.add( (path, tuple(params.iteritems())) )
+            entity.delete()
+        if len(redirect_info) > 1:
+            # In the face of ambiguity, refuse the temptation to guess.
+            self._after_deletion_path = 'view', ()
+        else:
+            self._after_deletion_path = iter(redirect_info).next()
+        if len(eidtypes) > 1:
+            self._cw.set_message(self._cw._('entities deleted'))
+        else:
+            self._cw.set_message(self._cw._('entity deleted'))
+
     def _action_apply(self):
         self._default_publish()
         self.reset()
@@ -260,11 +297,9 @@
         errorurl = self._cw.form.get('__errorurl')
         if errorurl:
             self._cw.cancel_edition(errorurl)
-        self._cw.message = self._cw._('edit canceled')
+        self._cw.set_message(self._cw._('edit canceled'))
         return self.reset()
 
     def _action_delete(self):
         self.delete_entities(self._cw.edited_eids(withtype=True))
         return self.reset()
-
-
--- a/web/views/editforms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/editforms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,18 +1,29 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of HTML automatic forms to create, delete, copy or edit a single entity
 or a list of entities of the same type
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
 
 from copy import copy
 
-from simplejson import dumps
-
 from logilab.mtconverter import xml_escape
 from logilab.common.decorators import cached
 
@@ -20,7 +31,7 @@
                                 specified_etype_implements, implements, yes)
 from cubicweb.view import EntityView
 from cubicweb import tags
-from cubicweb.web import uicfg, stdmsgs, eid_param, \
+from cubicweb.web import uicfg, stdmsgs, eid_param, dumps, \
      formfields as ff, formwidgets as fw
 from cubicweb.web.form import FormViewMixIn, FieldNotFound
 from cubicweb.web.views import forms
--- a/web/views/editviews.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/editviews.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Some views used to help to the edition process
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -15,7 +28,7 @@
 from cubicweb.view import EntityView, StartupView
 from cubicweb.selectors import (one_line_rset, non_final_entity,
                                 match_search_state)
-from cubicweb.web import httpcache, captcha
+from cubicweb.web import httpcache
 from cubicweb.web.views import baseviews, linksearch_select_url
 
 
@@ -95,17 +108,23 @@
         else:
             super(EditableFinalView, self).cell_call(row, col, props)
 
-
-class CaptchaView(StartupView):
-    __regid__ = 'captcha'
+try:
+    from cubicweb.web import captcha
+except ImportError:
+    # PIL not installed
+    pass
+else:
+    class CaptchaView(StartupView):
+        __regid__ = 'captcha'
 
-    http_cache_manager = httpcache.NoHTTPCacheManager
-    binary = True
-    templatable = False
-    content_type = 'image/jpg'
+        http_cache_manager = httpcache.NoHTTPCacheManager
+        binary = True
+        templatable = False
+        content_type = 'image/jpg'
 
-    def call(self):
-        text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'],
-                                     self._cw.vreg.config['captcha-font-size'])
-        self._cw.set_session_data('captcha', text)
-        self.w(data.read())
+        def call(self):
+            text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'],
+                                         self._cw.vreg.config['captcha-font-size'])
+            key = self._cw.form.get('captchakey', 'captcha')
+            self._cw.set_session_data(key, text)
+            self.w(data.read())
--- a/web/views/emailaddress.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/emailaddress.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for email addresses entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/embedding.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/embedding.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,11 +1,24 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Objects interacting together to provides the external page embeding
 functionality.
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/error.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/error.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of HTML errors views. Error view are generally implemented
 as startup views and are used for standard error pages (404, 500, etc.)
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/facets.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/facets.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,19 +1,31 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """the facets box and some basic facets
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-from simplejson import dumps
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import (non_final_entity, multi_lines_rset,
                                 match_context_prop, yes, relation_possible)
+from cubicweb.web import dumps
 from cubicweb.web.box import BoxTemplate
 from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
                                 prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
@@ -69,7 +81,7 @@
         rset, vid, divid, paginate = self._get_context(view)
         if rset.rowcount < 2: # XXX done by selectors, though maybe necessary when rset has been hijacked
             return
-        rqlst = self.cw_rset.syntax_tree()
+        rqlst = rset.syntax_tree()
         # union not yet supported
         if len(rqlst.children) != 1:
             return ()
@@ -105,7 +117,10 @@
     def display_bookmark_link(self, rset):
         eschema = self._cw.vreg.schema.eschema('Bookmark')
         if eschema.has_perm(self._cw, 'add'):
-            bk_path = 'view?rql=%s' % rset.printable_rql()
+            bk_path = 'rql=%s' % self._cw.url_quote(rset.printable_rql())
+            if self._cw.form.get('vid'):
+                bk_path += '&vid=%s' % self._cw.url_quote(self._cw.form['vid'])
+            bk_path = 'view?' + bk_path
             bk_title = self._cw._('my custom search')
             linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
             bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
--- a/web/views/formrenderers.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/formrenderers.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,35 @@
-"""form renderers, responsible to layout a form to html
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Renderers
+---------
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. Note::
+   Form renderers are responsible to layout a form to HTML.
+
+Here are the base renderers available:
+
+.. autoclass:: cubicweb.web.views.formrenderers.FormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.HTableFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityCompositeFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityFormRenderer
+.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
+
 """
 __docformat__ = "restructuredtext en"
 
@@ -12,12 +38,10 @@
 from logilab.common import dictattr
 from logilab.mtconverter import xml_escape
 
-from simplejson import dumps
-
 from cubicweb import tags
 from cubicweb.appobject import AppObject
 from cubicweb.selectors import implements, yes
-from cubicweb.web import eid_param, formwidgets as fwdgs
+from cubicweb.web import dumps, eid_param, formwidgets as fwdgs
 
 
 def checkbox(name, value, attrs='', checked=None):
@@ -37,12 +61,12 @@
 
 
 class FormRenderer(AppObject):
-    """basic renderer displaying fields in a two columns table label | value
+    """This is the 'default' renderer, displaying fields in a two columns table:
 
     +--------------+--------------+
     | field1 label | field1 input |
     +--------------+--------------+
-    | field1 label | field2 input |
+    | field2 label | field2 input |
     +--------------+--------------+
 
     +---------+
@@ -224,6 +248,8 @@
                     w(u' class="error"')
                 w(u'>')
                 w(field.render(form, self))
+                if error:
+                    self.render_error(w, error)
                 if self.display_help:
                     w(self.render_help(form, field))
                 w(u'</td></tr>')
@@ -241,7 +267,7 @@
 
     def render_error(self, w, err):
         """return validation error for widget's field, if any"""
-        w(u'<span class="error">%s</span>' % err)
+        w(u'<span class="errorMsg">%s</span>' % err)
 
 
 
@@ -254,7 +280,7 @@
 
 
 class HTableFormRenderer(FormRenderer):
-    """display fields horizontally in a table
+    """The 'htable' form renderer display fields horizontally in a table:
 
     +--------------+--------------+---------+
     | field1 label | field2 label |         |
@@ -298,7 +324,13 @@
 
 
 class EntityCompositeFormRenderer(FormRenderer):
-    """specific renderer for multiple entities edition form (muledit)"""
+    """This is a specific renderer for the multiple entities edition form
+    ('muledit').
+
+    Each entity form will be displayed in row off a table, with a check box for
+    each entities to indicate which ones are edited. Those checkboxes should be
+    automatically updated when something is edited.
+    """
     __regid__ = 'composite'
 
     _main_display_fields = None
@@ -357,7 +389,11 @@
 
 
 class EntityFormRenderer(BaseFormRenderer):
-    """specific renderer for entity edition form (edition)"""
+    """This is the 'default' renderer for entity's form.
+
+    You can still use form_renderer_id = 'base' if you want base FormRenderer
+    layout even when selected for an entity.
+    """
     __regid__ = 'default'
     # needs some additional points in some case (XXX explain cases)
     __select__ = implements('Any') & yes()
@@ -394,8 +430,8 @@
 
 
 class EntityInlinedFormRenderer(EntityFormRenderer):
-    """specific renderer for entity inlined edition form
-    (inline-[creation|edition])
+    """This is a specific renderer for entity's form inlined into another
+    entity's form.
     """
     __regid__ = 'inline'
 
--- a/web/views/forms.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/forms.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,45 @@
-"""some base form classes for CubicWeb web client
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Base form classes
+-----------------
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. Note:
+
+   Form is the glue that bind a context to a set of fields, and is rendered
+   using a form renderer. No display is actually done here, though you'll find
+   some attributes of form that are used to control the rendering process.
+
+Besides the automagic form we'll see later, they are barely two form
+classes in |cubicweb|:
+
+.. autoclass:: cubicweb.web.views.forms.FieldsForm
+.. autoclass:: cubicweb.web.views.forms.EntityFieldsForm
+
+As you have probably guessed, choosing between them is easy. Simply ask you the
+question 'I am editing an entity or not?'. If the answer is yes, use
+:class:`EntityFieldsForm`, else use :class:`FieldsForm`.
+
+Actually there exists a third form class:
+
+.. autoclass:: cubicweb.web.views.forms.CompositeForm
+
+but you'll use this one rarely.
 """
 __docformat__ = "restructuredtext en"
 
@@ -16,43 +52,77 @@
 from cubicweb import typed_eid
 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
 from cubicweb.web import uicfg, form, formwidgets as fwdgs
-from cubicweb.web.formfields import StringField, relvoc_unrelated, guess_field
+from cubicweb.web.formfields import relvoc_unrelated, guess_field
 
 
 class FieldsForm(form.Form):
-    """base class for fields based forms.
+    """This is the base class for fields based forms.
+
+    **Attributes**
 
     The following attributes may be either set on subclasses or given on
     form selection to customize the generated form:
 
-    * `needs_js`: sequence of javascript files that should be added to handle
-      this form (through `req.add_js`)
+    :attr:`needs_js`
+      sequence of javascript files that should be added to handle this form
+      (through :meth:`~cubicweb.web.request.Request.add_js`)
 
-    * `needs_css`: sequence of css files that should be added to handle this
-      form (through `req.add_css`)
+    :attr:`needs_css`
+      sequence of css files that should be added to handle this form (through
+      :meth:`~cubicweb.web.request.Request.add_css`)
+
+    :attr:`domid`
+      value for the "id" attribute of the <form> tag
+
+    :attr:`action`
+      value for the "action" attribute of the <form> tag
 
-    * `domid`: value for the "id" attribute of the <form> tag
+    :attr:`onsubmit`
+      value for the "onsubmit" attribute of the <form> tag
 
-    * `action`: value for the "action" attribute of the <form> tag
+    :attr:`cssclass`
+      value for the "class" attribute of the <form> tag
 
-    * `onsubmit`: value for the "onsubmit" attribute of the <form> tag
+    :attr:`cssstyle`
+      value for the "style" attribute of the <form> tag
 
-    * `cssclass`: value for the "class" attribute of the <form> tag
+    :attr:`cwtarget`
+      value for the "cubicweb:target" attribute of the <form> tag
+
+    :attr:`redirect_path`
+      relative to redirect to after submitting the form
 
-    * `cssstyle`: value for the "style" attribute of the <form> tag
+    :attr:`copy_nav_params`
+      flag telling if navigation parameters should be copied back in hidden
+      inputs
+
+    :attr:`form_buttons`
+      sequence of form control (:class:`~cubicweb.web.formwidgets.Button`
+      widgets instances)
 
-    * `cwtarget`: value for the "cubicweb:target" attribute of the <form> tag
+    :attr:`form_renderer_id`
+      identifier of the form renderer to use to render the form
 
-    * `redirect_path`: relative to redirect to after submitting the form
+    :attr:`fieldsets_in_order`
+      sequence of fieldset names , to control order
+
+    **Generic methods**
 
-    * `copy_nav_params`: flag telling if navigation paramenters should be copied
-      back in hidden input
+    .. automethod:: cubicweb.web.form.Form.field_by_name(name, role=None)
+    .. automethod:: cubicweb.web.form.Form.fields_by_name(name, role=None)
+
+    **Form construction methods**
 
-    * `form_buttons`:  form buttons sequence (button widgets instances)
+    .. automethod:: cubicweb.web.form.Form.remove_field(field)
+    .. automethod:: cubicweb.web.form.Form.append_field(field)
+    .. automethod:: cubicweb.web.form.Form.insert_field_before(field, name, role=None)
+    .. automethod:: cubicweb.web.form.Form.insert_field_after(field, name, role=None)
+    .. automethod:: cubicweb.web.form.Form.add_hidden(name, value=None, **kwargs)
 
-    * `form_renderer_id`: id of the form renderer to use to render the form
+    **Form rendering methods**
 
-    * `fieldsets_in_order`: fieldset name sequence, to control order
+    .. automethod:: cubicweb.web.views.forms.FieldsForm.render
+
     """
     __regid__ = 'base'
 
@@ -75,18 +145,6 @@
         """true if the form needs enctype=multipart/form-data"""
         return any(field.needs_multipart for field in self.fields)
 
-    def add_hidden(self, name, value=None, **kwargs):
-        """add an hidden field to the form"""
-        kwargs.setdefault('ignore_req_params', True)
-        kwargs.setdefault('widget', fwdgs.HiddenInput)
-        field = StringField(name=name, value=value, **kwargs)
-        if 'id' in kwargs:
-            # by default, hidden input don't set id attribute. If one is
-            # explicitly specified, ensure it will be set
-            field.widget.setdomid = True
-        self.append_field(field)
-        return field
-
     def add_media(self):
         """adds media (CSS & JS) required by this widget"""
         if self.needs_js:
@@ -95,8 +153,16 @@
             self._cw.add_css(self.needs_css)
 
     def render(self, formvalues=None, rendervalues=None, renderer=None, **kwargs):
-        """render this form, using the renderer given in args or the default
-        FormRenderer()
+        """Render this form, using the `renderer` given as argument or the
+        default according to :attr:`form_renderer_id`. The rendered form is
+        returned as an unicode string.
+
+        `formvalues` is an optional dictionary containing values that will be
+        considered as field's value.
+
+        Extra keyword arguments will be given to renderer's :meth:`render` method.
+
+        `rendervalues` is deprecated.
         """
         if rendervalues is not None:
             warn('[3.6] rendervalues argument is deprecated, all named arguments will be given instead',
@@ -148,6 +214,11 @@
 _AFF_KWARGS = uicfg.autoform_field_kwargs
 
 class EntityFieldsForm(FieldsForm):
+    """This class is designed for forms used to edit some entities. It should
+    handle for you all the underlying stuff necessary to properly work with the
+    generic :class:`~cubicweb.web.views.editcontroller.EditController`.
+    """
+
     __regid__ = 'base'
     __select__ = (match_kwargs('entity')
                   | (one_line_rset() & non_final_entity()))
@@ -267,7 +338,6 @@
 
 
 class CompositeFormMixIn(object):
-    """form composed of sub-forms"""
     __regid__ = 'composite'
     form_renderer_id = __regid__
 
@@ -287,7 +357,9 @@
 
 
 class CompositeForm(CompositeFormMixIn, FieldsForm):
-    pass
+    """Form composed of sub-forms. Typical usage is edition of multiple entities
+    at once.
+    """
 
 class CompositeEntityForm(CompositeFormMixIn, EntityFieldsForm):
     pass # XXX why is this class necessary?
--- a/web/views/ibreadcrumbs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/ibreadcrumbs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """navigation components definition for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -62,10 +75,7 @@
 
     def wpath_part(self, part, contextentity, last=False):
         if isinstance(part, Entity):
-            if last and part.eid == contextentity.eid:
-                self.w(xml_escape(part.view('breadcrumbtext')))
-            else:
-                self.w(part.view('breadcrumbs'))
+            self.w(part.view('breadcrumbs'))
         elif isinstance(part, tuple):
             url, title = part
             textsize = self._cw.property_value('navigation.short-line-size')
--- a/web/views/idownloadable.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/idownloadable.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for entities implementing IDownloadable
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/igeocodable.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/igeocodable.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,17 +1,29 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for entities implementing IGeocodable
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-import simplejson
-
 from cubicweb.interfaces import IGeocodable
 from cubicweb.view import EntityView
 from cubicweb.selectors import implements
+from cubicweb.web import json
 
 class GeocodingJsonView(EntityView):
     __regid__ = 'geocoding-json'
@@ -39,7 +51,7 @@
             'center': center,
             'markers': markers,
             }
-        self.w(simplejson.dumps(geodata))
+        self.w(json.dumps(geodata))
 
     def build_marker_data(self, row, extraparams):
         entity = self.cw_rset.get_entity(row, 0)
--- a/web/views/iprogress.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/iprogress.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for entities implementing IProgress
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -261,8 +274,7 @@
         self._cw.html_headers.add_onload('draw_progressbar("canvas%s", %i, %i, %i, "%s");' %
                                          (cid,
                                           int(100.*done/maxi), int(100.*(done+todo)/maxi),
-                                          int(100.*budget/maxi), color),
-                                         jsoncall=self._cw.json_request)
+                                          int(100.*budget/maxi), color))
         self.w(u'%s<br/>'
                u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>'
-               % (short_title.replace(' ','&nbsp;'), cid))
+               % (xml_escape(short_title), cid))
--- a/web/views/isioc.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/isioc.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for SIOC interfaces
 
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/magicsearch.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/magicsearch.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """a query preprocesser to handle quick search shortcuts for cubicweb
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/web/views/management.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/management.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """security management and error screens
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/massmailing.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/massmailing.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Mass mailing form views
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -11,7 +24,7 @@
 import operator
 
 from cubicweb.interfaces import IEmailable
-from cubicweb.selectors import implements, match_user_groups
+from cubicweb.selectors import implements, authenticated_user
 from cubicweb.view import EntityView
 from cubicweb.web import stdmsgs, action, form, formfields as ff
 from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton
@@ -22,7 +35,7 @@
     __regid__ = 'sendemail'
     # XXX should check email is set as well
     __select__ = (action.Action.__select__ & implements(IEmailable)
-                  & match_user_groups('managers', 'users'))
+                  & authenticated_user())
 
     title = _('send email')
     category = 'mainactions'
@@ -42,6 +55,11 @@
 class MassMailingForm(forms.FieldsForm):
     __regid__ = 'massmailing'
 
+    needs_js = ('cubicweb.widgets.js', 'cubicweb.massmailing.js')
+    needs_css = ('cubicweb.mailform.css')
+    domid = 'sendmail'
+    action = 'sendmail'
+
     sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
                             label=_('From:'),
                             value=lambda f: '%s <%s>' % (f._cw.user.dc_title(), f._cw.user.get_email()))
@@ -81,7 +99,6 @@
 
 class MassMailingFormRenderer(formrenderers.FormRenderer):
     __regid__ = 'massmailing'
-    button_bar_class = u'toolbar'
 
     def _render_fields(self, fields, w, form):
         w(u'<table class="headersform">')
@@ -115,15 +132,12 @@
     def render_buttons(self, w, form):
         pass
 
+
 class MassMailingFormView(form.FormViewMixIn, EntityView):
     __regid__ = 'massmailing'
-    __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
+    __select__ = implements(IEmailable) & authenticated_user()
 
     def call(self):
-        req = self._cw
-        req.add_js('cubicweb.widgets.js', 'cubicweb.massmailing.js')
-        req.add_css('cubicweb.mailform.css')
-        from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
-        form = self._cw.vreg['forms'].select('massmailing', self._cw, rset=self.cw_rset,
-                                             action='sendmail', domid='sendmail')
+        form = self._cw.vreg['forms'].select('massmailing', self._cw,
+                                             rset=self.cw_rset)
         self.w(form.render())
--- a/web/views/navigation.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/navigation.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """navigation components definition for CubicWeb web client
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/old_calendar.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/old_calendar.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """html calendar views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from datetime import date, time, timedelta
--- a/web/views/owl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/owl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """produces some Ontology Web Language schema and views
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/plots.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/plots.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,20 +1,32 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """basic plot views
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-from simplejson import dumps
-
 from logilab.common.date import datetime2ticks
 from logilab.mtconverter import xml_escape
 
 from cubicweb.utils import UStringIO
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import multi_columns_rset
+from cubicweb.web import dumps
 from cubicweb.web.views import baseviews
 
 @objectify_selector
@@ -115,8 +127,7 @@
                                     {'plotdefs': '\n'.join(plotdefs),
                                      'figid': figid,
                                      'plotdata': ','.join(plotdata),
-                                     'mode': self.timemode and "'time'" or 'null'},
-                                    jsoncall=req.json_request)
+                                     'mode': self.timemode and "'time'" or 'null'})
 
 
 class PlotView(baseviews.AnyRsetView):
--- a/web/views/primary.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/primary.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """The default primary view
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -15,7 +28,7 @@
 from cubicweb import Unauthorized
 from cubicweb.selectors import match_kwargs
 from cubicweb.view import EntityView
-from cubicweb.schema import display_name
+from cubicweb.schema import VIRTUAL_RTYPES, display_name
 from cubicweb.web import uicfg
 
 
@@ -102,7 +115,6 @@
         self.content_navigation_components('ctxtoolbar')
 
     def render_entity_metadata(self, entity):
-        # XXX deprecated
         entity.view('metadata', w=self.w)
 
     def render_entity_summary(self, entity):
@@ -115,11 +127,8 @@
         return u''
 
     def render_entity_attributes(self, entity, siderelations=None):
-        entity_attributes = self._section_def(entity, 'attributes')
-        if not entity_attributes:
-            return
-        self.w(u'<table>')
-        for rschema, tschemas, role, dispctrl in entity_attributes:
+        display_attributes = []
+        for rschema, _, role, dispctrl in self._section_def(entity, 'attributes'):
             vid = dispctrl.get('vid', 'reledit')
             if rschema.final or vid == 'reledit':
                 value = entity.view(vid, rtype=rschema.type, role=role)
@@ -129,16 +138,19 @@
                     value = self._cw.view(vid, rset)
                 else:
                     value = None
-            if self.skip_none and (value is None or value == ''):
-                continue
-            try:
-                self._render_attribute(dispctrl, rschema, value,
-                                       role=role, table=True)
-            except TypeError:
-                warn('[3.6] _render_attribute prototype has changed, '
-                     'please update %s' % self.__class___, DeprecationWarning)
-                self._render_attribute(rschema, value, role=role, table=True)
-        self.w(u'</table>')
+            if not self.skip_none or (value is not None and value != ''):
+                display_attributes.append( (rschema, role, dispctrl, value) )
+        if display_attributes:
+            self.w(u'<table>')
+            for rschema, role, dispctrl, value in display_attributes:
+                try:
+                    self._render_attribute(dispctrl, rschema, value,
+                                           role=role, table=True)
+                except TypeError:
+                    warn('[3.6] _render_attribute prototype has changed, please'
+                         ' update %s' % self.__class___, DeprecationWarning)
+                    self._render_attribute(rschema, value, role=role, table=True)
+            self.w(u'</table>')
 
     def render_entity_relations(self, entity, siderelations=None):
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):
@@ -202,6 +214,8 @@
         rdefs = []
         eschema = entity.e_schema
         for rschema, tschemas, role in eschema.relation_definitions(True):
+            if rschema in VIRTUAL_RTYPES:
+                continue
             matchtschemas = []
             for tschema in tschemas:
                 section = self.rsection.etype_get(eschema, rschema, role,
--- a/web/views/pyviews.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/pyviews.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Views to display bare python values
 
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/schema.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/schema.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Specific views for schema related entities
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/searchrestriction.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/searchrestriction.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """contains utility functions and some visual component to restrict results of
 a search
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/sessions.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/sessions.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """web session component: by dfault the session is actually the db connection
 object :/
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -22,6 +35,8 @@
         #assert isinstance(self.authmanager, RepositoryAuthenticationManager)
         self._sessions = {}
 
+    # dump_data / restore_data to avoid loosing open sessions on registry
+    # reloading
     def dump_data(self):
         return self._sessions
     def restore_data(self, data):
@@ -38,9 +53,9 @@
         if self.has_expired(session):
             self.close_session(session)
             raise InvalidSession()
-        # give an opportunity to auth manager to hijack the session
-        # (necessary with the RepositoryAuthenticationManager in case
-        #  the connection to the repository has expired)
+        # give an opportunity to auth manager to hijack the session (necessary
+        # with the RepositoryAuthenticationManager in case the connection to the
+        # repository has expired)
         try:
             session = self.authmanager.validate_session(req, session)
             # necessary in case session has been hijacked
--- a/web/views/sparql.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/sparql.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """SPARQL integration
 
-:organization: Logilab
-:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/startup.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/startup.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of HTML startup views. A startup view is global, e.g. doesn't
 apply to a result set.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/tableview.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/tableview.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,14 +1,30 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """generic table view, including filtering abilities
 
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-from simplejson import dumps
+try:
+    from json import dumps
+except ImportError:
+    from simplejson import dumps
 
 from logilab.mtconverter import xml_escape
 
@@ -23,6 +39,11 @@
 from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
 
 class TableView(AnyRsetView):
+    """The table view accepts any non-empty rset. It uses
+    introspection on the result set to compute column names and the
+    proper way to display the cells.
+    It is however highly configurable and accepts a wealth of options.
+    """
     __regid__ = 'table'
     title = _('table')
     finalview = 'final'
@@ -50,29 +71,30 @@
 
     def _generate_form(self, divid, baserql, fwidgets, hidden=True, vidargs={}):
         """display a form to filter table's content. This should only
-        occurs when a context eid is given
+        occur when a context eid is given
         """
+        w = self.w
         self._cw.add_css('cubicweb.facets.css')
         self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
         # drop False / None values from vidargs
         vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
-        self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
-               xml_escape(dumps([divid, 'table', False, vidargs])))
-        self.w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
-        self.w(u'<input type="hidden" name="divid" value="%s" />' % divid)
-        self.w(u'<input type="hidden" name="fromformfilter" value="1" />')
-        filter_hiddens(self.w, facets=','.join(wdg.facet.__regid__ for wdg in fwidgets),
+        w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
+          xml_escape(dumps([divid, 'table', False, vidargs])))
+        w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
+        w(u'<input type="hidden" name="divid" value="%s" />' % divid)
+        w(u'<input type="hidden" name="fromformfilter" value="1" />')
+        filter_hiddens(w, facets=','.join(wdg.facet.__regid__ for wdg in fwidgets),
                        baserql=baserql)
-        self.w(u'<table class="filter">\n')
-        self.w(u'<tr>\n')
+        w(u'<table class="filter">\n')
+        w(u'<tr>\n')
         for wdg in fwidgets:
-            self.w(u'<td>')
-            wdg.render(w=self.w)
-            self.w(u'</td>\n')
-        self.w(u'</tr>\n')
-        self.w(u'</table>\n')
-        self.w(u'</fieldset>\n')
-        self.w(u'</form>\n')
+            w(u'<td>')
+            wdg.render(w=w)
+            w(u'</td>\n')
+        w(u'</tr>\n')
+        w(u'</table>\n')
+        w(u'</fieldset>\n')
+        w(u'</form>\n')
 
     def main_var_index(self):
         """returns the index of the first non-attribute variable among the RQL
@@ -98,7 +120,7 @@
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
              displaycols=None, displayactions=None, actions=(), divid=None,
              cellvids=None, cellattrs=None, mainindex=None):
-        """Dumps a table displaying a composite query
+        """Produces a table displaying a composite query
 
         :param title: title added before table
         :param subvid: cell view
--- a/web/views/tabs.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/tabs.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """base classes to handle tabbed views
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
@@ -32,9 +45,7 @@
 
     def lazyview(self, vid, rql=None, eid=None, rset=None, tabid=None,
                  reloadable=False, show_spinbox=True, w=None):
-        """a lazy version of wview
-        first version only support lazy viewing for an entity at a time
-        """
+        """ a lazy version of wview """
         w = w or self.w
         self._cw.add_js('cubicweb.lazy.js')
         urlparams = {'vid' : vid, 'fname' : 'view'}
@@ -49,6 +60,8 @@
         if show_spinbox:
             w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
               % (tabid or vid, self._cw._('(loading ...)')))
+        else:
+            w(u'<div id="%s-hole"></div>' % (tabid or vid))
         w(u'<noscript><p><a class="style: hidden" id="seo-%s" href="%s">%s</a></p></noscript>'
           % (tabid or vid, xml_escape(self._cw.build_url(**urlparams)), xml_escape('%s (%s)') %
              (tabid or vid, self._cw._('follow this link if javascript is deactivated'))))
--- a/web/views/timeline.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/timeline.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,21 +1,33 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """basic support for SIMILE's timline widgets
 
 cf. http://code.google.com/p/simile-widgets/
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-import simplejson
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb.interfaces import ICalendarable
 from cubicweb.selectors import implements
 from cubicweb.view import EntityView, StartupView
+from cubicweb.web import json
 
 _ = unicode
 
@@ -40,7 +52,7 @@
                 events.append(event)
         timeline_data = {'dateTimeFormat': self.date_fmt,
                          'events': events}
-        self.w(simplejson.dumps(timeline_data))
+        self.w(json.dumps(timeline_data))
 
     # FIXME: those properties should be defined by the entity class
     def onclick_url(self, entity):
--- a/web/views/timetable.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/timetable.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """html calendar views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from logilab.mtconverter import xml_escape
--- a/web/views/treeview.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/treeview.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,19 +1,31 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Set of tree-building widgets, based on jQuery treeview plugin
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
-import simplejson as json
-
 from logilab.mtconverter import xml_escape
 from cubicweb.utils import make_uid
 from cubicweb.interfaces import ITree
 from cubicweb.selectors import implements
 from cubicweb.view import EntityView
+from cubicweb.web import json
 
 def treecookiename(treeid):
     return str('%s-treestate' % treeid)
@@ -46,8 +58,7 @@
         self._cw.add_css('jquery.treeview.css')
         self._cw.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
         self._cw.html_headers.add_onload(u"""
-jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid,
-                                         jsoncall=toplevel_thru_ajax)
+jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
 
     def call(self, subvid=None, treeid=None,
              initial_load=True, initial_thru_ajax=False, **morekwargs):
--- a/web/views/urlpublishing.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/urlpublishing.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,28 +1,39 @@
-"""associate url's path to view identifier / rql queries
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Associate url's path to view identifier / rql queries.
 
-It currently handle url's path with the forms
+It currently handles url path with the forms:
 
 * <publishing_method>
+* minimal REST publishing:
 
-* minimal REST publishing:
   * <eid>
   * <etype>[/<attribute name>/<attribute value>]*
-
 * folder navigation
 
-
-You can actually control URL (more exactly path) resolution using URL path
-evaluator.
-
-XXX actionpath and folderpath execute a query whose results is lost
-because of redirecting instead of direct traversal
+You can actually control URL (more exactly path) resolution using an
+URL path evaluator.
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+.. note::
+
+ Actionpath and Folderpath execute a query whose results is lost
+ because of redirecting instead of direct traversal.
 """
-
 __docformat__ = "restructuredtext en"
 
 from rql import TypeResolverException
@@ -37,17 +48,18 @@
     """
 
 class URLPublisherComponent(component.Component):
-    """associate url's path to view identifier / rql queries,
-    by applying a chain of urlpathevaluator components.
+    """Associate url path to view identifier / rql queries, by
+    applying a chain of urlpathevaluator components.
 
-    An evaluator is a URLPathEvaluator subclass with a .evaluate_path
+    An evaluator is a URLPathEvaluator subclass with an .evaluate_path
     method taking the request object and the path to publish as
-    argument.  It will either returns a publishing method identifier
-    and a rql query on success or raises a `PathDontMatch` exception
-    on failure. URL evaluators are called according to their `priority`
-    attribute, with 0 as the greatest priority and greater values as
-    lower priority.  The first evaluator returning a result or raising
-    something else than `PathDontMatch` will stop the handlers chain.
+    argument.  It will either return a publishing method identifier
+    and an rql query on success or raise a `PathDontMatch` exception
+    on failure. URL evaluators are called according to their
+    `priority` attribute, with 0 as the greatest priority and greater
+    values as lower priority. The first evaluator returning a result
+    or raising something else than `PathDontMatch` will stop the
+    handlers chain.
     """
     __regid__ = 'urlpublisher'
     vreg = None # XXX necessary until property for deprecation warning is on appobject
@@ -64,18 +76,18 @@
         self.evaluators = sorted(evaluators, key=lambda x: x.priority)
 
     def process(self, req, path):
-        """given an url (essentialy caracterized by a path on the server,
-        but additional information may be found in the request object), return
-        a publishing method identifier (eg controller) and an optional result
-        set
+        """Given an url (essentialy caracterized by a path on the
+        server, but additional information may be found in the request
+        object), return a publishing method identifier
+        (e.g. controller) and an optional result set.
 
-        :type req: `cubicweb.web.Request`
+        :type req: `cubicweb.web.request.CubicWebRequestBase`
         :param req: the request object
 
         :type path: str
         :param path: the path of the resource to publish
 
-        :rtype: tuple(str, `cubicweb.utils.ResultSet` or None)
+        :rtype: tuple(str, `cubicweb.rset.ResultSet` or None)
         :return: the publishing method identifier and an optional result set
 
         :raise NotFound: if no handler is able to decode the given path
--- a/web/views/urlrewrite.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/urlrewrite.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
-"""Rules based url rewriter component, to get configurable RESTful urls
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Rules based url rewriter component, to get configurable RESTful urls.
 
-:organization: Logilab
-:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 import re
 
@@ -40,17 +53,17 @@
 
 
 class URLRewriter(AppObject):
-    """base class for URL rewriters
+    """Base class for URL rewriters.
 
-    url rewriters should have a `rules` dict that maps an input URI
+    Url rewriters should have a `rules` dict that maps an input URI
     to something that should be used for rewriting.
 
     The actual logic that defines how the rules dict is used is implemented
-    in the `rewrite` method
+    in the `rewrite` method.
 
     A `priority` attribute might be used to indicate which rewriter
     should be tried first. The higher the priority is, the earlier the
-    rewriter will be tried
+    rewriter will be tried.
     """
     __metaclass__ = metarewriter
     __registry__ = 'urlrewriting'
@@ -62,11 +75,11 @@
 
 
 class SimpleReqRewriter(URLRewriter):
-    """The SimpleReqRewriters uses a `rules` dict that maps
-    input URI (regexp or plain string) to a dictionary to update the
-    request's form
+    """The SimpleReqRewriters uses a `rules` dict that maps input URI
+    (regexp or plain string) to a dictionary to update the request's
+    form.
 
-    If the input uri is a regexp, group substitution is allowed
+    If the input uri is a regexp, group substitution is allowed.
     """
     __regid__ = 'simple'
 
@@ -188,8 +201,8 @@
 
 
 class SchemaBasedRewriter(URLRewriter):
-    """Here, the rules dict maps regexps or plain strings to
-    callbacks that will be called with (input, uri, req, schema)
+    """Here, the rules dict maps regexps or plain strings to callbacks
+    that will be called with inputurl, uri, req, schema as parameters.
     """
     __regid__ = 'schemabased'
     rules = [
--- a/web/views/vcard.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/vcard.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """vcard import / export
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/wdoc.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/wdoc.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """inline help system, using ReST file in products `wdoc` directory
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/web/views/workflow.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/workflow.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,12 +1,25 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """workflow views:
 
 * IWorkflowable views and forms
 * workflow entities views (State, Transition, TrInfo)
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -35,6 +48,15 @@
 _abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
 _abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
                     False)
+_abaa.tag_subject_of(('*', 'wf_info_for', '*'), False)
+_abaa.tag_object_of(('*', 'wf_info_for', '*'), False)
+
+_abaa.tag_object_of(('*', 'state_of', 'CWEType'), True)
+_abaa.tag_object_of(('*', 'transition_of', 'CWEType'), True)
+_abaa.tag_subject_of(('Transition', 'destination_state', '*'), True)
+_abaa.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
+_abaa.tag_object_of(('*', 'destination_state', 'State'), True)
+_abaa.tag_subject_of(('State', 'allowed_transition', '*'), True)
 _abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
 _abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
 _abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
--- a/web/views/xbel.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/xbel.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """xbel views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
--- a/web/views/xmlrss.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/views/xmlrss.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """base xml and rss views
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -50,11 +63,14 @@
         self.w(u'<%s>\n' % (entity.e_schema))
         for rschema, attrschema in entity.e_schema.attribute_definitions():
             attr = rschema.type
-            try:
-                value = entity[attr]
-            except KeyError:
-                # Bytes
-                continue
+            if attr == 'eid':
+                value = entity.eid
+            else:
+                try:
+                    value = entity[attr]
+                except KeyError:
+                    # Bytes
+                    continue
             if value is not None:
                 if attrschema == 'Bytes':
                     from base64 import b64encode
--- a/web/webconfig.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/webconfig.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """common web configuration for twisted/modpython instances
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -113,29 +126,30 @@
           'group': 'web', 'inputlevel': 2,
           }),
         ('http-session-time',
-         {'type' : 'int',
+         {'type' : 'time',
           'default': 0,
-          'help': 'duration in seconds for HTTP sessions. 0 mean no expiration. '\
-          'Should be greater than RQL server\'s session-time.',
+          'help': "duration of the cookie used to store session identifier. "
+          "If 0, the cookie will expire when the user exist its browser. "
+          "Should be 0 or greater than repository\'s session-time.",
           'group': 'web', 'inputlevel': 2,
           }),
         ('cleanup-session-time',
-         {'type' : 'int',
-          'default': 43200,
-          'help': 'duration in seconds for which unused connections should be '\
-          'closed, to limit memory consumption. This is different from '\
-          'http-session-time since in some cases you may have an unexpired http '\
-          'session (e.g. valid session cookie) which will trigger transparent '\
-          'creation of a new session. In other cases, sessions may never expire \
-          and cause memory leak. Should be smaller than http-session-time, '\
-          'unless it\'s 0. Default to 12 h.',
+         {'type' : 'time',
+          'default': '24h',
+          'help': 'duration of inactivity after which a connection '
+          'will be closed, to limit memory consumption (avoid sessions that '
+          'never expire and cause memory leak when http-session-time is 0). '
+          'So even if http-session-time is 0 and the user don\'t close his '
+          'browser, he will have to reauthenticate after this time of '
+          'inactivity. Default to 24h.',
           'group': 'web', 'inputlevel': 2,
           }),
         ('cleanup-anonymous-session-time',
-         {'type' : 'int',
-          'default': 120,
-          'help': 'Same as cleanup-session-time but specific to anonymous '\
-          'sessions. Default to 2 min.',
+         {'type' : 'time',
+          'default': '5min',
+          'help': 'Same as cleanup-session-time but specific to anonymous '
+          'sessions. You can have a much smaller timeout here since it will be '
+          'transparent to the user. Default to 5min.',
           'group': 'web', 'inputlevel': 2,
           }),
         ('force-html-content-type',
@@ -303,11 +317,13 @@
         baseurl = self['base-url'] or self.default_base_url()
         if baseurl and baseurl[-1] != '/':
             baseurl += '/'
-        self.global_set_option('base-url', baseurl)
+        if not self.repairing:
+            self.global_set_option('base-url', baseurl)
         httpsurl = self['https-url']
         if httpsurl and httpsurl[-1] != '/':
             httpsurl += '/'
-            self.global_set_option('https-url', httpsurl)
+            if not self.repairing:
+                self.global_set_option('https-url', httpsurl)
 
     def _build_ext_resources(self):
         libresourcesfile = join(self.shared_dir(), 'data', 'external_resources')
@@ -328,7 +344,6 @@
                 files = [w.strip() for w in val.split(',') if w.strip()]
                 self.ext_resources[resource] = files
 
-
     # static files handling ###################################################
 
     @property
--- a/web/webctl.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/web/webctl.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,10 +1,23 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-ctl commands and command handlers common to twisted/modpython
 web configuration
 
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
@@ -18,7 +31,7 @@
         """bootstrap this configuration"""
         print '\n' + underline_title('Generic web configuration')
         config = self.config
-        if config.repo_method == 'pyro':
+        if config.repo_method == 'pyro' or config.pyro_enabled():
             print '\n' + underline_title('Pyro configuration')
             config.input_config('pyro', inputlevel)
         if ASK.confirm('Allow anonymous access ?', False):
--- a/wsgi/__init__.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/wsgi/__init__.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,3 +1,20 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """This package contains all WSGI specific code for cubicweb
 
 NOTE: this package borrows a lot of code to Django
@@ -6,10 +23,6 @@
 
 WSGI corresponding PEP: http://www.python.org/dev/peps/pep-0333/
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
 
--- a/wsgi/handler.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/wsgi/handler.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """WSGI request handler for cubicweb
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/wsgi/request.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/wsgi/request.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,13 +1,26 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """WSGI request adapter for cubicweb
 
 NOTE: each docstring tagged with ``COME FROM DJANGO`` means that
 the code has been taken (or adapted) from Djanco source code :
   http://www.djangoproject.com/
 
-:organization: Logilab
-:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 __docformat__ = "restructuredtext en"
--- a/xy.py	Wed Mar 24 10:23:31 2010 +0100
+++ b/xy.py	Wed Apr 28 11:54:13 2010 +0200
@@ -1,9 +1,22 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# logilab-common is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """map standard cubicweb schema to xml vocabularies
 
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
 from yams import xy