backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 02 Jun 2010 13:02:47 +0200
changeset 5627 a7e40cccdc9b
parent 5611 55366f5b7a9f (current diff)
parent 5626 c80e8aa1935e (diff)
child 5628 56042c65d511
backport stable
cwctl.py
cwvreg.py
devtools/devctl.py
doc/book/en/devrepo/vreg.rst
selectors.py
server/repository.py
server/sources/native.py
server/sources/storages.py
server/test/unittest_storage.py
vregistry.py
web/views/autoform.py
web/views/basecontrollers.py
web/views/navigation.py
web/views/tableview.py
--- a/cwctl.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/cwctl.py	Wed Jun 02 13:02:47 2010 +0200
@@ -201,7 +201,7 @@
     def run(self, args):
         """run the command with its specific arguments"""
         if args:
-            raise BadCommandUsage('Too much arguments')
+            raise BadCommandUsage('Too many arguments')
         from cubicweb.migration import ConfigurationProblem
         print 'CubicWeb %s (%s mode)' % (cwcfg.cubicweb_version(), cwcfg.mode)
         print
--- a/cwvreg.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/cwvreg.py	Wed Jun 02 13:02:47 2010 +0200
@@ -20,7 +20,7 @@
 The `VRegistry`
 ---------------
 
-The `VRegistry` can be seen as a two levels dictionary. It contains
+The `VRegistry` can be seen as a two-level dictionary. It contains
 all dynamically loaded objects (subclasses of :ref:`appobject`) to
 build a |cubicweb| application. Basically:
 
@@ -49,12 +49,12 @@
 .. index::
    vregistry: registration_callback
 
-On startup |cubicweb| loads application objects defined in its library
+On startup, |cubicweb| loads application objects defined in its library
 and in cubes used by the instance. Application objects from the
 library are loaded first, then those provided by cubes are loaded in
 dependency order (e.g. if your cube depends on an other, objects from
-the dependency will be loaded first). Cube's modules or packages where
-appobject are looked for is explained in :ref:`cubelayout`.
+the dependency will be loaded first). The layout of the modules or packages
+in a cube  is explained in :ref:`cubelayout`.
 
 For each module:
 
@@ -143,20 +143,22 @@
 
   - else, the higher the score, the better the object suits the context
 
-* the object with the higher score is selected.
+* the object with the highest score is selected.
 
 .. Note::
 
-  When no score is higher than the others, an exception is raised in development
+  When no single object has the highest score, an exception is raised in development
   mode to let you know that the engine was not able to identify the view to
   apply. This error is silenced in production mode and one of the objects with
-  the higher score is picked.
+  the highest score is picked.
 
-  In such cases you would need to review your design and make sure your selectors
-  or appobjects are properly defined.
+  In such cases you would need to review your design and make sure
+  your selectors or appobjects are properly defined. Such an error is
+  typically caused by either forgetting to change the __regid__ in a
+  derived class, or by having copy-pasted some code.
 
-For instance, if you are selecting the primary (eg `__regid__ =
-'primary'`) view (eg `__registry__ = 'views'`) for a result set
+For instance, if you are selecting the primary (`__regid__ =
+'primary'`) view (`__registry__ = 'views'`) for a result set
 containing a `Card` entity, two objects will probably be selectable:
 
 * the default primary view (`__select__ = implements('Any')`), meaning
@@ -166,9 +168,8 @@
   meaning that the object is selectable for Card entities
 
 Other primary views specific to other entity types won't be selectable in this
-case. Among selectable objects, the implements selector will return a higher
-score than the second view since it's more specific, so it will be selected as
-expected.
+case. Among selectable objects, the `implements('Card')` selector will return a higher
+score since it's more specific, so the correct view will be selected as expected.
 
 .. _SelectionAPI:
 
@@ -178,7 +179,7 @@
 Here is the selection API you'll get on every registry. Some of them, as the
 'etypes' registry, containing entity classes, extend it. In those methods,
 `*args, **kwargs` is what we call the **context**. Those arguments are given to
-selectors that will inspect there content and return a score accordingly.
+selectors that will inspect their content and return a score accordingly.
 
 .. automethod:: cubicweb.vregistry.Registry.select
 
--- a/devtools/devctl.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/devtools/devctl.py	Wed Jun 02 13:02:47 2010 +0200
@@ -274,7 +274,7 @@
     def run(self, args):
         """run the command with its specific arguments"""
         if args:
-            raise BadCommandUsage('Too much arguments')
+            raise BadCommandUsage('Too many arguments')
         import shutil
         import tempfile
         import yams
--- a/doc/book/en/devrepo/cubes/available-cubes.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/devrepo/cubes/available-cubes.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -2,8 +2,8 @@
 Available cubes
 ---------------
 
-An instance is based on several basic cubes. In the set of available
-basic cubes we can find for example :
+An instance is made of several basic cubes. In the set of available
+basic cubes we can find for example:
 
 Base entity types
 ~~~~~~~~~~~~~~~~~
@@ -22,14 +22,14 @@
 
 Classification
 ~~~~~~~~~~~~~~
-* folder_: Folder (to organize things but grouping them in folders)
+* folder_: Folder (to organize things by grouping them in folders)
 * keyword_: Keyword (to define classification schemes)
 * tag_: Tag (to tag anything)
 
 Other features
 ~~~~~~~~~~~~~~
 * basket_: Basket (like a shopping cart)
-* blog_: a blogging system uxing Blog and BlogEntry entity types
+* blog_: a blogging system using Blog and BlogEntry entity types
 * comment_: system to attach comment threads to entities)
 * email_: archiving management for emails (`Email`, `Emailpart`,
   `Emailthread`), trigger action in cubicweb through email
@@ -55,8 +55,9 @@
 .. _task: http://www.cubicweb.org/project/cubicweb-task
 .. _zone: http://www.cubicweb.org/project/cubicweb-zone
 
-To declare the use of a component, once installed, add the name of the component
-to the variable `__use__` in the file `__pkginfo__.py` of your own component.
+To declare the use of a cube, once installed, add the name of the cube
+and its dependency relation in the `__depends_cubes__` dictionary
+defined in the file `__pkginfo__.py` of your own component.
 
 .. note::
   The listed cubes above are available as debian-packages on `CubicWeb's forge`_.
--- a/doc/book/en/devrepo/cubes/cc-newcube.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/devrepo/cubes/cc-newcube.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -14,15 +14,15 @@
   hg ci
 
 If all went well, you should see the cube you just created in the list
-returned by ``cubicweb-ctl list`` in the section *Available cubes*,
-and if it is not the case please refer to :ref:`ConfigurationEnv`.
+returned by ``cubicweb-ctl list`` in the  *Available cubes* section. 
+If not, please refer to :ref:`ConfigurationEnv`.
 
 To reuse an existing cube, add it to the list named
-``__depends_cubes__`` and defined in :file:`__pkginfo__.py`.  This
-variable is used for the instance packaging (dependencies handled by
-system utility tools such as APT) and the usable cubes at the time the
-base is created (import_erschema('MyCube') will not properly work
-otherwise).
+``__depends_cubes__`` which is defined in :file:`__pkginfo__.py`.
+This variable is used for the instance packaging (dependencies handled
+by system utility tools such as APT) and to find used cubes when the
+database for the instance is created (import_erschema('MyCube') will
+not properly work otherwise).
 
 .. note::
 
--- a/doc/book/en/devrepo/cubes/layout.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/devrepo/cubes/layout.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -70,6 +70,7 @@
   |-- entities.py
   |-- hooks.py
   `-- views/
+      |-- __init__.py
       |-- forms.py
       |-- primary.py
       `-- widgets.py
@@ -78,14 +79,14 @@
 where :
 
 * ``schema`` contains the schema definition (server side only)
-* ``entities`` contains the entities definition (server side and web interface)
+* ``entities`` contains the entity definitions (server side and web interface)
 * ``hooks`` contains hooks and/or views notifications (server side only)
 * ``views`` contains the web interface components (web interface only)
 * ``test`` contains tests related to the cube (not installed)
 * ``i18n`` contains message catalogs for supported languages (server side and
   web interface)
-* ``data`` contains data files for static content (images, css, javascripts)
-  ...(web interface only)
+* ``data`` contains data files for static content (images, css,
+  javascript code)...(web interface only)
 * ``migration`` contains initialization files for new instances (``postcreate.py``)
   and a file containing dependencies of the component depending on the version
   (``depends.map``)
@@ -102,10 +103,12 @@
 The :file:`__init__.py` and :file:`site_cubicweb.py` files
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. XXX WRITEME
+
 The :file:`__pkginfo__.py` file
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-It contains metadata describing your cubes, mostly useful for
+It contains metadata describing your cube, mostly useful for
 packaging.
 
 
--- a/doc/book/en/devrepo/vreg.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/devrepo/vreg.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -1,8 +1,8 @@
 The VRegistry, selectors and application objects
 ================================================
 
-This chapter talks about core concepts of the |cubicweb| framework,
-that make it different from other frameworks (and maybe not easy to
+This chapter deals with some of the  core concepts of the |cubicweb| framework
+which make it different from other frameworks (and maybe not easy to
 grasp at a first glance). To be able to do advanced development with
 |cubicweb| you need a good understanding of what is explained below.
 
--- a/doc/book/en/intro/concepts.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/intro/concepts.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -173,15 +173,15 @@
 achieved by dynamic objects (`application objects` or `appobjects`) stored in a
 two-levels registry (the `vregistry`). Each object is affected to a registry with
 an identifier in this registry. You may have more than one object sharing an
-identifier in the same registry, At runtime, appobjects are selected in a
-registry according to the context. Selection is done by comparing *score*
+identifier in the same registry. At runtime, appobjects are selected in a
+registry according to the context. Selection is done by comparing the *score*
 returned by each appobject's *selector*.
 
 Application objects are stored in the vregistry using a two-level hierarchy :
 
   object's `__registry__` : object's `__regid__` : [list of app objects]
 
-E.g. the `vregistry` contains several (sub-)registries which hold a
+In other words, the `vregistry` contains several (sub-)registries which hold a
 list of appobjects associated to an identifier.
 
 The base class of appobjects is :class:`cubicweb.appobject.AppObject`.
@@ -189,15 +189,15 @@
 Selectors
 ~~~~~~~~~
 
-Each appobject has a selector, that is used to compute how well the object fits a
-given context. The better the object fits the context, the higher the score. They
-are the glue that tie appobjects to the data model. Using them appropriately is
+Each appobject has a selector that is used to compute how well the object fits a
+given context. The better the object fits the context, the higher the score. Scores
+are the glue that ties appobjects to the data model. Using them appropriately is
 an essential part of the construction of well behaved cubes.
 
 |cubicweb| provides a set of basic selectors that may be parametrized.  Also,
 selectors can be combined with the `~` unary operator (negation) and the binary
 operators `&` and `|` (respectivly 'and' and 'or') to build more complex
-selector. Of course complex selector may be combined too. Last but not least, you
+selectors. Of course complex selectors may be combined too. Last but not least, you
 can write your own selectors.
 
 The `vregistry`
@@ -339,5 +339,5 @@
 cubicweb application.
 
 .. note::
-   RQL queries executed in hooks and operations are *unsafe* by default, e.g. the
+   RQL queries executed in hooks and operations are *unsafe* by default, i.e. the
    read and write security is deactivated unless explicitly asked.
--- a/doc/book/en/tutorials/advanced/index.rst	Tue Jun 01 08:34:35 2010 +0200
+++ b/doc/book/en/tutorials/advanced/index.rst	Wed Jun 02 13:02:47 2010 +0200
@@ -8,12 +8,12 @@
 
 * basically a photo gallery
 
-* photo stored onto the fs and displayed dynamically through a web interface
+* photo stored on the file system and displayed dynamically through a web interface
 
 * navigation through folder (album), tags, geographical zone, people on the
   picture... using facets
 
-* advanced security (eg not everyone can see everything). More on this later.
+* advanced security (not everyone can see everything). More on this later.
 
 
 Cube creation and schema definition
@@ -24,7 +24,7 @@
 Step 1: creating a new cube for my web site
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-One note about my development environment: I wanted to use packaged
+One note about my development environment: I wanted to use the packaged
 version of CubicWeb and cubes while keeping my cube in my user
 directory, let's say `~src/cubes`.  I achieve this by setting the
 following environment variables::
@@ -43,10 +43,10 @@
 Step 2: pick building blocks into existing cubes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Almost everything I want represent in my web-site is somewhat already modelized in
-some cube that I'll extend for my need. So I'll pick the following cubes:
+Almost everything I want to handle in my web-site is somehow already modelized in
+existing cubes that I'll extend for my need. So I'll pick the following cubes:
 
-* `folder`, containing `Folder` entity type, which will be used as
+* `folder`, containing the `Folder` entity type, which will be used as
   both 'album' and a way to map file system folders. Entities are
   added to a given folder using the `filed_under` relation.
 
@@ -62,7 +62,7 @@
 * `comment`, providing a full commenting system allowing one to comment entity types
   supporting the `comments` relation by adding a `Comment` entity.
 
-* `tag`, providing a full tagging system as a easy and powerful way to classify
+* `tag`, providing a full tagging system as an easy and powerful way to classify
   entities supporting the `tags` relation by linking the to `Tag` entities. This
   will allows navigation into a large number of picture.
 
@@ -131,20 +131,20 @@
   picture.
 
 This schema will probably have to evolve as time goes (for security handling at
-least), but since the possibility to make schema evolving is one of CubicWeb
-feature (and goal), we won't worry and see that later when needed.
+least), but since the possibility to let a schema evolve is one of CubicWeb's
+features (and goals), we won't worry about it for now and see that later when needed.
 
 
 Step 4: creating the instance
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Now that I've a schema, I want to create an instance so I can start To
-create an instance using this new 'sytweb' cube, I run::
+Now that I have a schema, I want to create an instance. To
+do so using this new 'sytweb' cube, I run::
 
   cubicweb-ctl create sytweb sytweb_instance
 
-hint : if you get an error while the database is initialized, you can
-avoid having to reanswer to questions by runing ::
+Hint: if you get an error while the database is initialized, you can
+avoid having to answer the questions again by running::
 
    cubicweb-ctl db-create sytweb_instance
 
@@ -161,7 +161,7 @@
 Security, testing and migration
 -------------------------------
 
-This post will cover various topics:
+This part will cover various topics:
 
 * configuring security
 * migrating existing instance
@@ -174,10 +174,10 @@
   - ``authenticated``, only authenticated users can see it
   - ``restricted``, only a subset of authenticated users can see it
 * managers (e.g. me) can see everything
-* only authenticated user can see people
-* everyone can  see classifier entities, eg tag and zone
+* only authenticated users can see people
+* everyone can see classifier entities, such as tag and zone
 
-Also, unless explicity specified, visibility of an image should be the same as
+Also, unless explicitly specified, the visibility of an image should be the same as
 its parent folder, as well as visibility of a comment should be the same as the
 commented entity. If there is no parent entity, the default visibility is
 ``authenticated``.
@@ -198,19 +198,19 @@
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 In schema, you can grant access according to groups, or to some RQL expressions:
-users get access it the expression return some results. To implements the read
-security defined earlier, groups are not enough, we'll need RQL expression. Here
+users get access if the expression returns some results. To implement the read
+security defined earlier, groups are not enough, we'll need some RQL expression. Here
 is the idea:
 
-* add a `visibility` attribute on folder, image and comment, which may be one of
+* add a `visibility` attribute on Folder, Image and Comment, which may be one of
   the value explained above
 
-* add a `may_be_read_by` relation from folder, image and comment to users,
+* add a `may_be_read_by` relation from Folder, Image and Comment to users,
   which will define who can see the entity
 
 * security propagation will be done in hook.
 
-So the first thing to do is to modify my cube'schema.py to define those
+So the first thing to do is to modify my cube's schema.py to define those
 relations:
 
 .. sourcecode:: python
@@ -319,9 +319,9 @@
 system. Hooks are triggered on database event such as addition of new
 entity or relation.
 
-The trick part of the requirement is in *unless explicitly specified*, notably
-because when the entity addition hook is added, we don't know yet its 'parent'
-entity (eg folder of an image, image commented by a comment). To handle such things,
+The tricky part of the requirement is in *unless explicitly specified*, notably
+because when the entity is added, we don't know yet its 'parent'
+entity (e.g. Folder of an Image, Image commented by a Comment). To handle such things,
 CubicWeb provides `Operation`, which allow to schedule things to do at commit time.
 
 In our case we will:
@@ -554,7 +554,7 @@
 Step 4: writing the migration script and migrating the instance
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Prior to those changes, Iv'e created an instance, feeded it with some data, so I
+Prior to those changes, I  created an instance, feeded it with some data, so I
 don't want to create a new one, but to migrate the existing one. Let's see how to
 do that.
 
@@ -576,7 +576,7 @@
 * update the instance's schema by adding our two new relations and update the
   underlying database tables accordingly (the two first instructions)
 
-* update schema's permissions definition (the later instruction)
+* update schema's permissions definition (the last instruction)
 
 
 To migrate my instance I simply type::
--- a/etwist/service.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/etwist/service.py	Wed Jun 02 13:02:47 2010 +0200
@@ -71,6 +71,8 @@
             _check_env(os.environ)
             # create the site
             config = cwcfg.config_for(self.instance)
+            config.init_log(force=True)
+            logger.info('starting cubicweb instance %s ', self.instance)
             root_resource = CubicWebRootResource(config, False)
             website = server.Site(root_resource)
             # serve it via standard HTTP on port set in the configuration
--- a/selectors.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/selectors.py	Wed Jun 02 13:02:47 2010 +0200
@@ -111,7 +111,7 @@
 
     class UserLink(component.Component):
 	'''if the user is the anonymous user, build a link to login else a link
-	to the connected user object with a loggout link
+	to the connected user object with a logout link
 	'''
 	__regid__ = 'loggeduserlink'
 
@@ -576,28 +576,35 @@
         return rset and self.match_expected(len(rset.rows[0])) or 0
 
 
-@objectify_selector
-@lltrace
-def paginated_rset(cls, req, rset=None, **kwargs):
-    """Return 1 for result set with more rows than a page size.
+class paginated_rset(Selector):
+    """Return 1 or more for result set with more rows than one or more page
+    size.  You can specify expected number of pages to the initializer (default
+    to one), and you'll get that number of pages as score if the result set is
+    big enough.
 
     Page size is searched in (respecting order):
     * a `page_size` argument
     * a `page_size` form parameters
     * the :ref:`navigation.page-size` property
     """
-    if rset is None:
-        return 0
-    page_size = kwargs.get('page_size')
-    if page_size is None:
-        page_size = req.form.get('page_size')
+    def __init__(self, nbpages=1):
+        assert nbpages > 0
+        self.nbpages = nbpages
+
+    @lltrace
+    def __call__(self, cls, req, rset=None, **kwargs):
+        if rset is None:
+            return 0
+        page_size = kwargs.get('page_size')
         if page_size is None:
-            page_size = req.property_value('navigation.page-size')
-        else:
-            page_size = int(page_size)
-    if rset.rowcount <= page_size:
-        return 0
-    return 1
+            page_size = req.form.get('page_size')
+            if page_size is None:
+                page_size = req.property_value('navigation.page-size')
+            else:
+                page_size = int(page_size)
+        if rset.rowcount <= (page_size*self.nbpages):
+            return 0
+        return self.nbpages
 
 
 @objectify_selector
--- a/server/querier.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/querier.py	Wed Jun 02 13:02:47 2010 +0200
@@ -269,6 +269,7 @@
                 # transform in subquery when len(localchecks)>1 and groups
                 if nbtrees > 1 and (select.orderby or select.groupby or
                                     select.having or select.has_aggregat or
+                                    select.distinct or
                                     select.limit or select.offset):
                     newselect = Select()
                     # only select variables in subqueries
@@ -303,6 +304,7 @@
                         select.offset = 0
                     myunion = Union()
                     newselect.set_with([SubQuery(aliases, myunion)], check=False)
+                    newselect.distinct = select.distinct
                     solutions = [sol.copy() for sol in select.solutions]
                     cleanup_solutions(newselect, solutions)
                     newselect.set_possible_types(solutions)
--- a/server/repository.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/repository.py	Wed Jun 02 13:02:47 2010 +0200
@@ -423,6 +423,7 @@
         results['nb_active_threads'] = threading.activeCount()
         results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks)
         results['available_pools'] = self._available_pools.qsize()
+        results['threads'] = ', '.join(sorted(str(t) for t in threading.enumerate()))
         return results
 
     def get_schema(self):
--- a/server/sources/ldapuser.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/sources/ldapuser.py	Wed Jun 02 13:02:47 2010 +0200
@@ -201,6 +201,7 @@
 
     def init(self):
         """method called by the repository once ready to handle request"""
+        self.info('ldap init')
         self.repo.looping_task(self._interval, self.synchronize)
         self.repo.looping_task(self._query_cache.ttl.seconds/10,
                                self._query_cache.clear_expired)
@@ -221,8 +222,10 @@
                                         "source='%s'" % self.uri)
             for eid, b64extid in cursor.fetchall():
                 extid = b64decode(b64extid)
+                self.debug('ldap eid %s', eid)
                 # if no result found, _search automatically delete entity information
                 res = self._search(session, extid, BASE)
+                self.debug('ldap search %s', res)
                 if res:
                     ldapemailaddr = res[0].get(ldap_emailattr)
                     if ldapemailaddr:
@@ -269,6 +272,7 @@
         two queries are needed since passwords are stored crypted, so we have
         to fetch the salt first
         """
+        self.info('ldap authenticate %s', login)
         if password is None:
             raise AuthenticationError()
         searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))]
@@ -343,6 +347,7 @@
         possible type). If cachekey is given, the query necessary to fetch the
         results (but not the results themselves) may be cached using this key.
         """
+        self.debug('ldap syntax tree search')
         # XXX not handled : transform/aggregat function, join on multiple users...
         assert len(union.children) == 1, 'union not supported'
         rqlst = union.children[0]
@@ -494,19 +499,21 @@
     def _search(self, session, base, scope,
                 searchstr='(objectClass=*)', attrs=()):
         """make an ldap query"""
+        self.info('ldap search %s %s %s %s %s', self.uri, base, scope, searchstr, list(attrs))
         cnx = session.pool.connection(self.uri).cnx
         try:
             res = cnx.search_s(base, scope, searchstr, attrs)
         except ldap.PARTIAL_RESULTS:
             res = cnx.result(all=0)[1]
         except ldap.NO_SUCH_OBJECT:
+            self.info('ldap NO SUCH OBJECT')
             eid = self.extid2eid(base, 'CWUser', session, insert=False)
             if eid:
                 self.warning('deleting ldap user with eid %s and dn %s',
                              eid, base)
                 entity = session.entity_from_eid(eid, 'CWUser')
                 self.repo.delete_info(session, entity, self.uri, base)
-                self._cache.pop(base, None)
+                self.reset_cache()
             return []
 ##         except ldap.REFERRAL, e:
 ##             cnx = self.handle_referral(e)
@@ -541,6 +548,7 @@
             self._cache[rec_dn] = rec_dict
             result.append(rec_dict)
         #print '--->', result
+        self.info('ldap built results %s', result)
         return result
 
     def before_entity_insertion(self, session, lid, etype, eid):
@@ -551,6 +559,7 @@
         This method must return the an Entity instance representation of this
         entity.
         """
+        self.info('ldap before entity insertion')
         entity = super(LDAPUserSource, self).before_entity_insertion(session, lid, etype, eid)
         res = self._search(session, lid, BASE)[0]
         for attr in entity.e_schema.indexable_attributes():
@@ -561,6 +570,7 @@
         """called by the repository after an entity stored here has been
         inserted in the system table.
         """
+        self.info('ldap after entity insertion')
         super(LDAPUserSource, self).after_entity_insertion(session, dn, entity)
         for group in self.user_default_groups:
             session.execute('SET X in_group G WHERE X eid %(x)s, G name %(group)s',
--- a/server/sources/native.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/sources/native.py	Wed Jun 02 13:02:47 2010 +0200
@@ -444,7 +444,15 @@
             self.warning("trying to reconnect")
             session.pool.reconnect(self)
             cursor = self.doexec(session, sql, args)
-        results = self.process_result(cursor, cbs)
+        except (self.DbapiError,), exc:
+            # We get this one with pyodbc and SQL Server when connection was reset
+            if exc.args[0] == '08S01':
+                self.warning("trying to reconnect")
+                session.pool.reconnect(self)
+                cursor = self.doexec(session, sql, args)
+            else:
+                raise
+        results = self.process_result(cursor, cbs, session=session)
         assert dbg_results(results)
         return results
 
--- a/server/sources/storages.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/sources/storages.py	Wed Jun 02 13:02:47 2010 +0200
@@ -33,10 +33,10 @@
     """abstract storage
 
     * If `source_callback` is true (by default), the callback will be run during
-      query result process of fetched attribute's valu and should have the
+      query result process of fetched attribute's value and should have the
       following prototype::
 
-        callback(self, source, value)
+        callback(self, source, session, value)
 
       where `value` is the value actually stored in the backend. None values
       will be skipped (eg callback won't be called).
@@ -99,7 +99,7 @@
         self.default_directory = defaultdir
         self.fsencoding = fsencoding
 
-    def callback(self, source, value):
+    def callback(self, source, session, value):
         """sql generator callback when some attribute with a custom storage is
         accessed
         """
@@ -125,17 +125,18 @@
 
     def entity_updated(self, entity, attr):
         """an entity using this storage for attr has been updatded"""
+        oldpath = self.current_fs_path(entity, attr)
         if entity._cw.transaction_data.get('fs_importing'):
-            oldpath = self.current_fs_path(entity, attr)
             fpath = entity[attr].getvalue()
-            if oldpath != fpath:
-                hook.set_operation(entity._cw, 'bfss_deleted', oldpath,
-                                   DeleteFileOp)
             binary = Binary(file(fpath).read())
         else:
             binary = entity.pop(attr)
-            fpath = self.current_fs_path(entity, attr)
+            fpath = self.new_fs_path(entity, attr)
             UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue())
+        if oldpath != fpath:
+            entity[attr] = Binary(fpath)
+            hook.set_operation(entity._cw, 'bfss_deleted', oldpath,
+                               DeleteFileOp)
         return binary
 
     def entity_deleted(self, entity, attr):
--- a/server/sqlutils.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/sqlutils.py	Wed Jun 02 13:02:47 2010 +0200
@@ -165,6 +165,7 @@
         dbapi_module = self.dbhelper.dbapi_module
         self.OperationalError = dbapi_module.OperationalError
         self.InterfaceError = dbapi_module.InterfaceError
+        self.DbapiError = dbapi_module.Error
         self._binary = dbapi_module.Binary
         self._process_value = dbapi_module.process_value
         self._dbencoding = dbencoding
@@ -201,7 +202,7 @@
             return newargs
         return query_args
 
-    def process_result(self, cursor, column_callbacks=None):
+    def process_result(self, cursor, column_callbacks=None, session=None):
         """return a list of CubicWeb compliant values from data in the given cursor
         """
         # use two different implementations to avoid paying the price of
@@ -209,9 +210,10 @@
         # lookup
         if not column_callbacks:
             return self._process_result(cursor)
-        return self._cb_process_result(cursor, column_callbacks)
+        assert session
+        return self._cb_process_result(cursor, column_callbacks, session)
 
-    def _process_result(self, cursor, column_callbacks=None):
+    def _process_result(self, cursor):
         # begin bind to locals for optimization
         descr = cursor.description
         encoding = self._dbencoding
@@ -229,7 +231,7 @@
             results[i] = result
         return results
 
-    def _cb_process_result(self, cursor, column_callbacks):
+    def _cb_process_result(self, cursor, column_callbacks, session):
         # begin bind to locals for optimization
         descr = cursor.description
         encoding = self._dbencoding
@@ -248,7 +250,7 @@
                     value = process_value(value, descr[col], encoding, binary)
                 else:
                     for cb in cbstack:
-                        value = cb(self, value)
+                        value = cb(self, session, value)
                 result.append(value)
             results[i] = result
         return results
--- a/server/test/unittest_storage.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/test/unittest_storage.py	Wed Jun 02 13:02:47 2010 +0200
@@ -19,7 +19,7 @@
 
 from __future__ import with_statement
 
-from logilab.common.testlib import unittest_main
+from logilab.common.testlib import unittest_main, tag
 from cubicweb.devtools.testlib import CubicWebTC
 
 import os.path as osp
@@ -178,7 +178,7 @@
         self.assertEquals(f1.data.getvalue(), file(filepath).read(),
                           'files content differ')
 
-
+    @tag('Storage', 'BFSS', 'update')
     def test_bfss_update_with_existing_data(self):
         # use self.session to use server-side cache
         f1 = self.session.create_entity('File', data=Binary('some data'),
@@ -192,6 +192,52 @@
         f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0)
         self.assertEquals(f2.data.getvalue(), 'some other data')
 
+    @tag('Storage', 'BFSS', 'update', 'extension', 'commit')
+    def test_bfss_update_with_different_extension_commited(self):
+        # use self.session to use server-side cache
+        f1 = self.session.create_entity('File', data=Binary('some data'),
+                                        data_format=u'text/plain', data_name=u'foo.txt')
+        # NOTE: do not use set_attributes() which would automatically
+        #       update f1's local dict. We want the pure rql version to work
+        self.commit()
+        old_path = self.fspath(f1)
+        self.failUnless(osp.isfile(old_path))
+        self.assertEquals(osp.splitext(old_path)[1], '.txt')
+        self.execute('SET F data %(d)s, F data_name %(dn)s, F data_format %(df)s WHERE F eid %(f)s',
+                     {'d': Binary('some other data'), 'f': f1.eid, 'dn': u'bar.jpg', 'df': u'image/jpeg'})
+        self.commit()
+        # the new file exists with correct extension
+        # the old file is dead
+        f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0)
+        new_path = self.fspath(f2)
+        self.failIf(osp.isfile(old_path))
+        self.failUnless(osp.isfile(new_path))
+        self.assertEquals(osp.splitext(new_path)[1], '.jpg')
+
+    @tag('Storage', 'BFSS', 'update', 'extension', 'rollback')
+    def test_bfss_update_with_different_extension_rollbacked(self):
+        # use self.session to use server-side cache
+        f1 = self.session.create_entity('File', data=Binary('some data'),
+                                        data_format=u'text/plain', data_name=u'foo.txt')
+        # NOTE: do not use set_attributes() which would automatically
+        #       update f1's local dict. We want the pure rql version to work
+        self.commit()
+        old_path = self.fspath(f1)
+        old_data = f1.data.getvalue()
+        self.failUnless(osp.isfile(old_path))
+        self.assertEquals(osp.splitext(old_path)[1], '.txt')
+        self.execute('SET F data %(d)s, F data_name %(dn)s, F data_format %(df)s WHERE F eid %(f)s',
+                     {'d': Binary('some other data'), 'f': f1.eid, 'dn': u'bar.jpg', 'df': u'image/jpeg'})
+        self.rollback()
+        # the new file exists with correct extension
+        # the old file is dead
+        f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0)
+        new_path = self.fspath(f2)
+        new_data = f2.data.getvalue()
+        self.failUnless(osp.isfile(new_path))
+        self.assertEquals(osp.splitext(new_path)[1], '.txt')
+        self.assertEquals(old_path, new_path)
+        self.assertEquals(old_data, new_data)
 
     def test_bfss_update_with_fs_importing(self):
         # use self.session to use server-side cache
--- a/server/utils.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/server/utils.py	Wed Jun 02 13:02:47 2010 +0200
@@ -123,6 +123,10 @@
 class LoopTask(object):
     """threaded task restarting itself once executed"""
     def __init__(self, interval, func, args):
+        if interval <= 0:
+            raise ValueError('Loop task interval must be > 0 '
+                             '(current value: %f for %s)' % \
+                             (interval, func.__name__))
         self.interval = interval
         def auto_restart_func(self=self, func=func, args=args):
             try:
@@ -137,6 +141,7 @@
 
     def start(self):
         self._t = Timer(self.interval, self.func)
+        self._t.setName('%s-%s[%d]' % (self._t.getName(), self.name, self.interval))
         self._t.start()
 
     def cancel(self):
--- a/spa2rql.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/spa2rql.py	Wed Jun 02 13:02:47 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""SPARQL -> RQL translator
+"""SPARQL -> RQL translator"""
 
-"""
 from logilab.common import make_domains
 from rql import TypeResolverException
 from fyzz.yappsparser import parse
@@ -76,7 +75,7 @@
             nbctypes = len(ctypes)
             ctypes &= varpossibletypes
             if not ctypes:
-                raise TypeResolverException()
+                raise TypeResolverException('No possible type')
             return len(ctypes) != nbctypes
         except KeyError:
             self.possible_types[var] = varpossibletypes
@@ -98,8 +97,8 @@
                         modified = True
                 # restrict predicates according to allowed subject var types
                 if subjvar in self.possible_types:
-                    yams_predicates = [(s, r, o) for s, r, o in yams_predicates
-                                       if s == '*' or s in self.possible_types[subjvar]]
+                    yams_predicates[:] = [(s, r, o) for s, r, o in yams_predicates
+                                          if s == '*' or s in self.possible_types[subjvar]]
                 if isinstance(obj, ast.SparqlVar):
                     # make a valid rql var name
                     objvar = obj.name.upper()
@@ -111,11 +110,11 @@
                             modified = True
                     # restrict predicates according to allowed object var types
                     if objvar in self.possible_types:
-                        yams_predicates = [(s, r, o) for s, r, o in yams_predicates
-                                           if o == '*' or o in self.possible_types[objvar]]
+                        yams_predicates[:] = [(s, r, o) for s, r, o in yams_predicates
+                                              if o == '*' or o in self.possible_types[objvar]]
                 # ensure this still make sense
                 if not yams_predicates:
-                    raise TypeResolverException()
+                    raise TypeResolverException('No yams predicate')
                 if len(yams_predicates) != nbchoices:
                     modified = True
 
@@ -197,7 +196,8 @@
                 raise UnsupportedQuery()
             # make a valid rql var name
             subjvar = subj.name.upper()
-            if predicate == ('', 'a'):
+            if predicate in [('', 'a'),
+                             ('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'type')]:
                 # special 'is' relation
                 if not isinstance(obj, tuple):
                     raise UnsupportedQuery()
--- a/test/unittest_spa2rql.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/test/unittest_spa2rql.py	Wed Jun 02 13:02:47 2010 +0200
@@ -23,6 +23,7 @@
 xy.add_equivalence('Project', 'doap:Project')
 xy.add_equivalence('Project creation_date', 'doap:Project doap:created')
 xy.add_equivalence('Project name', 'doap:Project doap:name')
+xy.add_equivalence('Project name', 'doap:Project dc:title')
 
 
 config = TestServerConfiguration('data')
@@ -50,6 +51,14 @@
       ?project a doap:Project;
     }''', 'Any PROJECT WHERE PROJECT is Project')
 
+    def test_base_rdftype(self):
+        self._test('''
+    PREFIX doap: <http://usefulinc.com/ns/doap#>
+    PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+    SELECT ?project
+    WHERE  {
+      ?project rdf:type doap:Project.
+    }''', 'Any PROJECT WHERE PROJECT is Project')
 
     def test_base_attr_sel(self):
         self._test('''
@@ -171,6 +180,16 @@
               doap:name "cubicweb".
     }''', 'Any PROJECT WHERE PROJECT name %(a)s, PROJECT is Project', {'a': 'cubicweb'})
 
+    def test_dctitle_both_project_cwuser(self):
+        self._test('''
+    PREFIX doap: <http://usefulinc.com/ns/doap#>
+    PREFIX dc: <http://purl.org/dc/elements/1.1/>
+    SELECT ?project ?title
+    WHERE  {
+      ?project a doap:Project;
+              dc:title ?title.
+    }''', 'Any PROJECT, TITLE WHERE PROJECT name TITLE, PROJECT is Project')
+
 # # Two elements in the group
 # PREFIX :  <http://example.org/ns#>
 # SELECT *
--- a/vregistry.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/vregistry.py	Wed Jun 02 13:02:47 2010 +0200
@@ -439,6 +439,9 @@
                 mdate = self._mdate(fileordir)
                 if mdate is None:
                     continue # backup file, see _mdate implementation
+                elif "flymake" in fileordir:
+                    # flymake + pylint in use, don't consider these they will corrupt the registry
+                    continue
                 if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
                     self.info('File %s changed since last visit', fileordir)
                     return True
@@ -453,6 +456,9 @@
         mdate = self._mdate(filepath)
         if mdate is None:
             return # backup file, see _mdate implementation
+        elif "flymake" in filepath:
+            # flymake + pylint in use, don't consider these they will corrupt the registry
+            return
         # set update time before module loading, else we get some reloading
         # weirdness in case of syntax error or other error while importing the
         # module
--- a/web/component.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/component.py	Wed Jun 02 13:02:47 2010 +0200
@@ -130,11 +130,14 @@
         params = dict(params)
         params.update({self.start_param : start,
                        self.stop_param : stop,})
-        if path == 'json':
+        view = self.cw_extra_kwargs.get('view')
+        if view is not None and hasattr(view, 'page_navigation_url'):
+            url = view.page_navigation_url(self, path, params)
+        elif path == 'json':
             rql = params.pop('rql', self.cw_rset.printable_rql())
             # latest 'true' used for 'swap' mode
             url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
-                json.dumps(params.get('divid', 'paginated-content')),
+                json.dumps(params.get('divid', 'pageContent')),
                 json.dumps(rql), json.dumps(params.pop('vid', None)), json.dumps(params))
         else:
             url = self._cw.build_url(path, **params)
--- a/web/facet.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/facet.py	Wed Jun 02 13:02:47 2010 +0200
@@ -157,6 +157,10 @@
     if rqlst.groupby:
         rqlst.add_group_var(newvar)
     rqlst.add_selected(newvar)
+    # add is restriction if necessary
+    if mainvar.stinfo['typerel'] is None:
+        etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
+        rqlst.add_type_restriction(mainvar, etypes)
     return newvar
 
 def _remove_relation(rqlst, rel, var):
@@ -210,10 +214,6 @@
         _set_orderby(rqlst, attrvar, sortasc, sortfuncname)
     # add attribute variable to selection
     rqlst.add_selected(attrvar)
-    # add is restriction if necessary
-    if mainvar.stinfo['typerel'] is None:
-        etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
-        rqlst.add_type_restriction(mainvar, etypes)
     return var
 
 def _cleanup_rqlst(rqlst, mainvar):
--- a/web/test/unittest_views_basetemplates.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/test/unittest_views_basetemplates.py	Wed Jun 02 13:02:47 2010 +0200
@@ -34,3 +34,7 @@
         self.assertEquals(self._login_labels(), ['login or email', 'password'])
         self.set_option('allow-email-login', 'no')
         self.assertEquals(self._login_labels(), ['login', 'password'])
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+   unittest_main()
--- a/web/test/unittest_views_navigation.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/test/unittest_views_navigation.py	Wed Jun 02 13:02:47 2010 +0200
@@ -15,14 +15,13 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb.web.views.navigation unit tests
-
-"""
+"""cubicweb.web.views.navigation unit tests"""
 
 from logilab.common.testlib import unittest_main, mock_object
+
 from cubicweb.devtools.testlib import CubicWebTC
-
-from cubicweb.web.views.navigation import PageNavigation, SortedNavigation
+from cubicweb.web.views.navigation import (PageNavigation, SortedNavigation,
+                                           PageNavigationSelect)
 from cubicweb.web.views.ibreadcrumbs import BreadCrumbEntityVComponent
 
 BreadCrumbEntityVComponent.visible = True
@@ -41,15 +40,24 @@
 
     def test_navigation_selection_ordered(self):
         req = self.request()
-        rset = self.execute('Any X,N ORDERBY N WHERE X name N')
-        navcomp = self.vreg['components'].select('navigation', req, rset=rset)
+        rset = self.execute('Any X,N ORDERBY N LIMIT 40 WHERE X name N')
+        navcomp = self.vreg['components'].select('navigation', req, rset=rset, page_size=20)
         self.assertIsInstance(navcomp, SortedNavigation)
         req.set_search_state('W:X:Y:Z')
-        navcomp = self.vreg['components'].select('navigation', req, rset=rset)
+        navcomp = self.vreg['components'].select('navigation', req, rset=rset, page_size=20)
         self.assertIsInstance(navcomp, SortedNavigation)
         req.set_search_state('normal')
         html = navcomp.render()
 
+    def test_navigation_selection_large_rset(self):
+        req = self.request()
+        rset = self.execute('Any X,N LIMIT 120 WHERE X name N')
+        navcomp = self.vreg['components'].select('navigation', req, rset=rset, page_size=20)
+        self.assertIsInstance(navcomp, PageNavigationSelect)
+        rset = self.execute('Any X,N ORDERBY N LIMIT 120 WHERE X name N')
+        navcomp = self.vreg['components'].select('navigation', req, rset=rset, page_size=20)
+        self.assertIsInstance(navcomp, PageNavigationSelect)
+
     def test_navigation_selection_not_enough(self):
         req = self.request()
         rset = self.execute('Any X,N LIMIT 10 WHERE X name N')
--- a/web/test/unittest_views_searchrestriction.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/test/unittest_views_searchrestriction.py	Wed Jun 02 13:02:47 2010 +0200
@@ -48,17 +48,17 @@
     def test_1(self):
         self.assertEquals(self._generate(self.select, 'in_state', 'subject', 'name'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "B in_state A, A name C, B is CWUser")
+                          "B in_state A, B is CWUser, A name C")
 
     def test_2(self):
         self.assertEquals(self._generate(self.select, 'tags', 'object', 'name'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "A tags B, A name C, B is CWUser")
+                          "A tags B, B is CWUser, A name C")
 
     def test_3(self):
         self.assertEquals(self._generate(self.select, 'created_by', 'subject', 'login'),
                           "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
-                          "B created_by A, A login C, B is CWUser")
+                          "B created_by A, B is CWUser, A login C")
 
     def test_4(self):
         self.assertEquals(self._generate(self.parse('Any X WHERE X is CWUser'), 'created_by', 'subject', 'login'),
@@ -73,7 +73,7 @@
                             'V in_state VS, VS name "published", T created_by U')
         self.assertEquals(self._generate(select, 'created_by', 'subject', 'login'),
                           "DISTINCT Any A,B ORDERBY B WHERE T created_by U, "
-                          "T created_by A, A login B, T is Bookmark")
+                          "T created_by A, T is Bookmark, A login B")
 
     def test_nonregr2(self):
         #'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
--- a/web/views/autoform.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/autoform.py	Wed Jun 02 13:02:47 2010 +0200
@@ -648,7 +648,6 @@
     _default_form_action_path = 'validateform'
 
     # pre 3.8.3 compat
-    @property
     def set_action(self, action):
         self._action = action
     @deprecated('[3.9] use form.form_action()')
--- a/web/views/forms.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/forms.py	Wed Jun 02 13:02:47 2010 +0200
@@ -197,7 +197,7 @@
     _default_form_action_path = 'edit'
     def form_action(self):
         if self.action is None:
-            self._cw.build_url(self._default_form_action_path)
+            return self._cw.build_url(self._default_form_action_path)
         return self.action
 
     @deprecated('[3.6] use .add_hidden(name, value, **kwargs)')
--- a/web/views/navigation.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/navigation.py	Wed Jun 02 13:02:47 2010 +0200
@@ -36,29 +36,52 @@
 
     def call(self):
         """displays a resultset by page"""
-        w = self.w
-        req = self._cw
+        params = dict(self._cw.form)
+        self.clean_params(params)
+        basepath = self._cw.relative_path(includeparams=False)
+        self.w(u'<div class="pagination">')
+        self.w(u'%s&#160;' % self.previous_link(basepath, params))
+        self.w(u'[&#160;%s&#160;]' %
+               u'&#160;| '.join(self.iter_page_links(basepath, params)))
+        self.w(u'&#160;%s' % self.next_link(basepath, params))
+        self.w(u'</div>')
+
+    def index_display(self, start, stop):
+        return u'%s - %s' % (start+1, stop+1)
+
+    def iter_page_links(self, basepath, params):
         rset = self.cw_rset
         page_size = self.page_size
         start = 0
-        blocklist = []
-        params = dict(req.form)
-        self.clean_params(params)
-        basepath = req.relative_path(includeparams=False)
         while start < rset.rowcount:
             stop = min(start + page_size - 1, rset.rowcount - 1)
-            blocklist.append(self.page_link(basepath, params, start, stop,
-                                            self.index_display(start, stop)))
+            yield self.page_link(basepath, params, start, stop,
+                                 self.index_display(start, stop))
             start = stop + 1
+
+
+class PageNavigationSelect(PageNavigation):
+    """displays a resultset by page as PageNavigationSelect but in a <select>,
+    better when there are a lot of results.
+    """
+    __select__ = paginated_rset(4)
+
+    page_link_templ = u'<option value="%s" title="%s">%s</option>'
+    selected_page_link_templ = u'<option value="%s" selected="selected" title="%s">%s</option>'
+    def call(self):
+        params = dict(self._cw.form)
+        self.clean_params(params)
+        basepath = self._cw.relative_path(includeparams=False)
+        w = self.w
         w(u'<div class="pagination">')
         w(u'%s&#160;' % self.previous_link(basepath, params))
-        w(u'[&#160;%s&#160;]' % u'&#160;| '.join(blocklist))
+        w(u'<select onchange="javascript: document.location=this.options[this.selectedIndex].value">')
+        for option in self.iter_page_links(basepath, params):
+            w(option)
+        w(u'</select>')
         w(u'&#160;%s' % self.next_link(basepath, params))
         w(u'</div>')
 
-    def index_display(self, start, stop):
-        return u'%s - %s' % (start+1, stop+1)
-
 
 class SortedNavigation(NavigationComponent):
     """sorted navigation apply if navigation is needed (according to page size)
@@ -234,7 +257,7 @@
     if w is None:
         w = view.w
     nav = req.vreg['components'].select_or_none(
-        'navigation', req, rset=rset, page_size=page_size)
+        'navigation', req, rset=rset, page_size=page_size, view=view)
     if nav:
         if w is None:
             w = view.w
--- a/web/views/primary.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/primary.py	Wed Jun 02 13:02:47 2010 +0200
@@ -82,7 +82,7 @@
             self.w(u'</td><td>')
             self.w(u'<div class="primaryRight">')
             if hasattr(self, 'render_side_related'):
-                warn('render_side_related is deprecated')
+                warn('[3.2] render_side_related is deprecated')
                 self.render_side_related(entity, [])
             self.render_side_boxes(boxes)
             self.w(u'</div>')
@@ -95,7 +95,7 @@
             try:
                 comp.render(w=self.w, row=self.cw_row, view=self)
             except NotImplementedError:
-                warn('component %s doesnt implement cell_call, please update'
+                warn('[3.2] component %s doesnt implement cell_call, please update'
                      % comp.__class__, DeprecationWarning)
                 comp.render(w=self.w, view=self)
         self.w(u'</div>')
--- a/web/views/sparql.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/sparql.py	Wed Jun 02 13:02:47 2010 +0200
@@ -20,8 +20,8 @@
 """
 __docformat__ = "restructuredtext en"
 
-import rql
 from yams import xy
+from rql import TypeResolverException
 
 from lxml import etree
 from lxml.builder import E
@@ -51,25 +51,25 @@
 class SparqlFormView(form.FormViewMixIn, StartupView):
     __regid__ = 'sparql'
     def call(self):
-        form = self._cw.vreg.select('forms', 'sparql', self._cw)
+        form = self._cw.vreg['forms'].select('sparql', self._cw)
         self.w(form.render())
         sparql = self._cw.form.get('sparql')
         vid = self._cw.form.get('resultvid', 'table')
         if sparql:
             try:
                 qinfo = Sparql2rqlTranslator(self._cw.vreg.schema).translate(sparql)
-            except rql.TypeResolverException:
-                self.w(self._cw._('can not resolve entity types:') + u' ' + unicode('ex'))
+            except TypeResolverException, exc:
+                self.w(self._cw._('can not resolve entity types:') + u' ' + unicode(exc))
             except UnsupportedQuery:
                 self.w(self._cw._('we are not yet ready to handle this query'))
-            except xy.UnsupportedVocabulary, ex:
-                self.w(self._cw._('unknown vocabulary:') + u' ' + unicode('ex'))
+            except xy.UnsupportedVocabulary, exc:
+                self.w(self._cw._('unknown vocabulary:') + u' ' + unicode(exc))
             else:
+                rql, args = qinfo.finalize()
                 if vid == 'sparqlxml':
-                    url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
+                    url = self._cw.build_url('view', rql=(rql,args), vid=vid)
                     raise Redirect(url)
-                print qinfo.finalize()
-                rset = self._cw.execute(*qinfo.finalize())
+                rset = self._cw.execute(rql, args)
                 self.wview(vid, rset, 'null')
 
 
--- a/web/views/tableview.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/web/views/tableview.py	Wed Jun 02 13:02:47 2010 +0200
@@ -118,7 +118,8 @@
 
     def call(self, title=None, subvid=None, displayfilter=None, headers=None,
              displaycols=None, displayactions=None, actions=(), divid=None,
-             cellvids=None, cellattrs=None, mainindex=None):
+             cellvids=None, cellattrs=None, mainindex=None,
+             paginate=False, page_size=None):
         """Produces a table displaying a composite query
 
         :param title: title added before table
@@ -178,6 +179,9 @@
         if actions:
             self.render_actions(divid, actions)
         # render table
+        if paginate:
+            self.divid = divid # XXX iirk (see usage in page_navigation_url)
+            self.paginate(page_size=page_size, show_all_option=False)
         table = TableWidget(self)
         for column in self.get_columns(computed_labels, displaycols, headers,
                                        subvid, cellvids, cellattrs, mainindex):
@@ -187,6 +191,16 @@
         if not fromformfilter:
             self.w(u'</div>\n')
 
+    def page_navigation_url(self, navcomp, path, params):
+        if hasattr(self, 'divid'):
+            divid = self.divid
+        else:
+            divid = params.get('divid', 'pageContent'),
+        rql = params.pop('rql', self.cw_rset.printable_rql())
+        # latest 'true' used for 'swap' mode
+        return 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
+            dumps(divid), dumps(rql), dumps(self.__regid__), dumps(params))
+
     def show_hide_actions(self, divid, currentlydisplayed=False):
         showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
                              for what in ('Form', 'Show', 'Hide', 'Actions'))
--- a/xy.py	Tue Jun 01 08:34:35 2010 +0200
+++ b/xy.py	Wed Jun 02 13:02:47 2010 +0200
@@ -15,12 +15,11 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""map standard cubicweb schema to xml vocabularies
-
-"""
+"""map standard cubicweb schema to xml vocabularies"""
 
 from yams import xy
 
+xy.register_prefix('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf')
 xy.register_prefix('http://purl.org/dc/elements/1.1/', 'dc')
 xy.register_prefix('http://xmlns.com/foaf/0.1/',       'foaf')
 xy.register_prefix('http://usefulinc.com/ns/doap#',    'doap')
@@ -32,5 +31,5 @@
 xy.add_equivalence('created_by', 'dc:creator')
 xy.add_equivalence('description', 'dc:description')
 xy.add_equivalence('CWUser', 'foaf:Person')
-xy.add_equivalence('CWUser login', 'dc:title')
+xy.add_equivalence('CWUser login', 'foaf:Person dc:title')
 xy.add_equivalence('CWUser surname', 'foaf:Person foaf:name')