backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 07 Apr 2010 14:42:55 +0200
changeset 5177 395e1ff018ae
parent 5176 ddd5219d7eef (current diff)
parent 5175 6efb7a7ae570 (diff)
child 5184 955ee1b24756
backport stable
debian/control
devtools/__init__.py
devtools/testlib.py
server/msplanner.py
server/querier.py
server/repository.py
web/views/basecontrollers.py
--- a/debian/control	Wed Apr 07 14:42:44 2010 +0200
+++ b/debian/control	Wed Apr 07 14:42:55 2010 +0200
@@ -33,7 +33,7 @@
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database, cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (=> 1.0.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
 Recommends: pyro, cubicweb-documentation (= ${source:Version})
 Description: server part of the CubicWeb framework
  CubicWeb is a semantic web application framework.
--- a/devtools/__init__.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/devtools/__init__.py	Wed Apr 07 14:42:55 2010 +0200
@@ -197,6 +197,8 @@
         init_test_database_sqlite(config)
     elif driver == 'postgres':
         init_test_database_postgres(config)
+    elif driver == 'sqlserver2005':
+        init_test_database_sqlserver2005(config, source)
     else:
         raise ValueError('no initialization function for driver %r' % driver)
     config._cubes = None # avoid assertion error
@@ -219,11 +221,19 @@
 ### postgres test database handling ############################################
 
 def init_test_database_postgres(config):
-    """initialize a fresh sqlite databse used for testing purpose"""
+    """initialize a fresh postgresql databse used for testing purpose"""
     if config.init_repository:
         from cubicweb.server import init_repository
         init_repository(config, interactive=False, drop=True)
 
+### sqlserver2005 test database handling ############################################
+
+def init_test_database_sqlserver2005(config):
+    """initialize a fresh sqlserver databse used for testing purpose"""
+    if config.init_repository:
+        from cubicweb.server import init_repository
+        init_repository(config, interactive=False, drop=True, vreg=vreg)
+
 
 ### sqlite test database handling ##############################################
 
--- a/devtools/testlib.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/devtools/testlib.py	Wed Apr 07 14:42:55 2010 +0200
@@ -265,6 +265,12 @@
         self.setup_database()
         self.commit()
         MAILBOX[:] = [] # reset mailbox
+        self._cnxs = []
+
+    def tearDown(self):
+        for cnx in self._cnxs:
+            if not cnx._closed:
+                cnx.close()
 
     def setup_database(self):
         """add your database setup code by overriding this method"""
@@ -306,6 +312,7 @@
             self.cnx = repo_connect(self.repo, unicode(login),
                                     cnxprops=ConnectionProperties('inmemory'),
                                     **kwargs)
+            self._cnxs.append(self.cnx)
         if login == self.vreg.config.anonymous_user()[0]:
             self.cnx.anonymous_connection = True
         return self.cnx
@@ -314,6 +321,7 @@
         if not self.cnx is self._orig_cnx:
             try:
                 self.cnx.close()
+                self._cnxs.remove(self.cnx)
             except ProgrammingError:
                 pass # already closed
         self.cnx = self._orig_cnx
--- a/doc/book/en/annexes/faq.rst	Wed Apr 07 14:42:44 2010 +0200
+++ b/doc/book/en/annexes/faq.rst	Wed Apr 07 14:42:55 2010 +0200
@@ -6,8 +6,6 @@
 [XXX 'copy answer from forum' means reusing text from
 http://groups.google.com/group/google-appengine/browse_frm/thread/c9476925f5f66ec6
 and
-http://groups.google.com/group/google-appengine/browse_frm/thread/d791ce17e2716147/eb078f8cfe8426e0
-and
 http://groups.google.com/group/google-appengine/browse_frm/thread/f48cf6099973aef5/c28cd6934dd72457
 ]
 
@@ -26,8 +24,13 @@
 Python is the templating language that we use in *CubicWeb*, but again,
 it does not prevent you from using a templating language.
 
-The reason template languages are not used in this book is that
-experience has proved us that using pure python was less cumbersome.
+Moreover, CubicWeb currently supports `simpletal`_ out of the box and
+it is also possible to use the `cwtags`_ library to build html trees
+using the `with statement`_ with more comfort than raw strings.
+
+.. _`simpletal`: http://www.owlfish.com/software/simpleTAL/
+.. _`cwtags`: http://www.cubicweb.org/project/cwtags
+.. _`with statement`: http://www.python.org/dev/peps/pep-0343/
 
 Why do you think using pure python is better than using a template language ?
 -----------------------------------------------------------------------------
@@ -36,31 +39,24 @@
 already provides a consistent and strong architecture and syntax
 a templating language would not reach.
 
-When doing development, you need a real language and template
-languages are not real languages.
-
 Using Python instead of a template langage for describing the user interface
 makes it to maintain with real functions/classes/contexts without the need of
 learning a new dialect. By using Python, we use standard OOP techniques and
 this is a key factor in a robust application.
 
-The `cwtags` (http://www.cubicweb.org/project/cwtags) package can be
-used in cubes to help generate html from Python with more comfort than
-raw strings.
-
 Why do you use the LGPL license to prevent me from doing X ?
 ------------------------------------------------------------
 
 LGPL means that *if* you redistribute your application, you need to
 redistribute the changes you made to CubicWeb under the LGPL licence.
 
-Publishing a web site has nothing to do with redistributing
-source code. A fair amount of companies use modified LGPL code
-for internal use. And someone could publish a *CubicWeb* component
-under a BSD licence for others to plug into a LGPL framework without
-any problem. The only thing we are trying to prevent here is someone
-taking the framework and packaging it as closed source to his own
-clients.
+Publishing a web site has nothing to do with redistributing source
+code according to the terms of the LGPL. A fair amount of companies
+use modified LGPL code for internal use. And someone could publish a
+*CubicWeb* component under a BSD licence for others to plug into a
+LGPL framework without any problem. The only thing we are trying to
+prevent here is someone taking the framework and packaging it as
+closed source to his own clients.
 
 
 CubicWeb looks pretty recent. Is it stable ?
@@ -77,7 +73,7 @@
 SPARQL. Except that SPARQL did not exist when we started the project.
 With version 3.4, CubicWeb has support for SPARQL.
 
-That RQL language is what is going to make a difference with django-
+The RQL language is what is going to make a difference with django-
 like frameworks for several reasons.
 
 1. accessing data is *much* easier with it. One can write complex
@@ -192,23 +188,17 @@
 well defined context (request, result set). It enables much more
 dynamic views.
 
-What is the difference between `AppRsetObject` and `AppObject` ?
-----------------------------------------------------------------
-
-`AppRsetObject` instances are selected on a request and a result
-set. `AppObject` instances are directly selected by id.
-
 How to update a database after a schema modification ?
 ------------------------------------------------------
 
 It depends on what has been modified in the schema.
 
-* Update the permissions and properties of an entity or a relation:
+* update the permissions and properties of an entity or a relation:
   ``sync_schema_props_perms('MyEntityOrRelation')``.
 
-* Add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
+* add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
 
-* Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
+* add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
 
 
 How to create an anonymous user ?
@@ -401,3 +391,9 @@
   remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
 
 This is because you have to put your user in the "users" group. The user has to be in both groups.
+
+How do I translate an msg id defined (and translated) in another cube ?
+-----------------------------------------------------------------------
+
+You should put these translations in the `i18n/static-messages.pot`
+file of your own cube.
--- a/doc/book/en/development/devweb/js.rst	Wed Apr 07 14:42:44 2010 +0200
+++ b/doc/book/en/development/devweb/js.rst	Wed Apr 07 14:42:55 2010 +0200
@@ -4,9 +4,8 @@
 ----------
 
 *CubicWeb* uses quite a bit of javascript in its user interface and
-ships with jquery (1.3.x) and parts of the jquery UI
-library, plus a number of homegrown files and also other thirparty
-libraries.
+ships with jquery (1.3.x) and parts of the jquery UI library, plus a
+number of homegrown files and also other thir party libraries.
 
 All javascript files are stored in cubicweb/web/data/. There are
 around thirty js files there. In a cube it goes to data/.
@@ -23,7 +22,7 @@
 
 XXX external_resources variable (which needs love)
 
-CubicWeb javascript api
+CubicWeb javascript API
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 Javascript resources are typically loaded on demand, from views. The
@@ -57,13 +56,18 @@
 Important AJAX APIS
 ~~~~~~~~~~~~~~~~~~~
 
+* `asyncRemoteExec` and `remoteExec` are the base building blocks for
+  doing arbitrary async (resp. sync) communications with the server
+
+* `reloadComponent` is a convenience function to replace a DOM node
+  with server supplied content coming from a specific registry (this
+  is quite handy to refresh the content of some boxes for instances)
+
 * `jQuery.fn.loadxhtml` is an important extension to jQuery which
-  allow proper loading and in-place DOM update of xhtml views. It is
+  allows proper loading and in-place DOM update of xhtml views. It is
   suitably augmented to trigger necessary events, and process CubicWeb
   specific elements such as the facet system, fckeditor, etc.
 
-* `asyncRemoteExec` and `remoteExec` are the base building blocks for
-  doing arbitrary async (resp. sync) communications with the server
 
 A simple example with asyncRemoteExec
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -89,20 +93,108 @@
 
 .. sourcecode: javascript
 
-    function async_hello(name) {
+    function asyncHello(name) {
         var deferred = asyncRemoteExec('say_hello', name);
         deferred.addCallback(function (response) {
             alert(response);
         });
-        deferred.addErrback(function () {
+        deferred.addErrback(function (error) {
             alert('something fishy happened');
         });
      }
 
-     function sync_hello(name) {
+     function syncHello(name) {
          alert( remoteExec('say_hello', name) );
      }
 
+Anatomy of a reloadComponent call
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`reloadComponent` allows to dynamically replace some DOM node with new
+elements. It has the following signature:
+
+* `compid` (mandatory) is the name of the component to be reloaded
+
+* `rql` (optional) will be used to generate a result set given as
+  argument to the selected component
+
+* `registry` (optional) defaults to 'components' but can be any other
+  valid registry name
+
+* `nodeid` (optional) defaults to compid + 'Component' but can be any
+  explicitly specified DOM node id
+
+* `extraargs` (optional) should be a dictionary of values that will be
+  given to the cell_call method of the component
+
+A simple reloadComponent example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The server side implementation of `reloadComponent` is the
+js_component method of the JSonController.
+
+The following function implements a two-steps method to delete a
+standard bookmark and refresh the UI, while keeping the UI responsive.
+
+.. sourcecode:: javascript
+
+    function removeBookmark(beid) {
+        d = asyncRemoteExec('delete_bookmark', beid);
+        d.addCallback(function(boxcontent) {
+	    reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
+            document.location.hash = '#header';
+            updateMessage(_("bookmark has been removed"));
+         });
+    }
+
+`reloadComponent` is called with the id of the bookmark box as
+argument, no rql expression (because the bookmarks display is actually
+independant of any dataset context), a reference to the 'boxes'
+registry (which hosts all left, right and contextual boxes) and
+finally an explicit 'bookmarks_box' nodeid argument that stipulates
+the target DOM node.
+
+Anatomy of a loadxhtml call
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`jQuery.fn.loadxhtml` is an important extension to jQuery which allows
+proper loading and in-place DOM update of xhtml views. The existing
+`jQuery.load`_ function does not handle xhtml, hence the addition. The
+API of loadxhtml is roughly similar to that of `jQuery.load`_.
+
+.. _`jQuery.load`: http://api.jquery.com/load/
+
+
+* `url` (mandatory) should be a complete url (typically referencing
+  the JSonController, but this is not strictly mandatory)
+
+* `data` (optional) is a dictionary of values given to the
+  controller specified through an `url` argument; some keys may have a
+  special meaning depending on the choosen controller (such as `fname`
+  for the JSonController); the `callback` key, if present, must refer
+  to a function to be called at the end of loadxhtml (more on this
+  below)
+
+* `reqtype` (optional) specifies the request method to be used (get or
+  post); if the argument is 'post', then the post method is used,
+  otherwise the get method is used
+
+* `mode` (optional) is one of `replace` (the default) which means the
+  loaded node will replace the current node content, `swap` to replace
+  the current node with the loaded node, and `append` which will
+  append the loaded node to the current node content
+
+About the `callback` option:
+
+* it is called with two parameters: the current node, and a list
+  containing the loaded (and post-processed node)
+
+* whenever is returns another function, this function is called in
+  turn with the same parameters as above
+
+This mechanism allows callback chaining.
+
+
 A simple example with loadxhtml
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -110,7 +202,7 @@
 injected in the live DOM. The view will be of course selected
 server-side using an entity eid provided by the client side.
 
-.. sourcecode: python
+.. sourcecode:: python
 
     from cubicweb import typed_eid
     from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
@@ -121,7 +213,7 @@
         entity = self._cw.entity_from_eid(typed_eid(eid))
         return entity.view('frob', name=frobname)
 
-.. sourcecode: javascript
+.. sourcecode:: javascript
 
     function update_some_div(divid, eid, frobname) {
         var params = {fname:'frob_status', eid: eid, frobname:frobname};
@@ -131,7 +223,7 @@
 In this example, the url argument is the base json url of a cube
 instance (it should contain something like
 `http://myinstance/json?`). The actual JSonController method name is
-encoded in the `params` dictionnary using the `fname` key.
+encoded in the `params` dictionary using the `fname` key.
 
 A more real-life example from CubicWeb
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -144,7 +236,7 @@
 We present here a skeletal version of the mecanism used in CubicWeb
 and available in web/views/tabs.py, in the `LazyViewMixin` class.
 
-.. sourcecode: python
+.. sourcecode:: python
 
     def lazyview(self, vid, rql=None):
         """ a lazy version of wview """
@@ -161,7 +253,7 @@
                    load_now('#lazy-%(vid)s');});"""
             % {'event': 'load_%s' % vid, 'vid': vid})
 
-This creates a `div` with an specific event associated to it.
+This creates a `div` with a specific event associated to it.
 
 The full version deals with:
 
@@ -177,7 +269,7 @@
 
 The javascript side is quite simple, due to loadxhtml awesomeness.
 
-.. sourcecode: javascript
+.. sourcecode:: javascript
 
     function load_now(eltsel) {
         var lazydiv = jQuery(eltsel);
@@ -205,10 +297,10 @@
 computation-intensive web page could be scinded into one fast-loading
 part and a delayed part).
 
-In the server side, a simple call to a javascript function is
+On the server side, a simple call to a javascript function is
 sufficient.
 
-.. sourcecode: python
+.. sourcecode:: python
 
     def forceview(self, vid):
         """trigger an event that will force immediate loading of the view
@@ -218,49 +310,17 @@
 
 The browser-side definition follows.
 
-.. sourcecode: javascript
+.. sourcecode:: javascript
 
     function trigger_load(divid) {
         jQuery('#lazy-' + divd).trigger('load_' + divid);
     }
 
 
-Anatomy of a lodxhtml call
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The loadxhtml extension to jQuery accept many parameters with rich
-semantics. Let us detail these.
-
-* `url` (mandatory) should be a complete url, typically based on the
-  JSonController, but this is not strictly mandatory
-
-* `data` (optional) is a dictionnary of values given to the
-  controller specified through an `url` argument; some keys may have a
-  special meaning depending on the choosen controller (such as `fname`
-  for the JSonController); the `callback` key, if present, must refer
-  to a function to be called at the end of loadxhtml (more on this
-  below)
-
-* `reqtype` (optional) specifies the request method to be used (get or
-  post); if the argument is 'post', then the post method is used,
-  otherwise the get method is used
-
-* `mode` (optional) is one of `replace` (the default) which means the
-  loaded node will replace the current node content, `swap` to replace
-  the current node with the loaded node, and `append` which will
-  append the loaded node to the current node content
 
 
-About the `callback` option:
-
-* it is called with two parameters: the current node, and a list
-  containing the loaded (and post-processed node)
-
-* whenever is returns another function, this function is called in
-  turn with the same parameters as above
-
-This mecanism allows callback chaining.
-
+XXX reloadComponent
+XXX userCallback / user_callback
 
 Javascript library: overview
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/doc/book/en/development/entityclasses/application-logic.rst	Wed Apr 07 14:42:44 2010 +0200
+++ b/doc/book/en/development/entityclasses/application-logic.rst	Wed Apr 07 14:42:55 2010 +0200
@@ -8,7 +8,7 @@
 .. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
 
 Entities objects are used in the repository and web sides of
-CubicWeb. In the repository side of things, one should manipulate them
+CubicWeb. On the repository side of things, one should manipulate them
 in Hooks and Operations.
 
 Hooks and Operations provide support for the implementation of rules
@@ -19,7 +19,7 @@
 So a lot of an application's business rules will be written in Hooks
 (or Operations).
 
-In the web side, views also typically operate using entity
+On the web side, views also typically operate using entity
 objects. Obvious entity methods for use in views are the dublin code
 method like dc_title, etc. For separation of concerns reasons, one
 should ensure no ui logic pervades the entities level, and also no
--- a/hooks/workflow.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/hooks/workflow.py	Wed Apr 07 14:42:55 2010 +0200
@@ -193,7 +193,7 @@
             raise ValidationError(entity.eid, {None: msg})
         # True if we are coming back from subworkflow
         swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
-        cowpowers = ('managers' in session.user.groups
+        cowpowers = (session.user.is_in_group('managers')
                      or not session.write_security)
         # no investigate the requested state change...
         try:
--- a/server/msplanner.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/server/msplanner.py	Wed Apr 07 14:42:55 2010 +0200
@@ -1044,7 +1044,7 @@
                      for select in subquery.query.children]
             for sppi in sppis:
                 if sppi.needsplit or sppi.part_sources != ppi.part_sources:
-                    temptable = 'T%s' % make_uid(id(subquery))
+                    temptable = plan.make_temp_table_name('T%s' % make_uid(id(subquery)))
                     sstep = self._union_plan(plan, sppis, temptable)[0]
                     break
             else:
@@ -1077,7 +1077,7 @@
                 inputmap = self._ppi_subqueries(ppi)
                 aggrstep = need_aggr_step(select, sources)
                 if aggrstep:
-                    atemptable = 'T%s' % make_uid(id(select))
+                    atemptable = plan.make_temp_table_name('T%s' % make_uid(id(select)))
                     sunion = Union()
                     sunion.append(select)
                     selected = select.selection[:]
@@ -1121,7 +1121,7 @@
         subinputmap = self._ppi_subqueries(ppi)
         stepdefs = ppi.part_steps()
         if need_aggr_step(select, ppi.part_sources, stepdefs):
-            atemptable = 'T%s' % make_uid(id(select))
+            atemptable = plan.make_temp_table_name('T%s' % make_uid(id(select)))
             selection = select.selection[:]
             select_group_sort(select)
         else:
@@ -1171,6 +1171,7 @@
                 else:
                     table = '_T%s%s' % (''.join(sorted(v._ms_table_key() for v in terms)),
                                         ''.join(sorted(str(i) for i in solindices)))
+                    table = plan.make_temp_table_name(table)
                     ppi.build_non_final_part(minrqlst, solindices, sources,
                                              insertedvars, table)
         # finally: join parts, deal with aggregat/group/sorts if necessary
--- a/server/querier.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/server/querier.py	Wed Apr 07 14:42:55 2010 +0200
@@ -164,6 +164,13 @@
         finally:
             self.clean()
 
+    def make_temp_table_name(self, table):
+        """
+        return a temp table name according to db backend
+        """
+        return self.syssource.make_temp_table_name(table)
+
+
     def init_temp_table(self, table, selected, sol):
         """initialize sql schema and variable map for a temporary table which
         will be used to store result for the given rqlst
--- a/server/repository.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/server/repository.py	Wed Apr 07 14:42:55 2010 +0200
@@ -593,7 +593,7 @@
                 raise
             except:
                 # FIXME: check error to catch internal errors
-                self.exception('unexpected error')
+                self.exception('unexpected error while executing %s with %s', rqlstring, args)
                 raise
         finally:
             session.reset_pool()
--- a/server/sources/native.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/server/sources/native.py	Wed Apr 07 14:42:55 2010 +0200
@@ -669,6 +669,15 @@
             pass
         return None
 
+    def make_temp_table_name(self, table):
+        try: # XXX remove this once 
+            return self.dbhelper.temporary_table_name(table)
+        except AttributeError:
+            import warnings
+            warnings.warn('Please hg up logilab.database')
+            return table
+
+
     def temp_table_def(self, selected, sol, table):
         return make_schema(selected, sol, table, self.dbhelper.TYPE_MAPPING)
 
--- a/skeleton/test/pytestconf.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/skeleton/test/pytestconf.py	Wed Apr 07 14:42:55 2010 +0200
@@ -26,11 +26,11 @@
     parser.add_option('-u', '--dbuser', dest='dbuser', action='store',
                       default=login, help="database user")
     parser.add_option('-w', '--dbpassword', dest='dbpassword', action='store',
-                      default=login, help="database name")
+                      default=login, help="database user's password")
     parser.add_option('-n', '--dbname', dest='dbname', action='store',
                       default=None, help="database name")
     parser.add_option('--euser', dest='euser', action='store',
-                      default=login, help="esuer name")
+                      default=login, help="euser name")
     parser.add_option('--epassword', dest='epassword', action='store',
                       default=login, help="euser's password' name")
     return parser
--- a/web/data/cubicweb.ajax.js	Wed Apr 07 14:42:44 2010 +0200
+++ b/web/data/cubicweb.ajax.js	Wed Apr 07 14:42:55 2010 +0200
@@ -239,7 +239,7 @@
 function asyncRemoteExec(fname /* ... */) {
     setProgressCursor();
     var props = {'fname' : fname, 'pageid' : pageid,
-                      'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+                 'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
     var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
     deferred = deferred.addErrback(remoteCallFailed);
     deferred = deferred.addErrback(resetCursor);
--- a/web/views/basecontrollers.py	Wed Apr 07 14:42:44 2010 +0200
+++ b/web/views/basecontrollers.py	Wed Apr 07 14:42:55 2010 +0200
@@ -410,6 +410,7 @@
         extraargs = extraargs or {}
         stream = comp.set_stream()
         comp.render(**extraargs)
+        # XXX why not _call_view ?
         extresources = self._cw.html_headers.getvalue(skiphead=True)
         if extresources:
             stream.write(u'<div class="ajaxHtmlHead">\n')