Showtime !
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Wed, 05 Nov 2008 15:52:50 +0100
changeset 0 b97547f5f1fa
child 1 88d637274072
child 6 29ab115b9fcb
Showtime !
COPYING
MANIFEST
MANIFEST.in
README
__init__.py
__pkginfo__.py
_exceptions.py
bin/cubicweb-ctl
cleanappl.sh
common/__init__.py
common/appobject.py
common/entity.py
common/html4zope.py
common/i18n.py
common/mail.py
common/migration.py
common/mixins.py
common/mttransforms.py
common/registerers.py
common/rest.py
common/schema.py
common/selectors.py
common/tal.py
common/test/data/bootstrap_packages
common/test/data/entities.py
common/test/data/migration/0.0.3_Any.py
common/test/data/migration/0.0.4_Any.py
common/test/data/migration/0.1.0_Any.py
common/test/data/migration/0.1.0_common.py
common/test/data/migration/0.1.0_repository.py
common/test/data/migration/0.1.0_web.py
common/test/data/migration/0.1.2_Any.py
common/test/data/migration/depends.map
common/test/data/schema/Affaire.sql
common/test/data/schema/Note.py
common/test/data/schema/Note.sql
common/test/data/schema/Personne.sql
common/test/data/schema/Societe.sql
common/test/data/schema/relations.rel
common/test/data/server_migration/2.10.2_Any.sql
common/test/data/server_migration/2.5.0_Any.sql
common/test/data/server_migration/2.6.0_Any.sql
common/test/data/server_migration/bootstrapmigration_repository.py
common/test/unittest_entity.py
common/test/unittest_mail.py
common/test/unittest_migration.py
common/test/unittest_rest.py
common/test/unittest_uilib.py
common/test/unittest_utils.py
common/uilib.py
common/utils.py
common/view.py
cwconfig.py
cwctl.py
cwvreg.py
dbapi.py
debian.etch/control
debian/changelog
debian/compat
debian/control
debian/copyright
debian/cubicweb-client.dirs
debian/cubicweb-common.dirs
debian/cubicweb-common.postinst
debian/cubicweb-core.dirs
debian/cubicweb-ctl.bash_completion
debian/cubicweb-ctl.cubicweb.init
debian/cubicweb-ctl.dirs
debian/cubicweb-ctl.logrotate
debian/cubicweb-ctl.manpages
debian/cubicweb-ctl.postinst
debian/cubicweb-ctl.postrm
debian/cubicweb-ctl.prerm
debian/cubicweb-dev.dirs
debian/cubicweb-doc
debian/cubicweb-documentation.dirs
debian/cubicweb-documentation.install
debian/cubicweb-documentation.postinst
debian/cubicweb-documentation.prerm
debian/cubicweb-server.dirs
debian/cubicweb-server.postinst
debian/cubicweb-server.prerm
debian/cubicweb-twisted.dirs
debian/cubicweb-twisted.postinst
debian/cubicweb-twisted.prerm
debian/cubicweb-web.dirs
debian/cubicweb-web.postinst
debian/pycompat
debian/rules
devtools/__init__.py
devtools/_apptest.py
devtools/apptest.py
devtools/cwtwill.py
devtools/devctl.py
devtools/fake.py
devtools/fill.py
devtools/fix_po_encoding
devtools/htmlparser.py
devtools/livetest.py
devtools/migrtest.py
devtools/pkginfo.py
devtools/repotest.py
devtools/stresstester.py
devtools/test/data/bootstrap_packages
devtools/test/data/dbfill.conf
devtools/test/data/firstnames.txt
devtools/test/data/schema/Bug.sql
devtools/test/data/schema/Project.sql
devtools/test/data/schema/Story.sql
devtools/test/data/schema/Version.sql
devtools/test/data/schema/custom.py
devtools/test/data/schema/relations.rel
devtools/test/data/views/__init__.py
devtools/test/data/views/bug.py
devtools/test/runtests.py
devtools/test/unittest_dbfill.py
devtools/test/unittest_fill.py
devtools/test/unittest_testlib.py
devtools/testlib.py
doc/.static/logilab.png
doc/.static/sphinx-default.css
doc/.templates/layout.html
doc/Makefile
doc/argouml.log
doc/conf.py
doc/cubicweb-uml.txt
doc/cubicweb.png
doc/cubicweb.zargo
doc/cubicweb.zargo~0.14.1
doc/devmanual_fr/advanced_notes.txt
doc/devmanual_fr/archi_globale.dia
doc/devmanual_fr/archi_globale.png
doc/devmanual_fr/chap_autres_composants_ui.txt
doc/devmanual_fr/chap_bases_framework_cubicweb.txt
doc/devmanual_fr/chap_configuration_instance.txt
doc/devmanual_fr/chap_creation_instance.txt
doc/devmanual_fr/chap_definition_schema.txt
doc/devmanual_fr/chap_definition_workflows.txt
doc/devmanual_fr/chap_fondements_cubicweb.txt
doc/devmanual_fr/chap_i18n.txt
doc/devmanual_fr/chap_manipulation_donnees.txt
doc/devmanual_fr/chap_migration.txt
doc/devmanual_fr/chap_mise_en_place_environnement.txt
doc/devmanual_fr/chap_rql.txt
doc/devmanual_fr/chap_serveur_crochets.txt
doc/devmanual_fr/chap_serveur_notification.txt
doc/devmanual_fr/chap_tests.txt
doc/devmanual_fr/chap_ui_gestion_formulaire.txt
doc/devmanual_fr/chap_ui_js_json.txt
doc/devmanual_fr/chap_visualisation_donnees.txt
doc/devmanual_fr/gae.txt
doc/devmanual_fr/index.txt
doc/devmanual_fr/main_template_layout.dia
doc/devmanual_fr/main_template_layout.png
doc/devmanual_fr/makefile
doc/devmanual_fr/sect_cubicweb-ctl.txt
doc/devmanual_fr/sect_definition_entites.txt
doc/devmanual_fr/sect_definition_schema.txt
doc/devmanual_fr/sect_installation.txt
doc/devmanual_fr/sect_mercurial.txt
doc/devmanual_fr/sect_stdlib_schemas.txt
doc/devmanual_fr/sect_stdlib_vues.txt
doc/faq.fr.txt
doc/howto.fr.txt
doc/html-build/.doctrees/devmanual_fr/advanced_notes.doctree
doc/html-build/.doctrees/devmanual_fr/chap_autres_composants_ui.doctree
doc/html-build/.doctrees/devmanual_fr/chap_bases_framework_cubicweb.doctree
doc/html-build/.doctrees/devmanual_fr/chap_configuration_instance.doctree
doc/html-build/.doctrees/devmanual_fr/chap_definition_schema.doctree
doc/html-build/.doctrees/devmanual_fr/chap_definition_workflows.doctree
doc/html-build/.doctrees/devmanual_fr/chap_fondements_cubicweb.doctree
doc/html-build/.doctrees/devmanual_fr/chap_i18n.doctree
doc/html-build/.doctrees/devmanual_fr/chap_manipulation_donnees.doctree
doc/html-build/.doctrees/devmanual_fr/chap_migration.doctree
doc/html-build/.doctrees/devmanual_fr/chap_mise_en_place_environnement.doctree
doc/html-build/.doctrees/devmanual_fr/chap_rql.doctree
doc/html-build/.doctrees/devmanual_fr/chap_serveur_crochets.doctree
doc/html-build/.doctrees/devmanual_fr/chap_serveur_notification.doctree
doc/html-build/.doctrees/devmanual_fr/chap_tests.doctree
doc/html-build/.doctrees/devmanual_fr/chap_ui_gestion_formulaire.doctree
doc/html-build/.doctrees/devmanual_fr/chap_ui_js_json.doctree
doc/html-build/.doctrees/devmanual_fr/chap_visualisation_donnees.doctree
doc/html-build/.doctrees/devmanual_fr/index.doctree
doc/html-build/.doctrees/devmanual_fr/sect_cubicweb-ctl.doctree
doc/html-build/.doctrees/devmanual_fr/sect_definition_entites.doctree
doc/html-build/.doctrees/devmanual_fr/sect_definition_schema.doctree
doc/html-build/.doctrees/devmanual_fr/sect_installation.doctree
doc/html-build/.doctrees/devmanual_fr/sect_mercurial.doctree
doc/html-build/.doctrees/devmanual_fr/sect_stdlib_schemas.doctree
doc/html-build/.doctrees/devmanual_fr/sect_stdlib_vues.doctree
doc/html-build/.doctrees/environment.pickle
doc/html-build/.doctrees/index.doctree
doc/html-build/.doctrees/plan_formation_python_cubicweb.doctree
doc/html-build/.doctrees/querier.doctree
doc/html-build/.doctrees/securite.doctree
doc/html-build/.doctrees/source/index.doctree
doc/html-build/source/index.html
doc/index-content.txt
doc/index.txt
doc/makefile
doc/plan_formation_python_cubicweb.txt
doc/querier.txt
doc/securite.txt
doc/tutmanual_fr/images/lax-book.00-login.en.png
doc/tutmanual_fr/images/lax-book.01-start.en.png
doc/tutmanual_fr/images/lax-book.02-cookie-values.en.png
doc/tutmanual_fr/images/lax-book.02-create-blog.en.png
doc/tutmanual_fr/images/lax-book.03-list-one-blog.en.png
doc/tutmanual_fr/images/lax-book.03-site-config-panel.en.png
doc/tutmanual_fr/images/lax-book.03-state-submitted.en.png
doc/tutmanual_fr/images/lax-book.03-transitions-view.en.png
doc/tutmanual_fr/images/lax-book.04-detail-one-blog.en.png
doc/tutmanual_fr/images/lax-book.05-list-two-blog.en.png
doc/tutmanual_fr/images/lax-book.06-add-relation-entryof.en.png
doc/tutmanual_fr/images/lax-book.06-header-no-login.en.png
doc/tutmanual_fr/images/lax-book.06-main-template-layout.en.png
doc/tutmanual_fr/images/lax-book.06-main-template-logo.en.png
doc/tutmanual_fr/images/lax-book.06-simple-main-template.en.png
doc/tutmanual_fr/images/lax-book.07-detail-one-blogentry.en.png
doc/tutmanual_fr/images/lax-book.08-schema.en.png
doc/tutmanual_fr/images/lax-book.09-new-view-blogentry.en.png
doc/tutmanual_fr/images/lax-book.10-blog-with-two-entries.en.png
doc/tutmanual_fr/tut-create-app.en.txt
doc/tutmanual_fr/tut-create-app.fr.txt
embedded/README
embedded/mx/DateTime/ARPA.py
embedded/mx/DateTime/DateTime.py
embedded/mx/DateTime/ISO.py
embedded/mx/DateTime/Parser.py
embedded/mx/DateTime/Timezone.py
embedded/mx/DateTime/__init__.py
embedded/mx/DateTime/mxDateTime_python.py
embedded/mx/__init__.py
entities/__init__.py
entities/authobjs.py
entities/lib.py
entities/schemaobjs.py
entities/test/data/bootstrap_packages
entities/test/data/schema.py
entities/test/unittest_base.py
entities/wfobjs.py
etwist/__init__.py
etwist/request.py
etwist/server.py
etwist/twconfig.py
etwist/twctl.py
gettext.py
goa/__init__.py
goa/appobjects/__init__.py
goa/appobjects/components.py
goa/appobjects/dbmgmt.py
goa/appobjects/gauthservice.py
goa/appobjects/sessions.py
goa/bin/laxctl
goa/db.py
goa/dbinit.py
goa/dbmyams.py
goa/doc/FAQ.en.txt
goa/doc/README_LAX.fr.txt
goa/doc/devmanual_fr/advanced_notes.txt
goa/doc/devmanual_fr/archi_globale.dia
goa/doc/devmanual_fr/archi_globale.png
goa/doc/devmanual_fr/chap_autres_composants_ui.txt
goa/doc/devmanual_fr/chap_bases_framework_erudi.txt
goa/doc/devmanual_fr/chap_configuration_instance.txt
goa/doc/devmanual_fr/chap_definition_schema.txt
goa/doc/devmanual_fr/chap_definition_workflows.txt
goa/doc/devmanual_fr/chap_fondements_erudi.txt
goa/doc/devmanual_fr/chap_i18n.txt
goa/doc/devmanual_fr/chap_manipulation_donnees.txt
goa/doc/devmanual_fr/chap_migration.txt
goa/doc/devmanual_fr/chap_mise_en_place_environnement.txt
goa/doc/devmanual_fr/chap_rql.txt
goa/doc/devmanual_fr/chap_serveur_crochets.txt
goa/doc/devmanual_fr/chap_serveur_notification.txt
goa/doc/devmanual_fr/chap_tests.txt
goa/doc/devmanual_fr/chap_ui_gestion_formulaire.txt
goa/doc/devmanual_fr/chap_ui_js_json.txt
goa/doc/devmanual_fr/chap_visualisation_donnees.txt
goa/doc/devmanual_fr/index.txt
goa/doc/devmanual_fr/main_template_layout.dia
goa/doc/devmanual_fr/main_template_layout.png
goa/doc/devmanual_fr/makefile
goa/doc/devmanual_fr/sect_definition_entites.txt
goa/doc/devmanual_fr/sect_definition_schema.txt
goa/doc/devmanual_fr/sect_erudi-ctl.txt
goa/doc/devmanual_fr/sect_installation.txt
goa/doc/devmanual_fr/sect_mercurial.txt
goa/doc/devmanual_fr/sect_stdlib_schemas.txt
goa/doc/devmanual_fr/sect_stdlib_vues.txt
goa/doc/quickstart.txt
goa/doc/tutorial-wine.txt
goa/doc/tutorial.en.txt
goa/gaesource.py
goa/goaconfig.py
goa/goactl.py
goa/goavreg.py
goa/overrides/__init__.py
goa/overrides/mttransforms.py
goa/overrides/rqlannotation.py
goa/overrides/server__init__.py
goa/overrides/server_utils.py
goa/overrides/toolsutils.py
goa/rqlinterpreter.py
goa/skel/app.yaml.tmpl
goa/skel/custom.py
goa/skel/cw-cubes/README.txt
goa/skel/i18n/en.po
goa/skel/i18n/fr.po
goa/skel/loader.py
goa/skel/main.py
goa/skel/schema.py
goa/skel/views.py
goa/test/data/__init__.py
goa/test/data/bootstrap_packages
goa/test/data/schema.py
goa/test/data/settings.py
goa/test/data/views.py
goa/test/pytestconf.py
goa/test/unittest_db.py
goa/test/unittest_editcontroller.py
goa/test/unittest_metadata.py
goa/test/unittest_rql.py
goa/test/unittest_schema.py
goa/test/unittest_views.py
goa/testlib.py
goa/tools/__init__.py
goa/tools/generate_schema_img.py
goa/tools/i18n.py
goa/tools/laxctl.py
hercule.py
i18n/en.po
i18n/entities.pot
i18n/fr.po
interfaces.py
man/cubicweb-ctl.1
md5crypt.py
misc/cwdesklets/gfx/bg.png
misc/cwdesklets/gfx/border-left.png
misc/cwdesklets/gfx/logo_cw.png
misc/cwdesklets/gfx/rss.png
misc/cwdesklets/rql_query.display
misc/cwdesklets/rqlsensor/__init__.py
misc/cwdesklets/web_query.display
misc/cwfs/A_FAIRE
misc/cwfs/cwfs-spec.txt
misc/cwfs/cwfs.py
misc/cwfs/cwfs_test.py
misc/cwzope/cwzope.py
misc/migration/2.37.1_Any.py
misc/migration/2.39.0_Any.py
misc/migration/2.42.0_Any.py
misc/migration/2.42.1_Any.py
misc/migration/2.43.0_Any.py
misc/migration/2.44.0_Any.py
misc/migration/2.45.0_Any.py
misc/migration/2.46.0_Any.py
misc/migration/2.47.0_Any.py
misc/migration/2.48.8_Any.py
misc/migration/2.49.3_Any.py
misc/migration/2.50.0_Any.py
misc/migration/3.0.0_Any.py
misc/migration/bootstrapmigration_repository.py
misc/migration/postcreate.py
pylintrc
rset.py
schema.py
schemas/Bookmark.py
schemas/Card.py
schemas/_regproc.sql.mysql
schemas/_regproc.sql.postgres
schemas/base.py
schemas/bootstrap.py
schemaviewer.py
server/__init__.py
server/checkintegrity.py
server/hookhelper.py
server/hooks.py
server/hooksmanager.py
server/migractions.py
server/pool.py
server/querier.py
server/repository.py
server/rqlannotation.py
server/rqlrewrite.py
server/schemahooks.py
server/schemaserial.py
server/securityhooks.py
server/server.py
server/serverconfig.py
server/serverctl.py
server/session.py
server/sources/__init__.py
server/sources/native.py
server/sources/rql2sql.py
server/sqlutils.py
server/ssplanner.py
server/test/data/bootstrap_packages
server/test/data/config1/application_hooks.py
server/test/data/config1/bootstrap_packages
server/test/data/config1/server-ctl.conf
server/test/data/config1/sources
server/test/data/config2/application_hooks.py
server/test/data/config2/bootstrap_packages
server/test/data/config2/server-ctl.conf
server/test/data/config2/sources
server/test/data/hooks.py
server/test/data/migration/postcreate.py
server/test/data/migrschema/Affaire.py
server/test/data/migrschema/Folder2.py
server/test/data/migrschema/Note.py
server/test/data/migrschema/Personne.sql
server/test/data/migrschema/Societe.perms
server/test/data/migrschema/Societe.sql
server/test/data/migrschema/relations.rel
server/test/data/schema/Affaire.py
server/test/data/schema/Note.sql
server/test/data/schema/Personne.sql
server/test/data/schema/Societe.py
server/test/data/schema/custom.py
server/test/data/schema/note.py
server/test/data/schema/relations.rel
server/test/runtests.py
server/test/unittest_checkintegrity.py
server/test/unittest_config.py
server/test/unittest_hookhelper.py
server/test/unittest_hooks.py
server/test/unittest_hooksmanager.py
server/test/unittest_migractions.py
server/test/unittest_querier.py
server/test/unittest_repository.py
server/test/unittest_rql2sql.py
server/test/unittest_rqlannotation.py
server/test/unittest_rqlrewrite.py
server/test/unittest_schemaserial.py
server/test/unittest_security.py
server/test/unittest_session.py
server/test/unittest_sqlutils.py
server/test/unittest_ssplanner.py
server/test/unittest_tools.py
server/utils.py
setup.py
skeleton/MANIFEST.in
skeleton/__init__.py.tmpl
skeleton/__pkginfo__.py.tmpl
skeleton/data/cubes.CUBENAME.css
skeleton/data/cubes.CUBENAME.js
skeleton/data/external_resources.tmpl
skeleton/debian/DISTNAME.prerm.tmpl
skeleton/debian/changelog.tmpl
skeleton/debian/compat
skeleton/debian/control.tmpl
skeleton/debian/copyright.tmpl
skeleton/debian/rules.tmpl
skeleton/entities.py
skeleton/i18n/en.po
skeleton/i18n/fr.po
skeleton/migration/postcreate.py
skeleton/migration/precreate.py
skeleton/schema.py
skeleton/setup.py
skeleton/site_cubicweb.py
skeleton/sobjects.py
skeleton/test/data/bootstrap_cubes.tmpl
skeleton/test/pytestconf.py
skeleton/test/realdb_test_CUBENAME.py
skeleton/test/test_CUBENAME.py
skeleton/views.py
sobjects/__init__.py
sobjects/email.py
sobjects/hooks.py
sobjects/notification.py
sobjects/supervising.py
sobjects/test/data/bootstrap_packages
sobjects/test/data/schema.py
sobjects/test/data/sobjects/__init__.py
sobjects/test/unittest_email.py
sobjects/test/unittest_hooks.py
sobjects/test/unittest_notification.py
sobjects/test/unittest_supervising.py
test/data/bootstrap_packages
test/data/erqlexpr_on_ertype.py
test/data/rqlexpr_on_ertype_read.py
test/data/rrqlexpr_on_attr.py
test/data/rrqlexpr_on_eetype.py
test/unittest_cwconfig.py
test/unittest_cwctl.py
test/unittest_dbapi.py
test/unittest_rset.py
test/unittest_schema.py
test/unittest_vregistry.py
toolsutils.py
vregistry.py
web/__init__.py
web/_exceptions.py
web/action.py
web/application.py
web/box.py
web/component.py
web/controller.py
web/data/asc.gif
web/data/banner.png
web/data/bg.gif
web/data/bg_trame_grise.png
web/data/black-check.png
web/data/bullet.png
web/data/bullet_orange.png
web/data/button.png
web/data/calendar.gif
web/data/critical.png
web/data/cubicweb.acl.css
web/data/cubicweb.ajax.js
web/data/cubicweb.bookmarks.js
web/data/cubicweb.calendar.css
web/data/cubicweb.calendar.js
web/data/cubicweb.calendar_popup.css
web/data/cubicweb.compat.js
web/data/cubicweb.css
web/data/cubicweb.edition.js
web/data/cubicweb.fckcwconfig.js
web/data/cubicweb.form.css
web/data/cubicweb.formfilter.js
web/data/cubicweb.gmap.js
web/data/cubicweb.goa.js
web/data/cubicweb.html_tree.css
web/data/cubicweb.htmlhelpers.js
web/data/cubicweb.ie.css
web/data/cubicweb.iprogress.css
web/data/cubicweb.login.css
web/data/cubicweb.mailform.css
web/data/cubicweb.preferences.css
web/data/cubicweb.print.css
web/data/cubicweb.python.js
web/data/cubicweb.schema.css
web/data/cubicweb.sortable.js
web/data/cubicweb.suggest.css
web/data/cubicweb.tablesorter.css
web/data/cubicweb.timeline-bundle.js
web/data/cubicweb.timeline-ext.js
web/data/cubicweb.timetable.css
web/data/cubicweb.widgets.js
web/data/desc.gif
web/data/download.gif
web/data/dublincore-button.png
web/data/dublincore-icon.png
web/data/error.png
web/data/external_resources
web/data/favicon.ico
web/data/feed-icon.png
web/data/feed-icon16x16.png
web/data/feed-icon32x32.png
web/data/file.gif
web/data/folder-closed.gif
web/data/folder.gif
web/data/gmap.utility.labeledmarker.js
web/data/gmap_blue_marker.png
web/data/go.png
web/data/gradient-grey-up.png
web/data/gradient-grey.gif
web/data/help.png
web/data/help_ie.png
web/data/icon_blank.png
web/data/icon_bookmark.gif
web/data/icon_emailaddress.gif
web/data/icon_euser.gif
web/data/icon_map.png
web/data/icon_state.gif
web/data/information.png
web/data/jquery.autocomplete.css
web/data/jquery.autocomplete.js
web/data/jquery.js
web/data/jquery.json.js
web/data/jquery.tablesorter.js
web/data/jquery.treeview.css
web/data/jquery.treeview.js
web/data/liveclipboard-icon.png
web/data/loading.gif
web/data/logo.png
web/data/logo.xcf
web/data/mail.gif
web/data/microformats-button.png
web/data/microformats-icon.png
web/data/minus.gif
web/data/no-check-no-border.png
web/data/nomail.gif
web/data/nomail.xcf
web/data/plus.gif
web/data/puce.png
web/data/puce_down.png
web/data/puce_down_black.png
web/data/pygments.css
web/data/required.png
web/data/rss-button.png
web/data/rss.png
web/data/search.png
web/data/sendcancel.png
web/data/sendok.png
web/data/shadow.gif
web/data/timeline-bundle.css
web/data/timeline/blue-circle.png
web/data/timeline/bubble-arrows.png
web/data/timeline/bubble-body-and-arrows.png
web/data/timeline/bubble-body.png
web/data/timeline/bubble-bottom-arrow.png
web/data/timeline/bubble-bottom-left.png
web/data/timeline/bubble-bottom-right.png
web/data/timeline/bubble-bottom.png
web/data/timeline/bubble-left-arrow.png
web/data/timeline/bubble-left.png
web/data/timeline/bubble-right-arrow.png
web/data/timeline/bubble-right.png
web/data/timeline/bubble-top-arrow.png
web/data/timeline/bubble-top-left.png
web/data/timeline/bubble-top-right.png
web/data/timeline/bubble-top.png
web/data/timeline/close-button.png
web/data/timeline/copyright-vertical.png
web/data/timeline/copyright.png
web/data/timeline/dark-blue-circle.png
web/data/timeline/dark-green-circle.png
web/data/timeline/dark-red-circle.png
web/data/timeline/dull-blue-circle.png
web/data/timeline/dull-green-circle.png
web/data/timeline/dull-red-circle.png
web/data/timeline/gray-circle.png
web/data/timeline/green-circle.png
web/data/timeline/message-bottom-left.png
web/data/timeline/message-bottom-right.png
web/data/timeline/message-left.png
web/data/timeline/message-right.png
web/data/timeline/message-top-left.png
web/data/timeline/message-top-right.png
web/data/timeline/message.png
web/data/timeline/progress-running.gif
web/data/timeline/red-circle.png
web/data/timeline/sundial.png
web/data/timeline/top-bubble.png
web/data/treeview-black-line.gif
web/data/treeview-black.gif
web/data/treeview-default-line.gif
web/data/treeview-default.gif
web/data/treeview-famfamfam-line.gif
web/data/treeview-famfamfam.gif
web/data/treeview-gray-line.gif
web/data/treeview-gray.gif
web/data/treeview-red-line.gif
web/data/treeview-red.gif
web/facet.py
web/form.py
web/htmlwidgets.py
web/httpcache.py
web/request.py
web/test/data/bootstrap_packages
web/test/data/schema/Personne.sql
web/test/data/schema/Societe.sql
web/test/data/schema/relations.rel
web/test/data/schema/testschema.py
web/test/data/views.py
web/test/jstest_python.jst
web/test/runtests.py
web/test/test_views.py
web/test/testutils.js
web/test/unittest_application.py
web/test/unittest_controller.py
web/test/unittest_magicsearch.py
web/test/unittest_urlpublisher.py
web/test/unittest_urlrewrite.py
web/test/unittest_views_actions.py
web/test/unittest_views_apacherewrite.py
web/test/unittest_views_basecontrollers.py
web/test/unittest_views_baseforms.py
web/test/unittest_views_baseviews.py
web/test/unittest_views_embeding.py
web/test/unittest_views_navigation.py
web/test/unittest_views_searchrestriction.py
web/test/unittest_viewselector.py
web/test/unittest_webconfig.py
web/test/unittest_widgets.py
web/views/__init__.py
web/views/actions.py
web/views/ajaxedit.py
web/views/apacherewrite.py
web/views/authentication.py
web/views/basecomponents.py
web/views/basecontrollers.py
web/views/baseforms.py
web/views/basetemplates.py
web/views/baseviews.py
web/views/bookmark.py
web/views/boxes.py
web/views/calendar.py
web/views/card.py
web/views/debug.py
web/views/dynimages.py
web/views/edit_attributes.pt
web/views/edit_multiple.pt
web/views/edit_relations.pt
web/views/editcontroller.py
web/views/emailaddress.py
web/views/embedding.py
web/views/eproperties.py
web/views/error.py
web/views/euser.py
web/views/facets.py
web/views/ibreadcrumbs.py
web/views/idownloadable.py
web/views/igeocodable.py
web/views/iprogress.py
web/views/magicsearch.py
web/views/management.py
web/views/massmailing.py
web/views/navigation.py
web/views/old_calendar.py
web/views/plots.py
web/views/schemaentities.py
web/views/searchrestriction.py
web/views/sessions.py
web/views/startup.py
web/views/tableview.py
web/views/timeline.py
web/views/timetable.py
web/views/treeview.py
web/views/urlpublishing.py
web/views/urlrewrite.py
web/views/vcard.py
web/views/wdoc.py
web/views/wfentities.py
web/views/xbel.py
web/wdoc/ChangeLog_en
web/wdoc/ChangeLog_fr
web/wdoc/about_en.rst
web/wdoc/about_fr.rst
web/wdoc/add_content_en.rst
web/wdoc/add_content_fr.rst
web/wdoc/advanced_usage_en.rst
web/wdoc/advanced_usage_schema_en.rst
web/wdoc/advanced_usage_schema_fr.rst
web/wdoc/bookmarks_en.rst
web/wdoc/bookmarks_fr.rst
web/wdoc/custom_view_en.rst
web/wdoc/custom_view_fr.rst
web/wdoc/custom_view_last_update_en.rst
web/wdoc/custom_view_last_update_fr.rst
web/wdoc/custom_view_rss_en.rst
web/wdoc/custom_view_rss_fr.rst
web/wdoc/glossary_en.rst
web/wdoc/glossary_fr.rst
web/wdoc/images/userprefs_en.png
web/wdoc/images/userprefs_fr.png
web/wdoc/main_en.rst
web/wdoc/search_en.rst
web/wdoc/search_fr.rst
web/wdoc/search_sample_queries_en.rst
web/wdoc/search_sample_queries_fr.rst
web/wdoc/standard_usage_en.rst
web/wdoc/standard_usage_fr.rst
web/wdoc/toc.xml
web/wdoc/tut_rql_en.rst
web/wdoc/tut_rql_fr.rst
web/wdoc/userprefs_en.rst
web/wdoc/userprefs_fr.rst
web/webconfig.py
web/webctl.py
web/widgets.py
wsgi/__init__.py
wsgi/handler.py
wsgi/request.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,165 @@
+		   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  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. 
+
+  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.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  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.
+
+  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".
+
+  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.
+
+  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.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  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) 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
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  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:
+
+   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.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  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:
+
+   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.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   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.
+
+   d) Do one of the following:
+
+       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.
+
+       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. 
+
+   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.)
+
+  5. Combined Libraries.
+
+  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:
+
+   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.
+
+   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.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  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.
+
+  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.
+
+  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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,645 @@
+README
+pylintrc
+setup.py
+./__init__.py
+./__pkginfo__.py
+./_exceptions.py
+./cwconfig.py
+./cwctl.py
+./cwvreg.py
+./dbapi.py
+./gettext.py
+./hercule.py
+./interfaces.py
+./md5crypt.py
+./rset.py
+./schema.py
+./schemaviewer.py
+./toolsutils.py
+./vregistry.py
+./common/__init__.py
+./common/appobject.py
+./common/entity.py
+./common/html4zope.py
+./common/i18n.py
+./common/mail.py
+./common/migration.py
+./common/mixins.py
+./common/mttransforms.py
+./common/registerers.py
+./common/rest.py
+./common/schema.py
+./common/selectors.py
+./common/tal.py
+./common/uilib.py
+./common/utils.py
+./common/view.py
+./common/test/unittest_entity.py
+./common/test/unittest_mail.py
+./common/test/unittest_migration.py
+./common/test/unittest_rest.py
+./common/test/unittest_uilib.py
+./common/test/unittest_utils.py
+./devtools/__init__.py
+./devtools/_apptest.py
+./devtools/apptest.py
+./devtools/cwtwill.py
+./devtools/devctl.py
+./devtools/fake.py
+./devtools/fill.py
+./devtools/htmlparser.py
+./devtools/livetest.py
+./devtools/migrtest.py
+./devtools/pkginfo.py
+./devtools/repotest.py
+./devtools/stresstester.py
+./devtools/testlib.py
+./devtools/test/runtests.py
+./devtools/test/unittest_dbfill.py
+./devtools/test/unittest_fill.py
+./devtools/test/unittest_testlib.py
+./entities/__init__.py
+./entities/authobjs.py
+./entities/lib.py
+./entities/schemaobjs.py
+./entities/wfobjs.py
+./entities/test/unittest_base.py
+./etwist/__init__.py
+./etwist/request.py
+./etwist/server.py
+./etwist/twconfig.py
+./etwist/twctl.py
+./goa/__init__.py
+./goa/db.py
+./goa/dbinit.py
+./goa/dbmyams.py
+./goa/gaesource.py
+./goa/goaconfig.py
+./goa/goactl.py
+./goa/goavreg.py
+./goa/rqlinterpreter.py
+./goa/testlib.py
+./goa/appobjects/__init__.py
+./goa/appobjects/components.py
+./goa/appobjects/dbmgmt.py
+./goa/appobjects/gauthservice.py
+./goa/appobjects/sessions.py
+./goa/overrides/__init__.py
+./goa/overrides/mttransforms.py
+./goa/overrides/rqlannotation.py
+./goa/overrides/server__init__.py
+./goa/overrides/server_utils.py
+./goa/overrides/toolsutils.py
+./goa/test/pytestconf.py
+./goa/test/unittest_db.py
+./goa/test/unittest_editcontroller.py
+./goa/test/unittest_metadata.py
+./goa/test/unittest_rql.py
+./goa/test/unittest_schema.py
+./goa/test/unittest_views.py
+./goa/test/data/__init__.py
+./goa/test/data/schema.py
+./goa/test/data/settings.py
+./goa/test/data/views.py
+./goa/tools/__init__.py
+./goa/tools/generate_schema_img.py
+./goa/tools/i18n.py
+./goa/tools/laxctl.py
+./server/__init__.py
+./server/checkintegrity.py
+./server/hookhelper.py
+./server/hooks.py
+./server/hooksmanager.py
+./server/migractions.py
+./server/msplanner.py
+./server/mssteps.py
+./server/pool.py
+./server/querier.py
+./server/repository.py
+./server/rqlannotation.py
+./server/rqlrewrite.py
+./server/schemahooks.py
+./server/schemaserial.py
+./server/securityhooks.py
+./server/server.py
+./server/serverconfig.py
+./server/serverctl.py
+./server/session.py
+./server/sqlutils.py
+./server/ssplanner.py
+./server/utils.py
+./server/sources/__init__.py
+./server/sources/extlite.py
+./server/sources/ldapuser.py
+./server/sources/native.py
+./server/sources/pyrorql.py
+./server/sources/rql2sql.py
+./server/test/runtests.py
+./server/test/unittest_checkintegrity.py
+./server/test/unittest_config.py
+./server/test/unittest_hookhelper.py
+./server/test/unittest_hooks.py
+./server/test/unittest_hooksmanager.py
+./server/test/unittest_migractions.py
+./server/test/unittest_querier.py
+./server/test/unittest_repository.py
+./server/test/unittest_rql2sql.py
+./server/test/unittest_rqlannotation.py
+./server/test/unittest_rqlrewrite.py
+./server/test/unittest_schemaserial.py
+./server/test/unittest_security.py
+./server/test/unittest_session.py
+./server/test/unittest_sqlutils.py
+./server/test/unittest_ssplanner.py
+./server/test/unittest_tools.py
+./sobjects/__init__.py
+./sobjects/email.py
+./sobjects/hooks.py
+./sobjects/notification.py
+./sobjects/supervising.py
+./sobjects/test/unittest_email.py
+./sobjects/test/unittest_hooks.py
+./sobjects/test/unittest_notification.py
+./sobjects/test/unittest_supervising.py
+./test/unittest_cwconfig.py
+./test/unittest_cwctl.py
+./test/unittest_dbapi.py
+./test/unittest_rset.py
+./test/unittest_schema.py
+./test/unittest_vregistry.py
+./web/__init__.py
+./web/_exceptions.py
+./web/action.py
+./web/application.py
+./web/box.py
+./web/component.py
+./web/controller.py
+./web/facet.py
+./web/form.py
+./web/htmlwidgets.py
+./web/httpcache.py
+./web/request.py
+./web/webconfig.py
+./web/webctl.py
+./web/widgets.py
+./web/test/runtests.py
+./web/test/test_views.py
+./web/test/unittest_application.py
+./web/test/unittest_controller.py
+./web/test/unittest_magicsearch.py
+./web/test/unittest_urlpublisher.py
+./web/test/unittest_urlrewrite.py
+./web/test/unittest_views_actions.py
+./web/test/unittest_views_apacherewrite.py
+./web/test/unittest_views_basecontrollers.py
+./web/test/unittest_views_baseforms.py
+./web/test/unittest_views_baseviews.py
+./web/test/unittest_views_embeding.py
+./web/test/unittest_views_navigation.py
+./web/test/unittest_views_searchrestriction.py
+./web/test/unittest_viewselector.py
+./web/test/unittest_webconfig.py
+./web/test/unittest_widgets.py
+./web/views/__init__.py
+./web/views/actions.py
+./web/views/ajaxedit.py
+./web/views/apacherewrite.py
+./web/views/authentication.py
+./web/views/basecomponents.py
+./web/views/basecontrollers.py
+./web/views/baseforms.py
+./web/views/basetemplates.py
+./web/views/baseviews.py
+./web/views/bookmark.py
+./web/views/boxes.py
+./web/views/calendar.py
+./web/views/card.py
+./web/views/debug.py
+./web/views/dynimages.py
+./web/views/editcontroller.py
+./web/views/emailaddress.py
+./web/views/embedding.py
+./web/views/eproperties.py
+./web/views/error.py
+./web/views/euser.py
+./web/views/facets.py
+./web/views/ibreadcrumbs.py
+./web/views/idownloadable.py
+./web/views/igeocodable.py
+./web/views/iprogress.py
+./web/views/magicsearch.py
+./web/views/management.py
+./web/views/massmailing.py
+./web/views/navigation.py
+./web/views/old_calendar.py
+./web/views/plots.py
+./web/views/schemaentities.py
+./web/views/searchrestriction.py
+./web/views/sessions.py
+./web/views/startup.py
+./web/views/tableview.py
+./web/views/timeline.py
+./web/views/timetable.py
+./web/views/treeview.py
+./web/views/urlpublishing.py
+./web/views/urlrewrite.py
+./web/views/vcard.py
+./web/views/wdoc.py
+./web/views/wfentities.py
+./web/views/xbel.py
+./wsgi/__init__.py
+./wsgi/handler.py
+./wsgi/request.py
+bin/cubicweb-ctl
+common/test/data/bootstrap_packages
+common/test/data/entities.py
+common/test/data/migration/0.0.3_Any.py
+common/test/data/migration/0.0.4_Any.py
+common/test/data/migration/0.1.0_Any.py
+common/test/data/migration/0.1.0_common.py
+common/test/data/migration/0.1.0_repository.py
+common/test/data/migration/0.1.0_web.py
+common/test/data/migration/0.1.2_Any.py
+common/test/data/migration/depends.map
+common/test/data/schema/Affaire.sql
+common/test/data/schema/Note.py
+common/test/data/schema/Note.sql
+common/test/data/schema/Personne.sql
+common/test/data/schema/Societe.sql
+common/test/data/schema/relations.rel
+common/test/data/server_migration/2.10.2_Any.sql
+common/test/data/server_migration/2.5.0_Any.sql
+common/test/data/server_migration/2.6.0_Any.sql
+common/test/data/server_migration/bootstrapmigration_repository.py
+devtools/test/data/bootstrap_packages
+devtools/test/data/dbfill.conf
+devtools/test/data/firstnames.txt
+devtools/test/data/schema/Bug.sql
+devtools/test/data/schema/Project.sql
+devtools/test/data/schema/Story.sql
+devtools/test/data/schema/Version.sql
+devtools/test/data/schema/custom.py
+devtools/test/data/schema/relations.rel
+devtools/test/data/views/__init__.py
+devtools/test/data/views/bug.py
+doc/cubicweb.zargo
+doc/index.txt
+doc/makefile
+doc/plan_formation_python_cubicweb.txt
+doc/querier.txt
+doc/securite.txt
+doc/.static/logilab.png
+doc/.templates/layout.html
+doc/devmanual_fr/advanced_notes.txt
+doc/devmanual_fr/archi_globale.png
+doc/devmanual_fr/chap_autres_composants_ui.txt
+doc/devmanual_fr/chap_bases_framework_cubicweb.txt
+doc/devmanual_fr/chap_configuration_instance.txt
+doc/devmanual_fr/chap_definition_schema.txt
+doc/devmanual_fr/chap_definition_workflows.txt
+doc/devmanual_fr/chap_fondements_cubicweb.txt
+doc/devmanual_fr/chap_i18n.txt
+doc/devmanual_fr/chap_manipulation_donnees.txt
+doc/devmanual_fr/chap_migration.txt
+doc/devmanual_fr/chap_mise_en_place_environnement.txt
+doc/devmanual_fr/chap_rql.txt
+doc/devmanual_fr/chap_serveur_crochets.txt
+doc/devmanual_fr/chap_serveur_notification.txt
+doc/devmanual_fr/chap_tests.txt
+doc/devmanual_fr/chap_ui_gestion_formulaire.txt
+doc/devmanual_fr/chap_ui_js_json.txt
+doc/devmanual_fr/chap_visualisation_donnees.txt
+doc/devmanual_fr/index.txt
+doc/devmanual_fr/main_template_layout.png
+doc/devmanual_fr/makefile
+doc/devmanual_fr/sect_cubicweb-ctl.txt
+doc/devmanual_fr/sect_definition_entites.txt
+doc/devmanual_fr/sect_definition_schema.txt
+doc/devmanual_fr/sect_installation.txt
+doc/devmanual_fr/sect_mercurial.txt
+doc/devmanual_fr/sect_stdlib_schemas.txt
+doc/devmanual_fr/sect_stdlib_vues.txt
+doc/html-build/genindex.html
+doc/html-build/index.html
+doc/html-build/modindex.html
+doc/html-build/plan_formation_python_cubicweb.html
+doc/html-build/querier.html
+doc/html-build/search.html
+doc/html-build/securite.html
+doc/html-build/_images/archi_globale.png
+doc/html-build/_images/main_template_layout.png
+doc/html-build/_sources/index.txt
+doc/html-build/_sources/plan_formation_python_cubicweb.txt
+doc/html-build/_sources/querier.txt
+doc/html-build/_sources/securite.txt
+doc/html-build/_sources/devmanual_fr/advanced_notes.txt
+doc/html-build/_sources/devmanual_fr/chap_autres_composants_ui.txt
+doc/html-build/_sources/devmanual_fr/chap_bases_framework_cubicweb.txt
+doc/html-build/_sources/devmanual_fr/chap_configuration_instance.txt
+doc/html-build/_sources/devmanual_fr/chap_definition_schema.txt
+doc/html-build/_sources/devmanual_fr/chap_definition_workflows.txt
+doc/html-build/_sources/devmanual_fr/chap_fondements_cubicweb.txt
+doc/html-build/_sources/devmanual_fr/chap_i18n.txt
+doc/html-build/_sources/devmanual_fr/chap_manipulation_donnees.txt
+doc/html-build/_sources/devmanual_fr/chap_migration.txt
+doc/html-build/_sources/devmanual_fr/chap_mise_en_place_environnement.txt
+doc/html-build/_sources/devmanual_fr/chap_rql.txt
+doc/html-build/_sources/devmanual_fr/chap_serveur_crochets.txt
+doc/html-build/_sources/devmanual_fr/chap_serveur_notification.txt
+doc/html-build/_sources/devmanual_fr/chap_tests.txt
+doc/html-build/_sources/devmanual_fr/chap_ui_gestion_formulaire.txt
+doc/html-build/_sources/devmanual_fr/chap_ui_js_json.txt
+doc/html-build/_sources/devmanual_fr/chap_visualisation_donnees.txt
+doc/html-build/_sources/devmanual_fr/index.txt
+doc/html-build/_sources/devmanual_fr/sect_cubicweb-ctl.txt
+doc/html-build/_sources/devmanual_fr/sect_definition_entites.txt
+doc/html-build/_sources/devmanual_fr/sect_definition_schema.txt
+doc/html-build/_sources/devmanual_fr/sect_installation.txt
+doc/html-build/_sources/devmanual_fr/sect_mercurial.txt
+doc/html-build/_sources/devmanual_fr/sect_stdlib_schemas.txt
+doc/html-build/_sources/devmanual_fr/sect_stdlib_vues.txt
+doc/html-build/_sources/source/index.txt
+doc/html-build/_static/contents.png
+doc/html-build/_static/file.png
+doc/html-build/_static/logilab.png
+doc/html-build/_static/minus.png
+doc/html-build/_static/navigation.png
+doc/html-build/_static/plus.png
+doc/html-build/devmanual_fr/advanced_notes.html
+doc/html-build/devmanual_fr/chap_autres_composants_ui.html
+doc/html-build/devmanual_fr/chap_bases_framework_cubicweb.html
+doc/html-build/devmanual_fr/chap_configuration_instance.html
+doc/html-build/devmanual_fr/chap_definition_schema.html
+doc/html-build/devmanual_fr/chap_definition_workflows.html
+doc/html-build/devmanual_fr/chap_fondements_cubicweb.html
+doc/html-build/devmanual_fr/chap_i18n.html
+doc/html-build/devmanual_fr/chap_manipulation_donnees.html
+doc/html-build/devmanual_fr/chap_migration.html
+doc/html-build/devmanual_fr/chap_mise_en_place_environnement.html
+doc/html-build/devmanual_fr/chap_rql.html
+doc/html-build/devmanual_fr/chap_serveur_crochets.html
+doc/html-build/devmanual_fr/chap_serveur_notification.html
+doc/html-build/devmanual_fr/chap_tests.html
+doc/html-build/devmanual_fr/chap_ui_gestion_formulaire.html
+doc/html-build/devmanual_fr/chap_ui_js_json.html
+doc/html-build/devmanual_fr/chap_visualisation_donnees.html
+doc/html-build/devmanual_fr/index.html
+doc/html-build/devmanual_fr/sect_cubicweb-ctl.html
+doc/html-build/devmanual_fr/sect_definition_entites.html
+doc/html-build/devmanual_fr/sect_definition_schema.html
+doc/html-build/devmanual_fr/sect_installation.html
+doc/html-build/devmanual_fr/sect_mercurial.html
+doc/html-build/devmanual_fr/sect_stdlib_schemas.html
+doc/html-build/devmanual_fr/sect_stdlib_vues.html
+doc/html-build/source/index.html
+entities/test/data/bootstrap_packages
+entities/test/data/schema.py
+i18n/en.po
+i18n/entities.pot
+i18n/fr.po
+man/cubicweb-ctl.1
+misc/cwdesklets/rql_query.display
+misc/cwdesklets/web_query.display
+misc/cwdesklets/gfx/bg.png
+misc/cwdesklets/gfx/border-left.png
+misc/cwdesklets/gfx/logo_cw.png
+misc/cwdesklets/gfx/rss.png
+misc/cwdesklets/rqlsensor/__init__.py
+misc/cwzope/cwzope.py
+misc/migration/2.37.1_Any.py
+misc/migration/2.39.0_Any.py
+misc/migration/2.42.0_Any.py
+misc/migration/2.42.1_Any.py
+misc/migration/2.43.0_Any.py
+misc/migration/2.44.0_Any.py
+misc/migration/2.45.0_Any.py
+misc/migration/2.46.0_Any.py
+misc/migration/2.47.0_Any.py
+misc/migration/2.48.8_Any.py
+misc/migration/2.49.3_Any.py
+misc/migration/2.50.0_Any.py
+misc/migration/3.0.0_Any.py
+misc/migration/bootstrapmigration_repository.py
+misc/migration/postcreate.py
+schemas/Bookmark.py
+schemas/Card.py
+schemas/_regproc.sql.mysql
+schemas/_regproc.sql.postgres
+schemas/base.py
+schemas/bootstrap.py
+server/test/data/bootstrap_packages
+server/test/data/hooks.py
+server/test/data/config1/application_hooks.py
+server/test/data/config1/bootstrap_packages
+server/test/data/config1/server-ctl.conf
+server/test/data/config1/sources
+server/test/data/config2/application_hooks.py
+server/test/data/config2/bootstrap_packages
+server/test/data/config2/server-ctl.conf
+server/test/data/config2/sources
+server/test/data/migration/postcreate.py
+server/test/data/migrschema/Affaire.py
+server/test/data/migrschema/Folder2.py
+server/test/data/migrschema/Note.py
+server/test/data/migrschema/Personne.sql
+server/test/data/migrschema/Societe.perms
+server/test/data/migrschema/Societe.sql
+server/test/data/migrschema/relations.rel
+server/test/data/schema/Affaire.py
+server/test/data/schema/Note.sql
+server/test/data/schema/Personne.sql
+server/test/data/schema/Societe.py
+server/test/data/schema/custom.py
+server/test/data/schema/note.py
+server/test/data/schema/relations.rel
+sobjects/test/data/bootstrap_packages
+sobjects/test/data/schema.py
+sobjects/test/data/sobjects/__init__.py
+web/data/IE_styles.css
+web/data/MochiKit.js
+web/data/acl.css
+web/data/ajax.js
+web/data/asc.gif
+web/data/banner.png
+web/data/bg.gif
+web/data/bg_trame_grise.png
+web/data/black-check.png
+web/data/bookmarks.js
+web/data/bullet.png
+web/data/bullet_orange.png
+web/data/button.png
+web/data/calendar.css
+web/data/calendar.gif
+web/data/calendar.js
+web/data/calendar_popup.css
+web/data/compat.js
+web/data/critical.png
+web/data/cubicweb.css
+web/data/desc.gif
+web/data/download.gif
+web/data/dublincore-button.png
+web/data/dublincore-icon.png
+web/data/edition.js
+web/data/error.png
+web/data/external_resources
+web/data/favicon.ico
+web/data/fckcwconfig.js
+web/data/feed-icon.png
+web/data/feed-icon16x16.png
+web/data/feed-icon32x32.png
+web/data/file.gif
+web/data/folder-closed.gif
+web/data/folder.gif
+web/data/form.css
+web/data/formfilter.js
+web/data/gmap.js
+web/data/gmap.utility.labeledmarker.js
+web/data/gmap_blue_marker.png
+web/data/go.png
+web/data/goa.js
+web/data/gradient-grey-up.png
+web/data/gradient-grey.gif
+web/data/help.png
+web/data/help_ie.png
+web/data/html_tree.css
+web/data/htmlhelpers.js
+web/data/icon_blank.png
+web/data/icon_bookmark.gif
+web/data/icon_emailaddress.gif
+web/data/icon_euser.gif
+web/data/icon_map.png
+web/data/icon_state.gif
+web/data/information.png
+web/data/iprogress.css
+web/data/jquery.autocomplete.css
+web/data/jquery.autocomplete.js
+web/data/jquery.js
+web/data/jquery.json.js
+web/data/jquery.tablesorter.js
+web/data/jquery.treeview.css
+web/data/jquery.treeview.js
+web/data/liveclipboard-icon.png
+web/data/loading.gif
+web/data/login.css
+web/data/logo.png
+web/data/logo.xcf
+web/data/mail.gif
+web/data/mailform.css
+web/data/microformats-button.png
+web/data/microformats-icon.png
+web/data/minus.gif
+web/data/no-check-no-border.png
+web/data/nomail.gif
+web/data/nomail.xcf
+web/data/plus.gif
+web/data/preferences.css
+web/data/print.css
+web/data/puce.png
+web/data/puce_down.png
+web/data/puce_down_black.png
+web/data/pygments.css
+web/data/python.js
+web/data/required.png
+web/data/rss-button.png
+web/data/rss.png
+web/data/schema.css
+web/data/search.png
+web/data/sendcancel.png
+web/data/sendok.png
+web/data/shadow.gif
+web/data/simile-ajax-api.js
+web/data/simile-ajax-bundle.js
+web/data/sortable.js
+web/data/suggest.css
+web/data/tablesorter.css
+web/data/timeline-big-bundle.js
+web/data/timeline-bundle.css
+web/data/timeline-stubs.js
+web/data/timeline.ext.js
+web/data/timeline.js
+web/data/timetable.css
+web/data/treeview-black-line.gif
+web/data/treeview-black.gif
+web/data/treeview-default-line.gif
+web/data/treeview-default.gif
+web/data/treeview-famfamfam-line.gif
+web/data/treeview-famfamfam.gif
+web/data/treeview-gray-line.gif
+web/data/treeview-gray.gif
+web/data/treeview-red-line.gif
+web/data/treeview-red.gif
+web/data/widgets.js
+web/data/timeline/blue-circle.png
+web/data/timeline/bubble-arrows.png
+web/data/timeline/bubble-body-and-arrows.png
+web/data/timeline/bubble-body.png
+web/data/timeline/bubble-bottom-arrow.png
+web/data/timeline/bubble-bottom-left.png
+web/data/timeline/bubble-bottom-right.png
+web/data/timeline/bubble-bottom.png
+web/data/timeline/bubble-left-arrow.png
+web/data/timeline/bubble-left.png
+web/data/timeline/bubble-right-arrow.png
+web/data/timeline/bubble-right.png
+web/data/timeline/bubble-top-arrow.png
+web/data/timeline/bubble-top-left.png
+web/data/timeline/bubble-top-right.png
+web/data/timeline/bubble-top.png
+web/data/timeline/close-button.png
+web/data/timeline/copyright-vertical.png
+web/data/timeline/copyright.png
+web/data/timeline/dark-blue-circle.png
+web/data/timeline/dark-green-circle.png
+web/data/timeline/dark-red-circle.png
+web/data/timeline/dull-blue-circle.png
+web/data/timeline/dull-green-circle.png
+web/data/timeline/dull-red-circle.png
+web/data/timeline/gray-circle.png
+web/data/timeline/green-circle.png
+web/data/timeline/message-bottom-left.png
+web/data/timeline/message-bottom-right.png
+web/data/timeline/message-left.png
+web/data/timeline/message-right.png
+web/data/timeline/message-top-left.png
+web/data/timeline/message-top-right.png
+web/data/timeline/message.png
+web/data/timeline/progress-running.gif
+web/data/timeline/red-circle.png
+web/data/timeline/sundial.png
+web/data/timeline/top-bubble.png
+web/views/edit_attributes.pt
+web/views/edit_multiple.pt
+web/views/edit_relations.pt
+web/wdoc/ChangeLog_en
+web/wdoc/ChangeLog_fr
+web/wdoc/about_en.rst
+web/wdoc/about_fr.rst
+web/wdoc/add_content_en.rst
+web/wdoc/add_content_fr.rst
+web/wdoc/advanced_usage_en.rst
+web/wdoc/advanced_usage_schema_en.rst
+web/wdoc/advanced_usage_schema_fr.rst
+web/wdoc/bookmarks_en.rst
+web/wdoc/bookmarks_fr.rst
+web/wdoc/custom_view_en.rst
+web/wdoc/custom_view_fr.rst
+web/wdoc/custom_view_last_update_en.rst
+web/wdoc/custom_view_last_update_fr.rst
+web/wdoc/custom_view_rss_en.rst
+web/wdoc/custom_view_rss_fr.rst
+web/wdoc/glossary_en.rst
+web/wdoc/glossary_fr.rst
+web/wdoc/main_en.rst
+web/wdoc/search_en.rst
+web/wdoc/search_fr.rst
+web/wdoc/search_sample_queries_en.rst
+web/wdoc/search_sample_queries_fr.rst
+web/wdoc/standard_usage_en.rst
+web/wdoc/standard_usage_fr.rst
+web/wdoc/toc.xml
+web/wdoc/tut_rql_en.rst
+web/wdoc/tut_rql_fr.rst
+web/wdoc/userprefs_en.rst
+web/wdoc/userprefs_fr.rst
+web/wdoc/images/userprefs_en.png
+web/wdoc/images/userprefs_fr.png
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.in	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,28 @@
+include README
+include pylintrc
+include bin/cubicweb-*
+include man/cubicweb-ctl.1
+
+recursive-include doc *.txt *.zargo *.png *.html makefile
+
+recursive-include misc *
+
+recursive-include web/data *
+recursive-include web/wdoc *.rst *.png *.xml ChangeLog*
+
+include web/views/*.pt
+
+recursive-include etwist *.xml *.html
+
+recursive-include i18n *.pot *.po
+recursive-include schemas *.py *.rel *.sql.*
+
+recursive-include common/test/data *
+recursive-include entities/test/data *
+recursive-include sobjects/test/data *
+recursive-include server/test/data *
+recursive-include server/test sources*
+recursive-include web/test/data *.js *.css *.png *.gif *.jpg *.ico external_resources
+recursive-include devtools/test/data *
+
+prune misc/cwfs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,18 @@
+CubicWeb semantic web framework 
+===============================
+ 
+Install
+-------
+From the source distribution, extract the tarball and run ::
+  
+    python setup.py install
+  
+For deb and rpm packages, use the tools recommended by your distribution.
+
+  
+Documentation
+-------------
+Look in the doc/ subdirectory.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/__init__.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,299 @@
+"""CubicWeb is a generic framework to quickly build applications which describes
+relations between entitites.
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+from cubicweb.__pkginfo__ import version as __version__
+
+import __builtin__
+# '_' is available in builtins to mark internationalized string but should
+# not be used to do the actual translation
+if not hasattr(__builtin__, '_'):
+    __builtin__._ = unicode
+
+CW_SOFTWARE_ROOT = __path__[0]
+
+import sys, os, logging
+from StringIO import StringIO
+from urllib import quote as urlquote, unquote as urlunquote
+
+from logilab.common.decorators import cached
+
+
+LLDEBUG = 5
+logging.addLevelName(LLDEBUG, 'LLDEBUG')
+
+class CubicWebLogger(logging.Logger):
+
+    def lldebug(self, msg, *args, **kwargs):
+        """
+        Log 'msg % args' with severity 'DEBUG'.
+
+        To pass exception information, use the keyword argument exc_info with
+        a true value, e.g.
+
+        logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
+        """
+        if self.manager.disable >= LLDEBUG:
+            return
+        if LLDEBUG >= self.getEffectiveLevel():
+            self._log(LLDEBUG, msg, args, **kwargs)
+
+logging.setLoggerClass(CubicWebLogger)
+
+def set_log_methods(cls, logger):
+    """bind standart logger's methods as static methods on the class
+    """
+    cls._logger = logger
+    for attr in ('lldebug', 'debug', 'info', 'warning', 'error', 'critical', 'exception'):
+        setattr(cls, attr, getattr(logger, attr))
+
+if os.environ.get('APYCOT_ROOT'):
+    logging.basicConfig(level=logging.CRITICAL)
+else:
+    logging.basicConfig()
+
+
+set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
+
+# make all exceptions accessible from the package
+from cubicweb._exceptions import *
+
+# convert eid to the right type, raise ValueError if it's not a valid eid
+typed_eid = int
+
+
+#def log_thread(f, w, a):
+#    print f.f_code.co_filename, f.f_code.co_name
+#import threading
+#threading.settrace(log_thread)
+
+class Binary(StringIO):
+    """customize StringIO to make sure we don't use unicode"""
+    def __init__(self, buf= ''):
+        assert isinstance(buf, (str, buffer)), \
+               "Binary objects must use raw strings, not %s" % buf.__class__
+        StringIO.__init__(self, buf)
+
+    def write(self, data):
+        assert isinstance(data, (str, buffer)), \
+               "Binary objects must use raw strings, not %s" % data.__class__
+        StringIO.write(self, data)
+
+
+class RequestSessionMixIn(object):
+    """mixin class containing stuff shared by server session and web request
+    """
+    def __init__(self, vreg):
+        self.vreg = vreg
+        try:
+            encoding = vreg.property_value('ui.encoding')
+        except: # no vreg or property not registered
+            encoding = 'utf-8'
+        self.encoding = encoding
+        # cache result of execution for (rql expr / eids),
+        # should be emptied on commit/rollback of the server session / web 
+        # connection
+        self.local_perm_cache = {}
+
+    def property_value(self, key):
+        if self.user:
+            return self.user.property_value(key)
+        return self.vreg.property_value(key)
+    
+    def etype_rset(self, etype, size=1):
+        """return a fake result set for a particular entity type"""
+        from cubicweb.rset import ResultSet
+        rset = ResultSet([('A',)]*size, '%s X' % etype,
+                         description=[(etype,)]*size)
+        def get_entity(row, col=0, etype=etype, vreg=self.vreg, rset=rset):
+            return self.vreg.etype_class(etype)(self, rset, row, col)
+        rset.get_entity = get_entity
+        return self.decorate_rset(rset)
+
+    def eid_rset(self, eid, etype=None):
+        """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)
+        """
+        from cubicweb.rset import ResultSet
+        eid = typed_eid(eid)
+        if etype is None:
+            etype = self.describe(eid)[0]
+        rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
+                         [(etype,)])
+        return self.decorate_rset(rset)
+
+    def entity_from_eid(self, eid, etype=None):
+        rset = self.eid_rset(eid, etype)
+        if rset:
+            return rset.get_entity(0, 0)
+        else:
+            return None
+
+    # url generation methods ##################################################
+    
+    def build_url(self, method, base_url=None, **kwargs):
+        """return an absolute URL using params dictionary key/values as URL
+        parameters. Values are automatically URL quoted, and the
+        publishing method to use may be specified or will be guessed.
+        """
+        if base_url is None:
+            base_url = self.base_url()
+        if '_restpath' in kwargs:
+            assert method == 'view', method
+            path = kwargs.pop('_restpath')
+        else:
+            path = method
+        if not kwargs:
+            return u'%s%s' % (base_url, path)
+        return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
+        
+
+    def build_url_params(self, **kwargs):
+        """return encoded params to incorporate them in an URL"""
+        args = []
+        for param, values in kwargs.items():
+            if not isinstance(values, (list, tuple)):
+                values = (values,)
+            for value in values:
+                args.append(u'%s=%s' % (param, self.url_quote(value)))
+        return '&'.join(args)
+
+    def url_quote(self, value, safe=''):
+        """urllib.quote is not unicode safe, use this method to do the
+        necessary encoding / decoding. Also it's designed to quote each
+        part of a url path and so the '/' character will be encoded as well.
+        """
+        if isinstance(value, unicode):
+            quoted = urlquote(value.encode(self.encoding), safe=safe)
+            return unicode(quoted, self.encoding)
+        return urlquote(str(value), safe=safe)
+
+    def url_unquote(self, quoted):
+        """returns a unicode unquoted string
+        
+        decoding is based on `self.encoding` which is the encoding
+        used in `url_quote`
+        """
+        if isinstance(quoted, unicode):
+            quoted = quoted.encode(self.encoding)
+        try:
+            return unicode(urlunquote(quoted), self.encoding)
+        except UnicodeDecodeError: # might occurs on manually typed URLs
+            return unicode(urlunquote(quoted), 'iso-8859-1')
+    
+
+    # session's user related methods #####################################
+    
+    @cached
+    def user_data(self):
+        """returns a dictionnary with this user's information"""
+        userinfo = {}
+        if self.is_internal_session:
+            userinfo['login'] = "cubicweb"
+            userinfo['name'] = "cubicweb"
+            userinfo['email'] = ""
+            return userinfo
+        user = self.actual_session().user
+        rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A"
+        try:
+            firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0]
+            if firstname is None and lastname is None:
+                userinfo['name'] = ''
+            else:
+                userinfo['name'] = ("%s %s" % (firstname, lastname))
+            userinfo['email'] = email
+        except IndexError:
+            userinfo['name'] = None
+            userinfo['email'] = None
+        userinfo['login'] = user.login
+        return userinfo
+
+    def is_internal_session(self):
+        """overrided on the server-side"""
+        return False
+
+    # abstract methods to override according to the web front-end #############
+    
+    def base_url(self):
+        """return the root url of the application"""
+        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
+        
+
+# XXX 2.45 is allowing nicer entity type names, use this map for bw compat    
+ETYPE_NAME_MAP = {'Eetype': 'EEType',
+                  'Ertype': 'ERType',
+                  'Efrdef': 'EFRDef',
+                  'Enfrdef': 'ENFRDef',
+                  'Econstraint': 'EConstraint',
+                  'Econstrainttype': 'EConstraintType',
+                  'Epermission': 'EPermission',
+                  'Egroup': 'EGroup',
+                  'Euser': 'EUser',
+                  'Eproperty': 'EProperty',
+                  'Emailaddress': 'EmailAddress',
+                  'Rqlexpression': 'RQLExpression',
+                  'Trinfo': 'TrInfo',
+                  }
+
+
+
+# XXX cubic web cube migration map
+CW_MIGRATION_MAP = {'erudi': 'cubicweb',
+
+                    'eaddressbook': 'addressbook',
+                    'ebasket': 'basket',
+                    'eblog': 'blog',
+                    'ebook': 'book',
+                    'ecomment': 'comment',
+                    'ecompany': 'company',
+                    'econference':  'conference',
+                    'eemail': 'email',
+                    'eevent': 'event',
+                    'eexpense': 'expense',
+                    'efile': 'file',
+                    'einvoice': 'invoice',
+                    'elink': 'link',
+                    'emailinglist': 'mailinglist',
+                    'eperson': 'person',
+                    'eshopcart': 'shopcart',
+                    'eskillmat': 'skillmat',
+                    'etask': 'task',
+                    'eworkcase': 'workcase',
+                    'eworkorder': 'workorder',
+                    'ezone': 'zone',
+                    'i18ncontent': 'i18ncontent',
+                    'svnfile': 'vcsfile',
+                    
+                    'eclassschemes': 'keyword',
+                    'eclassfolders': 'folder',
+                    'eclasstags': 'tag',
+
+                    'jpl': 'jpl',
+                    'jplintra': 'jplintra',
+                    'jplextra': 'jplextra',
+                    'jplorg': 'jplorg',
+                    'jplrecia': 'jplrecia',
+                    'crm': 'crm',
+                    'agueol': 'agueol',
+                    'docaster': 'docaster',
+                    'asteretud': 'asteretud',
+                    
+                    # XXX temp
+                    'keywords': 'keyword',
+                    'folders': 'folder',
+                    'tags': 'tag',
+                    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/__pkginfo__.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,92 @@
+# pylint: disable-msg=W0622,C0103
+"""cubicweb global packaging information for the cubicweb knowledge management
+software
+"""
+
+distname = "cubicweb"
+modname = "cubicweb"
+
+numversion = (3, 0, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LCL'
+copyright = '''Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
+
+author = "Logilab"
+author_email = "contact@logilab.fr"
+
+short_desc = "a repository of entities / relations for knowledge management"
+long_desc = """CubicWeb is a entities / relations based knowledge management system
+developped at Logilab.
+
+This package contains:
+* a repository server
+* a RQL command line client to the repository
+* an adaptative modpython interface to the server
+* a bunch of other management tools
+"""
+
+web = ''
+ftp = ''
+pyversions = ['2.4']
+
+
+from os import listdir, environ
+from os.path import join, isdir
+import glob
+
+scripts = [s for s in glob.glob(join('bin', 'cubicweb-*'))
+           if not s.endswith('.bat')]
+include_dirs = [join('common', 'test', 'data'),
+                join('server', 'test', 'data'),
+                join('web', 'test', 'data'),
+                join('devtools', 'test', 'data'),]
+
+
+entities_dir = 'entities'
+schema_dir = 'schemas'
+sobjects_dir = 'sobjects'
+server_migration_dir = join('misc', 'migration')
+data_dir = join('web', 'data')
+wdoc_dir = join('web', 'wdoc')
+wdocimages_dir = join(wdoc_dir, 'images')
+views_dir = join('web', 'views')
+i18n_dir = 'i18n'
+
+if environ.get('APYCOT_ROOT'):
+    # --home install
+    pydir = 'python'
+else:
+    pydir = join('python2.4', 'site-packages')
+try:
+    data_files = [
+        # common data
+        #[join('share', 'cubicweb', 'entities'),
+        # [join(entities_dir, filename) for filename in listdir(entities_dir)]],
+        # server data
+        [join('share', 'cubicweb', 'schemas'),
+         [join(schema_dir, filename) for filename in listdir(schema_dir)]],
+        #[join('share', 'cubicweb', 'sobjects'),
+        # [join(sobjects_dir, filename) for filename in listdir(sobjects_dir)]],
+        [join('share', 'cubicweb', 'migration'),
+         [join(server_migration_dir, filename)
+          for filename in listdir(server_migration_dir)]],
+        # web data
+        [join('share', 'cubicweb', 'cubes', 'shared', 'data'),
+         [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', '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'),
+         [join(wdocimages_dir, fname) for fname in listdir(wdocimages_dir)]],
+        # XXX: .pt install should be handled properly in a near future version
+        [join('lib', pydir, 'cubicweb', 'web', 'views'),
+         [join(views_dir, fname) for fname in listdir(views_dir) if fname.endswith('.pt')]],
+        [join('share', 'cubicweb', 'cubes', 'shared', 'i18n'),
+         [join(i18n_dir, fname) for fname in listdir(i18n_dir)]],
+        ]
+except OSError:
+    # we are in an installed directory, don't care about this
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/_exceptions.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,148 @@
+"""Exceptions shared by different cubicweb packages.
+
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from yams import ValidationError
+
+# abstract exceptions #########################################################
+
+class CubicWebException(Exception):
+    """base class for cubicweb server exception"""
+    msg = ""
+    def __str__(self):
+        if self.msg:
+            if self.args:
+                return self.msg % tuple(self.args)
+            return self.msg
+        return ' '.join(str(arg) for arg in self.args)
+
+
+class ConfigurationError(CubicWebException):
+    """a misconfiguration error"""
+
+class InternalError(CubicWebException):
+    """base class for exceptions which should not occurs"""    
+
+class SecurityError(CubicWebException): 
+    """base class for cubicweb server security exception"""
+
+class RepositoryError(CubicWebException):
+    """base class for repository exceptions"""
+
+class SourceException(CubicWebException):
+    """base class for source exceptions"""
+
+class CubicWebRuntimeError(CubicWebException):
+    """base class for runtime exceptions"""
+    
+# repository exceptions #######################################################
+
+class ConnectionError(RepositoryError):
+    """raised when a bad connection id is given or when an attempt to establish
+    a connection failed"""
+
+class AuthenticationError(ConnectionError):
+    """raised when a bad connection id is given or when an attempt to establish
+    a connection failed"""
+
+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
+
+class UnknownEid(RepositoryError):
+    """the eid is not defined in the system tables"""
+    msg = 'No entity with eid %s in the repository'
+
+class ETypeNotSupportedBySources(RepositoryError, InternalError):
+    """no source support an entity type"""
+    msg = 'No source supports %r entity\'s type'
+
+class RTypeNotSupportedBySources(RepositoryError, InternalError):
+    """no source support a relation type"""
+    msg = 'No source supports %r relation\'s type'
+
+    
+# security exceptions #########################################################
+
+class Unauthorized(SecurityError):
+    """raised when a user tries to perform an action without sufficient
+    credentials
+    """
+    msg = 'You are not allowed to perform this operation'
+    msg1 = 'You are not allowed to perform %s operation on %s'
+    var = None
+    #def __init__(self, *args):
+    #    self.args = args
+        
+    def __str__(self):
+        try:
+            if self.args and len(self.args) == 2:
+                return self.msg1 % self.args
+            if self.args:
+                return ' '.join(self.args)
+            return self.msg
+        except Exception, ex:
+            return str(ex)
+    
+# source exceptions ###########################################################
+
+class EidNotInSource(SourceException):
+    """trying to access an object with a particular eid from a particular
+    source has failed
+    """
+    msg = 'No entity with eid %s in %s'
+    
+    
+# registry exceptions #########################################################
+
+class RegistryException(CubicWebException):
+    """raised when an unregistered view is called"""
+
+class RegistryNotFound(RegistryException):
+    """raised when an unknown registry is requested
+
+    this is usually a programming/typo error...
+    """
+    
+class ObjectNotFound(RegistryException):
+    """raised when an unregistered object is requested
+
+    this may be a programming/typo or a misconfiguration error
+    """
+    
+# class ViewNotFound(ObjectNotFound):
+#     """raised when an unregistered view is called"""
+    
+class NoSelectableObject(RegistryException):
+    """some views with the given vid have been found but no
+    one is applyable to the result set
+    """
+
+class UnknownProperty(RegistryException):
+    """property found in database but unknown in registry"""
+
+# query exception #############################################################
+
+class QueryError(CubicWebRuntimeError):
+    """a query try to do something it shouldn't"""
+
+class NotAnEntity(CubicWebRuntimeError):
+    """raised when get_entity is called for a column which doesn't contain
+    a non final entity
+    """
+
+# tools exceptions ############################################################
+
+class ExecutionError(Exception):
+    """server execution control error (already started, not running...)"""
+
+# pylint: disable-msg=W0611
+from logilab.common.clcommands import BadCommandUsage 
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cubicweb-ctl	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from cubicweb.cwctl import run
+import sys
+run(sys.argv[1:])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cleanappl.sh	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+rm -f web/test/tmpdb*
+rm -f web/tali18n.py
+
+rm -f applications/*/test/tmpdb*
+rm -f applications/*/tali18n.py
+rm -f applications/*/i18n/*_full.po
+rm -f applications/*/data/Schema.dot
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/__init__.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,52 @@
+"""Common subpackage of cubicweb : defines library functions used both on the
+hg stserver side and on the client side
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from logilab.common.adbh import FunctionDescr
+
+from cubicweb._exceptions import * # bw compat
+
+from rql.utils import register_function, iter_funcnode_variables
+
+class COMMA_JOIN(FunctionDescr):
+    supported_backends = ('postgres', 'sqlite',)
+    rtype = 'String'
+    
+    @classmethod
+    def st_description(cls, funcnode):
+        return ', '.join(term.get_description()
+                         for term in iter_funcnode_variables(funcnode))
+    
+register_function(COMMA_JOIN)  # XXX do not expose?
+
+
+class CONCAT_STRINGS(COMMA_JOIN):
+    aggregat = True
+    
+register_function(CONCAT_STRINGS) # XXX bw compat
+
+class GROUP_CONCAT(CONCAT_STRINGS):
+    supported_backends = ('mysql', 'postgres', 'sqlite',)
+    
+register_function(GROUP_CONCAT)
+
+
+class LIMIT_SIZE(FunctionDescr):
+    supported_backends = ('postgres', 'sqlite',)
+    rtype = 'String'
+    
+    @classmethod
+    def st_description(cls, funcnode):
+        return funcnode.children[0].get_description()
+    
+register_function(LIMIT_SIZE)
+
+
+class TEXT_LIMIT_SIZE(LIMIT_SIZE):
+    supported_backends = ('mysql', 'postgres', 'sqlite',)
+    
+register_function(TEXT_LIMIT_SIZE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/appobject.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,461 @@
+"""Base class for dynamically loaded objects manipulated in the web interface
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from mx.DateTime import now, oneSecond
+from simplejson import dumps
+
+from logilab.common.deprecation import obsolete
+from rql.stmts import Union, Select
+
+from cubicweb import Unauthorized
+from cubicweb.vregistry import VObject
+from cubicweb.common.utils import UStringIO
+from cubicweb.common.uilib import html_escape, ustrftime
+from cubicweb.common.registerers import yes_registerer, priority_registerer
+from cubicweb.common.selectors import yes_selector
+
+_MARKER = object()
+
+
+class Cache(dict):    
+    def __init__(self):
+        super(Cache, self).__init__()
+        self.cache_creation_date = None
+        self.latest_cache_lookup = now()
+    
+CACHE_REGISTRY = {}
+
+class AppRsetObject(VObject):
+    """This is the base class for CubicWeb application objects
+    which are selected according to a request and result set.
+    
+    Classes are kept in the vregistry and instantiation is done at selection
+    time.
+    
+    At registration time, the following attributes are set on the class:
+    :vreg:
+      the application's registry
+    :schema:
+      the application's schema
+    :config:
+      the application's configuration
+
+    At instantiation time, the following attributes are set on the instance:
+    :req:
+      current request
+    :rset:
+      result set on which the object is applied
+    """
+
+    @classmethod
+    def registered(cls, vreg):
+        cls.vreg = vreg
+        cls.schema = vreg.schema
+        cls.config = vreg.config
+        cls.register_properties()
+        return cls
+
+    @classmethod
+    def selected(cls, req, rset, row=None, col=None, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection according to a request, a result set, and optional
+        row and col
+        """
+        instance = cls(req, rset)
+        instance.row = row
+        instance.col = col
+        return instance
+
+    # Eproperties definition:
+    # key: id of the property (the actual EProperty key is build using
+    #      <registry name>.<obj id>.<property id>
+    # value: tuple (property type, vocabfunc, default value, property description)
+    #        possible types are those used by `logilab.common.configuration`
+    #
+    # notice that when it exists multiple objects with the same id (adaptation,
+    # overriding) only the first encountered definition is considered, so those
+    # objects can't try to have different default values for instance.
+    
+    property_defs = {}
+    
+    @classmethod
+    def register_properties(cls):
+        for propid, pdef in cls.property_defs.items():
+            pdef = pdef.copy() # may be shared
+            pdef['default'] = getattr(cls, propid, pdef['default'])
+            pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
+            cls.vreg.register_property(cls.propkey(propid), **pdef)
+        
+    @classmethod
+    def propkey(cls, propid):
+        return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
+            
+        
+    def __init__(self, req, rset):
+        super(AppRsetObject, self).__init__()
+        self.req = req
+        self.rset = rset
+
+    @property
+    def cursor(self): # XXX deprecate in favor of req.cursor?
+        msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.cursor
+        
+    def get_cache(self, cachename):
+        """
+        NOTE: cachename should be dotted names as in :
+        - cubicweb.mycache
+        - cubes.blog.mycache 
+        - etc.
+        """
+        if cachename in CACHE_REGISTRY:
+            cache = CACHE_REGISTRY[cachename]
+        else:
+            cache = Cache()
+            CACHE_REGISTRY[cachename] = cache
+        _now = now()
+        if _now > cache.latest_cache_lookup + oneSecond:
+            ecache = self.req.execute('Any C,T WHERE C is ECache, C name %(name)s, C timestamp T', 
+                                      {'name':cachename}).get_entity(0,0)
+            cache.latest_cache_lookup = _now
+            if not ecache.valid(cache.cache_creation_date):
+                cache.empty()
+                cache.cache_creation_date = _now
+        return cache
+
+    def propval(self, propid):
+        assert self.req
+        return self.req.property_value(self.propkey(propid))
+
+    
+    def limited_rql(self):
+        """return 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.common)
+        nav = self.vreg.select_component('navigation', self.req, self.rset)
+        if nav:
+            start, stop = nav.page_boundaries()
+            rql = self._limit_offset_rql(stop - start, start)
+        # result set may have be limited manually in which case navigation won't
+        # apply
+        elif self.rset.limited:
+            rql = self._limit_offset_rql(*self.rset.limited)
+        # navigation component doesn't apply and rset has not been limited, no
+        # need to limit query
+        else:
+            rql = self.rset.printable_rql()
+        return rql
+    
+    def _limit_offset_rql(self, limit, offset):
+        rqlst = self.rset.syntax_tree()
+        if len(rqlst.children) == 1:
+            select = rqlst.children[0]
+            olimit, ooffset = select.limit, select.offset
+            select.limit, select.offset = limit, offset
+            rql = rqlst.as_string(kwargs=self.rset.args)
+            # restore original limit/offset
+            select.limit, select.offset = olimit, ooffset
+        else:
+            newselect = Select()
+            newselect.limit = limit
+            newselect.offset = offset
+            aliases = [VariableRef(newselect.get_variable(vref.name, i))
+                       for i, vref in enumerate(rqlst.selection)]
+            newselect.set_with([SubQuery(aliases, rqlst)], check=False)
+            newunion = Union()
+            newunion.append(newselect)
+            rql = rqlst.as_string(kwargs=self.rset.args)
+            rqlst.parent = None
+        return rql
+    
+    # url generation methods ##################################################
+    
+    controller = 'view'
+    
+    def build_url(self, method=None, **kwargs):
+        """return an absolute URL using params dictionary key/values as URL
+        parameters. Values are automatically URL quoted, and the
+        publishing method to use may be specified or will be guessed.
+        """
+        # XXX I (adim) think that if method is passed explicitly, we should
+        #     not try to process it and directly call req.build_url()
+        if method is None:
+            method = self.controller
+            if method == 'view' and self.req.from_controller() == 'view' and \
+                   not '_restpath' in kwargs:
+                method = self.req.relative_path(includeparams=False) or 'view'
+        return self.req.build_url(method, **kwargs)
+
+    # various resources accessors #############################################
+
+    def etype_rset(self, etype, size=1):
+        """return a fake result set for a particular entity type"""
+        msg = '.etype_rset is deprecated, use req.etype_rset'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.etype_rset(etype, size=1)
+
+    def eid_rset(self, eid, etype=None):
+        """return a result set for the given eid"""
+        msg = '.eid_rset is deprecated, use req.eid_rset'
+        warn(msg, DeprecationWarning, stacklevel=2)
+        return self.req.eid_rset(eid, etype)
+    
+    def entity(self, row, col=0):
+        """short cut to get an entity instance for a particular row/column
+        (col default to 0)
+        """
+        return self.rset.get_entity(row, col)
+    
+    def complete_entity(self, row, col=0, skip_bytes=True):
+        """short cut to get an completed entity instance for a particular
+        row (all instance's attributes have been fetched)
+        """
+        entity = self.entity(row, col)
+        entity.complete(skip_bytes=skip_bytes)
+        return entity
+
+    def user_rql_callback(self, args, msg=None):
+        """register a user callback to execute some rql query and return an url
+        to call it ready to be inserted in html
+        """
+        def rqlexec(req, rql, args=None, key=None):
+            req.execute(rql, args, key)
+        return self.user_callback(rqlexec, args, msg)
+        
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        """register the given user callback and return an url to call it ready to be
+        inserted in html
+        """
+        self.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            # XXX < 2.48.3 bw compat
+            warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        cbname = self.req.register_onetime_callback(cb, *args)
+        msg = dumps(msg or '') 
+        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+            cbname, msg)
+
+    # formating methods #######################################################
+
+    def tal_render(self, template, variables):
+        """render a precompiled page template with variables in the given
+        dictionary as context
+        """
+        from cubicweb.common.tal import CubicWebContext
+        context = CubicWebContext()
+        context.update({'self': self, 'rset': self.rset, '_' : self.req._,
+                        'req': self.req, 'user': self.req.user})
+        context.update(variables)
+        output = UStringIO()
+        template.expand(context, output)
+        return output.getvalue()
+
+    def format_date(self, date, date_format=None, time=False):
+        """return a string for a mx date time according to application's
+        configuration
+        """
+        if date:
+            if date_format is None:
+                if time:
+                    date_format = self.req.property_value('ui.datetime-format')
+                else:
+                    date_format = self.req.property_value('ui.date-format')
+            return ustrftime(date, date_format)
+        return u''
+
+    def format_time(self, time):
+        """return a string for a mx date time according to application's
+        configuration
+        """
+        if time:
+            return ustrftime(time, self.req.property_value('ui.time-format'))
+        return u''
+
+    def format_float(self, num):
+        """return a string for floating point number according to application's
+        configuration
+        """
+        if num:
+            return self.req.property_value('ui.float-format') % num
+        return u''
+    
+    # security related methods ################################################
+    
+    def ensure_ro_rql(self, rql):
+        """raise an exception if the given rql is not a select query"""
+        first = rql.split(' ', 1)[0].lower()
+        if first in ('insert', 'set', 'delete'):
+            raise Unauthorized(self.req._('only select queries are authorized'))
+
+    # .accepts handling utilities #############################################
+    
+    accepts = ('Any',)
+
+    @classmethod
+    def accept_rset(cls, req, rset, row, col):
+        """apply the following rules:
+        * if row is None, return the sum of values returned by the method
+          for each entity's type in the result set. If any score is 0,
+          return 0.
+        * if row is specified, return the value returned by the method with
+          the entity's type of this row
+        """
+        if row is None:
+            score = 0
+            for etype in rset.column_types(0):
+                accepted = cls.accept(req.user, etype)
+                if not accepted:
+                    return 0
+                score += accepted
+            return score
+        return cls.accept(req.user, rset.description[row][col or 0])
+        
+    @classmethod
+    def accept(cls, user, etype):
+        """score etype, returning better score on exact match"""
+        if 'Any' in cls.accepts:
+            return 1
+        eschema = cls.schema.eschema(etype)
+        matching_types = [e.type for e in eschema.ancestors()]
+        matching_types.append(etype)
+        for index, basetype in enumerate(matching_types):
+            if basetype in cls.accepts:
+                return 2 + index
+        return 0
+    
+    # .rtype  handling utilities ##############################################
+    
+    @classmethod
+    def relation_possible(cls, etype):
+        """tell if a relation with etype entity is possible according to 
+        mixed class'.etype, .rtype and .target attributes
+
+        XXX should probably be moved out to a function
+        """
+        schema = cls.schema
+        rtype = cls.rtype
+        eschema = schema.eschema(etype)
+        if hasattr(cls, 'role'):
+            role = cls.role
+        elif cls.target == 'subject':
+            role = 'object'
+        else:
+            role = 'subject'
+        # check if this relation is possible according to the schema
+        try:
+            if role == 'object':
+                rschema = eschema.object_relation(rtype)
+            else:
+                rschema = eschema.subject_relation(rtype)
+        except KeyError:
+            return False            
+        if hasattr(cls, 'etype'):
+            letype = cls.etype
+            try:
+                if role == 'object':
+                    return etype in rschema.objects(letype)
+                else:
+                    return etype in rschema.subjects(letype)
+            except KeyError, ex:
+                return False
+        return True
+
+    
+    # XXX deprecated (since 2.43) ##########################
+    
+    @obsolete('use req.datadir_url')
+    def datadir_url(self):
+        """return url of the application's data directory"""
+        return self.req.datadir_url
+
+    @obsolete('use req.external_resource()')
+    def external_resource(self, rid, default=_MARKER):
+        return self.req.external_resource(rid, default)
+
+        
+class AppObject(AppRsetObject):
+    """base class for application objects which are not selected
+    according to a result set, only by their identifier.
+    
+    Those objects may not have req, rset and cursor set.
+    """
+    
+    @classmethod
+    def selected(cls, *args, **kwargs):
+        """by default web app objects are usually instantiated on
+        selection
+        """
+        return cls(*args, **kwargs)
+
+    def __init__(self, req=None, rset=None, **kwargs):
+        self.req = req
+        self.rset = rset
+        self.__dict__.update(kwargs)
+
+
+class ReloadableMixIn(object):
+    """simple mixin for reloadable parts of UI"""
+    
+    def user_callback(self, cb, args, msg=None, nonify=False):
+        """register the given user callback and return an url to call it ready to be
+        inserted in html
+        """
+        self.req.add_js('cubicweb.ajax.js')
+        if nonify:
+            _cb = cb
+            def cb(*args):
+                _cb(*args)
+        cbname = self.req.register_onetime_callback(cb, *args)
+        return self.build_js(cbname, html_escape(msg or ''))
+        
+    def build_update_js_call(self, cbname, msg):
+        rql = html_escape(self.rset.printable_rql())
+        return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
+            cbname, self.id, rql, msg, self.__registry__, self.div_id())
+    
+    def build_reload_js_call(self, cbname, msg):
+        return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
+
+    build_js = build_update_js_call # expect updatable component by default
+    
+    def div_id(self):
+        return ''
+
+
+class ComponentMixIn(ReloadableMixIn):
+    """simple mixin for component object"""
+    __registry__ = 'components'
+    __registerer__ = yes_registerer
+    __selectors__ = (yes_selector,)
+    __select__ = classmethod(*__selectors__)
+
+    def div_class(self):
+        return '%s %s' % (self.propval('htmlclass'), self.id)
+
+    def div_id(self):
+        return '%sComponent' % self.id
+
+
+class Component(ComponentMixIn, AppObject):
+    """base class for non displayable components
+    """
+
+class SingletonComponent(Component):
+    """base class for non displayable unique components
+    """
+    __registerer__ = priority_registerer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/entity.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,1094 @@
+"""Base class for entity objects manipulated in clients
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common import interface
+from logilab.common.compat import all
+from logilab.common.decorators import cached
+from logilab.mtconverter import TransformData, TransformError
+from rql.utils import rqlvar_maker
+
+from cubicweb import Unauthorized
+from cubicweb.vregistry import autoselectors
+from cubicweb.rset import ResultSet
+from cubicweb.common.appobject import AppRsetObject
+from cubicweb.common.registerers import id_registerer
+from cubicweb.common.selectors import yes_selector
+from cubicweb.common.uilib import printable_value, html_escape, soup2xhtml
+from cubicweb.common.mixins import MI_REL_TRIGGERS
+from cubicweb.common.mttransforms import ENGINE
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
+
+_marker = object()
+
+def greater_card(rschema, subjtypes, objtypes, index):
+    for subjtype in subjtypes:
+        for objtype in objtypes:
+            card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+            if card in '+*':
+                return card
+    return '1'
+
+
+class RelationTags(object):
+    
+    MODE_TAGS = frozenset(('link', 'create'))
+    CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
+                               'inlineview'))
+
+    def __init__(self, eclass, tagdefs):
+        self.eclass = eclass
+        self._tagdefs = {}
+        for relation, tags in tagdefs.iteritems():
+            # tags must become a set
+            if isinstance(tags, basestring):
+                tags = set((tags,))
+            elif not isinstance(tags, set):
+                tags = set(tags)
+            # relation must become a 3-uple (rtype, targettype, role)
+            if isinstance(relation, basestring):
+                self._tagdefs[(relation, '*', 'subject')] = tags
+                self._tagdefs[(relation, '*', 'object')] = tags
+            elif len(relation) == 1: # useful ?
+                self._tagdefs[(relation[0], '*', 'subject')] = tags
+                self._tagdefs[(relation[0], '*', 'object')] = tags
+            elif len(relation) == 2:
+                rtype, ttype = relation
+                ttype = bw_normalize_etype(ttype) # XXX bw compat
+                self._tagdefs[rtype, ttype, 'subject'] = tags
+                self._tagdefs[rtype, ttype, 'object'] = tags
+            elif len(relation) == 3:
+                relation = list(relation)  # XXX bw compat
+                relation[1] = bw_normalize_etype(relation[1])
+                self._tagdefs[tuple(relation)] = tags
+            else:
+                raise ValueError('bad rtag definition (%r)' % (relation,))
+        
+
+    def __initialize__(self):
+        # eclass.[*]schema are only set when registering
+        self.schema = self.eclass.schema
+        eschema = self.eschema = self.eclass.e_schema
+        rtags = self._tagdefs
+        # expand wildcards in rtags and add automatic tags
+        for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
+            rtype = rschema.type
+            star_tags = rtags.pop((rtype, '*', role), set())
+            for tschema in tschemas:
+                tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
+                if role == 'subject':
+                    X, Y = eschema, tschema
+                    card = rschema.rproperty(X, Y, 'cardinality')[0]
+                    composed = rschema.rproperty(X, Y, 'composite') == 'object'
+                else:
+                    X, Y = tschema, eschema
+                    card = rschema.rproperty(X, Y, 'cardinality')[1]
+                    composed = rschema.rproperty(X, Y, 'composite') == 'subject'
+                # set default category tags if needed
+                if not tags & self.CATEGORY_TAGS:
+                    if card in '1+':
+                        if not rschema.is_final() and composed:
+                            category = 'generated'
+                        elif rschema.is_final() and (
+                            rschema.type.endswith('_format')
+                            or rschema.type.endswith('_encoding')):
+                            category = 'generated'
+                        else:
+                            category = 'primary'
+                    elif rschema.is_final():
+                        if (rschema.type.endswith('_format')
+                            or rschema.type.endswith('_encoding')):
+                            category = 'generated'
+                        else:
+                            category = 'secondary'
+                    else: 
+                        category = 'generic'
+                    tags.add(category)
+                if not tags & self.MODE_TAGS:
+                    if card in '?1':
+                        # by default, suppose link mode if cardinality doesn't allow
+                        # more than one relation
+                        mode = 'link'
+                    elif rschema.rproperty(X, Y, 'composite') == role:
+                        # if self is composed of the target type, create mode
+                        mode = 'create'
+                    else:
+                        # link mode by default
+                        mode = 'link'
+                    tags.add(mode)
+
+    def _default_target(self, rschema, role='subject'):
+        eschema = self.eschema
+        if role == 'subject':
+            return eschema.subject_relation(rschema).objects(eschema)[0]
+        else:
+            return eschema.object_relation(rschema).subjects(eschema)[0]
+
+    # dict compat
+    def __getitem__(self, key):
+        if isinstance(key, basestring):
+            key = (key,)
+        return self.get_tags(*key)
+
+    __contains__ = __getitem__
+    
+    def get_tags(self, rtype, targettype=None, role='subject'):
+        rschema = self.schema.rschema(rtype)
+        if targettype is None:
+            tschema = self._default_target(rschema, role)
+        else:
+            tschema = self.schema.eschema(targettype)
+        return self._tagdefs[(rtype, tschema.type, role)]
+
+    __call__ = get_tags
+    
+    def get_mode(self, rtype, targettype=None, role='subject'):
+        # XXX: should we make an assertion on rtype not being final ?
+        # assert not rschema.is_final()
+        tags = self.get_tags(rtype, targettype, role)
+        # do not change the intersection order !
+        modes = tags & self.MODE_TAGS
+        assert len(modes) == 1
+        return modes.pop()
+
+    def get_category(self, rtype, targettype=None, role='subject'):
+        tags = self.get_tags(rtype, targettype, role)
+        categories = tags & self.CATEGORY_TAGS
+        assert len(categories) == 1
+        return categories.pop()
+
+    def is_inlined(self, rtype, targettype=None, role='subject'):
+        # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
+        return 'inlineview' in self.get_tags(rtype, targettype, role)
+
+
+class metaentity(autoselectors):
+    """this metaclass sets the relation tags on the entity class
+    and deals with the `widgets` attribute
+    """
+    def __new__(mcs, name, bases, classdict):
+        # collect baseclass' rtags
+        tagdefs = {}
+        widgets = {}
+        for base in bases:
+            tagdefs.update(getattr(base, '__rtags__', {}))
+            widgets.update(getattr(base, 'widgets', {}))
+        # update with the class' own rtgas
+        tagdefs.update(classdict.get('__rtags__', {}))
+        widgets.update(classdict.get('widgets', {}))
+        # XXX decide whether or not it's a good idea to replace __rtags__
+        #     good point: transparent support for inheritance levels >= 2
+        #     bad point: we loose the information of which tags are specific
+        #                to this entity class
+        classdict['__rtags__'] = tagdefs
+        classdict['widgets'] = widgets
+        eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
+        # adds the "rtags" attribute
+        eclass.rtags = RelationTags(eclass, tagdefs)
+        return eclass
+
+
+class Entity(AppRsetObject, dict):
+    """an entity instance has e_schema automagically set on
+    the class and instances has access to their issuing cursor.
+    
+    A property is set for each attribute and relation on each entity's type
+    class. Becare that among attributes, 'eid' is *NEITHER* stored in the
+    dict containment (which acts as a cache for other attributes dynamically
+    fetched)
+
+    :type e_schema: `cubicweb.schema.EntitySchema`
+    :ivar e_schema: the entity's schema
+
+    :type rest_var: str
+    :cvar rest_var: indicates which attribute should be used to build REST urls
+                    If None is specified, the first non-meta attribute will
+                    be used
+                    
+    :type skip_copy_for: list
+    :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
+    """
+    __metaclass__ = metaentity
+    __registry__ = 'etypes'
+    __registerer__ = id_registerer
+    __selectors__ = (yes_selector,)
+    widgets = {}
+    id = None
+    e_schema = None
+    eid = None
+    rest_attr = None
+    skip_copy_for = ()
+
+    @classmethod
+    def registered(cls, registry):
+        """build class using descriptor at registration time"""
+        assert cls.id is not None
+        super(Entity, cls).registered(registry)
+        if cls.id != 'Any':
+            cls.__initialize__()
+        return cls
+                
+    MODE_TAGS = set(('link', 'create'))
+    CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
+    @classmethod
+    def __initialize__(cls):
+        """initialize a specific entity class by adding descriptors to access
+        entity type's attributes and relations
+        """
+        etype = cls.id
+        assert etype != 'Any', etype
+        cls.e_schema = eschema = cls.schema.eschema(etype)
+        for rschema, _ in eschema.attribute_definitions():
+            if rschema.type == 'eid':
+                continue
+            setattr(cls, rschema.type, Attribute(rschema.type))
+        mixins = []
+        for rschema, _, x in eschema.relation_definitions():
+            if (rschema, x) in MI_REL_TRIGGERS:
+                mixin = MI_REL_TRIGGERS[(rschema, x)]
+                if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
+                    mixins.append(mixin)
+                for iface in getattr(mixin, '__implements__', ()):
+                    if not interface.implements(cls, iface):
+                        interface.extend(cls, iface)
+            if x == 'subject':
+                setattr(cls, rschema.type, SubjectRelation(rschema))
+            else:
+                attr = 'reverse_%s' % rschema.type
+                setattr(cls, attr, ObjectRelation(rschema))
+        if mixins:
+            cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
+            cls.debug('plugged %s mixins on %s', mixins, etype)
+        cls.rtags.__initialize__()
+    
+    @classmethod
+    def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
+                  settype=True, ordermethod='fetch_order'):
+        """return a rql to fetch all entities of the class type"""
+        restrictions = restriction or []
+        if settype:
+            restrictions.append('%s is %s' % (mainvar, cls.id))
+        if fetchattrs is None:
+            fetchattrs = cls.fetch_attrs
+        selection = [mainvar]
+        orderby = []
+        # start from 26 to avoid possible conflicts with X
+        varmaker = rqlvar_maker(index=26)
+        cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
+                                orderby, restrictions, user, ordermethod)
+        rql = 'Any %s' % ','.join(selection)
+        if orderby:
+            rql +=  ' ORDERBY %s' % ','.join(orderby)
+        rql += ' WHERE %s' % ', '.join(restrictions)
+        return rql
+    
+    @classmethod
+    def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs,
+                            selection, orderby, restrictions, user,
+                            ordermethod='fetch_order', visited=None):
+        eschema = cls.e_schema
+        if visited is None:
+            visited = set((eschema.type,))
+        elif eschema.type in visited:
+            # avoid infinite recursion
+            return
+        else:
+            visited.add(eschema.type)
+        _fetchattrs = []
+        for attr in fetchattrs:
+            try:
+                rschema = eschema.subject_relation(attr)
+            except KeyError:
+                cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
+                            attr, cls.id)
+                continue
+            if not user.matching_groups(rschema.get_groups('read')):
+                continue
+            var = varmaker.next()
+            selection.append(var)
+            restriction = '%s %s %s' % (mainvar, attr, var)
+            restrictions.append(restriction)
+            if not rschema.is_final():
+                # XXX this does not handle several destination types
+                desttype = rschema.objects(eschema.type)[0]
+                card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+                if card not in '?1':
+                    selection.pop()
+                    restrictions.pop()
+                    continue
+                if card == '?':
+                    restrictions[-1] += '?' # left outer join if not mandatory
+                destcls = cls.vreg.etype_class(desttype)
+                destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
+                                            selection, orderby, restrictions,
+                                            user, ordermethod, visited=visited)
+            orderterm = getattr(cls, ordermethod)(attr, var)
+            if orderterm:
+                orderby.append(orderterm)
+        return selection, orderby, restrictions
+
+    def __init__(self, req, rset, row=None, col=0):
+        AppRsetObject.__init__(self, req, rset)
+        dict.__init__(self)
+        self.row, self.col = row, col
+        self._related_cache = {}
+        if rset is not None:
+            self.eid = rset[row][col]
+        else:
+            self.eid = None
+        self._is_saved = True
+        
+    def __repr__(self):
+        return '<Entity %s %s %s at %s>' % (
+            self.e_schema, self.eid, self.keys(), id(self))
+
+    def __nonzero__(self):
+        return True
+
+    def __hash__(self):
+        return id(self)
+
+    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
+        occasion to do weird stuff such as autocast (File -> Image for instance).
+        
+        This method must return the actual entity to be added.
+        """
+        return self
+    
+    def set_eid(self, eid):
+        self.eid = self['eid'] = eid
+
+    def has_eid(self):
+        """return True if the entity has an attributed eid (False
+        meaning that the entity has to be created
+        """
+        try:
+            int(self.eid)
+            return True
+        except (ValueError, TypeError):
+            return False
+
+    def is_saved(self):
+        """during entity creation, there is some time during which the entity
+        has an eid attributed though it's not saved (eg during before_add_entity
+        hooks). You can use this method to ensure the entity has an eid *and* is
+        saved in its source.
+        """
+        return self.has_eid() and self._is_saved
+    
+    @cached
+    def metainformation(self):
+        res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
+        res['source'] = self.req.source_defs()[res['source']]
+        return res
+
+    def check_perm(self, action):
+        self.e_schema.check_perm(self.req, action, self.eid)
+
+    def has_perm(self, action):
+        return self.e_schema.has_perm(self.req, action, self.eid)
+        
+    def view(self, vid, __registry='views', **kwargs):
+        """shortcut to apply a view on this entity"""
+        return self.vreg.render(__registry, vid, self.req, rset=self.rset,
+                                row=self.row, col=self.col, **kwargs)
+
+    def absolute_url(self, method=None, **kwargs):
+        """return an absolute url to view this entity"""
+        # in linksearch mode, we don't want external urls else selecting
+        # the object for use in the relation is tricky
+        # XXX search_state is web specific
+        if getattr(self.req, 'search_state', ('normal',))[0] == 'normal':
+            kwargs['base_url'] = self.metainformation()['source'].get('base-url')
+        if method is None or method == 'view':
+            kwargs['_restpath'] = self.rest_path()
+        else:
+            kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
+        return self.build_url(method, **kwargs)
+
+    def rest_path(self):
+        """returns a REST-like (relative) path for this entity"""
+        mainattr, needcheck = self._rest_attr_info()
+        etype = str(self.e_schema)
+        if mainattr == 'eid':
+            value = self.eid
+        else:
+            value = getattr(self, mainattr)
+            if value is None:
+                return '%s/eid/%s' % (etype.lower(), self.eid)
+        if needcheck:
+            # make sure url is not ambiguous
+            rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
+            if value is not None:
+                nbresults = self.req.execute(rql, {'value' : value})[0][0]
+                # may an assertion that nbresults is not 0 would be a good idea
+                if nbresults != 1: # no ambiguity
+                    return '%s/eid/%s' % (etype.lower(), self.eid)
+        return '%s/%s' % (etype.lower(), self.req.url_quote(value))
+
+    @classmethod
+    def _rest_attr_info(cls):
+        mainattr, needcheck = 'eid', True
+        if cls.rest_attr:
+            mainattr = cls.rest_attr
+            needcheck = not cls.e_schema.has_unique_values(mainattr)
+        else:
+            for rschema in cls.e_schema.subject_relations():
+                if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
+                    mainattr = str(rschema)
+                    needcheck = False
+                    break
+        if mainattr == 'eid':
+            needcheck = False
+        return mainattr, needcheck
+
+    @cached
+    def formatted_attrs(self):
+        """returns the list of attributes which have some format information
+        (i.e. rich text strings)
+        """
+        attrs = []
+        for rschema, attrschema in self.e_schema.attribute_definitions():
+            if attrschema.type == 'String' and self.has_format(rschema):
+                attrs.append(rschema.type)
+        return attrs
+        
+    def format(self, attr):
+        """return the mime type format for an attribute (if specified)"""
+        return getattr(self, '%s_format' % attr, None)
+    
+    def text_encoding(self, attr):
+        """return the text encoding for an attribute, default to site encoding
+        """
+        encoding = getattr(self, '%s_encoding' % attr, None)
+        return encoding or self.vreg.property_value('ui.encoding')
+
+    def has_format(self, attr):
+        """return true if this entity's schema has a format field for the given
+        attribute
+        """
+        return self.e_schema.has_subject_relation('%s_format' % attr)
+    
+    def has_text_encoding(self, attr):
+        """return true if this entity's schema has ab encoding field for the
+        given attribute
+        """
+        return self.e_schema.has_subject_relation('%s_encoding' % attr)
+
+    def printable_value(self, attr, value=_marker, attrtype=None,
+                        format='text/html', displaytime=True):
+        """return a displayable value (i.e. unicode string) which may contains
+        html tags
+        """
+        attr = str(attr)
+        if value is _marker:
+            value = getattr(self, attr)
+        if isinstance(value, basestring):
+            value = value.strip()
+        if value is None or value == '': # don't use "not", 0 is an acceptable value
+            return u''
+        if attrtype is None:
+            attrtype = self.e_schema.destination(attr)
+        props = self.e_schema.rproperties(attr)
+        if attrtype == 'String':
+            # internalinalized *and* formatted string such as schema
+            # description...
+            if props.get('internationalizable'):
+                value = self.req._(value)
+            attrformat = self.format(attr)
+            if attrformat:
+                return self.mtc_transform(value, attrformat, format,
+                                          self.req.encoding)
+        elif attrtype == 'Bytes':
+            attrformat = self.format(attr)
+            if attrformat:
+                try:
+                    encoding = getattr(self, '%s_encoding' % attr)
+                except AttributeError:
+                    encoding = self.req.encoding
+                return self.mtc_transform(value.getvalue(), attrformat, format,
+                                          encoding)
+            return u''
+        value = printable_value(self.req, attrtype, value, props, displaytime)
+        if format == 'text/html':
+            value = html_escape(value)
+        return value
+
+    def mtc_transform(self, data, format, target_format, encoding,
+                      _engine=ENGINE):
+        trdata = TransformData(data, format, encoding, appobject=self)
+        data = _engine.convert(trdata, target_format).decode()
+        if format == 'text/html':
+            data = soup2xhtml(data, self.req.encoding)                
+        return data
+    
+    # entity cloning ##########################################################
+
+    def copy_relations(self, ceid):
+        """copy relations of the object with the given eid on this object
+
+        By default meta and composite relations are skipped.
+        Overrides this if you want another behaviour
+        """
+        assert self.has_eid()
+        execute = self.req.execute
+        for rschema in self.e_schema.subject_relations():
+            if rschema.meta or rschema.is_final():
+                continue
+            # skip already defined relations
+            if getattr(self, rschema.type):
+                continue
+            if rschema.type in self.skip_copy_for:
+                continue
+            if rschema.type == 'in_state':
+                # if the workflow is defining an initial state (XXX AND we are
+                # not in the managers group? not done to be more consistent)
+                # don't try to copy in_state
+                if execute('Any S WHERE S state_of ET, ET initial_state S,'
+                           'ET name %(etype)s', {'etype': str(self.e_schema)}):
+                    continue
+            # skip composite relation
+            if self.e_schema.subjrproperty(rschema, 'composite'):
+                continue
+            # skip relation with card in ?1 else we either change the copied
+            # object (inlined relation) or inserting some inconsistency
+            if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
+                continue
+            rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
+                rschema.type, rschema.type)
+            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+            self.clear_related_cache(rschema.type, 'subject')
+        for rschema in self.e_schema.object_relations():
+            if rschema.meta:
+                continue
+            # skip already defined relations
+            if getattr(self, 'reverse_%s' % rschema.type):
+                continue
+            # skip composite relation
+            if self.e_schema.objrproperty(rschema, 'composite'):
+                continue
+            # skip relation with card in ?1 else we either change the copied
+            # object (inlined relation) or inserting some inconsistency
+            if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
+                continue
+            rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
+                rschema.type, rschema.type)
+            execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+            self.clear_related_cache(rschema.type, 'object')
+
+    # data fetching methods ###################################################
+
+    @cached
+    def as_rset(self):
+        """returns a resultset containing `self` information"""
+        rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
+                         {'x': self.eid}, [(self.id,)])
+        return self.req.decorate_rset(rset)
+                       
+    def to_complete_relations(self):
+        """by default complete final relations to when calling .complete()"""
+        for rschema in self.e_schema.subject_relations():
+            if rschema.is_final():
+                continue
+            if len(rschema.objects(self.e_schema)) > 1:
+                # ambigous relations, the querier doesn't handle
+                # outer join correctly in this case
+                continue
+            if rschema.inlined:
+                matching_groups = self.req.user.matching_groups
+                if matching_groups(rschema.get_groups('read')) and \
+                   all(matching_groups(es.get_groups('read'))
+                       for es in rschema.objects(self.e_schema)):
+                    yield rschema, 'subject'
+                    
+    def to_complete_attributes(self, skip_bytes=True):
+        for rschema, attrschema in self.e_schema.attribute_definitions():
+            # skip binary data by default
+            if skip_bytes and attrschema.type == 'Bytes':
+                continue
+            attr = rschema.type
+            if attr == 'eid':
+                continue
+            # password retreival is blocked at the repository server level
+            if not self.req.user.matching_groups(rschema.get_groups('read')) \
+                   or attrschema.type == 'Password':
+                self[attr] = None
+                continue
+            yield attr
+            
+    def complete(self, attributes=None, skip_bytes=True):
+        """complete this entity by adding missing attributes (i.e. query the
+        repository to fill the entity)
+
+        :type skip_bytes: bool
+        :param skip_bytes:
+          if true, attribute of type Bytes won't be considered
+        """
+        assert self.has_eid()
+        varmaker = rqlvar_maker()
+        V = varmaker.next()
+        rql = ['WHERE %s eid %%(x)s' % V]
+        selected = []
+        for attr in (attributes or self.to_complete_attributes(skip_bytes)):
+            # if attribute already in entity, nothing to do
+            if self.has_key(attr):
+                continue
+            # case where attribute must be completed, but is not yet in entity
+            var = varmaker.next()
+            rql.append('%s %s %s' % (V, attr, var))
+            selected.append((attr, var))
+        # +1 since this doen't include the main variable
+        lastattr = len(selected) + 1
+        if attributes is None:
+            # fetch additional relations (restricted to 0..1 relations)
+            for rschema, role in self.to_complete_relations():
+                rtype = rschema.type
+                if self.relation_cached(rtype, role):
+                    continue
+                var = varmaker.next()
+                if role == 'subject':
+                    targettype = rschema.objects(self.e_schema)[0]
+                    card = rschema.rproperty(self.e_schema, targettype,
+                                             'cardinality')[0]
+                    if card == '1':
+                        rql.append('%s %s %s' % (V, rtype, var))
+                    else: # '?"
+                        rql.append('%s %s %s?' % (V, rtype, var))
+                else:
+                    targettype = rschema.subjects(self.e_schema)[1]
+                    card = rschema.rproperty(self.e_schema, targettype,
+                                             'cardinality')[1]
+                    if card == '1':
+                        rql.append('%s %s %s' % (var, rtype, V))
+                    else: # '?"
+                        rql.append('%s? %s %s' % (var, rtype, V))
+                assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
+                                                      role, card)
+                selected.append(((rtype, role), var))
+        if selected:
+            # select V, we need it as the left most selected variable
+            # 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.req, 'unsafe_execute', self.req.execute)
+            rset = 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]
+            # handle relations
+            for i in xrange(lastattr, len(rset)):
+                rtype, x = selected[i-1][0]
+                value = rset[i]
+                if value is None:
+                    rrset = ResultSet([], rql, {'x': self.eid})
+                    self.req.decorate_rset(rrset)
+                else:
+                    rrset = self.req.eid_rset(value)
+                self.set_related_cache(rtype, x, rrset)
+                
+    def get_value(self, name):
+        """get value for the attribute relation <name>, query the repository
+        to get the value if necessary.
+
+        :type name: str
+        :param name: name of the attribute to get
+        """
+        try:
+            value = self[name]
+        except KeyError:
+            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??
+            execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+            try:
+                rset = execute(rql, {'x': self.eid}, 'x')
+            except Unauthorized:
+                self[name] = value = None
+            else:
+                assert rset.rowcount <= 1, (self, rql, rset.rowcount)
+                try:
+                    self[name] = value = rset.rows[0][0]
+                except IndexError:
+                    # probably a multisource error
+                    self.critical("can't get value for attribute %s of entity with eid %s",
+                                  name, self.eid)
+                    if self.e_schema.destination(name) == 'String':
+                        self[name] = value = self.req._('unaccessible')
+                    else:
+                        self[name] = value = None
+        return value
+
+    def related(self, rtype, role='subject', limit=None, entities=False):
+        """returns a resultset of related entities
+        
+        :param role: is the role played by 'self' in the relation ('subject' or 'object')
+        :param limit: resultset's maximum size
+        :param entities: if True, the entites are returned; if False, a result set is returned
+        """
+        try:
+            return self.related_cache(rtype, role, entities, limit)
+        except KeyError:
+            pass
+        assert self.has_eid()
+        rql = self.related_rql(rtype, role)
+        rset = self.req.execute(rql, {'x': self.eid}, 'x')
+        self.set_related_cache(rtype, role, rset)
+        return self.related(rtype, role, limit, entities)
+
+    def related_rql(self, rtype, role='subject'):
+        rschema = self.schema[rtype]
+        if role == 'subject':
+            targettypes = rschema.objects(self.e_schema)
+            restriction = 'E eid %%(x)s, E %s X' % rtype
+            card = greater_card(rschema, (self.e_schema,), targettypes, 0)
+        else:
+            targettypes = rschema.subjects(self.e_schema)
+            restriction = 'E eid %%(x)s, X %s E' % rtype
+            card = greater_card(rschema, targettypes, (self.e_schema,), 1)
+        if len(targettypes) > 1:
+            fetchattrs = set()
+            for ttype in targettypes:
+                etypecls = self.vreg.etype_class(ttype)
+                fetchattrs &= frozenset(etypecls.fetch_attrs)
+            rql = etypecls.fetch_rql(self.req.user, [restriction], fetchattrs,
+                                     settype=False)
+        else:
+            etypecls = self.vreg.etype_class(targettypes[0])
+            rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
+        # optimisation: remove ORDERBY if cardinality is 1 or ? (though
+        # greater_card return 1 for those both cases)
+        if card == '1':
+            if ' ORDERBY ' in rql:
+                rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
+                                       rql.split(' WHERE ', 1)[1])
+        elif not ' ORDERBY ' in rql:
+            args = tuple(rql.split(' WHERE ', 1))
+            rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
+        return rql
+    
+    # generic vocabulary methods ##############################################
+
+    def vocabulary(self, rtype, role='subject', limit=None):
+        """vocabulary functions must return a list of couples
+        (label, eid) that will typically be used to fill the
+        edition view's combobox.
+        
+        If `eid` is None in one of these couples, it should be
+        interpreted as a separator in case vocabulary results are grouped
+        """
+        try:
+            vocabfunc = getattr(self, '%s_%s_vocabulary' % (role, rtype))
+        except AttributeError:
+            vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+        # NOTE: it is the responsibility of `vocabfunc` to sort the result
+        #       (direclty through RQL or via a python sort). This is also
+        #       important because `vocabfunc` might return a list with
+        #       couples (label, None) which act as separators. In these
+        #       cases, it doesn't make sense to sort results afterwards.
+        return vocabfunc(rtype, limit)
+            
+    def subject_relation_vocabulary(self, rtype, limit=None):
+        """defaut vocabulary method for the given relation, looking for
+        relation's object entities (i.e. self is the subject)
+        """
+        if isinstance(rtype, basestring):
+            rtype = self.schema.rschema(rtype)
+        done = None
+        assert not rtype.is_final(), rtype
+        if self.has_eid():
+            done = set(e.eid for e in getattr(self, str(rtype)))
+        result = []
+        rsetsize = None
+        for objtype in rtype.objects(self.e_schema):
+            if limit is not None:
+                rsetsize = limit - len(result)
+            result += self.relation_vocabulary(rtype, objtype, 'subject',
+                                               rsetsize, done)
+            if limit is not None and len(result) >= limit:
+                break
+        return result
+
+    def object_relation_vocabulary(self, rtype, limit=None):
+        """defaut vocabulary method for the given relation, looking for
+        relation's subject entities (i.e. self is the object)
+        """
+        if isinstance(rtype, basestring):
+            rtype = self.schema.rschema(rtype)
+        done = None
+        if self.has_eid():
+            done = set(e.eid for e in getattr(self, 'reverse_%s' % rtype))
+        result = []
+        rsetsize = None
+        for subjtype in rtype.subjects(self.e_schema):
+            if limit is not None:
+                rsetsize = limit - len(result)
+            result += self.relation_vocabulary(rtype, subjtype, 'object',
+                                               rsetsize, done)
+            if limit is not None and len(result) >= limit:
+                break
+        return result
+
+    def relation_vocabulary(self, rtype, targettype, role,
+                            limit=None, done=None):
+        if done is None:
+            done = set()
+        req = self.req
+        rset = self.unrelated(rtype, targettype, role, limit)
+        res = []
+        for entity in rset.entities():
+            if entity.eid in done:
+                continue
+            done.add(entity.eid)
+            res.append((entity.view('combobox'), entity.eid))
+        return res
+
+    def unrelated_rql(self, rtype, targettype, role, ordermethod=None,
+                      vocabconstraints=True):
+        """build a rql to fetch `targettype` entities unrelated to this entity
+        using (rtype, role) relation
+        """
+        ordermethod = ordermethod or 'fetch_unrelated_order'
+        if isinstance(rtype, basestring):
+            rtype = self.schema.rschema(rtype)
+        if role == 'subject':
+            evar, searchedvar = 'S', 'O'
+            subjtype, objtype = self.e_schema, targettype
+        else:
+            searchedvar, evar = 'S', 'O'
+            objtype, subjtype = self.e_schema, targettype
+        if self.has_eid():
+            restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
+        else:
+            restriction = []
+        constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+        if vocabconstraints:
+            # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
+            # will be included as well
+            restriction += [cstr.restriction for cstr in constraints
+                            if isinstance(cstr, RQLVocabularyConstraint)]
+        else:
+            restriction += [cstr.restriction for cstr in constraints
+                            if isinstance(cstr, RQLConstraint)]
+        etypecls = self.vreg.etype_class(targettype)
+        rql = etypecls.fetch_rql(self.req.user, restriction,
+                                 mainvar=searchedvar, ordermethod=ordermethod)
+        # ensure we have an order defined
+        if not ' ORDERBY ' in rql:
+            before, after = rql.split(' WHERE ', 1)
+            rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
+        return rql
+    
+    def unrelated(self, rtype, targettype, role='subject', limit=None,
+                  ordermethod=None):
+        """return a result set of target type objects that may be related
+        by a given relation, with self as subject or object
+        """
+        rql = self.unrelated_rql(rtype, targettype, role, ordermethod)
+        if limit is not None:
+            before, after = rql.split(' WHERE ', 1)
+            rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
+        if self.has_eid():
+            return self.req.execute(rql, {'x': self.eid})
+        return self.req.execute(rql)
+        
+    # relations cache handling ################################################
+    
+    def relation_cached(self, rtype, role):
+        """return true if the given relation is already cached on the instance
+        """
+        return '%s_%s' % (rtype, role) in self._related_cache
+    
+    def related_cache(self, rtype, role, entities=True, limit=None):
+        """return values for the given relation if it's cached on the instance,
+        else raise `KeyError`
+        """
+        res = self._related_cache['%s_%s' % (rtype, role)][entities]
+        if limit:
+            if entities:
+                res = res[:limit]
+            else:
+                res = res.limit(limit)
+        return res
+    
+    def set_related_cache(self, rtype, role, rset, col=0):
+        """set cached values for the given relation"""
+        if rset:
+            related = list(rset.entities(col))
+            rschema = self.schema.rschema(rtype)
+            if role == 'subject':
+                rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
+                                          'cardinality')[1]
+                target = 'object'
+            else:
+                rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
+                                          'cardinality')[0]
+                target = 'subject'
+            if rcard in '?1':
+                for rentity in related:
+                    rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self])
+        else:
+            related = []
+        self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
+        
+    def clear_related_cache(self, rtype=None, role=None):
+        """clear cached values for the given relation or the entire cache if
+        no relation is given
+        """
+        if rtype is None:
+            self._related_cache = {}
+        else:
+            assert role
+            self._related_cache.pop('%s_%s' % (rtype, role), None)
+        
+    # raw edition utilities ###################################################
+    
+    def set_attributes(self, **kwargs):
+        assert kwargs
+        relations = []
+        for key in kwargs:
+            relations.append('X %s %%(%s)s' % (key, key))
+        kwargs['x'] = self.eid
+        self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
+                         kwargs, 'x')
+        for key, val in kwargs.iteritems():
+            self[key] = val
+            
+    def delete(self):
+        assert self.has_eid(), self.eid
+        self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
+                         {'x': self.eid})
+    
+    # server side utilities ###################################################
+        
+    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
+        are checked here, constraint on actual relations are checked in hooks
+        """
+        # necessary since eid is handled specifically and yams require it to be
+        # in the dictionary
+        if self.req is None:
+            _ = unicode
+        else:
+            _ = self.req._
+        self.e_schema.check(self, creation=creation, _=_)
+
+    def fti_containers(self, _done=None):
+        if _done is None:
+            _done = set()
+        _done.add(self.eid)
+        containers = tuple(self.e_schema.fulltext_containers())
+        if containers:
+            for rschema, target in containers:
+                if target == 'object':
+                    targets = getattr(self, rschema.type)
+                else:
+                    targets = getattr(self, 'reverse_%s' % rschema)
+                for entity in targets:
+                    if entity.eid in _done:
+                        continue
+                    for container in entity.fti_containers(_done):
+                        yield container
+        else:
+            yield self
+                    
+    def get_words(self):
+        """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
+        
+        :rtype: list
+        :return: the list of indexable word of this entity
+        """
+        from indexer.query_objects import tokenize
+        words = []
+        for rschema in self.e_schema.indexable_attributes():
+            try:
+                value = self.printable_value(rschema, format='text/plain')
+            except TransformError, ex:
+                continue
+            except:
+                self.exception("can't add value of %s to text index for entity %s",
+                               rschema, self.eid)
+                continue
+            if value:
+                words += tokenize(value)
+        
+        for rschema, role in self.e_schema.fulltext_relations():
+            if role == 'subject':
+                for entity in getattr(self, rschema.type):
+                    words += entity.get_words()
+            else: # if role == 'object':
+                for entity in getattr(self, 'reverse_%s' % rschema.type):
+                    words += entity.get_words()
+        return words
+
+
+# attribute and relation descriptors ##########################################
+
+class Attribute(object):
+    """descriptor that controls schema attribute access"""
+
+    def __init__(self, attrname):
+        assert attrname != 'eid'
+        self._attrname = attrname
+
+    def __get__(self, eobj, eclass):
+        if eobj is None:
+            return self
+        return eobj.get_value(self._attrname)
+
+    def __set__(self, eobj, value):
+        # XXX bw compat
+        # would be better to generate UPDATE queries than the current behaviour
+        eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)")
+        eobj[self._attrname] = value
+
+
+class Relation(object):
+    """descriptor that controls schema relation access"""
+    _role = None # for pylint
+
+    def __init__(self, rschema):
+        self._rschema = rschema
+        self._rtype = rschema.type
+
+    def __get__(self, eobj, eclass):
+        if eobj is None:
+            raise AttributeError('%s cannot be only be accessed from instances'
+                                 % self._rtype)
+        return eobj.related(self._rtype, self._role, entities=True)
+    
+    def __set__(self, eobj, value):
+        raise NotImplementedError
+
+
+class SubjectRelation(Relation):
+    """descriptor that controls schema relation access"""
+    _role = 'subject'
+    
+class ObjectRelation(Relation):
+    """descriptor that controls schema relation access"""
+    _role = 'object'
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(Entity, getLogger('cubicweb.entity'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/html4zope.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
+minimum of formatting information.  A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_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))
+
+class Writer(CSS1Writer):
+    """css writer using our html translator"""
+    def __init__(self, base_url):
+        CSS1Writer.__init__(self)
+        self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+    def apply_template(self):
+        """overriding this is necessary with docutils >= 0.5"""
+        return self.visitor.astext()
+
+class URLBinder:
+    def __init__(self, url, klass):
+        self.base_url = url
+        self.translator_class = HTMLTranslator
+        
+    def __call__(self, document):
+        translator = self.translator_class(document)
+        translator.base_url = self.base_url
+        return translator
+    
+class HTMLTranslator(CSS1HTMLTranslator):
+    """ReST tree to html translator"""
+
+    def astext(self):
+        """return the extracted html"""
+        return ''.join(self.body)
+    
+    def visit_title(self, node):
+        """Only 6 section levels are supported by HTML."""
+        if isinstance(node.parent, nodes.topic):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='topic-title'))
+            if node.parent.hasattr('id'):
+                self.body.append(
+                    self.starttag({}, 'a', '', name=node.parent['id']))
+                self.context.append('</a></p>\n')
+            else:
+                self.context.append('</p>\n')
+        elif self.section_level == 0:
+            # document title
+            self.head.append('<title>%s</title>\n'
+                             % self.encode(node.astext()))
+            self.body.append(self.starttag(node, 'h%d' % default_level, '',
+                                           CLASS='title'))
+            self.context.append('</h%d>\n' % default_level)
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (
+                default_level+self.section_level-1), ''))
+            atts = {}
+            if node.hasattr('refid'):
+                atts['class'] = 'toc-backref'
+                atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+            self.body.append(self.starttag({}, 'a', '', **atts))
+            self.context.append('</a></h%s>\n' % (
+                default_level+self.section_level-1))
+
+    def visit_subtitle(self, node):
+        """format a subtitle"""
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append(self.starttag(node, 'p', '',
+                                           CLASS='sidebar-subtitle'))
+            self.context.append('</p>\n')
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (default_level+1), '',
+                                CLASS='subtitle'))
+            self.context.append('</h%s>\n' % (default_level+1))
+
+    def visit_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+    def depart_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+
+    def visit_reference(self, node):
+        """syt: i want absolute urls"""
+        if node.has_key('refuri'):
+            href = node['refuri']
+            if ( self.settings.cloak_email_addresses
+                 and href.startswith('mailto:')):
+                href = self.cloak_mailto(href)
+                self.in_mailto = 1
+        else:
+            assert node.has_key('refid'), \
+                   'References must have "refuri" or "refid" attribute.'
+            href = '%s#%s' % (self.base_url, node['refid'])
+        atts = {'href': href, 'class': 'reference'}
+        if not isinstance(node.parent, nodes.TextElement):
+            assert len(node) == 1 and isinstance(node[0], nodes.image)
+            atts['class'] += ' image-reference'
+        self.body.append(self.starttag(node, 'a', '', **atts))
+
+    ## override error messages to avoid XHTML problems ########################
+    def visit_problematic(self, node):
+        pass
+
+    def depart_problematic(self, node):
+        pass
+    
+    def visit_system_message(self, node):
+        backref_text = ''
+        if len(node['backrefs']):
+            backrefs = node['backrefs']
+            if len(backrefs) == 1:
+                backref_text = '; <em>backlink</em>'
+            else:
+                i = 1
+                backlinks = []
+                for backref in backrefs:
+                    backlinks.append(str(i))
+                    i += 1
+                backref_text = ('; <em>backlinks: %s</em>'
+                                % ', '.join(backlinks))
+        if node.hasattr('line'):
+            line = ', line %s' % node['line']
+        else:
+            line = ''
+        a_start = a_end = ''
+        error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+            a_start, node['type'], node['level'], a_end,
+            self.encode(node['source']), line, backref_text)
+        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+    def depart_system_message(self, node):
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/i18n.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,93 @@
+"""Some i18n/gettext utilities.
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import re
+import os
+from os.path import join, abspath, basename, splitext, exists
+from glob import glob
+
+from cubicweb.toolsutils import create_dir
+
+def extract_from_tal(files, output_file):
+    """extract i18n strings from tal and write them into the given output file
+    using standard python gettext marker (_)
+    """
+    output = open(output_file, 'w')
+    for filepath in files:
+        for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()):
+            print >> output, '_("%s")' % match.group(2)
+    output.close()
+
+
+def add_msg(w, msgid):
+    """write an empty pot msgid definition"""
+    if isinstance(msgid, unicode):
+        msgid = msgid.encode('utf-8')
+    msgid = msgid.replace('"', r'\"').splitlines()
+    if len(msgid) > 1:
+        w('msgid ""\n')
+        for line in msgid:
+            w('"%s"' % line.replace('"', r'\"'))
+    else:
+        w('msgid "%s"\n' % msgid[0])
+    w('msgstr ""\n\n')
+
+
+def execute(cmd):
+    """display the command, execute it and raise an Exception if returned
+    status != 0
+    """
+    print cmd.replace(os.getcwd() + os.sep, '')
+    status = os.system(cmd)
+    if status != 0:
+        raise Exception()
+
+
+def available_catalogs(i18ndir=None):
+    if i18ndir is None:
+        wildcard = '*.po'
+    else:
+        wildcard = join(i18ndir, '*.po')
+    for popath in glob(wildcard):
+        lang = splitext(basename(popath))[0]
+        yield lang, popath
+
+
+def compile_i18n_catalogs(sourcedirs, destdir, langs):
+    """generate .mo files for a set of languages into the `destdir` i18n directory
+    """
+    from logilab.common.fileutils import ensure_fs_mode
+    print 'compiling %s catalogs...' % destdir
+    errors = []
+    for lang in langs:
+        langdir = join(destdir, lang, 'LC_MESSAGES')
+        if not exists(langdir):
+            create_dir(langdir)
+        pofiles = [join(path, '%s.po' % lang) for path in sourcedirs]
+        pofiles = [pof for pof in pofiles if exists(pof)]
+        mergedpo = join(destdir, '%s_merged.po' % lang)
+        try:
+            # merge application messages' catalog with the stdlib's one
+            execute('msgcat --use-first --sort-output --strict %s > %s'
+                    % (' '.join(pofiles), mergedpo))
+            # make sure the .mo file is writeable and compile with *msgfmt*
+            applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
+            try:
+                ensure_fs_mode(applmo)
+            except OSError:
+                pass # suppose not exists
+            execute('msgfmt %s -o %s' % (mergedpo, applmo))
+        except Exception, ex:
+            errors.append('while handling language %s: %s' % (lang, ex))
+        try:
+            # clean everything
+            os.unlink(mergedpo)
+        except Exception:
+            continue
+    return errors
+                         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/mail.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,94 @@
+"""Common utilies to format / semd emails.
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEImage import MIMEImage
+from email.Header import Header
+
+
+def header(ustring):
+    return Header(ustring.encode('UTF-8'), 'UTF-8')
+
+def addrheader(uaddr, uname=None):
+    # even if an email address should be ascii, encode it using utf8 since
+    # application tests may generate non ascii email address
+    addr = uaddr.encode('UTF-8') 
+    if uname:
+        return '%s <%s>' % (header(uname).encode(), addr)
+    return addr
+
+
+def format_mail(uinfo, to_addrs, content, subject="",
+                cc_addrs=(), msgid=None, references=(), config=None):
+    """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
+
+    to_addrs and cc_addrs are expected to be a list of email address without
+    name
+    """
+    assert type(content) is unicode, repr(content)
+    msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
+    # safety: keep only the first newline
+    subject = subject.splitlines()[0]
+    msg['Subject'] = header(subject)
+    if uinfo.get('email'):
+        email = uinfo['email']
+    elif config and config['sender-addr']:
+        email = unicode(config['sender-addr'])
+    else:
+        email = u''
+    if uinfo.get('name'):
+        name = uinfo['name']
+    elif config and config['sender-addr']:
+        name = unicode(config['sender-name'])
+    else:
+        name = u''
+    msg['From'] = addrheader(email, name)
+    if config and config['sender-addr'] and config['sender-addr'] != email:
+        appaddr = addrheader(config['sender-addr'], config['sender-name'])
+        msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
+    elif email:
+        msg['Reply-to'] = msg['From']
+    if config is not None:
+        msg['X-CW'] = config.appid
+    msg['To'] = ', '.join(addrheader(addr) for addr in to_addrs if addr is not None)
+    if cc_addrs:
+        msg['Cc'] = ', '.join(addrheader(addr) for addr in cc_addrs if addr is not None)
+    if msgid:
+        msg['Message-id'] = msgid
+    if references:
+        msg['References'] = ', '.join(references)
+    return msg
+
+
+class HtmlEmail(MIMEMultipart):
+
+    def __init__(self, subject, textcontent, htmlcontent,
+                 sendermail=None, sendername=None, recipients=None, ccrecipients=None):
+        MIMEMultipart.__init__(self, 'related')
+        self['Subject'] = header(subject)
+        self.preamble = 'This is a multi-part message in MIME format.'
+        # Attach alternative text message
+        alternative = MIMEMultipart('alternative')
+        self.attach(alternative)
+        msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
+        alternative.attach(msgtext)
+        # Attach html message
+        msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
+        alternative.attach(msghtml)
+        if sendermail or sendername:
+            self['From'] = addrheader(sendermail, sendername)
+        if recipients:
+            self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
+        if ccrecipients:
+            self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
+
+    def attach_image(self, data, htmlId):
+        image = MIMEImage(data)
+        image.add_header('Content-ID', '<%s>' % htmlId)
+        self.attach(image)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/migration.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,358 @@
+"""utility to ease migration of application version to newly installed
+version
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import sys
+import os
+import logging
+from tempfile import mktemp
+from os.path import exists, join, basename, splitext
+
+from logilab.common.decorators import cached
+from logilab.common.configuration import REQUIRED, read_old_config
+
+
+def migration_files(config, toupgrade):
+    """return an orderer list of path of scripts to execute to upgrade
+    an installed application according to installed cube and cubicweb versions
+    """
+    merged = []
+    for cube, fromversion, toversion in toupgrade:
+        if cube == 'cubicweb':
+            migrdir = config.migration_scripts_dir()
+        else:
+            migrdir = config.cube_migration_scripts_dir(cube)
+        scripts = filter_scripts(config, migrdir, fromversion, toversion)
+        merged += [s[1] for s in scripts]
+    if config.accept_mode('Any'):
+        migrdir = config.migration_scripts_dir()
+        merged.insert(0, join(migrdir, 'bootstrapmigration_repository.py'))
+    return merged
+
+
+def filter_scripts(config, directory, fromversion, toversion, quiet=True):
+    """return a list of paths of migration files to consider to upgrade
+    from a version to a greater one
+    """
+    from logilab.common.changelog import Version # doesn't work with appengine
+    assert fromversion
+    assert toversion
+    assert isinstance(fromversion, tuple), fromversion.__class__
+    assert isinstance(toversion, tuple), toversion.__class__
+    assert fromversion <= toversion, (fromversion, toversion)
+    if not exists(directory):
+        if not quiet:
+            print directory, "doesn't exists, no migration path"
+        return []
+    if fromversion == toversion:
+        return []
+    result = []
+    for fname in os.listdir(directory):
+        if fname.endswith('.pyc') or fname.endswith('.pyo') \
+               or fname.endswith('~'):
+            continue
+        fpath = join(directory, fname)
+        try:
+            tver, mode = fname.split('_', 1)
+        except ValueError:
+            continue
+        mode = mode.split('.', 1)[0]
+        if not config.accept_mode(mode):
+            continue
+        try:
+            tver = Version(tver)
+        except ValueError:
+            continue
+        if tver <= fromversion:
+            continue
+        if tver > toversion:
+            continue
+        result.append((tver, fpath))
+    # be sure scripts are executed in order
+    return sorted(result)
+
+
+IGNORED_EXTENSIONS = ('.swp', '~')
+
+
+def execscript_confirm(scriptpath):
+    """asks for confirmation before executing a script and provides the
+    ability to show the script's content
+    """
+    while True:
+        confirm = raw_input('** execute %r (Y/n/s[how]) ?' % scriptpath)
+        confirm = confirm.strip().lower()
+        if confirm in ('n', 'no'):
+            return False
+        elif confirm in ('s', 'show'):
+            stream = open(scriptpath)
+            scriptcontent = stream.read()
+            stream.close()
+            print
+            print scriptcontent
+            print
+        else:
+            return True
+
+def yes(*args, **kwargs):
+    return True
+
+
+class MigrationHelper(object):
+    """class holding CubicWeb Migration Actions used by migration scripts"""
+
+    def __init__(self, config, interactive=True, verbosity=1):
+        self.config = config
+        self.config.init_log(logthreshold=logging.ERROR, debug=True)
+        # 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
+        self.verbosity = verbosity
+        self.need_wrap = True
+        if not interactive or not verbosity:
+            self.confirm = yes
+            self.execscript_confirm = yes
+        else:
+            self.execscript_confirm = execscript_confirm
+        self._option_changes = []
+        self.__context = {'confirm': self.confirm,
+                          'config': self.config,
+                          'interactive_mode': interactive,
+                          }
+
+    def repo_connect(self):
+        return self.config.repository()
+        
+    def migrate(self, vcconf, toupgrade, options):
+        """upgrade the given set of cubes
+        
+        `cubes` is an ordered list of 3-uple:
+        (cube, fromversion, toversion)
+        """
+        if options.fs_only:
+            # monkey path configuration.accept_mode so database mode (e.g. Any)
+            # won't be accepted
+            orig_accept_mode = self.config.accept_mode
+            def accept_mode(mode):
+                if mode == 'Any':
+                    return False
+                return orig_accept_mode(mode)
+            self.config.accept_mode = accept_mode
+        scripts = migration_files(self.config, toupgrade)
+        if scripts:
+            vmap = dict( (pname, (fromver, tover)) for pname, fromver, tover in toupgrade)
+            self.__context.update({'applcubicwebversion': vcconf['cubicweb'],
+                                   'cubicwebversion': self.config.cubicweb_version(),
+                                   'versions_map': vmap})
+            self.scripts_session(scripts)
+        else:
+            print 'no migration script to execute'            
+
+    def shutdown(self):
+        pass
+    
+    def __getattribute__(self, name):
+        try:
+            return object.__getattribute__(self, name)
+        except AttributeError:
+            cmd = 'cmd_%s' % name
+            if hasattr(self, cmd):
+                meth = getattr(self, cmd) 
+                return lambda *args, **kwargs: self.interact(args, kwargs,
+                                                             meth=meth)
+            raise
+        raise AttributeError(name)
+            
+    def interact(self, args, kwargs, meth):
+        """execute the given method according to user's confirmation"""
+        msg = 'execute command: %s(%s) ?' % (
+            meth.__name__[4:],
+            ', '.join([repr(arg) for arg in args] +
+                      ['%s=%r' % (n,v) for n,v in kwargs.items()]))
+        if 'ask_confirm' in kwargs:
+            ask_confirm = kwargs.pop('ask_confirm')
+        else:
+            ask_confirm = True
+        if not ask_confirm or self.confirm(msg):
+            return meth(*args, **kwargs)
+
+    def confirm(self, question, shell=True, abort=True, retry=False):
+        """ask for confirmation and return true on positive answer
+
+        if `retry` is true the r[etry] answer may return 2
+        """
+        print question,
+        possibleanswers = 'Y/n'
+        if abort:
+            possibleanswers += '/a[bort]'
+        if shell:
+            possibleanswers += '/s[hell]'
+        if retry:
+            possibleanswers += '/r[etry]'
+        try:
+            confirm = raw_input('(%s): ' % ( possibleanswers, ))
+            answer = confirm.strip().lower()
+        except (EOFError, KeyboardInterrupt):
+            answer = 'abort'
+        if answer in ('n', 'no'):
+            return False
+        if answer in ('r', 'retry'):
+            return 2
+        if answer in ('a', 'abort'):
+            self.rollback()
+            raise SystemExit(1)
+        if shell and answer in ('s', 'shell'):
+            self.interactive_shell()
+            return self.confirm(question)
+        return True
+
+    def interactive_shell(self):
+        self.confirm = yes
+        self.need_wrap = False
+        # avoid '_' to be added to builtins by sys.display_hook
+        def do_not_add___to_builtins(obj):
+            if obj is not None:
+                print repr(obj)
+        sys.displayhook = do_not_add___to_builtins
+        local_ctx = self._create_context()
+        try:
+            import readline
+            from rlcompleter import Completer
+        except ImportError:
+            # readline not available
+            pass
+        else:        
+            readline.set_completer(Completer(local_ctx).complete)
+            readline.parse_and_bind('tab: complete')
+            histfile = os.path.join(os.environ["HOME"], ".eshellhist")
+            try:
+                readline.read_history_file(histfile)
+            except IOError:
+                pass
+        from code import interact
+        banner = """entering the migration python shell
+just type migration commands or arbitrary python code and type ENTER to execute it
+type "exit" or Ctrl-D to quit the shell and resume operation"""
+        # give custom readfunc to avoid http://bugs.python.org/issue1288615
+        def unicode_raw_input(prompt):
+            return unicode(raw_input(prompt), sys.stdin.encoding)
+        interact(banner, readfunc=unicode_raw_input, local=local_ctx)
+        readline.write_history_file(histfile)
+        # delete instance's confirm attribute to avoid questions
+        del self.confirm
+        self.need_wrap = True
+
+    @cached
+    def _create_context(self):
+        """return a dictionary to use as migration script execution context"""
+        context = self.__context
+        for attr in dir(self):
+            if attr.startswith('cmd_'):
+                if self.need_wrap:
+                    context[attr[4:]] = getattr(self, attr[4:])
+                else:
+                    context[attr[4:]] = getattr(self, attr)
+        return context
+    
+    def process_script(self, migrscript, funcname=None, *args, **kwargs):
+        """execute a migration script
+        in interactive mode,  display the migration script path, ask for
+        confirmation and execute it if confirmed
+        """
+        assert migrscript.endswith('.py'), migrscript
+        if self.execscript_confirm(migrscript):
+            scriptlocals = self._create_context().copy()
+            if funcname is None:
+                pyname = '__main__'
+            else:
+                pyname = splitext(basename(migrscript))[0]
+            scriptlocals.update({'__file__': migrscript, '__name__': pyname})
+            execfile(migrscript, scriptlocals)
+            if funcname is not None:
+                try:
+                    func = scriptlocals[funcname]
+                    self.info('found %s in locals', funcname)
+                    assert callable(func), '%s (%s) is not callable' % (func, funcname)
+                except KeyError:
+                    self.critical('no %s in script %s', funcname, migrscript)
+                    return None
+                return func(*args, **kwargs)
+                    
+    def scripts_session(self, migrscripts):
+        """execute some scripts in a transaction"""
+        try:
+            for migrscript in migrscripts:
+                self.process_script(migrscript)
+            self.commit()
+        except:
+            self.rollback()
+            raise
+
+    def cmd_option_renamed(self, oldname, newname):
+        """a configuration option has been renamed"""
+        self._option_changes.append(('renamed', oldname, newname))
+
+    def cmd_option_group_change(self, option, oldgroup, newgroup):
+        """a configuration option has been moved in another group"""
+        self._option_changes.append(('moved', option, oldgroup, newgroup))
+
+    def cmd_option_added(self, optname):
+        """a configuration option has been added"""
+        self._option_changes.append(('added', optname))
+
+    def cmd_option_removed(self, optname):
+        """a configuration option has been removed"""
+        # can safely be ignored
+        #self._option_changes.append(('removed', optname))
+
+    def cmd_option_type_changed(self, optname, oldtype, newvalue):
+        """a configuration option's type has changed"""
+        self._option_changes.append(('typechanged', optname, oldtype, newvalue))
+        
+    def cmd_add_cube(self, cube):
+        origcubes = self.config.cubes()
+        newcubes = [p for p in self.config.expand_cubes([cube]) 
+                       if not p in origcubes]
+        if newcubes:
+            assert cube in newcubes
+            self.config.add_cubes(newcubes)
+        return newcubes
+
+    def cmd_remove_cube(self, cube):
+        origcubes = self.config._cubes
+        basecubes = list(origcubes)
+        for pkg in self.config.expand_cubes([cube]):
+            try:
+                basecubes.remove(pkg)
+            except ValueError:
+                continue
+        self.config._cubes = tuple(self.config.expand_cubes(basecubes))
+        removed = [p for p in origcubes if not p in self.config._cubes]
+        assert cube in removed, \
+               "can't remove cube %s, used as a dependancy" % cube
+        return removed
+    
+    def rewrite_configuration(self):
+        # import locally, show_diffs unavailable in gae environment
+        from cubicweb.toolsutils import show_diffs
+        configfile = self.config.main_config_file()
+        if self._option_changes:
+            read_old_config(self.config, self._option_changes, configfile)
+        newconfig = mktemp()
+        for optdescr in self._option_changes:
+            if optdescr[0] == 'added':
+                optdict = self.config.get_option_def(optdescr[1])
+                if optdict.get('default') is REQUIRED:
+                    self.config.input_option(option, optdict)
+        self.config.generate_config(open(newconfig, 'w'))
+        show_diffs(configfile, newconfig)
+        if exists(newconfig):
+            os.unlink(newconfig)
+
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(MigrationHelper, getLogger('cubicweb.migration'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/mixins.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,393 @@
+"""mixins of entity/views organized somewhat in a graph or tree structure
+
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.decorators import cached
+
+from cubicweb.common.selectors import interface_selector
+from cubicweb.interfaces import IWorkflowable, IEmailable, ITree
+
+
+class TreeMixIn(object):
+    """base tree-mixin providing the tree interface
+
+    This mixin has to be inherited explicitly and configured using the
+    tree_attribute, parent_target and children_target class attribute to
+    benefit from this default implementation
+    """
+    tree_attribute = None
+    # XXX misnamed
+    parent_target = 'subject'
+    children_target = 'object'
+    
+    def different_type_children(self, entities=True):
+        """return children entities of different type as this entity.
+        
+        according to the `entities` parameter, return entity objects or the
+        equivalent result set
+        """
+        res = self.related(self.tree_attribute, self.children_target,
+                           entities=entities)
+        if entities:
+            return [e for e in res if e.e_schema != self.e_schema]
+        return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.col)
+
+    def same_type_children(self, entities=True):
+        """return children entities of the same type as this entity.
+        
+        according to the `entities` parameter, return entity objects or the
+        equivalent result set
+        """
+        res = self.related(self.tree_attribute, self.children_target,
+                           entities=entities)
+        if entities:
+            return [e for e in res if e.e_schema == self.e_schema]
+        return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col)
+    
+    def iterchildren(self, _done=None):
+        if _done is None:
+            _done = set()
+        for child in self.children():
+            if child.eid in _done:
+                self.error('loop in %s tree', self.id.lower())
+                continue
+            yield child
+            _done.add(child.eid)
+
+    def prefixiter(self, _done=None):
+        if _done is None:
+            _done = set()
+        if self.eid in _done:
+            return
+        yield self
+        _done.add(self.eid)
+        for child in self.iterchildren(_done):
+            try:
+                for entity in child.prefixiter(_done):
+                    yield entity
+            except AttributeError:
+                pass
+    
+    @cached
+    def path(self):
+        """returns the list of eids from the root object to this object"""
+        path = []
+        parent = self
+        while parent:
+            if parent.eid in path:
+                self.error('loop in %s tree', self.id.lower())
+                break
+            path.append(parent.eid)
+            try:
+                # check we are not leaving the tree
+                if (parent.tree_attribute != self.tree_attribute or
+                    parent.parent_target != self.parent_target):
+                    break
+                parent = parent.parent()
+            except AttributeError:
+                break
+
+        path.reverse()
+        return path
+    
+    def notification_references(self, view):
+        """used to control References field of email send on notification
+        for this entity. `view` is the notification view.
+        
+        Should return a list of eids which can be used to generate message ids
+        of previously sent email
+        """
+        return self.path()[:-1]
+
+
+    ## ITree interface ########################################################
+    def parent(self):
+        """return the parent entity if any, else None (e.g. if we are on the
+        root
+        """
+        try:
+            return self.related(self.tree_attribute, self.parent_target,
+                                entities=True)[0]
+        except (KeyError, IndexError):
+            return None
+
+    def children(self, entities=True, sametype=False):
+        """return children entities
+
+        according to the `entities` parameter, return entity objects or the
+        equivalent result set
+        """
+        if sametype:
+            return self.same_type_children(entities)
+        else:
+            return self.related(self.tree_attribute, self.children_target,
+                                entities=entities)
+
+    def children_rql(self):
+        return self.related_rql(self.tree_attribute, self.children_target)
+    
+    def __iter__(self):
+        return self.iterchildren()
+
+    def is_leaf(self):
+        print '*' * 80
+        return len(self.children()) == 0
+
+    def is_root(self):
+        return self.parent() is None
+
+    def root(self):
+        """return the root object"""
+        return self.req.eid_rset(self.path()[0]).get_entity(0, 0)
+
+
+class WorkflowableMixIn(object):
+    """base mixin providing workflow helper methods for workflowable entities.
+    This mixin will be automatically set on class supporting the 'in_state'
+    relation (which implies supporting 'wf_info_for' as well)
+    """
+    __implements__ = (IWorkflowable,)
+    
+    @property
+    def state(self):
+        return self.in_state[0].name
+    
+    @property
+    def displayable_state(self):
+        return self.req._(self.state)
+
+    def wf_state(self, statename):
+        rset = self.req.execute('Any S, SN WHERE S name %(n)s, S state_of E, E name %(e)s',
+                                {'n': statename, 'e': str(self.e_schema)})
+        if rset:
+            return rset.get_entity(0, 0)
+        return None
+    
+    def wf_transition(self, trname):
+        rset = self.req.execute('Any T, TN WHERE T name %(n)s, T transition_of E, E name %(e)s',
+                                {'n': trname, 'e': str(self.e_schema)})
+        if rset:
+            return rset.get_entity(0, 0)
+        return None
+    
+    def change_state(self, stateeid, trcomment=None, trcommentformat=None):
+        """change the entity's state according to a state defined in given
+        parameters
+        """
+        if trcomment:
+            self.req.set_shared_data('trcomment', trcomment)
+        if trcommentformat:
+            self.req.set_shared_data('trcommentformat', trcommentformat)
+        self.req.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
+                         {'x': self.eid, 's': stateeid}, 'x')
+    
+    def can_pass_transition(self, trname):
+        """return the Transition instance if the current user can pass the
+        transition with the given name, else None
+        """
+        stateeid = self.in_state[0].eid
+        rset = self.req.execute('Any T,N,DS WHERE S allowed_transition T,'
+                                'S eid %(x)s,T name %(trname)s,ET name %(et)s,'
+                                'T name N,T destination_state DS,T transition_of ET',
+                                {'x': stateeid, 'et': str(self.e_schema),
+                                 'trname': trname}, 'x')
+        for tr in rset.entities():
+            if tr.may_be_passed(self.eid, stateeid):
+                return tr
+    
+    def latest_trinfo(self):
+        """return the latest transition information for this entity"""
+        return self.reverse_wf_info_for[-1]
+            
+    # specific vocabulary methods #############################################
+
+    def subject_in_state_vocabulary(self, rschema, limit=None):
+        """vocabulary method for the in_state relation, looking for
+        relation's object entities (i.e. self is the subject) according
+        to initial_state, state_of and next_state relation
+        """
+        if not self.has_eid() or not self.in_state:
+            # get the initial state
+            rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
+            rset = self.req.execute(rql, {'etype': str(self.e_schema)})
+            if rset:
+                return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])]
+            return []
+        results = []
+        for tr in self.in_state[0].transitions(self):
+            state = tr.destination_state[0]
+            results.append((state.view('combobox'), state.eid))
+        return sorted(results)
+            
+    # __method methods ########################################################
+    
+    def set_state(self, params=None):
+        """change the entity's state according to a state defined in given
+        parameters, used to be called using __method controler facility
+        """
+        params = params or self.req.form
+        self.change_state(int(params.pop('state')), params.get('trcomment'),
+                          params.get('trcommentformat'))
+        self.req.set_message(self.req._('__msg state changed'))
+
+
+
+class EmailableMixIn(object):
+    """base mixin providing the default get_email() method used by
+    the massmailing view
+
+    NOTE: The default implementation is based on the
+    primary_email / use_email scheme
+    """
+    __implements__ = (IEmailable,)
+    
+    def get_email(self):
+        if getattr(self, 'primary_email', None):
+            return self.primary_email[0].address
+        if getattr(self, 'use_email', None):
+            return self.use_email[0].address
+        return None
+
+    @classmethod
+    def allowed_massmail_keys(cls):
+        """returns a set of allowed email substitution keys
+
+        The default is to return the entity's attribute list but an
+        entity class might override this method to allow extra keys.
+        For instance, the Person class might want to return a `companyname`
+        key.
+        """
+        return set(rs.type for rs, _ in cls.e_schema.attribute_definitions())
+
+    def as_email_context(self):
+        """returns the dictionary as used by the sendmail controller to
+        build email bodies.
+        
+        NOTE: the dictionary keys should match the list returned by the
+        `allowed_massmail_keys` method.
+        """
+        return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
+
+
+    
+MI_REL_TRIGGERS = {
+    ('in_state',    'subject'): WorkflowableMixIn,
+    ('primary_email',   'subject'): EmailableMixIn,
+    ('use_email',   'subject'): EmailableMixIn,
+    }
+
+
+
+def _done_init(done, view, row, col):
+    """handle an infinite recursion safety belt"""
+    if done is None:
+        done = set()
+    entity = view.entity(row, col)
+    if entity.eid in done:
+        msg = entity.req._('loop in %s relation (%s)'
+                           % (entity.tree_attribute, entity.eid))
+        return None, msg
+    done.add(entity.eid)
+    return done, entity
+
+
+class TreeViewMixIn(object):
+    """a recursive tree view"""
+    id = 'tree'
+    item_vid = 'treeitem'
+    __selectors__ = (interface_selector,)
+    accepts_interfaces = (ITree,)
+
+    def call(self, done=None, **kwargs):
+        if done is None:
+            done = set()
+        super(TreeViewMixIn, self).call(done=done, **kwargs)
+            
+    def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+        done, entity = _done_init(done, self, row, col)
+        if done is None:
+            # entity is actually an error message
+            self.w(u'<li class="badcontent">%s</li>' % entity)
+            return
+        self.open_item(entity)
+        entity.view(vid or self.item_vid, w=self.w, **kwargs)
+        relatedrset = entity.children(entities=False)
+        self.wview(self.id, relatedrset, 'null', done=done, **kwargs)
+        self.close_item(entity)
+
+    def open_item(self, entity):
+        self.w(u'<li class="%s">\n' % entity.id.lower())
+    def close_item(self, entity):
+        self.w(u'</li>\n')
+
+
+class TreePathMixIn(object):
+    """a recursive path view"""
+    id = 'path'
+    item_vid = 'oneline'
+    separator = u'&nbsp;&gt;&nbsp;'
+
+    def call(self, **kwargs):
+        self.w(u'<div class="pathbar">')
+        super(TreePathMixIn, self).call(**kwargs)
+        self.w(u'</div>')
+        
+    def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+        done, entity = _done_init(done, self, row, col)
+        if done is None:
+            # entity is actually an error message
+            self.w(u'<span class="badcontent">%s</span>' % entity)
+            return
+        parent = entity.parent()
+        if parent:
+            parent.view(self.id, w=self.w, done=done)
+            self.w(self.separator)
+        entity.view(vid or self.item_vid, w=self.w)
+
+
+class ProgressMixIn(object):
+    """provide default implementations for IProgress interface methods"""
+
+    @property
+    @cached
+    def cost(self):
+        return self.progress_info()['estimated']
+
+    @property
+    @cached
+    def revised_cost(self):
+        return self.progress_info().get('estimatedcorrected', self.cost)
+
+    @property
+    @cached
+    def done(self):
+        return self.progress_info()['done']
+
+    @property
+    @cached
+    def todo(self):
+        return self.progress_info()['todo']
+
+    @cached
+    def progress_info(self):
+        raise NotImplementedError()
+
+    def finished(self):
+        return not self.in_progress()
+
+    def in_progress(self):
+        raise NotImplementedError()
+    
+    def progress(self):
+        try:
+            return 100. * self.done / self.revised_cost
+        except ZeroDivisionError:
+            # total cost is 0 : if everything was estimated, task is completed
+            if self.progress_info().get('notestmiated'):
+                return 0.
+            return 100
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/mttransforms.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,82 @@
+"""mime type transformation engine for cubicweb, based on mtconverter
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab import mtconverter
+
+from logilab.mtconverter.engine import TransformEngine
+from logilab.mtconverter.transform import Transform
+from logilab.mtconverter import (register_base_transforms,
+                                 register_pil_transforms, 
+                                 register_pygments_transforms)
+
+from cubicweb.common.uilib import rest_publish, html_publish, remove_html_tags
+
+HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
+
+# CubicWeb specific transformations
+
+class rest_to_html(Transform):
+    inputs = ('text/rest', 'text/x-rst')
+    output = 'text/html'
+    def _convert(self, trdata):
+        return rest_publish(trdata.appobject, trdata.decode())
+
+class html_to_html(Transform):
+    inputs = HTML_MIMETYPES
+    output = 'text/html'
+    def _convert(self, trdata):
+        return html_publish(trdata.appobject, trdata.data)
+
+class ept_to_html(Transform):
+    inputs = ('text/cubicweb-page-template',)
+    output = 'text/html'
+    output_encoding = 'utf-8'
+    def _convert(self, trdata):
+        from cubicweb.common.tal import compile_template
+        value = trdata.encode(self.output_encoding)
+        return trdata.appobject.tal_render(compile_template(value), {})
+
+
+# Instantiate and configure the transformation engine
+
+mtconverter.UNICODE_POLICY = 'replace'
+
+ENGINE = TransformEngine()
+ENGINE.add_transform(rest_to_html())
+ENGINE.add_transform(html_to_html())
+ENGINE.add_transform(ept_to_html())
+
+if register_pil_transforms(ENGINE, verb=False):
+    HAS_PIL_TRANSFORMS = True
+else:
+    HAS_PIL_TRANSFORMS = False
+    
+try:
+    from logilab.mtconverter.transforms import pygmentstransforms
+    for mt in ('text/plain',) + HTML_MIMETYPES:
+        try:
+            pygmentstransforms.mimetypes.remove(mt)
+        except ValueError:
+            continue
+    register_pygments_transforms(ENGINE, verb=False)
+
+    def patch_convert(cls):
+        def _convert(self, trdata, origconvert=cls._convert):
+            try:
+                trdata.appobject.req.add_css('pygments.css')
+            except AttributeError: # session has no add_css, only http request
+                pass
+            return origconvert(self, trdata)
+        cls._convert = _convert
+    patch_convert(pygmentstransforms.PygmentsHTMLTransform)
+    
+    HAS_PYGMENTS_TRANSFORMS = True
+except ImportError:
+    HAS_PYGMENTS_TRANSFORMS = False
+    
+register_base_transforms(ENGINE, verb=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/registerers.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,205 @@
+"""This file contains some basic registerers required by application objects
+registry to handle registration at startup time.
+
+A registerer is responsible to tell if an object should be registered according
+to the application's schema or to already registered object
+
+:organization: Logilab
+:copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.vregistry import registerer
+
+
+def _accepts_interfaces(obj):
+    return sorted(getattr(obj, 'accepts_interfaces', ()))
+
+
+class yes_registerer(registerer):
+    """register without any other action"""
+    def do_it_yourself(self, registered):
+        return self.vobject
+
+class priority_registerer(registerer):
+    """systematically kick previous registered class and register the
+    wrapped class (based on the fact that directory containing vobjects
+    are loaded from the most generic to the most specific).
+
+    This is usually for templates or startup views where we want to
+    keep only the latest in the load path
+    """
+    def do_it_yourself(self, registered):
+        if registered:
+            if len(registered) > 1:
+                self.warning('priority_registerer found more than one registered objects '
+                             '(registerer monkey patch ?)')
+            for regobj in registered[:]:
+                self.kick(registered, regobj)
+        return self.vobject
+    
+    def remove_equivalents(self, registered):
+        for _obj in registered[:]:
+            if self.equivalent(_obj):
+                self.kick(registered, _obj)
+                break
+            
+    def remove_all_equivalents(self, registered):
+        for _obj in registered[:]:
+            if _obj is self.vobject:
+                continue
+            if self.equivalent(_obj):
+                self.kick(registered, _obj)
+
+    def equivalent(self, other):
+        raise NotImplementedError(self, self.vobject)
+
+
+class kick_registerer(registerer):
+    """systematically kick previous registered class and don't register the
+    wrapped class. This is temporarily used to discard library object registrable
+    but that we don't want to use
+    """
+    def do_it_yourself(self, registered):
+        if registered:
+            self.kick(registered, registered[-1])
+        return 
+    
+
+class accepts_registerer(priority_registerer):
+    """register according to the .accepts attribute of the wrapped
+    class, which should be a tuple refering some entity's types
+
+    * if no type is defined the application'schema, skip the wrapped
+      class
+    * if the class defines a requires attribute, each entity type defined
+      in the requires list must be in the schema
+    * if an object previously registered has equivalent .accepts
+      attribute, kick it out
+    * register
+    """
+    def do_it_yourself(self, registered):
+        # if object is accepting interface, we have register it now and
+        # remove it latter if no object is implementing accepted interfaces
+        if _accepts_interfaces(self.vobject):
+            return self.vobject
+        if not 'Any' in self.vobject.accepts:
+            for ertype in self.vobject.accepts:
+                if ertype in self.schema:
+                    break
+            else:
+                self.skip()
+                return None
+        for required in getattr(self.vobject, 'requires', ()):
+            if required not in self.schema:
+                self.skip()
+                return
+        self.remove_equivalents(registered)
+        return self.vobject
+    
+    def equivalent(self, other):
+        if _accepts_interfaces(self.vobject) != _accepts_interfaces(other):
+            return False
+        try:
+            newaccepts = list(other.accepts)
+            for etype in self.vobject.accepts:
+                try:
+                    newaccepts.remove(etype)
+                except ValueError:
+                    continue
+            if newaccepts:
+                other.accepts = tuple(newaccepts)
+                return False
+            return True
+        except AttributeError:
+            return False
+
+
+class id_registerer(priority_registerer):
+    """register according to the "id" attribute of the wrapped class,
+    refering to an entity type.
+    
+    * if the type is not Any and is not defined the application'schema,
+      skip the wrapped class
+    * if an object previously registered has the same .id attribute,
+      kick it out
+    * register
+    """
+    def do_it_yourself(self, registered):
+        etype = self.vobject.id
+        if etype != 'Any' and not self.schema.has_entity(etype):
+            self.skip()
+            return
+        self.remove_equivalents(registered)
+        return self.vobject
+    
+    def equivalent(self, other):
+        return other.id == self.vobject.id
+
+
+class etype_rtype_registerer(registerer):
+    """registerer handling optional .etype and .rtype attributes.:
+    
+    * if .etype is set and is not an entity type defined in the
+      application schema, skip the wrapped class
+    * if .rtype or .relname is set and is not a relation type defined in
+      the application schema, skip the wrapped class
+    * register
+    """
+    def do_it_yourself(self, registered):
+        cls = self.vobject
+        if hasattr(cls, 'etype'):
+            if not self.schema.has_entity(cls.etype):
+                return
+        rtype = getattr(cls, 'rtype', None)
+        if rtype and not self.schema.has_relation(rtype):
+            return
+        return cls
+
+class etype_rtype_priority_registerer(etype_rtype_registerer):
+    """add priority behaviour to the etype_rtype_registerer
+    """
+    def do_it_yourself(self, registered):
+        cls = super(etype_rtype_priority_registerer, self).do_it_yourself(registered)
+        if cls:
+            registerer = priority_registerer(self.registry, cls)
+            cls = registerer.do_it_yourself(registered)
+        return cls
+
+class action_registerer(etype_rtype_registerer):
+    """'all in one' actions registerer, handling optional .accepts,
+    .etype and .rtype attributes:
+    
+    * if .etype is set and is not an entity type defined in the
+      application schema, skip the wrapped class
+    * if .rtype or .relname is set and is not a relation type defined in
+      the application schema, skip the wrapped class
+    * if .accepts is set, delegate to the accepts_registerer
+    * register
+    """
+    def do_it_yourself(self, registered):
+        cls = super(action_registerer, self).do_it_yourself(registered)
+        if hasattr(cls, 'accepts'):
+            registerer = accepts_registerer(self.registry, cls)
+            cls = registerer.do_it_yourself(registered)
+        return cls
+
+
+class extresources_registerer(priority_registerer):
+    """'registerer according to a .need_resources attributes which
+    should list necessary resource identifiers for the wrapped object.
+    If one of its resources is missing, don't register
+    """
+    def do_it_yourself(self, registered):
+        if not hasattr(self.config, 'has_resource'):
+            return
+        for resourceid in self.vobject.need_resources:
+            if not self.config.has_resource(resourceid):
+                return
+        return super(extresources_registerer, self).do_it_yourself(registered)
+    
+
+__all__ = [cls.__name__ for cls in globals().values()
+           if isinstance(cls, type) and issubclass(cls, registerer)
+           and not cls is registerer]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/rest.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,223 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.common.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+    if isinstance(v, str):
+        states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    try:
+        try:
+            eid_num, rest = text.split(u':', 1)
+        except:
+            eid_num, rest = text, '#'+text
+        eid_num = int(eid_num)
+        if eid_num < 0:
+            raise ValueError
+    except ValueError:
+        msg = inliner.reporter.error(
+            'EID number must be a positive number; "%s" is invalid.'
+            % text, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    # Base URL mainly used by inliner.pep_reference; so this is correct:
+    context = inliner.document.settings.context
+    refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+    ref = refedentity.absolute_url()
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    text = text.strip()
+    try:
+        wikiid, rest = text.split(u':', 1)
+    except:
+        wikiid, rest = text, text
+    context = inliner.document.settings.context
+    cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+                                   {'id': wikiid})
+    if cardrset:
+        ref = cardrset.get_entity(0, 0).absolute_url()
+    else:
+        schema = context.schema
+        if schema.eschema('Card').has_perm(context.req, 'add'):
+            ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+        else:
+            ref = '#'
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    """Include a reST file as part of the content of this reST file.
+
+    same as standard include directive but using config.locate_doc_resource to
+    get actual file to include.
+
+    Most part of this implementation is copied from `include` directive defined
+    in `docutils.parsers.rst.directives.misc`
+    """
+    context = state.document.settings.context
+    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.req.lang, context.vreg.property_value('ui.language')),
+                      context.config.available_languages()):
+        rid = '%s_%s.rst' % (fid, lang)
+        resourcedir = context.config.locate_doc_file(rid)
+        if resourcedir:
+            break
+    else:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\nno resource matching %s.'
+              % (name, fid),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    path = join(resourcedir, rid)
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    try:
+        state.document.settings.record_dependencies.add(path)
+        include_file = io.FileInput(
+            source_path=path, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler,
+            handle_io_errors=None)
+    except IOError, error:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\n%s: %s.'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    try:
+        include_text = include_file.read()
+    except UnicodeError, error:
+        severe = state_machine.reporter.severe(
+              'Problem with "%s" directive:\n%s: %s'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    if options.has_key('literal'):
+        literal_block = nodes.literal_block(include_text, include_text,
+                                            source=path)
+        literal_block.line = 1
+        return literal_block
+    else:
+        include_lines = statemachine.string2lines(include_text,
+                                                  convert_whitespace=1)
+        state_machine.insert_input(include_lines, path)
+        return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+                              'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+class CubicWebReSTParser(Parser):
+    """The (customized) reStructuredText parser."""
+
+    def __init__(self):
+        self.initial_state = 'Body'
+        self.state_classes = states.state_classes
+        self.inliner = states.Inliner()
+        self.statemachine = states.RSTStateMachine(
+              state_classes=self.state_classes,
+              initial_state=self.initial_state,
+              debug=0)
+
+    def parse(self, inputstring, document):
+        """Parse `inputstring` and populate `document`, a document tree."""
+        self.setup_parse(inputstring, document)
+        inputlines = statemachine.string2lines(inputstring,
+                                               convert_whitespace=1)
+        self.statemachine.run(inputlines, document, inliner=self.inliner)
+        self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+    """publish a string formatted as ReStructured Text to HTML
+    
+    :type context: a cubicweb application object
+
+    :type data: str
+    :param data: some ReST text
+
+    :rtype: unicode
+    :return:
+      the data formatted as HTML or the original data if an error occured
+    """
+    req = context.req
+    if isinstance(data, unicode):
+        encoding = 'unicode'
+    else:
+        encoding = req.encoding
+    settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+                'warning_stream': StringIO(), 'context': context,
+                # 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, 
+                }
+    if context:
+        if hasattr(req, 'url'):
+            base_url = req.url()
+        elif hasattr(context, 'absolute_url'):
+            base_url = context.absolute_url()
+        else:
+            base_url = req.base_url()
+    else:
+        base_url = None
+    try:
+        return publish_string(writer=Writer(base_url=base_url),
+                              parser=_REST_PARSER, source=data,
+                              settings_overrides=settings)
+    except Exception:
+        LOGGER.exception('error while publishing ReST text')
+        if not isinstance(data, unicode):
+            data = unicode(data, encoding, 'replace')
+        return html_escape(req._('error while publishing ReST text')
+                           + '\n\n' + data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/schema.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,3 @@
+from warnings import warn
+warn('moved to cubicweb.schema', DeprecationWarning, stacklevel=2)
+from cubicweb.schema import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/selectors.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,413 @@
+"""This file contains some basic selectors required by application objects.
+
+A selector is responsible to score how well an object may be used with a
+given result set (publishing time selection)
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+from logilab.common.compat import all
+
+from cubicweb import Unauthorized
+from cubicweb.cwvreg import DummyCursorError
+from cubicweb.vregistry import chainall, chainfirst
+from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.schema import split_expression
+
+
+def lltrace(selector):
+    # don't wrap selectors if not in development mode
+    if CubicWebConfiguration.mode == 'installed':
+        return selector
+    def traced(cls, *args, **kwargs):
+        ret = selector(cls, *args, **kwargs)
+        cls.lldebug('selector %s returned %s for %s', selector.__name__, ret, cls)
+        return ret
+    return traced
+    
+# very basic selectors ########################################################
+
+def yes_selector(cls, *args, **kwargs):
+    """accept everything"""
+    return 1
+
+@lltrace
+def norset_selector(cls, req, rset, *args, **kwargs):
+    """accept no result set"""
+    if rset is None:
+        return 1
+    return 0
+
+@lltrace
+def rset_selector(cls, req, rset, *args, **kwargs):
+    """accept result set, whatever the number of result"""
+    if rset is not None:
+        return 1
+    return 0
+
+@lltrace
+def anyrset_selector(cls, req, rset, *args, **kwargs):
+    """accept any non empty result set"""
+    if rset and rset.rowcount: # XXX if rset is not None and rset.rowcount > 0:
+        return 1
+    return 0
+    
+@lltrace
+def emptyrset_selector(cls, req, rset, *args, **kwargs):
+    """accept empty result set"""
+    if rset is not None and rset.rowcount == 0:
+        return 1
+    return 0
+
+@lltrace
+def onelinerset_selector(cls, req, rset, row=None, *args, **kwargs):
+    """accept result set with a single line of result"""
+    if rset is not None and (row is not None or rset.rowcount == 1):
+        return 1
+    return 0
+
+@lltrace
+def twolinerset_selector(cls, req, rset, *args, **kwargs):
+    """accept result set with at least two lines of result"""
+    if rset is not None and rset.rowcount > 1:
+        return 1
+    return 0
+
+@lltrace
+def twocolrset_selector(cls, req, rset, *args, **kwargs):
+    """accept result set with at least one line and two columns of result"""
+    if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
+        return 1
+    return 0
+
+@lltrace
+def largerset_selector(cls, req, rset, *args, **kwargs):
+    """accept result sets with more rows than the page size
+    """
+    if rset is None or len(rset) <= req.property_value('navigation.page-size'):
+        return 0
+    return 1
+
+@lltrace
+def sortedrset_selector(cls, req, rset, row=None, col=None):
+    """accept sorted result set"""
+    rqlst = rset.syntax_tree()
+    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+        return 0
+    return 2
+
+@lltrace
+def oneetyperset_selector(cls, req, rset, *args, **kwargs):
+    """accept result set where entities in the first columns are all of the
+    same type
+    """
+    if len(rset.column_types(0)) != 1:
+        return 0
+    return 1
+
+@lltrace
+def multitype_selector(cls, req, rset, **kwargs):
+    """accepts resultsets containing several entity types"""
+    if rset:
+        etypes = rset.column_types(0)
+        if len(etypes) > 1:
+            return 1
+    return 0
+
+@lltrace
+def searchstate_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """extend the anyrset_selector by checking if the current search state
+    is in a .search_states attribute of the wrapped class
+
+    search state should be either 'normal' or 'linksearch' (eg searching for an
+    object to create a relation with another)
+    """
+    try:
+        if not req.search_state[0] in cls.search_states:
+            return 0
+    except AttributeError:
+        return 1 # class don't care about search state, accept it
+    return 1
+
+@lltrace
+def anonymous_selector(cls, req, *args, **kwargs):
+    """accept if user is anonymous"""
+    if req.cnx.anonymous_connection:
+        return 1
+    return 0
+
+@lltrace
+def not_anonymous_selector(cls, req, *args, **kwargs):
+    """accept if user is anonymous"""
+    return not anonymous_selector(cls, req, *args, **kwargs)
+
+
+# not so basic selectors ######################################################
+
+@lltrace
+def req_form_params_selector(cls, req, *args, **kwargs):
+    """check if parameters specified by the form_params attribute on
+    the wrapped class are specified in request form parameters
+    """
+    score = 0
+    for param in cls.form_params:
+        val = req.form.get(param)
+        if not val:
+            return 0
+        score += 1
+    return score + 1
+
+@lltrace
+def kwargs_selector(cls, req, *args, **kwargs):
+    """check if arguments specified by the expected_kwargs attribute on
+    the wrapped class are specified in given named parameters
+    """
+    values = []
+    for arg in cls.expected_kwargs:
+        if not arg in kwargs:
+            return 0
+    return 1
+
+@lltrace
+def etype_form_selector(cls, req, *args, **kwargs):
+    """check etype presence in request form *and* accepts conformance"""
+    if 'etype' not in req.form and 'etype' not in kwargs:
+        return 0
+    try:
+        etype = req.form['etype']
+    except KeyError:
+        etype = kwargs['etype']
+    # value is a list or a tuple if web request form received several
+    # values for etype parameter
+    assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
+    if 'Any' in cls.accepts:
+        return 1
+    # no Any found, we *need* exact match
+    if etype not in cls.accepts:
+        return 0
+    # exact match must return a greater value than 'Any'-match
+    return 2
+
+@lltrace
+def _nfentity_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """accept non final entities
+    if row is not specified, use the first one
+    if col is not specified, use the first one
+    """
+    etype = rset.description[row or 0][col or 0]
+    if etype is None: # outer join
+        return 0
+    if cls.schema.eschema(etype).is_final():
+        return 0
+    return 1
+
+@lltrace
+def _rqlcondition_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """accept single entity result set if the entity match an rql condition
+    """
+    if cls.condition:
+        eid = rset[row or 0][col or 0]
+        if 'U' in frozenset(split_expression(cls.condition)):
+            rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition
+        else:
+            rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition
+        try:
+            return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x'))
+        except Unauthorized:
+            return 0
+        
+    return 1
+
+@lltrace
+def _interface_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """accept uniform result sets, and apply the following rules:
+
+    * wrapped class must have a accepts_interfaces attribute listing the
+      accepted ORed interfaces
+    * if row is None, return the sum of values returned by the method
+      for each entity's class in the result set. If any score is 0,
+      return 0.
+    * if row is specified, return the value returned by the method with
+      the entity's class of this row
+    """
+    score = 0
+    # check 'accepts' to give priority to more specific classes
+    if row is None:
+        for etype in rset.column_types(col or 0):
+            eclass = cls.vreg.etype_class(etype)
+            escore = 0
+            for iface in cls.accepts_interfaces:
+                escore += iface.is_implemented_by(eclass)
+            if not escore:
+                return 0
+            score += escore
+            if eclass.id in getattr(cls, 'accepts', ()):
+                score += 2
+        return score + 1
+    etype = rset.description[row][col or 0]
+    if etype is None: # outer join
+        return 0
+    eclass = cls.vreg.etype_class(etype)
+    for iface in cls.accepts_interfaces:
+        score += iface.is_implemented_by(eclass)
+    if score:
+        if eclass.id in getattr(cls, 'accepts', ()):
+            score += 2
+        else:
+            score += 1
+    return score
+
+@lltrace
+def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
+    if row is None:
+        rows = xrange(rset.rowcount)
+    else:
+        rows = (row,)
+    for row in rows:
+        try:
+            score = cls.score_entity(rset.get_entity(row, col or 0))
+        except DummyCursorError:
+            # get a dummy cursor error, that means we are currently
+            # using a dummy rset to list possible views for an entity
+            # type, not for an actual result set. In that case, we
+            # don't care of the value, consider the object as selectable
+            return 1
+        if not score:
+            return 0
+    return 1
+
+@lltrace
+def accept_rset_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """simply delegate to cls.accept_rset method"""
+    return cls.accept_rset(req, rset, row=row, col=col)
+
+@lltrace
+def but_etype_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """restrict the searchstate_accept_one_selector to exclude entity's type
+    refered by the .etype attribute
+    """
+    if rset.description[row or 0][col or 0] == cls.etype:
+        return 0
+    return 1
+
+@lltrace
+def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """only check if the user has read access on the entity's type refered
+    by the .etype attribute and on the relations's type refered by the
+    .rtype attribute if set.
+    """
+    schema = cls.schema
+    perm = getattr(cls, 'require_permission', 'read')
+    if hasattr(cls, 'etype'):
+        eschema = schema.eschema(cls.etype)
+        if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
+            return 0
+    if hasattr(cls, 'rtype'):
+        if not schema.rschema(cls.rtype).has_perm(req, perm):
+            return 0
+    return 1
+
+@lltrace
+def accept_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
+    if hasattr(cls, 'rtype'):
+        if row is None:
+            for etype in rset.column_types(col or 0):
+                if not cls.relation_possible(etype):
+                    return 0
+        elif not cls.relation_possible(rset.description[row][col or 0]):
+            return 0
+    return 1
+
+@lltrace
+def one_has_relation_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """check if the user has read access on the relations's type refered by the
+    .rtype attribute of the class, and if at least one entity type in the
+    result set has this relation.
+    """
+    schema = cls.schema
+    perm = getattr(cls, 'require_permission', 'read')
+    if not schema.rschema(cls.rtype).has_perm(req, perm):
+        return 0
+    if row is None:
+        for etype in rset.column_types(col or 0):
+            if cls.relation_possible(etype):
+                return 1
+    elif cls.relation_possible(rset.description[row][col or 0]):
+        return 1
+    return 0
+
+@lltrace
+def in_group_selector(cls, req, rset=None, row=None, col=None, **kwargs):
+    """select according to user's groups"""
+    if not cls.require_groups:
+        return 1
+    user = req.user
+    if user is None:
+        return int('guests' in cls.require_groups)
+    score = 0
+    if 'owners' in cls.require_groups and rset:
+        if row is not None:
+            eid = rset[row][col or 0]
+            if user.owns(eid):
+                score = 1
+        else:
+            score = all(user.owns(r[col or 0]) for r in rset)
+    score += user.matching_groups(cls.require_groups)
+    if score:
+        # add 1 so that an object with one matching group take priority
+        # on an object without require_groups
+        return score + 1 
+    return 0
+
+@lltrace
+def add_etype_selector(cls, req, rset, row=None, col=None, **kwargs):
+    """only check if the user has add access on the entity's type refered
+    by the .etype attribute.
+    """
+    if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
+        return 0
+    return 1
+
+@lltrace
+def contextprop_selector(cls, req, rset, row=None, col=None, context=None,
+                          **kwargs):
+    propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
+    if not propval:
+        propval = cls.context
+    if context is not None and propval is not None and context != propval:
+        return 0
+    return 1
+
+@lltrace
+def primaryview_selector(cls, req, rset, row=None, col=None, view=None,
+                          **kwargs):
+    if view is not None and not view.is_primary():
+        return 0
+    return 1
+
+
+# compound selectors ##########################################################
+
+nfentity_selector = chainall(anyrset_selector, _nfentity_selector)
+interface_selector = chainall(nfentity_selector, _interface_selector)
+
+accept_selector = chainall(nfentity_selector, accept_rset_selector)
+accept_one_selector = chainall(onelinerset_selector, accept_selector)